Significantly improve consolehook (Amiga console.device emulation) for headless/CLI use:
- Add thread-safe input ring buffer and input thread for non-blocking reads.
- Implement graceful shutdown of input thread on reset/quit.
- Allow custom root path via new `-cli[=path]` command-line option.
- Update emulation prefs for better compatibility and expand ROM selection.
- Normalize console output, add safety checks, and update API signatures.
- Export `set_console_input_mode` for Windows.
- Improve GitHub Actions workflow for Windows builds.
These changes make console emulation more robust, responsive, and configurable.
+@@ -0,0 +1,146 @@
name: Build WinUAE binary
on:
workflow_dispatch:
push:
- branches:
+ branches:
- master
pull_request:
branches:
- master
-
+
env:
SOLUTION_FILE_PATH: od-win32\\winuae_msvc15
jobs:
Build-WinUAE-32bit-binary:
- runs-on: windows-latest
+ runs-on: windows-2026
steps:
- - uses: actions/checkout@v4
-
- - name: Add MSBuild to PATH
- uses: microsoft/setup-msbuild@v2
- with:
- vs-version: '[18.0]'
-
- # Running roughly step 4 of README.md
- - name: Download WinUAE includes and libs
- shell: powershell
- run: Invoke-WebRequest -Uri "https://download.abime.net/winuae/files/b/winuaeinclibs.zip" -OutFile "winuaeinclibs.zip"
-
- - name: Unpack WinUAE includes and libs to C:\\dev
- uses: ihiroky/extract-action@v1
- with:
- file_path: winuaeinclibs.zip
- extract_dir: C:\\dev
-
- # Running roughly step 6 of README.md
- - name: Download AROS ROM cpp
- shell: powershell
- run: Invoke-WebRequest -Uri "https://download.abime.net/winuae/files/b/aros.rom.cpp.zip" -OutFile "aros.rom.cpp.zip"
-
- - name: Unpack AROS ROM cpp
- uses: ihiroky/extract-action@v1
- with:
- file_path: aros.rom.cpp.zip
- extract_dir: .
-
- # Running roughly step 7 of README.md
- - name: Add NASM to PATH
- uses: ilammy/setup-nasm@v1.5.1
-
- # Running roughly step 12 of README.md
- - name: Build Win32 FullRelease
- working-directory: ${{env.GITHUB_WORKSPACE}}
- # Add additional options to the MSBuild command line here (like platform or verbosity level).
- # See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference
- run: msbuild /m /p:Platform=Win32 /p:Configuration=FullRelease ${{env.SOLUTION_FILE_PATH}}
-
- - uses: actions/upload-artifact@v4
- with:
- name: WinUAE 32-bit
- path: D:\\Amiga
+ - uses: actions/checkout@v4
+
+ - name: Diagnostics (VS instances)
+ shell: powershell
+ run: |
+ if (Test-Path "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe") {
+ & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -all -prerelease -products * -format json
+ } elseif (Get-Command vswhere.exe -ErrorAction SilentlyContinue) {
+ vswhere.exe -all -prerelease -products * -format json
+ } else {
+ Write-Host "vswhere.exe not found"
+ }
+
+ - name: Add MSBuild to PATH
+ uses: microsoft/setup-msbuild@v2
+ with:
+ vs-version: '18.0'
+
+ - name: Diagnostics (MSBuild)
+ shell: powershell
+ run: |
+ Get-Command msbuild -ErrorAction SilentlyContinue | Format-List *
+ msbuild -version
+
+ # Running roughly step 4 of README.md
+ - name: Download WinUAE includes and libs
+ shell: powershell
+ run: Invoke-WebRequest -Uri "https://download.abime.net/winuae/files/b/winuaeinclibs.zip" -OutFile "winuaeinclibs.zip"
+
+ - name: Unpack WinUAE includes and libs to C:\\dev
+ uses: ihiroky/extract-action@v1
+ with:
+ file_path: winuaeinclibs.zip
+ extract_dir: C:\\dev
+
+ # Running roughly step 6 of README.md
+ - name: Download AROS ROM cpp
+ shell: powershell
+ run: Invoke-WebRequest -Uri "https://download.abime.net/winuae/files/b/aros.rom.cpp.zip" -OutFile "aros.rom.cpp.zip"
+
+ - name: Unpack AROS ROM cpp
+ uses: ihiroky/extract-action@v1
+ with:
+ file_path: aros.rom.cpp.zip
+ extract_dir: .
+
+ # Running roughly step 7 of README.md
+ - name: Add NASM to PATH
+ uses: ilammy/setup-nasm@v1.5.1
+
+ # Running roughly step 12 of README.md
+ - name: Build Win32 FullRelease
+ working-directory: ${{env.GITHUB_WORKSPACE}}
+ # Add additional options to the MSBuild command line here (like platform or verbosity level).
+ # See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference
+ run: msbuild /m /p:Platform=Win32 /p:Configuration=FullRelease ${{env.SOLUTION_FILE_PATH}}
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: WinUAE 32-bit
+ path: D:\\Amiga
Build-WinUAE-64bit-binary:
- runs-on: windows-latest
+ runs-on: windows-2026
steps:
- - uses: actions/checkout@v4
-
- - name: Add MSBuild to PATH
- uses: microsoft/setup-msbuild@v2
- with:
- vs-version: '[18.0]'
-
- # Running roughly step 4 of README.md
- - name: Download WinUAE includes and libs
- shell: powershell
- run: Invoke-WebRequest -Uri "https://download.abime.net/winuae/files/b/winuaeinclibs.zip" -OutFile "winuaeinclibs.zip"
-
- - name: Unpack WinUAE includes and libs to C:\\dev
- uses: ihiroky/extract-action@v1
- with:
- file_path: winuaeinclibs.zip
- extract_dir: C:\\dev
-
- # Running roughly step 6 of README.md
- - name: Download AROS ROM cpp
- shell: powershell
- run: Invoke-WebRequest -Uri "https://download.abime.net/winuae/files/b/aros.rom.cpp.zip" -OutFile "aros.rom.cpp.zip"
-
- - name: Unpack AROS ROM cpp
- uses: ihiroky/extract-action@v1
- with:
- file_path: aros.rom.cpp.zip
- extract_dir: .
-
- # Running roughly step 7 of README.md
- - name: Add NASM to PATH
- uses: ilammy/setup-nasm@v1.5.1
-
- # Running roughly step 12 of README.md
- - name: Build x64 FullRelease
- working-directory: ${{env.GITHUB_WORKSPACE}}
- # Add additional options to the MSBuild command line here (like platform or verbosity level).
- # See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference
- run: msbuild /m /p:Platform=x64 /p:Configuration=FullRelease ${{env.SOLUTION_FILE_PATH}}
-
- - uses: actions/upload-artifact@v4
- with:
- name: WinUAE 64-bit
- path: D:\\Amiga
+ - uses: actions/checkout@v4
+
+ - name: Diagnostics (VS instances)
+ shell: powershell
+ run: |
+ if (Test-Path "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe") {
+ & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -all -prerelease -products * -format json
+ } elseif (Get-Command vswhere.exe -ErrorAction SilentlyContinue) {
+ vswhere.exe -all -prerelease -products * -format json
+ } else {
+ Write-Host "vswhere.exe not found"
+ }
+
+ - name: Add MSBuild to PATH
+ uses: microsoft/setup-msbuild@v2
+ with:
+ vs-version: '18.0'
+
+ - name: Diagnostics (MSBuild)
+ shell: powershell
+ run: |
+ Get-Command msbuild -ErrorAction SilentlyContinue | Format-List *
+ msbuild -version
+
+ # Running roughly step 4 of README.md
+ - name: Download WinUAE includes and libs
+ shell: powershell
+ run: Invoke-WebRequest -Uri "https://download.abime.net/winuae/files/b/winuaeinclibs.zip" -OutFile "winuaeinclibs.zip"
+
+ - name: Unpack WinUAE includes and libs to C:\\dev
+ uses: ihiroky/extract-action@v1
+ with:
+ file_path: winuaeinclibs.zip
+ extract_dir: C:\\dev
+
+ # Running roughly step 6 of README.md
+ - name: Download AROS ROM cpp
+ shell: powershell
+ run: Invoke-WebRequest -Uri "https://download.abime.net/winuae/files/b/aros.rom.cpp.zip" -OutFile "aros.rom.cpp.zip"
+
+ - name: Unpack AROS ROM cpp
+ uses: ihiroky/extract-action@v1
+ with:
+ file_path: aros.rom.cpp.zip
+ extract_dir: .
+
+ # Running roughly step 7 of README.md
+ - name: Add NASM to PATH
+ uses: ilammy/setup-nasm@v1.5.1
+
+ # Running roughly step 12 of README.md
+ - name: Build x64 FullRelease
+ working-directory: ${{env.GITHUB_WORKSPACE}}
+ # Add additional options to the MSBuild command line here (like platform or verbosity level).
+ # See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference
+ run: msbuild /m /p:Platform=x64 /p:Configuration=FullRelease ${{env.SOLUTION_FILE_PATH}}
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: WinUAE 64-bit
+ path: D:\\Amiga
-
-
#include "sysconfig.h"
#include "sysdeps.h"
#include "rommgr.h"
#include "uae.h"
#include "threaddep/thread.h"
-#include "keybuf.h"
-
+#include "fsdb.h"
+#include "custom.h"
#include "consolehook.h"
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <unistd.h>
+#endif
+
+extern int consoleopen;
+
+#define CONSOLEHOOK_DEBUG 0
+
static uaecptr beginio;
-void consolehook_config (struct uae_prefs *p)
+static uae_sem_t console_in_sem;
+static uae_atomic console_in_init;
+static uae_atomic console_thread_started;
+static uae_atomic console_stop;
+
+static unsigned char console_in_buf[8192];
+static volatile uae_u32 console_in_rp;
+static volatile uae_u32 console_in_wp;
+
+static void console_in_init_once(void)
{
- struct uaedev_config_info ci = { 0 };
- int roms[] = { 15, 31, 16, 46, -1 };
+ if (console_in_init)
+ return;
+ console_in_init = 1;
+ uae_sem_init(&console_in_sem, 0, 1);
+ console_in_rp = console_in_wp = 0;
+}
+static uae_u32 console_in_avail_nolock(void)
+{
+ return (console_in_wp - console_in_rp) & (sizeof console_in_buf - 1);
+}
+
+static void console_in_push(unsigned char b)
+{
+ uae_sem_wait(&console_in_sem);
+ uae_u32 next = (console_in_wp + 1) & (sizeof console_in_buf - 1);
+ if (next != console_in_rp) {
+ console_in_buf[console_in_wp] = b;
+ console_in_wp = next;
+ }
+ uae_sem_post(&console_in_sem);
+#if CONSOLEHOOK_DEBUG
+ write_log(_T("[consolehook] push %02X avail=%u\n"), b, console_in_avail_nolock());
+#endif
+}
+
+static uae_u32 console_in_pop(unsigned char *dst, uae_u32 maxlen)
+{
+ uae_u32 got = 0;
+ uae_sem_wait(&console_in_sem);
+ while (got < maxlen && console_in_rp != console_in_wp) {
+ dst[got++] = console_in_buf[console_in_rp];
+ console_in_rp = (console_in_rp + 1) & (sizeof console_in_buf - 1);
+ }
+ uae_sem_post(&console_in_sem);
+ return got;
+}
+
+/* CMD_READ must not block: it is called on the emulation thread. */
+
+void consolehook_shutdown(void)
+{
+ console_stop = 1;
+}
+
+void consolehook_config (struct uae_prefs *p, const TCHAR *custom_path)
+{
+ struct uaedev_config_info ci = { 0 };
+ int roms[8];
+ roms[0] = 11;
+ roms[1] = 15;
+#ifdef AMIBERRY
+ roms[2] = 276;
+ roms[3] = 281;
+ roms[4] = 286;
+ roms[5] = 291;
+ roms[6] = 304;
+ roms[7] = -1;
+#endif
+
+ consoleopen = 0;
default_prefs (p, true, 0);
//p->headless = 1;
p->produce_sound = 0;
configure_rom (p, roms, 0);
p->cpu_model = 68020;
p->fpu_model = 68882;
+ p->chipset_mask = CSMASK_AGA | CSMASK_ECS_AGNUS | CSMASK_ECS_DENISE;
+ p->cpu_compatible = p->address_space_24 = false;
p->m68k_speed = -1;
- p->cachesize = 8192;
- p->cpu_compatible = 0;
- p->address_space_24 = 0;
- p->chipmem.size = 0x00200000;
+#ifdef JIT
+ p->cachesize = 16384;
+#else
+ p->cachesize = 0;
+#endif
+ p->chipmem.size = 0x200000;
p->fastmem[0].size = 0x00800000;
p->bogomem.size = 0;
p->nr_floppies = 1;
p->turbo_emulation = 0;
//p->win32_automount_drives = 2;
//p->win32_automount_cddrives = 2;
-
- _tcscpy (ci.rootdir, _T("."));
+ p->cs_compatible = CP_A1200;
+ built_in_chipset_prefs (p);
+
+ uci_set_defaults(&ci, false);
+ if (custom_path && custom_path[0]) {
+ // Use the custom path provided via command line
+ my_canonicalize_path(custom_path, ci.rootdir, MAX_DPATH);
+ } else {
+ // Use current working directory as before
+ TCHAR cwd[MAX_DPATH];
+ cwd[0] = 0;
+#ifdef _WIN32
+ GetCurrentDirectory(MAX_DPATH, cwd);
+#else
+ getcwd(cwd, MAX_DPATH);
+#endif
+ if (cwd[0])
+ my_canonicalize_path(cwd, ci.rootdir, MAX_DPATH);
+ else
+ _tcscpy(ci.rootdir, _T("."));
+ }
_tcscpy (ci.volname, _T("CLIBOOT"));
_tcscpy (ci.devname, _T("DH0"));
ci.bootpri = 15;
static void console_thread (void *v)
{
uae_set_thread_priority (NULL, 1);
+ console_in_init_once();
+ set_console_input_mode(0);
for (;;) {
+ if (console_stop)
+ break;
TCHAR wc = console_getch ();
- char c[2];
-
- c[0] = 0;
- c[1] = 0;
- ua_copy (c, 1, &wc);
- record_key_direct((0x10 << 1) | 0, false);
- record_key_direct((0x10 << 1) | 1, false);
+ if (!wc)
+ continue;
+ char c = (char)wc;
+ /* Normalize host console keys to what Amiga console.device expects. */
+ if (c == '\r')
+ c = '\n';
+ console_in_push((unsigned char)c);
}
+ set_console_input_mode(1);
+ console_thread_started = 0;
}
int consolehook_activate (void)
{
beginio = oldbeginio;
write_log (_T("console.device at %08X\n"), condev);
-
- uae_start_thread (_T("consolereader"), console_thread, NULL, NULL);
+ open_console();
+ console_in_init_once();
+ if (!console_thread_started) {
+ console_thread_started = 1;
+ console_stop = 0;
+ uae_start_thread (_T("consolereader"), console_thread, NULL, NULL);
+ }
}
uaecptr consolehook_beginio(TrapContext *ctx, uaecptr request)
{
uae_u32 io_data = trap_get_long(ctx, request + 40); // 0x28
uae_u32 io_length = trap_get_long(ctx, request + 36); // 0x24
- uae_u32 io_actual = trap_get_long(ctx, request + 32); // 0x20
- uae_u32 io_offset = trap_get_long(ctx, request + 44); // 0x2c
+ (void)trap_get_long(ctx, request + 32); // io_actual (unused)
+ (void)trap_get_long(ctx, request + 44); // io_offset (unused)
uae_u16 cmd = trap_get_word(ctx, request + 28);
if (cmd == CMD_WRITE) {
- int len = io_length;
- char *dbuf;
- TCHAR *buf;
- if (len == -1) {
- dbuf = xmalloc(char, MAX_DPATH);
- trap_get_string(ctx, dbuf, io_data, MAX_DPATH);
- len = uaestrlen(dbuf);
- } else {
- dbuf = xmalloc(char, len);
+ open_console();
+ int len = (int)io_length;
+ if (len < 0)
+ len = 0;
+ if (len > 1024 * 1024)
+ len = 1024 * 1024;
+ if (len > 0 && io_data) {
+ char *dbuf = xmalloc(char, len);
+ if (!dbuf)
+ return beginio;
trap_get_bytes(ctx, dbuf, io_data, len);
+
+ for (int i = 0; i < len; i++) {
+ TCHAR ch = (TCHAR)dbuf[i];
+ if (ch == '\n')
+ console_out_f(_T("\r"));
+ console_out_f(_T("%c"), ch);
+ }
+ console_flush();
+ xfree(dbuf);
}
- buf = xmalloc(TCHAR, len + 1);
- au_copy(buf, len, dbuf);
- buf[len] = 0;
- f_out(stdout, _T("%s"), buf);
- xfree(buf);
- xfree(dbuf);
} else if (cmd == CMD_READ) {
-
- write_log (_T("%08x: CMD=%d LEN=%d OFF=%d ACT=%d\n"), request, cmd, io_length, io_offset, io_actual);
-
+ console_in_init_once();
+ uae_u32 maxlen = io_length;
+ if ((uae_s32)maxlen < 0)
+ maxlen = 0;
+ if (maxlen > 4096)
+ maxlen = 4096;
+ unsigned char tmp[4096];
+ uae_u32 got = 0;
+ if (maxlen && io_data) // Fix: check io_data (buf) is not NULL before use
+ got = console_in_pop(tmp, maxlen);
+#if CONSOLEHOOK_DEBUG
+ write_log(_T("[consolehook] CMD_READ max=%u got=%u\n"), maxlen, got);
+#endif
+ if (got && io_data) // Fix: check io_data (buf) is not NULL before use
+ trap_put_bytes(ctx, tmp, io_data, got);
+ trap_put_long(ctx, request + 32, got); // io_actual
}
return beginio;
}
int consolehook_activate(void);
void consolehook_ret(TrapContext *ctx, uaecptr condev, uaecptr oldbeginio);
uaecptr consolehook_beginio(TrapContext *ctx, uaecptr request);
-void consolehook_config(struct uae_prefs *p);
+void consolehook_config(struct uae_prefs *p, const TCHAR *custom_path);
+void consolehook_shutdown(void);
#endif /* UAE_CONSOLEHOOK_H */
extern void reopen_console(void);
extern void activate_console(void);
extern void deactivate_console(void);
+extern void set_console_input_mode(int);
extern void console_out (const TCHAR *);
extern void console_out_f (const TCHAR *, ...);
extern void console_flush (void);
bool cloanto_rom = 0;
bool kickstart_rom = 1;
bool console_emulation = 0;
+TCHAR console_path[MAX_DPATH] = { 0 };
struct gui_info gui_data;
currprefs.quitstatefile[0] = changed_prefs.quitstatefile[0] = 0;
if (quit_program == 0) {
+ consolehook_shutdown();
quit_program = -UAE_RESET;
if (keyboardreset)
quit_program = -UAE_RESET_KEYBOARD;
#ifdef DEBUGGER
deactivate_debugger ();
#endif
+ consolehook_shutdown();
if (quit_program != -UAE_QUIT)
quit_program = -UAE_QUIT;
target_quit ();
started = true;
for (i = 1; i < argc; i++) {
+ if (_tcsncmp(argv[i], _T("-cli="), 5) == 0 || _tcsncmp(argv[i], _T("--cli="), 6) == 0) {
+ console_emulation = 1;
+ TCHAR *path = _tcschr(argv[i], _T('=')) + 1;
+ if (path && path[0]) {
+ _tcsncpy(console_path, path, MAX_DPATH - 1);
+ console_path[MAX_DPATH - 1] = 0;
+ }
+ continue;
+ }
+ if (_tcscmp(argv[i], _T("-cli")) == 0 || _tcscmp(argv[i], _T("--cli")) == 0) {
+ console_emulation = 1;
+ // Check if next argument is a path (not starting with '-')
+ if (i + 1 < argc && argv[i + 1][0] != _T('-')) {
+ i++;
+ _tcsncpy(console_path, argv[i], MAX_DPATH - 1);
+ console_path[MAX_DPATH - 1] = 0;
+ }
+ continue;
+ }
if (!_tcsncmp (argv[i], _T("-diskswapper="), 13)) {
TCHAR *txt = parsetextpath (argv[i] + 13);
parse_diskswapper (txt);
}
if (console_emulation) {
- consolehook_config (&currprefs);
+ consolehook_config (&currprefs, console_path[0] ? console_path : NULL);
fixup_prefs (&currprefs, true);
}
return GetConsoleWindow ();
}
-static void set_console_input_mode(int line)
+void set_console_input_mode(int line)
{
if (console_input_linemode < 0)
return;