Compare commits
1 Commits
webview-ne
...
a826c724ee
Author | SHA1 | Date | |
---|---|---|---|
|
a826c724ee |
@@ -162,6 +162,7 @@ min=23
|
||||
nes=23
|
||||
sna=23
|
||||
snes=23
|
||||
rom=23
|
||||
bat=24
|
||||
sh=24
|
||||
sys=25
|
||||
|
@@ -68,6 +68,7 @@ sna=/kolibrios/emul/e80/e80
|
||||
gb=/kolibrios/emul/gameboy
|
||||
gbc=/kolibrios/emul/gameboy
|
||||
min=/kolibrios/emul/pokemini
|
||||
rom=/kolibrios/emul/uxn
|
||||
nc=/kolibrios/utils/cnc_editor/cnc_editor
|
||||
kf=/sys/KF_VIEW
|
||||
csv=/sys/table
|
||||
|
@@ -192,6 +192,7 @@ nc=/kolibrios/utils/cnc_editor/cnc_editor
|
||||
ch8=/kolibrios/emul/chip8/chip8
|
||||
md=/kolibrios/emul/dgen/dgen
|
||||
gen=/kolibrios/emul/dgen/dgen
|
||||
rom=/kolibrios/emul/uxn
|
||||
|
||||
zip=$Unz
|
||||
7z=$Unz
|
||||
|
@@ -521,7 +521,7 @@ bool GetUrl(dword _http_url)
|
||||
http.get(_http_url);
|
||||
return true;
|
||||
} else if (!strncmp(_http_url,"https://",8)) {
|
||||
strcpy(#new_url_full, "http://176.223.130.192:82/?site=");
|
||||
strcpy(#new_url_full, "http://gate.aspero.pro/?site=");
|
||||
strncat(#new_url_full, _http_url, URL_SIZE);
|
||||
http.get(#new_url_full);
|
||||
return true;
|
||||
|
@@ -168,9 +168,9 @@ void StartDownloading()
|
||||
ResetDownloadSpeed();
|
||||
pb.back_color = 0xFFFfff;
|
||||
if (!strncmp(#uEdit,"https:",6)) {
|
||||
miniprintf(#get_url, "http://176.223.130.192:82/?site=%s", #uEdit);
|
||||
// notify("'HTTPS for download temporary is not supported,\ntrying to download the file via HTTP' -W");
|
||||
// miniprintf(#uEdit, "http://%s", #uEdit+8);
|
||||
//miniprintf(#get_url, "http://gate.aspero.pro/?site=%s", #uEdit);
|
||||
notify("'HTTPS for download temporary is not supported,\ntrying to download the file via HTTP' -W");
|
||||
miniprintf(#uEdit, "http://%s", #uEdit+8);
|
||||
}
|
||||
strcpy(#get_url, #uEdit);
|
||||
|
||||
|
@@ -112,7 +112,7 @@ struct _http
|
||||
dword _http::get(dword _url)
|
||||
{
|
||||
cur_url = _url;
|
||||
if (streqrp(cur_url, "http://176.223.130.192:82/?site=")) cur_url += 29;
|
||||
if (streqrp(cur_url, "http://gate.aspero.pro/?site=")) cur_url += 29;
|
||||
http_get stdcall (_url, 0, 0, #accept_language);
|
||||
transfer = EAX;
|
||||
return EAX;
|
||||
|
@@ -100,10 +100,6 @@ lib_init: ;//////////////////////////////////////////////////////////////////;;
|
||||
mov [mem.alloc], eax
|
||||
mov [mem.free], ebx
|
||||
mov [mem.realloc], ecx
|
||||
|
||||
cmp [dll.load], edx
|
||||
je .ok
|
||||
|
||||
mov [dll.load], edx
|
||||
|
||||
invoke dll.load, @IMPORT
|
||||
@@ -119,7 +115,6 @@ lib_init: ;//////////////////////////////////////////////////////////////////;;
|
||||
invoke ini.get_str, inifile, sec_proxy, key_password, proxyPassword, 256, proxyPassword
|
||||
popa
|
||||
|
||||
.ok:
|
||||
DEBUGF 1, "HTTP library: init OK\n"
|
||||
xor eax, eax
|
||||
ret
|
||||
|
@@ -78,10 +78,6 @@ proc lib_init ;///////////////////////////////////////////////////////////////;;
|
||||
mov [mem.alloc], eax
|
||||
mov [mem.free], ebx
|
||||
mov [mem.realloc], ecx
|
||||
|
||||
cmp [dll.load], edx
|
||||
je .ok
|
||||
|
||||
mov [dll.load], edx
|
||||
|
||||
or edx, edx
|
||||
|
@@ -37,10 +37,6 @@ proc libini._.init ;////////////////////////////////////////////////////////////
|
||||
mov [mem.alloc], eax
|
||||
mov [mem.free], ebx
|
||||
mov [mem.realloc], ecx
|
||||
|
||||
cmp [dll.load], edx
|
||||
je .ok
|
||||
|
||||
mov [dll.load], edx
|
||||
|
||||
invoke dll.load, @IMPORT
|
||||
|
@@ -33,7 +33,6 @@ use_ColorDialog
|
||||
;--------------------------------------------------
|
||||
align 16
|
||||
lib_init:
|
||||
xor eax, eax
|
||||
ret
|
||||
|
||||
;--------------------------------------------------
|
||||
|
6
programs/emulator/uxn/.gitignore
vendored
Normal file
6
programs/emulator/uxn/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# SPDX-FileCopyrightText: 2025 iyzsong@envs.net
|
||||
#
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
zig-out
|
||||
.zig-cache
|
17
programs/emulator/uxn/README
Normal file
17
programs/emulator/uxn/README
Normal file
@@ -0,0 +1,17 @@
|
||||
// SPDX-FileCopyrightText: 2025 iyzsong@envs.net
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
Uxn/Varvara emulator for Kolibri OS
|
||||
|
||||
Based on https://github.com/chmod222/zuxn
|
||||
|
||||
compile: zig build --release=fast
|
||||
result: zig-out/bin/uxn
|
||||
run: uxn SOME.rom
|
||||
control:
|
||||
Up/Down/Left/Right
|
||||
Ctrl/Shift/Alt/Home
|
||||
F1: change scale (1x, 2x, 3x)
|
||||
|
||||
TODO: file/directory stat, audio latency, open dialog?
|
36
programs/emulator/uxn/build.zig
Normal file
36
programs/emulator/uxn/build.zig
Normal file
@@ -0,0 +1,36 @@
|
||||
// SPDX-FileCopyrightText: 2025 iyzsong@envs.net
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const target_query = std.Target.Query{
|
||||
.cpu_arch = std.Target.Cpu.Arch.x86,
|
||||
.os_tag = std.Target.Os.Tag.freestanding,
|
||||
.abi = std.Target.Abi.none,
|
||||
.cpu_model = std.Target.Query.CpuModel{ .explicit = &std.Target.x86.cpu.i586 },
|
||||
};
|
||||
const target = b.resolveTargetQuery(target_query);
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
const zuxn = b.dependency("zuxn", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
const elf = b.addExecutable(.{
|
||||
.name = "uxn.elf",
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.unwind_tables = .none,
|
||||
});
|
||||
elf.root_module.addImport("uxn-core", zuxn.module("uxn-core"));
|
||||
elf.root_module.addImport("uxn-varvara", zuxn.module("uxn-varvara"));
|
||||
elf.setLinkerScript(b.path("src/linker.ld"));
|
||||
const bin = elf.addObjCopy(.{
|
||||
.format = .bin,
|
||||
});
|
||||
const install_bin = b.addInstallBinFile(bin.getOutput(), "uxn");
|
||||
b.getInstallStep().dependOn(&install_bin.step);
|
||||
b.installArtifact(elf);
|
||||
}
|
21
programs/emulator/uxn/build.zig.zon
Normal file
21
programs/emulator/uxn/build.zig.zon
Normal file
@@ -0,0 +1,21 @@
|
||||
// SPDX-FileCopyrightText: 2025 iyzsong@envs.net
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
.{
|
||||
.name = .uxn_kolibrios,
|
||||
.version = "0.0.0",
|
||||
.fingerprint = 0x3aef20f25c0a0218,
|
||||
.minimum_zig_version = "0.14.0",
|
||||
.dependencies = .{
|
||||
.zuxn = .{
|
||||
.url = "git+https://github.com/chmod222/zuxn.git#fc3a76724fa87dd08039438b56fc326ac3d51e4d",
|
||||
.hash = "zuxn-0.0.1-XnoOpbqsAgD-fU6rv_AoLffA1utIzXuae2cmnHj6SzE6",
|
||||
},
|
||||
},
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"src",
|
||||
},
|
||||
}
|
590
programs/emulator/uxn/src/kolibri.zig
Normal file
590
programs/emulator/uxn/src/kolibri.zig
Normal file
@@ -0,0 +1,590 @@
|
||||
// SPDX-FileCopyrightText: 2025 iyzsong@envs.net
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
pub const SYS = enum(i32) {
|
||||
terminate_process = -1,
|
||||
create_window = 0,
|
||||
put_pixel = 1,
|
||||
get_key = 2,
|
||||
get_sys_time = 3,
|
||||
draw_text = 4,
|
||||
sleep = 5,
|
||||
put_image = 7,
|
||||
define_button = 8,
|
||||
thread_info = 9,
|
||||
wait_event = 10,
|
||||
check_event = 11,
|
||||
redraw = 12,
|
||||
draw_rect = 13,
|
||||
get_screen_size = 14,
|
||||
get_button = 17,
|
||||
system = 18,
|
||||
screen_put_image = 25,
|
||||
system_get = 26,
|
||||
get_sys_date = 29,
|
||||
mouse_get = 37,
|
||||
set_events_mask = 40,
|
||||
style_settings = 48,
|
||||
create_thread = 51,
|
||||
board = 63,
|
||||
keyboard = 66,
|
||||
change_window = 67,
|
||||
sys_misc = 68,
|
||||
file = 70,
|
||||
blitter = 73,
|
||||
};
|
||||
|
||||
pub const Event = enum(u32) {
|
||||
none = 0,
|
||||
redraw = 1,
|
||||
key = 2,
|
||||
button = 3,
|
||||
background = 5,
|
||||
mouse = 6,
|
||||
ipc = 7,
|
||||
};
|
||||
|
||||
pub inline fn syscall0(number: SYS) u32 {
|
||||
return asm volatile ("int $0x40"
|
||||
: [ret] "={eax}" (-> u32),
|
||||
: [number] "{eax}" (@intFromEnum(number)),
|
||||
);
|
||||
}
|
||||
|
||||
pub inline fn syscall1(number: SYS, arg1: u32) u32 {
|
||||
return asm volatile ("int $0x40"
|
||||
: [ret] "={eax}" (-> u32),
|
||||
: [number] "{eax}" (@intFromEnum(number)),
|
||||
[arg1] "{ebx}" (arg1),
|
||||
);
|
||||
}
|
||||
|
||||
pub inline fn syscall2(number: SYS, arg1: u32, arg2: u32) u32 {
|
||||
return asm volatile ("int $0x40"
|
||||
: [ret] "={eax}" (-> u32),
|
||||
: [number] "{eax}" (@intFromEnum(number)),
|
||||
[arg1] "{ebx}" (arg1),
|
||||
[arg2] "{ecx}" (arg2),
|
||||
);
|
||||
}
|
||||
|
||||
pub inline fn syscall3(number: SYS, arg1: u32, arg2: u32, arg3: u32) u32 {
|
||||
return asm volatile ("int $0x40"
|
||||
: [ret] "={eax}" (-> u32),
|
||||
: [number] "{eax}" (@intFromEnum(number)),
|
||||
[arg1] "{ebx}" (arg1),
|
||||
[arg2] "{ecx}" (arg2),
|
||||
[arg3] "{edx}" (arg3),
|
||||
);
|
||||
}
|
||||
|
||||
pub inline fn syscall4(number: SYS, arg1: u32, arg2: u32, arg3: u32, arg4: u32) u32 {
|
||||
return asm volatile ("int $0x40"
|
||||
: [ret] "={eax}" (-> u32),
|
||||
: [number] "{eax}" (@intFromEnum(number)),
|
||||
[arg1] "{ebx}" (arg1),
|
||||
[arg2] "{ecx}" (arg2),
|
||||
[arg3] "{edx}" (arg3),
|
||||
[arg4] "{esi}" (arg4),
|
||||
);
|
||||
}
|
||||
|
||||
pub inline fn syscall5(number: SYS, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 {
|
||||
return asm volatile ("int $0x40"
|
||||
: [ret] "={eax}" (-> u32),
|
||||
: [number] "{eax}" (@intFromEnum(number)),
|
||||
[arg1] "{ebx}" (arg1),
|
||||
[arg2] "{ecx}" (arg2),
|
||||
[arg3] "{edx}" (arg3),
|
||||
[arg4] "{esi}" (arg4),
|
||||
[arg5] "{edi}" (arg5),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn terminateProcess() noreturn {
|
||||
_ = syscall0(SYS.terminate_process);
|
||||
unreachable;
|
||||
}
|
||||
|
||||
pub const WindowFlags = struct {
|
||||
skinned: bool = true,
|
||||
fixed: bool = true,
|
||||
no_title: bool = false,
|
||||
relative_coordinates: bool = false,
|
||||
no_fill: bool = false,
|
||||
unmovable: bool = false,
|
||||
};
|
||||
|
||||
pub fn createWindow(x: u16, y: u16, width: u16, height: u16, bgcolor: u24, flags: WindowFlags, title: [*:0]const u8) void {
|
||||
var f1: u32 = 0x00000000;
|
||||
if (flags.no_fill)
|
||||
f1 |= 0x40000000;
|
||||
if (flags.relative_coordinates)
|
||||
f1 |= 0x20000000;
|
||||
if (!flags.no_title)
|
||||
f1 |= 0x10000000;
|
||||
if (flags.skinned) {
|
||||
if (flags.fixed) {
|
||||
f1 |= 0x04000000;
|
||||
} else {
|
||||
f1 |= 0x03000000;
|
||||
}
|
||||
} else {
|
||||
f1 |= 0x01000000;
|
||||
}
|
||||
var f2: u32 = 0x00000000;
|
||||
if (flags.unmovable)
|
||||
f2 = 0x01000000;
|
||||
_ = syscall5(SYS.create_window, @as(u32, x) * 0x10000 + width, @as(u32, y) * 0x10000 + height, f1 | @as(u32, bgcolor), f2 | @as(u32, bgcolor), @intFromPtr(title));
|
||||
}
|
||||
|
||||
pub fn putPixel(x: u16, y: u16, color: u24) void {
|
||||
_ = syscall3(SYS.put_pixel, x, y, color);
|
||||
}
|
||||
|
||||
pub fn invertPixel(x: u16, y: u16) void {
|
||||
_ = syscall3(SYS.put_pixel, x, y, 0x01000000);
|
||||
}
|
||||
|
||||
pub const Key = packed struct(u32) {
|
||||
_unused: u8 = 0,
|
||||
key: u8 = 0,
|
||||
scancode: u8 = 0,
|
||||
empty: u8 = 1,
|
||||
|
||||
pub fn pressed(self: *const Key) bool {
|
||||
return self.key & 0x80 > 0;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn getKey() Key {
|
||||
return @bitCast(syscall0(SYS.get_key));
|
||||
}
|
||||
|
||||
pub fn getSysTime() u24 {
|
||||
return @intCast(syscall0(SYS.get_sys_time));
|
||||
}
|
||||
|
||||
pub fn getButton() u32 {
|
||||
return syscall0(SYS.get_button);
|
||||
}
|
||||
|
||||
pub fn terminateThreadId(id: u32) void {
|
||||
_ = syscall2(SYS.system, 18, id);
|
||||
}
|
||||
|
||||
pub fn drawText(x: u16, y: u16, color: u24, text: [*:0]const u8) void {
|
||||
_ = syscall5(SYS.draw_text, @as(u32, x) * 0x10000 + y, 0x80000000 | @as(u32, color), @intFromPtr(text), 0, 0);
|
||||
}
|
||||
|
||||
pub fn sleep(centisecond: u32) void {
|
||||
_ = syscall1(SYS.sleep, centisecond);
|
||||
}
|
||||
|
||||
pub fn beginDraw() void {
|
||||
_ = syscall1(SYS.redraw, 1);
|
||||
}
|
||||
|
||||
pub fn endDraw() void {
|
||||
_ = syscall1(SYS.redraw, 2);
|
||||
}
|
||||
|
||||
pub fn putImage(image: [*]const u8, width: u16, height: u16, x: u16, y: u16) void {
|
||||
_ = syscall3(SYS.put_image, @intFromPtr(image), @as(u32, width) * 0x10000 + height, @as(u32, x) * 0x10000 + y);
|
||||
}
|
||||
|
||||
pub fn drawRect(x: u16, y: u16, width: u16, height: u16, color: u24) void {
|
||||
_ = syscall3(SYS.draw_rect, @as(u32, x) * 0x10000 + width, @as(u32, y) * 0x10000 + height, color);
|
||||
}
|
||||
|
||||
pub fn getScreenSize() packed struct(u32) { height: u16, width: u16 } {
|
||||
return @bitCast(syscall0(SYS.get_screen_size));
|
||||
}
|
||||
|
||||
pub fn waitEvent() Event {
|
||||
return @enumFromInt(syscall0(SYS.wait_event));
|
||||
}
|
||||
|
||||
pub fn checkEvent() Event {
|
||||
return @enumFromInt(syscall0(SYS.check_event));
|
||||
}
|
||||
|
||||
pub fn createThread(entry: *const fn () void, stack: []u8) u32 {
|
||||
return syscall3(SYS.create_thread, 1, @intFromPtr(entry), @intFromPtr(stack.ptr) + stack.len);
|
||||
}
|
||||
|
||||
pub fn debugWrite(byte: u8) void {
|
||||
_ = syscall2(SYS.board, 1, byte);
|
||||
}
|
||||
|
||||
pub fn debugWriteText(bytes: []const u8) void {
|
||||
for (bytes) |byte| {
|
||||
debugWrite(byte);
|
||||
}
|
||||
}
|
||||
|
||||
pub const EventsMask = packed struct(u32) {
|
||||
redraw: bool = true, // 0
|
||||
key: bool = true,
|
||||
button: bool = true,
|
||||
_reserved: bool = false,
|
||||
background: bool = false,
|
||||
mouse: bool = false,
|
||||
ipc: bool = false,
|
||||
network: bool = false,
|
||||
debug: bool = false,
|
||||
_unused: u23 = 0,
|
||||
};
|
||||
|
||||
pub fn setEventsMask(mask: EventsMask) EventsMask {
|
||||
return @bitCast(syscall1(SYS.set_events_mask, @bitCast(mask)));
|
||||
}
|
||||
|
||||
pub fn getSkinHeight() u16 {
|
||||
return @intCast(syscall1(SYS.style_settings, 4));
|
||||
}
|
||||
|
||||
pub fn screenPutImage(image: [*]const u32, width: u16, height: u16, x: u16, y: u16) void {
|
||||
_ = syscall3(SYS.screen_put_image, @intFromPtr(image), @as(u32, width) * 0x10000 + height, @as(u32, x) * 0x10000 + y);
|
||||
}
|
||||
|
||||
pub fn systemGetTimeCount() u32 {
|
||||
return syscall1(SYS.system_get, 9);
|
||||
}
|
||||
|
||||
pub fn getSysDate() u24 {
|
||||
return @intCast(syscall0(SYS.get_sys_date));
|
||||
}
|
||||
|
||||
pub fn mouseGetScreenPosition() packed struct(u32) { y: u16, x: u16 } {
|
||||
return @bitCast(syscall1(SYS.mouse_get, 0));
|
||||
}
|
||||
|
||||
pub fn mouseGetWindowPosition() packed struct(u32) { y: u16, x: u16 } {
|
||||
return @bitCast(syscall1(SYS.mouse_get, 1));
|
||||
}
|
||||
|
||||
pub fn loadCursorIndirect(image: *const [32 * 32]u32, spotx: u5, spoty: u5) u32 {
|
||||
return syscall3(SYS.mouse_get, 4, @intFromPtr(image), 0x0002 | (@as(u32, spotx) << 24) | (@as(u32, spoty) << 16));
|
||||
}
|
||||
|
||||
pub fn setCursor(cursor: u32) u32 {
|
||||
return syscall2(SYS.mouse_get, 5, cursor);
|
||||
}
|
||||
|
||||
pub const MouseEvents = packed struct(u32) {
|
||||
left_hold: bool = false,
|
||||
right_hold: bool = false,
|
||||
middle_hold: bool = false,
|
||||
button4_hold: bool = false,
|
||||
button5_hold: bool = false,
|
||||
_unused0: u3 = 0,
|
||||
left_pressed: bool = false,
|
||||
right_pressed: bool = false,
|
||||
middle_pressed: bool = false,
|
||||
_unused1: u4 = 0,
|
||||
vertical_scroll: bool = false,
|
||||
left_released: bool = false,
|
||||
right_released: bool = false,
|
||||
middle_released: bool = false,
|
||||
_unused2: u4 = 0,
|
||||
horizontal_scroll: bool = false,
|
||||
left_double_clicked: bool = false,
|
||||
_unused3: u7 = 0,
|
||||
};
|
||||
|
||||
pub fn mouseGetEvents() MouseEvents {
|
||||
return @bitCast(syscall1(SYS.mouse_get, 3));
|
||||
}
|
||||
|
||||
pub fn heapInit() u32 {
|
||||
return syscall1(SYS.sys_misc, 11);
|
||||
}
|
||||
|
||||
pub fn memAlloc(size: u32) *anyopaque {
|
||||
return @ptrFromInt(syscall2(SYS.sys_misc, 12, size));
|
||||
}
|
||||
|
||||
pub fn memFree(ptr: *anyopaque) void {
|
||||
_ = syscall2(SYS.sys_misc, 13, @intFromPtr(ptr));
|
||||
}
|
||||
|
||||
pub fn memRealloc(size: u32, ptr: *anyopaque) *anyopaque {
|
||||
return @ptrFromInt(syscall3(SYS.sys_misc, 20, size, @intFromPtr(ptr)));
|
||||
}
|
||||
|
||||
fn alloc(ctx: *anyopaque, len: usize, alignment: std.mem.Alignment, ret_addr: usize) ?[*]u8 {
|
||||
_ = ctx;
|
||||
_ = alignment;
|
||||
_ = ret_addr;
|
||||
return @ptrCast(memAlloc(len));
|
||||
}
|
||||
|
||||
fn free(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, ret_addr: usize) void {
|
||||
_ = ctx;
|
||||
_ = alignment;
|
||||
_ = ret_addr;
|
||||
memFree(@ptrCast(memory.ptr));
|
||||
}
|
||||
|
||||
fn resize(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, new_len: usize, ret_addr: usize) bool {
|
||||
_ = ctx;
|
||||
_ = alignment;
|
||||
_ = ret_addr;
|
||||
_ = memRealloc(new_len, @ptrCast(memory.ptr));
|
||||
return true;
|
||||
}
|
||||
|
||||
fn remap(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, new_len: usize, ret_addr: usize) ?[*]u8 {
|
||||
_ = ctx;
|
||||
_ = memory;
|
||||
_ = alignment;
|
||||
_ = new_len;
|
||||
_ = ret_addr;
|
||||
return null;
|
||||
}
|
||||
|
||||
pub const allocator: std.mem.Allocator = .{
|
||||
.ptr = undefined,
|
||||
.vtable = &.{
|
||||
.alloc = alloc,
|
||||
.free = free,
|
||||
.resize = resize,
|
||||
.remap = remap,
|
||||
},
|
||||
};
|
||||
|
||||
pub fn loadDriver(name: [*:0]const u8) u32 {
|
||||
return syscall2(SYS.sys_misc, 16, @intFromPtr(name));
|
||||
}
|
||||
|
||||
pub fn controlDriver(drv: u32, func: u32, in: ?[]const u32, out: ?[]const *anyopaque) u32 {
|
||||
const ioctl: packed struct(u192) {
|
||||
drv: u32,
|
||||
func: u32,
|
||||
inptr: u32,
|
||||
insize: u32,
|
||||
outptr: u32,
|
||||
outsize: u32,
|
||||
} = .{
|
||||
.drv = drv,
|
||||
.func = func,
|
||||
.inptr = if (in) |v| @intFromPtr(v.ptr) else 0,
|
||||
.insize = if (in) |v| v.len * 4 else 0,
|
||||
.outptr = if (out) |v| @intFromPtr(v.ptr) else 0,
|
||||
.outsize = if (out) |v| v.len * 4 else 0,
|
||||
};
|
||||
return syscall2(SYS.sys_misc, 17, @intFromPtr(&ioctl));
|
||||
}
|
||||
|
||||
pub const Signal = packed struct(u192) {
|
||||
kind: u32,
|
||||
data0: u32,
|
||||
data1: u32,
|
||||
data2: u32,
|
||||
data3: u32,
|
||||
data4: u32,
|
||||
};
|
||||
|
||||
pub fn waitSignal(sig: *Signal) void {
|
||||
_ = syscall2(SYS.sys_misc, 14, @intFromPtr(sig));
|
||||
}
|
||||
|
||||
pub const Sound = struct {
|
||||
drv: u32,
|
||||
|
||||
pub const Buffer = struct {
|
||||
drv: u32,
|
||||
handle: u32,
|
||||
|
||||
pub fn play(self: *const Buffer, flags: u32) void {
|
||||
_ = controlDriver(self.drv, 10, &.{ self.handle, flags }, null);
|
||||
}
|
||||
|
||||
pub fn set(self: *const Buffer, src: []u8, offset: u32) void {
|
||||
_ = controlDriver(self.drv, 8, &.{ self.handle, @intFromPtr(src.ptr), offset, src.len }, null);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn init() Sound {
|
||||
return .{
|
||||
.drv = loadDriver("INFINITY"),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn createBuffer(self: *const Sound, format: u32, size: u32) Buffer {
|
||||
var ret: u32 = 0;
|
||||
_ = controlDriver(self.drv, 1, &.{ format, size }, &.{&ret});
|
||||
return .{
|
||||
.drv = self.drv,
|
||||
.handle = ret,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const InputMode = enum(u32) {
|
||||
normal = 0,
|
||||
scancodes = 1,
|
||||
};
|
||||
|
||||
pub fn setInputMode(mode: InputMode) void {
|
||||
_ = syscall2(SYS.keyboard, 1, @intFromEnum(mode));
|
||||
}
|
||||
|
||||
pub fn changeWindow(x: u32, y: u32, width: u32, height: u32) void {
|
||||
_ = syscall4(SYS.change_window, x, y, width, height);
|
||||
}
|
||||
|
||||
pub const ControlKeys = packed struct(u32) {
|
||||
left_shift: bool,
|
||||
right_shift: bool,
|
||||
left_ctrl: bool,
|
||||
right_ctrl: bool,
|
||||
left_alt: bool,
|
||||
right_alt: bool,
|
||||
caps_lock: bool,
|
||||
num_lock: bool,
|
||||
scroll_lock: bool,
|
||||
left_win: bool,
|
||||
right_win: bool,
|
||||
_unused: u21,
|
||||
};
|
||||
|
||||
pub fn getControlKeys() ControlKeys {
|
||||
return @bitCast(syscall1(SYS.keyboard, 3));
|
||||
}
|
||||
|
||||
const FileInfo = packed struct(u200) {
|
||||
subfn: u32,
|
||||
offset: u64,
|
||||
size: u32,
|
||||
buffer: u32,
|
||||
path0: u8 = 0,
|
||||
pathptr: *const u8,
|
||||
};
|
||||
|
||||
pub fn readFile(pathname: [*:0]const u8, offset: u64, buffer: []u8) !u32 {
|
||||
const info: FileInfo = .{
|
||||
.subfn = 0,
|
||||
.offset = offset,
|
||||
.size = buffer.len,
|
||||
.buffer = @intFromPtr(buffer.ptr),
|
||||
.pathptr = @ptrCast(pathname),
|
||||
};
|
||||
const err = asm volatile ("int $0x40"
|
||||
: [ret] "={eax}" (-> u32),
|
||||
: [number] "{eax}" (SYS.file),
|
||||
[info] "{ebx}" (&info),
|
||||
);
|
||||
const size = asm volatile (""
|
||||
: [ret] "={ebx}" (-> u32),
|
||||
);
|
||||
return switch (err) {
|
||||
0 => size,
|
||||
10 => error.AccessDenied,
|
||||
6 => size,
|
||||
else => error.Unexpected,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn createFile(pathname: [*:0]const u8, buffer: []u8) !u32 {
|
||||
const info: FileInfo = .{
|
||||
.subfn = 2,
|
||||
.offset = 0,
|
||||
.size = buffer.len,
|
||||
.buffer = @intFromPtr(buffer.ptr),
|
||||
.pathptr = @ptrCast(pathname),
|
||||
};
|
||||
const err = asm volatile ("int $0x40"
|
||||
: [ret] "={eax}" (-> u32),
|
||||
: [number] "{eax}" (SYS.file),
|
||||
[info] "{ebx}" (&info),
|
||||
);
|
||||
const size = asm volatile (""
|
||||
: [ret] "={ebx}" (-> u32),
|
||||
);
|
||||
return switch (err) {
|
||||
0 => size,
|
||||
10 => error.AccessDenied,
|
||||
8 => size,
|
||||
else => error.Unexpected,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn writeFile(pathname: [*:0]const u8, offset: u64, buffer: []u8) !u32 {
|
||||
const info: FileInfo = .{
|
||||
.subfn = 3,
|
||||
.offset = offset,
|
||||
.size = buffer.len,
|
||||
.buffer = @intFromPtr(buffer.ptr),
|
||||
.pathptr = @ptrCast(pathname),
|
||||
};
|
||||
const err = asm volatile ("int $0x40"
|
||||
: [ret] "={eax}" (-> u32),
|
||||
: [number] "{eax}" (SYS.file),
|
||||
[info] "{ebx}" (&info),
|
||||
);
|
||||
const size = asm volatile (""
|
||||
: [ret] "={ebx}" (-> u32),
|
||||
);
|
||||
return switch (err) {
|
||||
0 => size,
|
||||
10 => error.AccessDenied,
|
||||
5 => error.FileNotFound,
|
||||
else => error.Unexpected,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deleteFile(pathname: [*:0]const u8) !void {
|
||||
const info: FileInfo = .{
|
||||
.subfn = 8,
|
||||
.offset = 0,
|
||||
.size = 0,
|
||||
.buffer = 0,
|
||||
.pathptr = @ptrCast(pathname),
|
||||
};
|
||||
const err = syscall1(SYS.file, @intFromPtr(&info));
|
||||
if (err != 0)
|
||||
return error.Unexpected;
|
||||
}
|
||||
|
||||
pub const File = struct {
|
||||
pathname: [*:0]const u8,
|
||||
offset: u64 = 0,
|
||||
|
||||
pub fn reader(file: *File) std.io.GenericReader(*File, anyerror, struct {
|
||||
fn read(context: *File, buffer: []u8) !usize {
|
||||
const size = try readFile(context.pathname, context.offset, buffer);
|
||||
context.offset += size;
|
||||
return size;
|
||||
}
|
||||
}.read) {
|
||||
return .{ .context = file };
|
||||
}
|
||||
};
|
||||
|
||||
pub const BlitterFlags = packed struct(u32) {
|
||||
rop: u4 = 0,
|
||||
background: bool = false,
|
||||
transparent: bool = false,
|
||||
reserved1: u23 = 0,
|
||||
client_relative: bool = true,
|
||||
reserved2: u2 = 0,
|
||||
};
|
||||
|
||||
pub fn blitter(dstx: u32, dsty: u32, dstw: u32, dsth: u32, srcx: u32, srcy: u32, srcw: u32, srch: u32, src: *const u8, pitch: u32, flags: BlitterFlags) void {
|
||||
_ = syscall2(SYS.blitter, @bitCast(flags), @intFromPtr(&[_]u32{ dstx, dsty, dstw, dsth, srcx, srcy, srcw, srch, @intFromPtr(src), pitch }));
|
||||
}
|
||||
|
||||
pub const DebugWriter = std.io.GenericWriter(void, anyerror, struct {
|
||||
fn write(context: void, bytes: []const u8) !usize {
|
||||
_ = context;
|
||||
debugWriteText(bytes);
|
||||
return bytes.len;
|
||||
}
|
||||
}.write);
|
||||
|
||||
pub const debug_writer: DebugWriter = .{ .context = {} };
|
42
programs/emulator/uxn/src/linker.ld
Normal file
42
programs/emulator/uxn/src/linker.ld
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 iyzsong@envs.net
|
||||
*
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
.text 0x00000000 :
|
||||
{
|
||||
/* MENUET01 header */
|
||||
LONG(0x554e454d);
|
||||
LONG(0x31305445);
|
||||
LONG(1);
|
||||
LONG(_start);
|
||||
LONG(_image_end);
|
||||
LONG(_memory_end);
|
||||
LONG(_stack_top);
|
||||
LONG(_cmdline);
|
||||
LONG(0);
|
||||
*(.text)
|
||||
*(.text.*)
|
||||
}
|
||||
.rodata : ALIGN(8)
|
||||
{
|
||||
*(.rodata)
|
||||
*(.rodata.*)
|
||||
}
|
||||
.data : ALIGN(8)
|
||||
{
|
||||
*(.data)
|
||||
}
|
||||
_image_end = .;
|
||||
|
||||
.bss : ALIGN(8)
|
||||
{
|
||||
*(.bss)
|
||||
. = . + 4K;
|
||||
_stack_top = .;
|
||||
}
|
||||
_memory_end = .;
|
||||
}
|
417
programs/emulator/uxn/src/main.zig
Normal file
417
programs/emulator/uxn/src/main.zig
Normal file
@@ -0,0 +1,417 @@
|
||||
// SPDX-FileCopyrightText: 2025 iyzsong@envs.net
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
const std = @import("std");
|
||||
const kos = @import("kolibri.zig");
|
||||
const uxn = @import("uxn-core");
|
||||
const varvara = @import("uxn-varvara");
|
||||
|
||||
const allocator = kos.allocator;
|
||||
export var _cmdline: [1024]u8 = undefined;
|
||||
|
||||
pub const std_options: std.Options = .{
|
||||
.log_level = .info,
|
||||
.logFn = struct {
|
||||
fn log(comptime level: std.log.Level, comptime scope: @Type(.enum_literal), comptime format: []const u8, args: anytype) void {
|
||||
_ = level;
|
||||
_ = scope;
|
||||
kos.debug_writer.print(format, args) catch return;
|
||||
}
|
||||
}.log,
|
||||
};
|
||||
|
||||
const VarvaraDefault = varvara.VarvaraSystem(kos.DebugWriter, kos.DebugWriter);
|
||||
const emu = struct {
|
||||
var cpu: uxn.Cpu = undefined;
|
||||
var sys: VarvaraDefault = undefined;
|
||||
var rom: *[0x10000]u8 = undefined;
|
||||
var pixels: []u8 = undefined;
|
||||
var screen_width: u32 = undefined;
|
||||
var screen_height: u32 = undefined;
|
||||
var null_cursor: u32 = undefined;
|
||||
var hide_cursor: bool = false;
|
||||
var audio_thread: ?u32 = null;
|
||||
var scale: u4 = 1;
|
||||
|
||||
fn init(rompath: [*:0]const u8) !void {
|
||||
const screen = &emu.sys.screen_device;
|
||||
var rom_file = kos.File{ .pathname = rompath };
|
||||
emu.rom = try uxn.loadRom(allocator, rom_file.reader());
|
||||
emu.cpu = uxn.Cpu.init(emu.rom);
|
||||
emu.sys = try VarvaraDefault.init(allocator, kos.debug_writer, kos.debug_writer);
|
||||
emu.cpu.device_intercept = struct {
|
||||
var file_offsets: [2]u64 = .{ 0, 0 };
|
||||
|
||||
fn bcd8(x: u8) u8 {
|
||||
return (x & 0xf) + 10 * ((x & 0xf0) >> 4);
|
||||
}
|
||||
|
||||
fn getFilePortSlice(dev: *varvara.file.File, comptime port: comptime_int) []u8 {
|
||||
const ports = varvara.file.ports;
|
||||
const ptr: usize = dev.loadPort(u16, &cpu, port);
|
||||
|
||||
return if (port == ports.name)
|
||||
std.mem.sliceTo(cpu.mem[ptr..], 0x00)
|
||||
else
|
||||
return cpu.mem[ptr..ptr +| dev.loadPort(u16, &cpu, ports.length)];
|
||||
}
|
||||
|
||||
pub fn intercept(self: *uxn.Cpu, addr: u8, kind: uxn.Cpu.InterceptKind, data: ?*anyopaque) !void {
|
||||
_ = data;
|
||||
const port: u4 = @truncate(addr & 0x0f);
|
||||
if (audio_thread == null and addr >= 0x30 and addr < 0x70) {
|
||||
audio_thread = kos.createThread(&audio, allocator.alloc(u8, 32 * 1024) catch unreachable);
|
||||
}
|
||||
switch (addr >> 4) {
|
||||
0xa, 0xb => {
|
||||
if (kind != .output)
|
||||
return;
|
||||
|
||||
const idx = (addr >> 4) - 0xa;
|
||||
const dev = &sys.file_devices[idx];
|
||||
const ports = varvara.file.ports;
|
||||
switch (port) {
|
||||
ports.stat + 1 => {
|
||||
// TODO: file/directory stat
|
||||
dev.storePort(u16, &cpu, ports.success, 0);
|
||||
},
|
||||
ports.delete => {
|
||||
const name_slice = getFilePortSlice(dev, ports.name);
|
||||
_ = kos.deleteFile(@ptrCast(name_slice)) catch {};
|
||||
dev.storePort(u16, &cpu, ports.success, 0);
|
||||
},
|
||||
ports.name + 1 => {
|
||||
file_offsets[idx] = 0;
|
||||
dev.storePort(u16, &cpu, ports.success, 1);
|
||||
},
|
||||
ports.read + 1 => {
|
||||
const name_slice = getFilePortSlice(dev, ports.name);
|
||||
const data_slice = getFilePortSlice(dev, ports.read);
|
||||
const ret: u32 = kos.readFile(@ptrCast(name_slice), file_offsets[idx], data_slice) catch 0;
|
||||
file_offsets[idx] += ret;
|
||||
dev.storePort(u16, &cpu, ports.success, @intCast(ret));
|
||||
},
|
||||
ports.write + 1 => {
|
||||
const append = dev.loadPort(u8, &cpu, ports.append) == 0x01;
|
||||
const pathname: [*:0]const u8 = @ptrCast(getFilePortSlice(dev, ports.name));
|
||||
const buffer = getFilePortSlice(dev, ports.write);
|
||||
var ret: u32 = 0;
|
||||
if (append) {
|
||||
ret = kos.writeFile(pathname, file_offsets[idx], buffer) catch |err| blk: {
|
||||
if (err == error.FileNotFound) {
|
||||
break :blk kos.createFile(pathname, buffer) catch 0;
|
||||
} else {
|
||||
break :blk 0;
|
||||
}
|
||||
};
|
||||
} else {
|
||||
ret = kos.createFile(pathname, buffer) catch 0;
|
||||
}
|
||||
dev.storePort(u16, &cpu, ports.success, @intCast(ret));
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
0xc => {
|
||||
if (kind != .input)
|
||||
return;
|
||||
|
||||
const dev = &sys.datetime_device;
|
||||
const date = kos.getSysDate();
|
||||
const time = kos.getSysTime();
|
||||
switch (port) {
|
||||
0x0, 0x1 => {
|
||||
const year: u8 = bcd8(@truncate(date & 0xff));
|
||||
dev.storePort(u16, &cpu, 0x0, @as(u16, 2000) + bcd8(year));
|
||||
},
|
||||
0x02 => {
|
||||
const month: u8 = bcd8(@truncate((date & 0xff00) >> 8));
|
||||
dev.storePort(u8, &cpu, port, month);
|
||||
},
|
||||
0x03 => {
|
||||
const day: u8 = bcd8(@truncate((date & 0xff0000) >> 16));
|
||||
dev.storePort(u8, &cpu, port, day);
|
||||
},
|
||||
0x04 => {
|
||||
const hour: u8 = bcd8(@truncate(time & 0xff));
|
||||
dev.storePort(u8, &cpu, port, hour);
|
||||
},
|
||||
0x05 => {
|
||||
const minute: u8 = bcd8(@truncate((time & 0xff00) >> 8));
|
||||
dev.storePort(u8, &cpu, port, minute);
|
||||
},
|
||||
0x06 => {
|
||||
const second: u8 = bcd8(@truncate((time & 0xff0000) >> 16));
|
||||
dev.storePort(u8, &cpu, port, second);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
else => try emu.sys.intercept(self, addr, kind),
|
||||
}
|
||||
}
|
||||
}.intercept;
|
||||
emu.cpu.output_intercepts = varvara.full_intercepts.output;
|
||||
emu.cpu.input_intercepts = varvara.full_intercepts.input;
|
||||
|
||||
try emu.cpu.evaluateVector(0x0100);
|
||||
screen_width = screen.width * emu.scale;
|
||||
screen_height = screen.height * emu.scale;
|
||||
emu.pixels = try allocator.alloc(u8, @as(usize, 4) * screen_width * screen_height);
|
||||
}
|
||||
|
||||
fn exit() void {
|
||||
if (audio_thread) |tid| {
|
||||
kos.terminateThreadId(tid);
|
||||
}
|
||||
kos.terminateProcess();
|
||||
}
|
||||
|
||||
fn audio() void {
|
||||
var samples: [8192]i16 = undefined;
|
||||
var sig: kos.Signal = undefined;
|
||||
const buf = kos.Sound.init().createBuffer(3 | 0x10000000, 0);
|
||||
buf.play(0);
|
||||
while (true) {
|
||||
kos.waitSignal(&sig);
|
||||
if (sig.kind != 0xFF000001) continue;
|
||||
@memset(&samples, 0);
|
||||
for (0..samples.len / 512) |i| {
|
||||
const w = samples[i * 512 .. (i + 1) * 512];
|
||||
for (&sys.audio_devices) |*poly| {
|
||||
if (poly.duration <= 0) {
|
||||
poly.evaluateFinishVector(&cpu) catch unreachable;
|
||||
}
|
||||
poly.updateDuration();
|
||||
poly.renderAudio(w);
|
||||
}
|
||||
}
|
||||
for (0..samples.len) |i| {
|
||||
samples[i] <<= 6;
|
||||
}
|
||||
buf.set(@ptrCast(&samples), sig.data2);
|
||||
}
|
||||
}
|
||||
|
||||
fn update() !void {
|
||||
const screen = &sys.screen_device;
|
||||
const colors = &sys.system_device.colors;
|
||||
if (sys.system_device.exit_code) |_| {
|
||||
exit();
|
||||
}
|
||||
if (screen_width != screen.width * scale or screen_height != screen.height * scale) {
|
||||
const skin_height = kos.getSkinHeight();
|
||||
allocator.free(emu.pixels);
|
||||
screen_width = screen.width * scale;
|
||||
screen_height = screen.height * scale;
|
||||
emu.pixels = try allocator.alloc(u8, @as(usize, 4) * screen_width * screen_height);
|
||||
kos.changeWindow(100, 100, screen_width + 9, screen_height + skin_height + 4);
|
||||
}
|
||||
try screen.evaluateFrame(&cpu);
|
||||
if (screen.dirty_region) |region| {
|
||||
for (region.y0..region.y1) |y| {
|
||||
for (region.x0..region.x1) |x| {
|
||||
const idx = y * screen.width + x;
|
||||
const pal = (@as(u4, screen.foreground[idx]) << 2) | screen.background[idx];
|
||||
const color = colors[if ((pal >> 2) > 0) (pal >> 2) else (pal & 0x3)];
|
||||
for (0..scale) |sy| {
|
||||
for (0..scale) |sx| {
|
||||
pixels[4 * ((y * scale + sy) * screen.width * scale + (x * scale + sx)) + 2] = color.r;
|
||||
pixels[4 * ((y * scale + sy) * screen.width * scale + (x * scale + sx)) + 1] = color.g;
|
||||
pixels[4 * ((y * scale + sy) * screen.width * scale + (x * scale + sx)) + 0] = color.b;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const ix = region.x0 * scale;
|
||||
const iy = region.y0 * scale;
|
||||
const iw = (region.x1 - region.x0) * scale;
|
||||
const ih = (region.y1 - region.y0) * scale;
|
||||
kos.blitter(ix, iy, iw, ih, ix, iy, iw, ih, @ptrCast(emu.pixels.ptr), screen.width * scale * 4, .{});
|
||||
screen.dirty_region = null;
|
||||
}
|
||||
}
|
||||
|
||||
fn changeScale() void {
|
||||
const screen = &sys.screen_device;
|
||||
scale = switch (scale) {
|
||||
1 => 2,
|
||||
2 => 3,
|
||||
3 => 1,
|
||||
else => 1,
|
||||
};
|
||||
screen.forceRedraw();
|
||||
}
|
||||
};
|
||||
|
||||
export fn _start() noreturn {
|
||||
const cursor: [32 * 32]u32 = .{0} ** (32 * 32);
|
||||
var counter: u32 = 0;
|
||||
var last_tick = kos.systemGetTimeCount();
|
||||
|
||||
_ = kos.heapInit();
|
||||
_ = kos.setEventsMask(.{ .mouse = true });
|
||||
kos.setInputMode(.scancodes);
|
||||
emu.null_cursor = kos.loadCursorIndirect(&cursor, 0, 0);
|
||||
emu.init(@ptrCast(&_cmdline)) catch unreachable;
|
||||
|
||||
const screen = &emu.sys.screen_device;
|
||||
const controller = &emu.sys.controller_device;
|
||||
|
||||
const callbacks = struct {
|
||||
fn redraw() void {
|
||||
const skin_height = kos.getSkinHeight();
|
||||
kos.beginDraw();
|
||||
kos.createWindow(300, 300, screen.width * emu.scale + 9, screen.height * emu.scale + skin_height + 4, 0x000000, .{ .skinned = true, .no_fill = true, .relative_coordinates = true }, "UXN");
|
||||
kos.endDraw();
|
||||
}
|
||||
|
||||
fn key() void {
|
||||
const symtab: [0x80]u8 = .{
|
||||
// 0x0*
|
||||
0, 27, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 8, '\t',
|
||||
// 0x1*
|
||||
'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\r', 0, 'a', 's',
|
||||
// 0x2*
|
||||
'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', 0, '\\', 'z', 'x', 'c', 'v',
|
||||
// 0x3*
|
||||
'b', 'n', 'm', ',', '.', '/', 0, 0, 0, ' ', 0, 0, 0, 0, 0, 0,
|
||||
// 0x00* + SHIFT
|
||||
0, 27, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', 8, '\t',
|
||||
// 0x10* + SHIFT
|
||||
'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\r', 0, 'A', 'S',
|
||||
// 0x20* + SHIFT
|
||||
'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~', 0, '|', 'Z', 'X', 'C', 'V',
|
||||
// 0x30* + SHIFT,
|
||||
'B', 'N', 'M', '<', '>', '?', 0, 0, 0, ' ', 0, 0, 0, 0, 0, 0,
|
||||
};
|
||||
const scancode1 = kos.getKey().key;
|
||||
const input: ?union(enum) {
|
||||
button: struct {
|
||||
flags: varvara.controller.ButtonFlags,
|
||||
pressed: bool,
|
||||
},
|
||||
key: u8,
|
||||
} = switch (scancode1) {
|
||||
0xe0 => blk: {
|
||||
const scancode2 = kos.getKey().key;
|
||||
break :blk switch (scancode2) {
|
||||
0x1d => .{ .button = .{ .flags = .{ .ctrl = true }, .pressed = true } },
|
||||
0x9d => .{ .button = .{ .flags = .{ .ctrl = true }, .pressed = false } },
|
||||
0x38 => .{ .button = .{ .flags = .{ .alt = true }, .pressed = true } },
|
||||
0xb8 => .{ .button = .{ .flags = .{ .alt = true }, .pressed = false } },
|
||||
0x47 => .{ .button = .{ .flags = .{ .start = true }, .pressed = true } },
|
||||
0xc7 => .{ .button = .{ .flags = .{ .start = true }, .pressed = false } },
|
||||
0x48 => .{ .button = .{ .flags = .{ .up = true }, .pressed = true } },
|
||||
0xc8 => .{ .button = .{ .flags = .{ .up = true }, .pressed = false } },
|
||||
0x50 => .{ .button = .{ .flags = .{ .down = true }, .pressed = true } },
|
||||
0xd0 => .{ .button = .{ .flags = .{ .down = true }, .pressed = false } },
|
||||
0x4b => .{ .button = .{ .flags = .{ .left = true }, .pressed = true } },
|
||||
0xcb => .{ .button = .{ .flags = .{ .left = true }, .pressed = false } },
|
||||
0x4d => .{ .button = .{ .flags = .{ .right = true }, .pressed = true } },
|
||||
0xcd => .{ .button = .{ .flags = .{ .right = true }, .pressed = false } },
|
||||
else => null,
|
||||
};
|
||||
},
|
||||
0x3b => blk: {
|
||||
emu.changeScale();
|
||||
break :blk null;
|
||||
},
|
||||
0x1d => .{ .button = .{ .flags = .{ .ctrl = true }, .pressed = true } },
|
||||
0x9d => .{ .button = .{ .flags = .{ .ctrl = true }, .pressed = false } },
|
||||
0x38 => .{ .button = .{ .flags = .{ .alt = true }, .pressed = true } },
|
||||
0xb8 => .{ .button = .{ .flags = .{ .alt = true }, .pressed = false } },
|
||||
0x2a, 0x36 => .{ .button = .{ .flags = .{ .shift = true }, .pressed = true } },
|
||||
0xaa, 0xb6 => .{ .button = .{ .flags = .{ .shift = true }, .pressed = false } },
|
||||
else => blk: {
|
||||
if (scancode1 > 0x40) {
|
||||
break :blk null;
|
||||
}
|
||||
const ctrls = kos.getControlKeys();
|
||||
const k = if (ctrls.left_shift or ctrls.right_shift) symtab[scancode1 + 0x40] else symtab[scancode1];
|
||||
break :blk if (k > 0) .{ .key = k } else null;
|
||||
},
|
||||
};
|
||||
|
||||
if (input) |v| {
|
||||
switch (v) {
|
||||
.button => {
|
||||
if (v.button.pressed) {
|
||||
controller.pressButtons(&emu.cpu, v.button.flags, 0) catch unreachable;
|
||||
} else {
|
||||
controller.releaseButtons(&emu.cpu, v.button.flags, 0) catch unreachable;
|
||||
}
|
||||
},
|
||||
.key => {
|
||||
controller.pressKey(&emu.cpu, v.key) catch unreachable;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn button() void {
|
||||
const btn = kos.getButton();
|
||||
if (btn >> 8 == 1)
|
||||
emu.exit();
|
||||
}
|
||||
|
||||
fn mouse() void {
|
||||
const dev = &emu.sys.mouse_device;
|
||||
const pos = kos.mouseGetWindowPosition();
|
||||
const events = kos.mouseGetEvents();
|
||||
const mouse_pressed: varvara.mouse.ButtonFlags = .{
|
||||
.left = events.left_pressed,
|
||||
.middle = events.middle_pressed,
|
||||
.right = events.right_pressed,
|
||||
._unused = 0,
|
||||
};
|
||||
const mouse_released: varvara.mouse.ButtonFlags = .{
|
||||
.left = events.left_released,
|
||||
.middle = events.middle_released,
|
||||
.right = events.right_released,
|
||||
._unused = 0,
|
||||
};
|
||||
if (emu.hide_cursor and pos.y > emu.screen_height) {
|
||||
_ = kos.setCursor(0);
|
||||
emu.hide_cursor = false;
|
||||
}
|
||||
if (!emu.hide_cursor and pos.y < emu.screen_height) {
|
||||
_ = kos.setCursor(emu.null_cursor);
|
||||
emu.hide_cursor = true;
|
||||
}
|
||||
dev.updatePosition(&emu.cpu, pos.x / emu.scale, pos.y / emu.scale) catch unreachable;
|
||||
if (@as(u8, @bitCast(mouse_pressed)) > 0) {
|
||||
dev.pressButtons(&emu.cpu, mouse_pressed) catch unreachable;
|
||||
}
|
||||
if (@as(u8, @bitCast(mouse_released)) > 0) {
|
||||
dev.releaseButtons(&emu.cpu, mouse_released) catch unreachable;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
callbacks.redraw();
|
||||
while (true) {
|
||||
while (true) {
|
||||
const event = kos.checkEvent();
|
||||
switch (event) {
|
||||
.none => break,
|
||||
.redraw => callbacks.redraw(),
|
||||
.key => callbacks.key(),
|
||||
.button => callbacks.button(),
|
||||
.mouse => callbacks.mouse(),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
const tick = kos.systemGetTimeCount();
|
||||
counter += (tick - last_tick) * 3;
|
||||
last_tick = tick;
|
||||
|
||||
if (counter > 5) {
|
||||
counter -= 5;
|
||||
emu.update() catch unreachable;
|
||||
} else {
|
||||
kos.sleep(1);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user