Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
//! By convention, root.zig is the root source file when making a library.
const print = std.debug.print;
const std = @import("std");
const builtin = @import("builtin");
const os = std.os;
const assert = std.debug.assert;
const c = @import("c");
// Constants for peripherals.
const screen_RCLCK: u8 = 0b00000100;
const screen_SRCLCK: u8 = 0b00000010;
const screen_SER: u8 = 0b00000001;
const controller_SER1: u8 = 0b00000001;
const controller_CONN1: u8 = 0b00000010;
const controller_SER2: u8 = 0b00000100;
const controller_CONN2: u8 = 0b00001000;
const controller_SHLD: u8 = 0b00000001;
const controller_CLK: u8 = 0b00000010;
const pixel_w = 10;
const pixel_h = 20;
const pixel_size = 20;
const window_w = pixel_w * pixel_size;
const window_h = pixel_h * pixel_size;
var window: *c.SDL_Window = undefined;
var renderer: *c.SDL_Renderer = undefined;
// var progmem = [_]u8{0} ** 65536;
var ram: [65536]u8 = @splat(0);
var state: c.struct_Spork8State = .{
.inst_reg = 0,
.flags_reg = 0,
.mi_counter = 0,
.bus = 0,
.get_progmem_byte = get_progmem_byte,
.get_ram_byte = get_ram_byte,
.set_ram_byte = set_ram_byte,
.module_values = @splat(0),
};
var screen_buffer: u200 = 2;
var screen_buffer_internal: u200 = 2;
var controller_buffer1: u8 = 0;
var controller_buffer2: u8 = 0;
// Snake 2
const static_cartridge = [_]u8{
0x66, 0x80, 0x09, 0x66, 0x80, 0x5d, 0x66, 0x80, 0x00, 0x09, 0x00, 0x11, 0xff, 0x13, 0x00, 0x04,
0x03, 0x4f, 0x04, 0x04, 0x4f, 0x9e, 0x01, 0x69, 0x80, 0x0f, 0x37, 0x00, 0x12, 0x01, 0x37, 0x00,
0x21, 0x00, 0x37, 0x00, 0x20, 0x05, 0x37, 0x03, 0x00, 0x04, 0x37, 0x03, 0x01, 0x04, 0x37, 0x03,
0x02, 0x04, 0x37, 0x03, 0x03, 0x04, 0x37, 0x03, 0x04, 0x04, 0x37, 0x03, 0x05, 0x04, 0x37, 0x04,
0x00, 0x00, 0x37, 0x04, 0x01, 0x01, 0x37, 0x04, 0x02, 0x02, 0x37, 0x04, 0x03, 0x03, 0x37, 0x04,
0x04, 0x04, 0x37, 0x04, 0x05, 0x05, 0x37, 0x00, 0x14, 0x54, 0x66, 0x80, 0x03, 0x13, 0x80, 0x66,
0x80, 0x75, 0x5c, 0x9e, 0x01, 0x55, 0x69, 0x80, 0x5f, 0x66, 0x80, 0xba, 0x66, 0x81, 0x27, 0x66,
0x81, 0x60, 0x66, 0x80, 0x5d, 0x11, 0x01, 0x16, 0x00, 0x16, 0x02, 0x16, 0x00, 0x16, 0x01, 0x87,
0x69, 0x80, 0xac, 0x16, 0x03, 0x16, 0x01, 0x87, 0x69, 0x80, 0x9e, 0x16, 0x03, 0x16, 0x01, 0x87,
0x69, 0x80, 0xb3, 0x16, 0x03, 0x16, 0x01, 0x87, 0x69, 0x80, 0xa5, 0x66, 0x80, 0x62, 0x37, 0x00,
0x12, 0x00, 0x66, 0x80, 0x8b, 0x37, 0x00, 0x12, 0x01, 0x66, 0x80, 0x9b, 0x37, 0x00, 0x12, 0x02,
0x66, 0x80, 0x83, 0x37, 0x00, 0x12, 0x03, 0x66, 0x80, 0x93, 0x0a, 0x00, 0x12, 0x7a, 0x02, 0x69,
0x80, 0xc5, 0x67, 0x80, 0xf2, 0x0a, 0x00, 0x20, 0x05, 0x04, 0x03, 0x28, 0x04, 0x04, 0x2e, 0x49,
0x99, 0x01, 0x30, 0x00, 0x20, 0x0a, 0x00, 0x12, 0x79, 0x02, 0x5c, 0x67, 0x80, 0xe3, 0x99, 0x01,
0x66, 0x80, 0xe8, 0x9e, 0x01, 0x66, 0x80, 0xe8, 0x0c, 0x00, 0x20, 0x04, 0x03, 0x07, 0x46, 0x66,
0x81, 0x1c, 0x0a, 0x00, 0x20, 0x05, 0x04, 0x04, 0x28, 0x04, 0x03, 0x2e, 0x49, 0x99, 0x01, 0x30,
0x00, 0x20, 0x0a, 0x00, 0x12, 0x79, 0x00, 0x5c, 0x67, 0x81, 0x10, 0x99, 0x01, 0x66, 0x81, 0x15,
0x9e, 0x01, 0x66, 0x81, 0x15, 0x0c, 0x00, 0x20, 0x04, 0x04, 0x07, 0x46, 0x0a, 0x00, 0x21, 0x99,
0x01, 0x30, 0x00, 0x21, 0x66, 0x80, 0x6c, 0x04, 0x02, 0x09, 0x00, 0x11, 0xc8, 0x13, 0x00, 0x4f,
0x9e, 0x01, 0x69, 0x81, 0x2f, 0x0c, 0x00, 0x21, 0x04, 0x04, 0x07, 0x26, 0x54, 0xa8, 0x8b, 0xa8,
0x04, 0x03, 0x27, 0xa8, 0x04, 0x02, 0x05, 0x11, 0x01, 0x46, 0x5c, 0x99, 0x01, 0x55, 0x7b, 0x00,
0x20, 0x68, 0x81, 0x38, 0x0c, 0x00, 0x14, 0x04, 0x02, 0x07, 0x11, 0x01, 0x46, 0x66, 0x80, 0x6f,
0x13, 0xc8, 0x04, 0x02, 0x09, 0x00, 0x2b, 0x79, 0x00, 0x11, 0x00, 0x9c, 0x01, 0x58, 0x99, 0x02,
0x58, 0x5c, 0x9e, 0x01, 0x55, 0x69, 0x81, 0x66, 0x15, 0x04, 0x15, 0x00, 0x66, 0x80, 0x72,
};
var cartridge: [65536]u8 = @splat(0);
// Jump to cartridge
const progmem_eeprom = [_]u8{ 0x66, 0x80, 0x00 };
fn get_progmem_byte(address: c_ushort) callconv(.c) u8 {
if (address & 1 << 15 != 0) {
const cart_address = address & 0x7FFF;
if (cart_address >= cartridge.len) return 0;
return cartridge[cart_address];
}
if (address >= progmem_eeprom.len) return 0;
return progmem_eeprom[address];
}
fn get_ram_byte(address: c_ushort) callconv(.c) u8 {
return ram[address];
}
fn set_ram_byte(address: c_ushort, value: u8) callconv(.c) void {
ram[address] = value;
}
var fully_initialized = false;
pub fn run(args: std.process.Args.Iterator, allocator: std.mem.Allocator, io: std.Io) !u8 {
app_err.reset();
var mutable_args = args;
_ = mutable_args.next();
if (mutable_args.next()) |asm_path| {
const cwd = std.Io.Dir.cwd();
var file_contents = try allocator.alloc(u8, 65_536);
defer allocator.free(file_contents);
_ = try cwd.readFile(io, asm_path, file_contents);
for (file_contents[0x8000..], 0x8000..) |value, index| {
cartridge[index - 0x8000] = value;
}
} else {
for (static_cartridge, 0..) |value, index| {
cartridge[index] = value;
}
}
var empty_argv: [0:null]?[*:0]u8 = .{};
const status: u8 = @truncate(@as(c_uint, @bitCast(c.SDL_RunApp(empty_argv.len, @ptrCast(&empty_argv), sdlMainC, null))));
return app_err.load() orelse status;
}
fn sdlAppInit(appstate: ?*?*anyopaque, argv: [][*:0]u8) !c.SDL_AppResult {
_ = appstate;
_ = argv;
try errify(c.SDL_SetAppMetadata("Spork 8", "0.0.1", "com.happyspork.Spork8Emulator"));
try errify(c.SDL_Init(c.SDL_INIT_VIDEO));
errify(c.SDL_SetHint(c.SDL_HINT_RENDER_VSYNC, "1")) catch {};
try errify(c.SDL_CreateWindowAndRenderer("Spork 8", window_w, window_h, 0, @ptrCast(&window), @ptrCast(&renderer)));
errdefer c.SDL_DestroyWindow(window);
errdefer c.SDL_DestroyRenderer(renderer);
c.spork8_state_reset(&state);
errdefer comptime unreachable;
fully_initialized = true;
return c.SDL_APP_CONTINUE;
}
fn sdlAppIterate(appstate: ?*anyopaque) !c.SDL_AppResult {
_ = appstate;
var need_redraw = false;
for (0..1000) |_| {
const old_outa_value = state.module_values[c.OUTA];
const old_ioa_value = c.spork8_state_get_output_value(&state, c.IOA);
c.spork8_state_cycle(&state);
// Update screen.
const new_outa_state = state.module_values[c.OUTA];
if ((old_outa_value & screen_SRCLCK) == 0 and (new_outa_state & screen_SRCLCK) != 0) {
screen_buffer_internal <<= 1;
if ((new_outa_state & screen_SER) != 0)
screen_buffer_internal |= 1;
}
if ((old_outa_value & screen_RCLCK) == 0 and (new_outa_state & screen_RCLCK) != 0) {
screen_buffer = screen_buffer_internal;
need_redraw = true;
break;
// print("Need redraw: {}\n", .{need_redraw});
}
// Update controller.
const new_ioa_value = c.spork8_state_get_output_value(&state, c.IOA);
if ((old_ioa_value & controller_CLK) == 0 and (new_ioa_value & controller_CLK) != 0) {
if (new_ioa_value & controller_SHLD != 0) {
// print("Shifting controller\n", .{});
controller_buffer1 >>= 1;
controller_buffer2 >>= 1;
} else {
var count: c_int = 0;
const keyboard_state = c.SDL_GetKeyboardState(&count);
const scancodes1 = [8]c_int{
c.SDL_SCANCODE_LEFT,
c.SDL_SCANCODE_UP,
c.SDL_SCANCODE_RIGHT,
c.SDL_SCANCODE_DOWN,
c.SDL_SCANCODE_ESCAPE,
c.SDL_SCANCODE_RETURN,
c.SDL_SCANCODE_N,
c.SDL_SCANCODE_M,
};
controller_buffer1 = 0;
for (scancodes1, 0..) |value, index| {
const u3_index: u3 = @intCast(index);
const u8_value: u8 = if (keyboard_state[@intCast(value)]) 1 else 0;
controller_buffer1 |= (u8_value << u3_index);
}
const scancodes2 = [8]c_int{
c.SDL_SCANCODE_A,
c.SDL_SCANCODE_W,
c.SDL_SCANCODE_D,
c.SDL_SCANCODE_S,
c.SDL_SCANCODE_TAB,
c.SDL_SCANCODE_R,
c.SDL_SCANCODE_F,
c.SDL_SCANCODE_G,
};
controller_buffer2 = 0;
for (scancodes2, 0..) |value, index| {
const u3_index: u3 = @intCast(index);
const u8_value: u8 = if (keyboard_state[@intCast(value)]) 1 else 0;
controller_buffer2 |= (u8_value << u3_index);
}
// print("Set controller state: {}\n", .{controller_buffer});
}
var ser_value = if (controller_buffer1 & 1 != 0) controller_SER1 else 0;
ser_value |= if (controller_buffer2 & 1 != 0) controller_SER2 else 0;
c.spork8_state_set_input_value(&state, c.IOA, controller_CONN1 | ser_value);
}
}
// Draw.
if (need_redraw) {
// print("Drawing\n", .{});
try errify(c.SDL_SetRenderDrawColor(renderer, 0x47, 0x5b, 0x8d, 0xff));
try errify(c.SDL_RenderClear(renderer));
try errify(c.SDL_SetRenderScale(renderer, 1, 1));
{
try errify(c.SDL_SetRenderDrawColor(renderer, 0xff, 0xff, 0xff, 0xff));
for (0..pixel_h) |y| {
for (0..pixel_w) |x| {
const index = y * pixel_w + x;
if (screen_buffer & (@as(u200, 1) <<| index) == 0) {
continue;
}
const rect: c.SDL_FRect = .{ .x = @floatFromInt(x * pixel_size), .y = @floatFromInt(y * pixel_size), .w = @floatFromInt(pixel_size), .h = @floatFromInt(pixel_size) };
try errify(c.SDL_RenderFillRect(renderer, &rect));
}
}
}
try errify(c.SDL_SetRenderScale(renderer, 1, 1));
try errify(c.SDL_RenderPresent(renderer));
}
return c.SDL_APP_CONTINUE;
}
fn sdlAppEvent(appstate: ?*anyopaque, event: *c.SDL_Event) !c.SDL_AppResult {
_ = appstate;
switch (event.type) {
c.SDL_EVENT_QUIT => {
return c.SDL_APP_SUCCESS;
},
else => {},
}
return c.SDL_APP_CONTINUE;
}
fn sdlAppQuit(appstate: ?*anyopaque, result: anyerror!c.SDL_AppResult) void {
_ = appstate;
_ = result catch |err| if (err == error.SdlError) {
// sdl_log.err("{s}", .{c.SDL_GetError()});
};
if (fully_initialized) {
c.SDL_DestroyRenderer(renderer);
c.SDL_DestroyWindow(window);
fully_initialized = false;
}
}
/// Converts the return value of an SDL function to an error union.
inline fn errify(value: anytype) error{SdlError}!switch (@typeInfo(@TypeOf(value))) {
.bool => void,
.pointer, .optional => @TypeOf(value.?),
.int => |info| switch (info.signedness) {
.signed => @TypeOf(@max(0, value)),
.unsigned => @TypeOf(value),
},
else => @compileError("unerrifiable type: " ++ @typeName(@TypeOf(value))),
} {
return switch (@typeInfo(@TypeOf(value))) {
.bool => if (!value) error.SdlError,
.pointer, .optional => value orelse error.SdlError,
.int => |info| switch (info.signedness) {
.signed => if (value >= 0) @max(0, value) else error.SdlError,
.unsigned => if (value != 0) value else error.SdlError,
},
else => comptime unreachable,
};
}
//#region SDL main callbacks boilerplate
pub fn main() !u8 {
app_err.reset();
var empty_argv: [0:null]?[*:0]u8 = .{};
const status: u8 = @truncate(@as(c_uint, @bitCast(c.SDL_RunApp(empty_argv.len, @ptrCast(&empty_argv), sdlMainC, null))));
return app_err.load() orelse status;
}
fn sdlMainC(argc: c_int, argv: ?[*:null]?[*:0]u8) callconv(.c) c_int {
return c.SDL_EnterAppMainCallbacks(argc, @ptrCast(argv), sdlAppInitC, sdlAppIterateC, sdlAppEventC, sdlAppQuitC);
}
fn sdlAppInitC(appstate: ?*?*anyopaque, argc: c_int, argv: ?[*:null]?[*:0]u8) callconv(.c) c.SDL_AppResult {
return sdlAppInit(appstate.?, @ptrCast(argv.?[0..@intCast(argc)])) catch |err| app_err.store(err);
}
fn sdlAppIterateC(appstate: ?*anyopaque) callconv(.c) c.SDL_AppResult {
return sdlAppIterate(appstate) catch |err| app_err.store(err);
}
fn sdlAppEventC(appstate: ?*anyopaque, event: ?*c.SDL_Event) callconv(.c) c.SDL_AppResult {
return sdlAppEvent(appstate, event.?) catch |err| app_err.store(err);
}
fn sdlAppQuitC(appstate: ?*anyopaque, result: c.SDL_AppResult) callconv(.c) void {
sdlAppQuit(appstate, app_err.load() orelse result);
}
var app_err: ErrorStore = .{};
const ErrorStore = struct {
const status_not_stored = 0;
const status_storing = 1;
const status_stored = 2;
status: c.SDL_AtomicInt = .{ .value = 0 },
err: anyerror = undefined,
trace_index: usize = undefined,
trace_addrs: [32]usize = undefined,
fn reset(es: *ErrorStore) void {
_ = c.SDL_SetAtomicInt(&es.status, status_not_stored);
}
fn store(es: *ErrorStore, err: anyerror) c.SDL_AppResult {
if (c.SDL_CompareAndSwapAtomicInt(&es.status, status_not_stored, status_storing)) {
es.err = err;
if (@errorReturnTrace()) |src_trace| {
es.trace_index = src_trace.index;
const len = @min(es.trace_addrs.len, src_trace.instruction_addresses.len);
@memcpy(es.trace_addrs[0..len], src_trace.instruction_addresses[0..len]);
}
_ = c.SDL_SetAtomicInt(&es.status, status_stored);
}
return c.SDL_APP_FAILURE;
}
fn load(es: *ErrorStore) ?anyerror {
if (c.SDL_GetAtomicInt(&es.status) != status_stored) return null;
if (@errorReturnTrace()) |dst_trace| {
dst_trace.index = es.trace_index;
const len = @min(dst_trace.instruction_addresses.len, es.trace_addrs.len);
@memcpy(dst_trace.instruction_addresses[0..len], es.trace_addrs[0..len]);
}
return es.err;
}
};
//#endregion SDL main callbacks boilerplate
0b87c4a36This file is pinned to the selected release. Download the original to open it in your design tool.