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-03-17 01:28:24 +00:00
|
|
|
const Log2Int = std.math.Log2Int;
|
2022-07-27 17:49:55 +00:00
|
|
|
const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
|
2021-12-29 21:09:00 +00:00
|
|
|
|
2022-05-23 15:58:31 +00:00
|
|
|
// 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
|
|
|
|
comptime std.debug.assert(@typeInfo(U).Int.bits <= @typeInfo(T).Int.bits);
|
2022-01-01 09:41:50 +00:00
|
|
|
|
2022-05-23 15:58:31 +00:00
|
|
|
const iT = std.meta.Int(.signed, @typeInfo(T).Int.bits);
|
|
|
|
const ExtU = if (@typeInfo(U).Int.signedness == .unsigned) T else iT;
|
|
|
|
const shift = @intCast(Log2Int(T), @typeInfo(T).Int.bits - @typeInfo(U).Int.bits);
|
|
|
|
|
|
|
|
return @bitCast(T, @bitCast(iT, @as(ExtU, @truncate(U, value)) << shift) >> shift);
|
2021-12-29 21:09:00 +00:00
|
|
|
}
|
2022-03-14 11:54:48 +00:00
|
|
|
|
2022-03-17 01:28:24 +00:00
|
|
|
/// See https://godbolt.org/z/W3en9Eche
|
2022-04-08 05:13:41 +00:00
|
|
|
pub inline fn rotr(comptime T: type, x: T, r: anytype) T {
|
|
|
|
if (@typeInfo(T).Int.signedness == .signed)
|
|
|
|
@compileError("cannot rotate signed integer");
|
2022-03-17 01:28:24 +00:00
|
|
|
|
2022-04-08 05:13:41 +00:00
|
|
|
const ar = @intCast(Log2Int(T), @mod(r, @typeInfo(T).Int.bits));
|
|
|
|
return x >> ar | x << (1 +% ~ar);
|
2022-03-17 01:28:24 +00:00
|
|
|
}
|
|
|
|
|
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-05-17 11:55:23 +00:00
|
|
|
self.fps = self.count.swap(0, .SeqCst);
|
|
|
|
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
|
|
|
|
|
|
|
pub fn intToBytes(comptime T: type, value: anytype) [@sizeOf(T)]u8 {
|
|
|
|
comptime std.debug.assert(@typeInfo(T) == .Int);
|
|
|
|
|
|
|
|
var result: [@sizeOf(T)]u8 = undefined;
|
|
|
|
|
|
|
|
var i: Log2Int(T) = 0;
|
|
|
|
while (i < result.len) : (i += 1) result[i] = @truncate(u8, value >> i * @bitSizeOf(u8));
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
2022-03-22 17:41:18 +00:00
|
|
|
|
|
|
|
/// The Title from the GBA Cartridge may be null padded to a maximum
|
2022-04-08 05:13:58 +00:00
|
|
|
/// length of 12 bytes.
|
2022-03-22 17:41:18 +00:00
|
|
|
///
|
2022-04-08 05:13:58 +00:00
|
|
|
/// This function returns a slice of everything just before the first
|
2022-03-22 17:41:18 +00:00
|
|
|
/// `\0`
|
2022-04-27 23:08:44 +00:00
|
|
|
pub fn asString(title: [12]u8) []const u8 {
|
2022-03-22 17:41:18 +00:00
|
|
|
var len = title.len;
|
|
|
|
for (title) |char, i| {
|
|
|
|
if (char == 0) {
|
|
|
|
len = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return title[0..len];
|
|
|
|
}
|
|
|
|
|
2022-04-08 05:13:58 +00:00
|
|
|
/// Copies a Title and returns either an identical or similar
|
2022-03-22 17:41:18 +00:00
|
|
|
/// array consisting of ASCII that won't make any file system angry
|
|
|
|
///
|
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-03-22 17:41:18 +00:00
|
|
|
var result: [12]u8 = title;
|
|
|
|
|
|
|
|
for (result) |*char| {
|
2022-03-22 17:54:37 +00:00
|
|
|
if (char.* == '/' or char.* == '\\') char.* = '_';
|
2022-03-22 17:41:18 +00:00
|
|
|
if (char.* == 0) break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2022-04-10 07:28:05 +00:00
|
|
|
pub const FilePaths = struct {
|
|
|
|
rom: []const u8,
|
|
|
|
bios: ?[]const u8,
|
|
|
|
save: ?[]const u8,
|
|
|
|
};
|
2022-06-15 04:08:43 +00:00
|
|
|
|
|
|
|
pub fn readUndefined(log: anytype, comptime format: []const u8, args: anytype) u8 {
|
2022-06-15 04:18:45 +00:00
|
|
|
log.warn(format, args);
|
2022-06-15 04:08:43 +00:00
|
|
|
if (builtin.mode == .Debug) std.debug.panic("TODO: Implement I/O Register", .{});
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn writeUndefined(log: anytype, comptime format: []const u8, args: anytype) void {
|
2022-06-15 04:18:45 +00:00
|
|
|
log.warn(format, args);
|
2022-06-15 04:08:43 +00:00
|
|
|
if (builtin.mode == .Debug) std.debug.panic("TODO: Implement I/O Register", .{});
|
|
|
|
}
|
2022-07-27 17:49:55 +00:00
|
|
|
|
|
|
|
pub const Logger = struct {
|
|
|
|
const Self = @This();
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn mgbaLog(self: *Self, arm7tdmi: *const Arm7tdmi, opcode: u32) void {
|
|
|
|
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";
|
|
|
|
|
|
|
|
if (arm7tdmi.cpsr.t.read()) {
|
|
|
|
if (opcode >> 11 == 0x1E) {
|
|
|
|
// Instruction 1 of a BL Opcode, print in ARM mode
|
|
|
|
const low = arm7tdmi.bus.debugRead(u16, arm7tdmi.r[15]);
|
|
|
|
const bl_opcode = @as(u32, opcode) << 16 | low;
|
|
|
|
|
|
|
|
self.print(arm_fmt, Self.fmtArgs(arm7tdmi, bl_opcode)) catch @panic("failed to write to log file");
|
|
|
|
} else {
|
|
|
|
self.print(thumb_fmt, Self.fmtArgs(arm7tdmi, opcode)) catch @panic("failed to write to log file");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
self.print(arm_fmt, Self.fmtArgs(arm7tdmi, opcode)) catch @panic("failed to write to log file");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn fmtArgs(arm7tdmi: *const Arm7tdmi, opcode: u32) FmtArgTuple {
|
|
|
|
return .{
|
|
|
|
arm7tdmi.r[0],
|
|
|
|
arm7tdmi.r[1],
|
|
|
|
arm7tdmi.r[2],
|
|
|
|
arm7tdmi.r[3],
|
|
|
|
arm7tdmi.r[4],
|
|
|
|
arm7tdmi.r[5],
|
|
|
|
arm7tdmi.r[6],
|
|
|
|
arm7tdmi.r[7],
|
|
|
|
arm7tdmi.r[8],
|
|
|
|
arm7tdmi.r[9],
|
|
|
|
arm7tdmi.r[10],
|
|
|
|
arm7tdmi.r[11],
|
|
|
|
arm7tdmi.r[12],
|
|
|
|
arm7tdmi.r[13],
|
|
|
|
arm7tdmi.r[14],
|
|
|
|
arm7tdmi.r[15],
|
|
|
|
arm7tdmi.cpsr.raw,
|
|
|
|
opcode,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const FmtArgTuple = std.meta.Tuple(&.{ u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32 });
|