Compare commits
5
Commits
b77449faeb
...
a06ef0192d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a06ef0192d | ||
|
|
e8407afcdb | ||
|
|
fe10216f8b | ||
|
|
53a9965e7c | ||
|
|
27c13b8dc2 |
+26
@@ -181,6 +181,20 @@ kernel: kernel-deps
|
||||
programs:
|
||||
$(MAKE) -C programs
|
||||
|
||||
# Build out-of-tree ports (~/dev/mtkports) and stage their app bundles into
|
||||
# programs/bin/apps/. Opt-in: the base ISO ships only what lives in
|
||||
# programs/src/, so this is never a dependency of the default build. Run it
|
||||
# before 'make ramdisk' to include ports in the image.
|
||||
MTKPORTS ?= $(HOME)/dev/mtkports
|
||||
|
||||
.PHONY: ports
|
||||
ports: programs
|
||||
@if [ -d "$(MTKPORTS)" ]; then \
|
||||
$(MAKE) -C "$(MTKPORTS)" install MONTAUKOS="$(CURDIR)"; \
|
||||
else \
|
||||
echo "ports: $(MTKPORTS) not found - skipping (set MTKPORTS=/path/to/mtkports)"; \
|
||||
fi
|
||||
|
||||
.PHONY: ramdisk
|
||||
ramdisk: limine/limine kernel programs
|
||||
mkdir -p programs/bin/boot/limine
|
||||
@@ -245,6 +259,18 @@ release: limine/limine kernel ramdisk
|
||||
cp kernel/bin-$(ARCH)/kernel iso_root/boot/
|
||||
strip iso_root/boot/kernel
|
||||
cp -r programs/bin .release-tmp
|
||||
# A release ISO ships only in-tree apps, so drop any out-of-tree port bundles
|
||||
# that happen to be staged in programs/bin/apps (each carries a .port marker).
|
||||
# Pass RELEASE_WITH_PORTS=1 to deliberately cut a ports-included image.
|
||||
ifneq ($(RELEASE_WITH_PORTS),1)
|
||||
@for m in $$(find .release-tmp/apps -name .port 2>/dev/null); do \
|
||||
d=$$(dirname $$m); \
|
||||
echo "release: excluding port bundle $$(basename $$d)"; \
|
||||
rm -rf "$$d"; \
|
||||
done
|
||||
else
|
||||
@echo "release: RELEASE_WITH_PORTS=1 - including out-of-tree port bundles"
|
||||
endif
|
||||
find .release-tmp -name '*.elf' -exec strip {} +
|
||||
./scripts/mkramdisk.sh .release-tmp iso_root/boot/ramdisk.tar
|
||||
rm -rf .release-tmp
|
||||
|
||||
-1
Submodule doomgeneric deleted from fc60163949
@@ -12,4 +12,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define MONTAUK_BUILD_NUMBER 89
|
||||
#define MONTAUK_BUILD_NUMBER 92
|
||||
|
||||
@@ -15,9 +15,14 @@ namespace montauk::abi {
|
||||
static bool Sys_IsKeyAvailable() {
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
if (proc && proc->redirected) {
|
||||
Ipc::HandleSnapshot snapshot;
|
||||
Ipc::Mailbox* mailbox = GetRedirKeyMailbox(proc, snapshot);
|
||||
if (mailbox != nullptr) return Ipc::MailboxHasMessage(mailbox);
|
||||
Ipc::HandleSnapshot inputSnapshot;
|
||||
Ipc::HandleSnapshot mailboxSnapshot;
|
||||
Ipc::Stream* input = GetRedirInStream(proc, inputSnapshot);
|
||||
Ipc::Mailbox* mailbox = GetRedirKeyMailbox(proc, mailboxSnapshot);
|
||||
if (input != nullptr || mailbox != nullptr) {
|
||||
if (input != nullptr && Ipc::StreamHasData(input)) return true;
|
||||
return mailbox != nullptr && Ipc::MailboxHasMessage(mailbox);
|
||||
}
|
||||
}
|
||||
return Drivers::PS2::Keyboard::IsKeyAvailable();
|
||||
}
|
||||
@@ -97,4 +102,41 @@ namespace montauk::abi {
|
||||
}
|
||||
return Drivers::PS2::Keyboard::GetChar();
|
||||
}
|
||||
|
||||
/*
|
||||
* Non-blocking counterpart of Sys_GetChar: returns the next character, or
|
||||
* 0 when none is pending. IsKeyAvailable is not enough to make GetChar
|
||||
* safe to call -- a queued key-release or modifier event reports as
|
||||
* "available" but carries no ascii, and GetChar would go on to block. So
|
||||
* the drain has to happen on this side of the syscall boundary.
|
||||
*/
|
||||
static char Sys_GetCharNb() {
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
if (proc && proc->redirected) {
|
||||
Ipc::HandleSnapshot inputSnapshot;
|
||||
Ipc::HandleSnapshot mailboxSnapshot;
|
||||
Ipc::Stream* input = GetRedirInStream(proc, inputSnapshot);
|
||||
Ipc::Mailbox* mailbox = GetRedirKeyMailbox(proc, mailboxSnapshot);
|
||||
if (input != nullptr || mailbox != nullptr) {
|
||||
if (input != nullptr) {
|
||||
uint8_t c = 0;
|
||||
if (Ipc::StreamRead(input, &c, 1, true) > 0) return (char)c;
|
||||
}
|
||||
|
||||
while (mailbox != nullptr) {
|
||||
KeyEvent ev{};
|
||||
uint16_t len = sizeof(ev);
|
||||
if (Ipc::MailboxRecv(mailbox, nullptr, &ev, &len, true) <= 0) break;
|
||||
if (ev.pressed && ev.ascii != 0) return ev.ascii;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
while (Drivers::PS2::Keyboard::IsKeyAvailable()) {
|
||||
auto k = Drivers::PS2::Keyboard::GetKey();
|
||||
if (k.Pressed && k.Ascii != 0) return k.Ascii;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -134,6 +134,8 @@ namespace montauk::abi {
|
||||
return 0;
|
||||
case SYS_GETCHAR:
|
||||
return (int64_t)Sys_GetChar();
|
||||
case SYS_GETCHAR_NB:
|
||||
return (int64_t)Sys_GetCharNb();
|
||||
case SYS_PING:
|
||||
return (int64_t)Sys_Ping((uint32_t)frame->arg1, (uint32_t)frame->arg2);
|
||||
case SYS_SPAWN:
|
||||
|
||||
@@ -307,6 +307,9 @@ namespace montauk::abi {
|
||||
static constexpr uint64_t SYS_WIFI_CONNECT_ASYNC = 164; // (ssid, password) -> 0 accepted, <0 on error
|
||||
static constexpr uint64_t SYS_NETIFS = 165; // (NetIfInfo*, maxCount) -> count
|
||||
|
||||
/* Keyboard.hpp */
|
||||
static constexpr uint64_t SYS_GETCHAR_NB = 166; // () -> ascii, 0 if nothing pending; never blocks
|
||||
|
||||
// Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM).
|
||||
static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz
|
||||
static constexpr int SDR_PARAM_SAMPLE_RATE = 1; // sample rate, Hz
|
||||
|
||||
+4
-10
@@ -49,7 +49,7 @@ BINDIR := bin
|
||||
PROGRAMS := $(notdir $(wildcard src/*))
|
||||
|
||||
# Programs with custom Makefiles (built separately).
|
||||
CUSTOM_BUILDS := 2048 doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display terminal klog procmgr powermgr calculator charmap desktop login shell paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs libloader crashpad
|
||||
CUSTOM_BUILDS := 2048 fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display terminal klog procmgr powermgr calculator charmap desktop login shell paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs libloader crashpad
|
||||
SYSTEM_PROGRAMS := $(filter-out $(CUSTOM_BUILDS),$(PROGRAMS))
|
||||
|
||||
# Build targets: system programs go to bin/os/, apps go to bin/apps/<name>/.
|
||||
@@ -89,9 +89,9 @@ WPDIR := data/wallpapers
|
||||
WPSRC := $(wildcard $(WPDIR)/*.jpg)
|
||||
WPDST := $(patsubst $(WPDIR)/%,$(BINDIR)/os/wallpapers/%,$(WPSRC))
|
||||
|
||||
.PHONY: all clean 2048 doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display terminal klog procmgr powermgr calculator charmap login desktop shell paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs icons fonts configs bearssl libc tls libjpeg libjpegwrite install-apps libloader crashpad check-syscalls gen-syscalls
|
||||
.PHONY: all clean 2048 fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display terminal klog procmgr powermgr calculator charmap login desktop shell paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs icons fonts configs bearssl libc tls libjpeg libjpegwrite install-apps libloader crashpad check-syscalls gen-syscalls
|
||||
|
||||
all: bearssl libc libjpeg libjpegwrite tls libloader devkit $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display terminal klog procmgr powermgr calculator charmap 2048 doom paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs login desktop shell icons fonts install-apps crashpad $(MANDST) $(WWWDST) $(CA_CERTS) $(CONFIGDST) $(FWDST) $(LICDST) $(WPDST)
|
||||
all: bearssl libc libjpeg libjpegwrite tls libloader devkit $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display terminal klog procmgr powermgr calculator charmap 2048 paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs login desktop shell icons fonts install-apps crashpad $(MANDST) $(WWWDST) $(CA_CERTS) $(CONFIGDST) $(FWDST) $(LICDST) $(WPDST)
|
||||
|
||||
# Build BearSSL static library (cross-compiled for freestanding x86_64).
|
||||
BEARSSL_INCLUDES := -isystem $(shell cd .. && pwd)/kernel/freestnd-c-hdrs/x86_64/include -isystem $(abspath include/libc)
|
||||
@@ -284,11 +284,6 @@ mandelbrot: libc
|
||||
$(MAKE) -C src/2048
|
||||
|
||||
# Build RPG demo game via its own Makefile (depends on libc).
|
||||
# Build doom via its own Makefile (depends on libc and the shared-lib loader,
|
||||
# both of which it links).
|
||||
doom: libc libloader
|
||||
$(MAKE) -C src/doom
|
||||
|
||||
# Build screenshot tool (depends on libc, libjpegwrite, libloader, and libdialogs).
|
||||
screenshot: libc libjpegwrite libloader dialogs
|
||||
$(MAKE) -C src/screenshot
|
||||
@@ -334,7 +329,7 @@ crashpad: libc
|
||||
$(MAKE) -C src/crashpad
|
||||
|
||||
# Install app bundles (manifests, icons, data files) into bin/apps/<name>/.
|
||||
install-apps: 2048 doom paint spreadsheet wordprocessor weather wikipedia imageviewer fontpreview pdfviewer disks devexplorer installer audio music video bluetooth network display terminal klog procmgr powermgr calculator charmap screenshot texteditor mandelbrot printers timezone crashpad
|
||||
install-apps: 2048 paint spreadsheet wordprocessor weather wikipedia imageviewer fontpreview pdfviewer disks devexplorer installer audio music video bluetooth network display terminal klog procmgr powermgr calculator charmap screenshot texteditor mandelbrot printers timezone crashpad
|
||||
../scripts/install_apps.sh
|
||||
|
||||
# Copy man pages into bin/man/ so mkramdisk.sh picks them up.
|
||||
@@ -464,7 +459,6 @@ clean:
|
||||
$(MAKE) -C lib/libjpegwrite clean
|
||||
$(MAKE) -C lib/tls clean
|
||||
$(MAKE) -C src/fetch clean
|
||||
$(MAKE) -C src/doom clean
|
||||
$(MAKE) -C src/login clean
|
||||
$(MAKE) -C src/shell clean
|
||||
$(MAKE) -C src/desktop clean
|
||||
|
||||
@@ -228,6 +228,7 @@ namespace montauk::abi {
|
||||
static constexpr uint64_t SYS_WIFI_RESULTS = 163; // (WifiNetwork*, maxCount) -> count, no radio work
|
||||
static constexpr uint64_t SYS_WIFI_CONNECT_ASYNC = 164; // (ssid, password) -> 0 accepted, <0 on error
|
||||
static constexpr uint64_t SYS_NETIFS = 165; // (NetIfInfo*, maxCount) -> count
|
||||
static constexpr uint64_t SYS_GETCHAR_NB = 166; // () -> ascii, 0 if nothing pending; never blocks
|
||||
|
||||
// Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM).
|
||||
static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz
|
||||
|
||||
@@ -29,6 +29,11 @@ struct dirent *readdir(DIR *dirp);
|
||||
int closedir(DIR *dirp);
|
||||
void rewinddir(DIR *dirp);
|
||||
|
||||
int scandir(const char *dirp, struct dirent ***namelist,
|
||||
int (*filter)(const struct dirent *),
|
||||
int (*compar)(const struct dirent **, const struct dirent **));
|
||||
int alphasort(const struct dirent **a, const struct dirent **b);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -11,6 +11,24 @@ extern "C" {
|
||||
#define INFINITY __builtin_inff()
|
||||
#define NAN __builtin_nanf("")
|
||||
|
||||
/*
|
||||
* X/Open math constants. Not in ISO C, but ported code assumes them freely
|
||||
* (NetSurf's about:chart uses M_PI and M_PI_2).
|
||||
*/
|
||||
#define M_E 2.7182818284590452354
|
||||
#define M_LOG2E 1.4426950408889634074
|
||||
#define M_LOG10E 0.43429448190325182765
|
||||
#define M_LN2 0.69314718055994530942
|
||||
#define M_LN10 2.30258509299404568402
|
||||
#define M_PI 3.14159265358979323846
|
||||
#define M_PI_2 1.57079632679489661923
|
||||
#define M_PI_4 0.78539816339744830962
|
||||
#define M_1_PI 0.31830988618379067154
|
||||
#define M_2_PI 0.63661977236758134308
|
||||
#define M_2_SQRTPI 1.12837916709551257390
|
||||
#define M_SQRT2 1.41421356237309504880
|
||||
#define M_SQRT1_2 0.70710678118654752440
|
||||
|
||||
/* C99 floating-point classification. */
|
||||
#define FP_NAN 0
|
||||
#define FP_INFINITE 1
|
||||
|
||||
@@ -189,6 +189,7 @@ extern "C" {
|
||||
#define MTK_SYS_WIFI_RESULTS 163
|
||||
#define MTK_SYS_WIFI_CONNECT_ASYNC 164
|
||||
#define MTK_SYS_NETIFS 165
|
||||
#define MTK_SYS_GETCHAR_NB 166
|
||||
/* @SYSCALLS-END */
|
||||
|
||||
#define MTK_SOCK_TCP 1
|
||||
|
||||
@@ -95,6 +95,17 @@ int ungetc(int c, FILE *stream);
|
||||
char *fgets(char *s, int size, FILE *stream);
|
||||
int fputs(const char *s, FILE *stream);
|
||||
|
||||
/* MontaukOS extension: non-blocking readiness probe for stdin.
|
||||
Returns 1 when a complete line is buffered, so a following fgets/fgetc on
|
||||
stdin returns without blocking; 0 otherwise. Partially typed input is kept.
|
||||
This is what a select()-style poll over stdin has to be built on -- stdio
|
||||
descriptors are not waitable kernel objects. */
|
||||
int montauk_stdin_ready(void);
|
||||
|
||||
/* Blocks until a complete line is buffered on stdin, then returns 1. Same
|
||||
buffer as montauk_stdin_ready(); nothing is consumed either way. */
|
||||
int montauk_stdin_wait(void);
|
||||
|
||||
void perror(const char *s);
|
||||
FILE *tmpfile(void);
|
||||
char *tmpnam(char *s);
|
||||
|
||||
@@ -25,6 +25,7 @@ char *strncpy(char *dest, const char *src, size_t n);
|
||||
char *strcat(char *dest, const char *src);
|
||||
char *strncat(char *dest, const char *src, size_t n);
|
||||
char *strdup(const char *s);
|
||||
char *strndup(const char *s, size_t n);
|
||||
char *strchr(const char *s, int c);
|
||||
char *strrchr(const char *s, int c);
|
||||
char *strpbrk(const char *s, const char *accept);
|
||||
|
||||
@@ -23,6 +23,8 @@ int read(int fd, void *buf, size_t count);
|
||||
int write(int fd, const void *buf, size_t count);
|
||||
int close(int fd);
|
||||
long lseek(int fd, long offset, int whence);
|
||||
ssize_t pread(int fd, void *buf, size_t count, off_t offset);
|
||||
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
|
||||
int chdir(const char *path);
|
||||
char *getcwd(char *buf, size_t size);
|
||||
int access(const char *path, int mode);
|
||||
@@ -59,6 +61,8 @@ long sysconf(int name);
|
||||
extern char **environ;
|
||||
|
||||
unsigned int sleep(unsigned int seconds);
|
||||
int usleep(unsigned long usec);
|
||||
int ftruncate(int fd, long length);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -197,6 +197,8 @@ namespace montauk {
|
||||
inline bool is_key_available() { return (bool)syscall0(montauk::abi::SYS_ISKEYAVAILABLE); }
|
||||
inline void getkey(montauk::abi::KeyEvent* out) { syscall1(montauk::abi::SYS_GETKEY, (uint64_t)out); }
|
||||
inline char getchar() { return (char)syscall0(montauk::abi::SYS_GETCHAR); }
|
||||
// Returns 0 rather than blocking when no character is pending.
|
||||
inline char getchar_nb() { return (char)syscall0(montauk::abi::SYS_GETCHAR_NB); }
|
||||
inline uint64_t input_wait(uint64_t observedSerial, uint64_t timeoutMs) {
|
||||
return (uint64_t)syscall2(montauk::abi::SYS_INPUT_WAIT, observedSerial, timeoutMs);
|
||||
}
|
||||
|
||||
+201
-13
@@ -101,6 +101,7 @@ static inline long _zos_syscall4(long nr, long a1, long a2, long a3, long a4) {
|
||||
#define SYS_FREE 12
|
||||
#define SYS_GETMILLISECONDS 14
|
||||
#define SYS_GETCHAR 18
|
||||
#define SYS_GETCHAR_NB 166
|
||||
#define SYS_SPAWN 20
|
||||
#define SYS_WAITPID 23
|
||||
#define SYS_GETARGS 25
|
||||
@@ -571,6 +572,18 @@ char *strdup(const char *s) {
|
||||
return d;
|
||||
}
|
||||
|
||||
char *strndup(const char *s, size_t n) {
|
||||
size_t len = 0;
|
||||
char *d;
|
||||
if (s == NULL) return NULL;
|
||||
while (len < n && s[len] != '\0') len++;
|
||||
d = (char *)malloc(len + 1);
|
||||
if (d == NULL) return NULL;
|
||||
memcpy(d, s, len);
|
||||
d[len] = '\0';
|
||||
return d;
|
||||
}
|
||||
|
||||
const char *strerror(int errnum) {
|
||||
switch (errnum) {
|
||||
case 0: return "success";
|
||||
@@ -1466,9 +1479,23 @@ FILE *stdin = &_stdin_file;
|
||||
FILE *stdout = &_stdout_file;
|
||||
FILE *stderr = &_stderr_file;
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
stdin line editing.
|
||||
|
||||
Input is gathered a line at a time so backspace can still take a character
|
||||
back. The gather is incremental: _stdin_pump() may be called without
|
||||
blocking, in which case a half-typed line simply stays in the buffer until
|
||||
the next call. That is what lets montauk_stdin_ready() answer "is a line
|
||||
available?" without stealing keystrokes -- see the select() shim in the
|
||||
NetSurf port, which is the reason this is not just a blocking read.
|
||||
|
||||
_stdin_line_ready distinguishes "buffer holds a complete line" from "buffer
|
||||
holds a partial one"; readers must never look at the buffer without it.
|
||||
------------------------------------------------------------------------ */
|
||||
static char _stdin_linebuf[512];
|
||||
static size_t _stdin_line_len = 0;
|
||||
static size_t _stdin_line_pos = 0;
|
||||
static int _stdin_line_ready = 0;
|
||||
|
||||
static void _stdin_echo_char(char c) {
|
||||
if (c == '\b') {
|
||||
@@ -1480,13 +1507,25 @@ static void _stdin_echo_char(char c) {
|
||||
_zos_syscall1(SYS_PUTCHAR, (unsigned char)c);
|
||||
}
|
||||
|
||||
static int _stdin_fill_line(void) {
|
||||
_stdin_line_len = 0;
|
||||
_stdin_line_pos = 0;
|
||||
/* Gather keystrokes into _stdin_linebuf. Returns 1 when a complete line is
|
||||
buffered. With block == 0, returns 0 as soon as input runs dry, keeping
|
||||
whatever has been typed so far for the next call. */
|
||||
static int _stdin_pump(int block) {
|
||||
if (_stdin_line_ready) {
|
||||
if (_stdin_line_pos < _stdin_line_len) return 1;
|
||||
/* Previous line fully read; start gathering the next one. */
|
||||
_stdin_line_ready = 0;
|
||||
_stdin_line_len = 0;
|
||||
_stdin_line_pos = 0;
|
||||
}
|
||||
|
||||
while (_stdin_line_len + 1 < sizeof(_stdin_linebuf)) {
|
||||
int c = (int)_zos_syscall0(SYS_GETCHAR);
|
||||
if (c <= 0) continue;
|
||||
for (;;) {
|
||||
int c = block ? (int)_zos_syscall0(SYS_GETCHAR)
|
||||
: (int)_zos_syscall0(SYS_GETCHAR_NB);
|
||||
if (c <= 0) {
|
||||
if (block) continue;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (c == '\r') c = '\n';
|
||||
|
||||
@@ -1501,6 +1540,7 @@ static int _stdin_fill_line(void) {
|
||||
if (c == '\n') {
|
||||
_stdin_linebuf[_stdin_line_len++] = (char)c;
|
||||
_stdin_echo_char((char)c);
|
||||
_stdin_line_ready = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -1508,15 +1548,27 @@ static int _stdin_fill_line(void) {
|
||||
_stdin_linebuf[_stdin_line_len++] = (char)c;
|
||||
_stdin_echo_char((char)c);
|
||||
}
|
||||
}
|
||||
|
||||
if (_stdin_line_len > 0) {
|
||||
_stdin_linebuf[_stdin_line_len++] = '\n';
|
||||
_stdin_echo_char('\n');
|
||||
return 1;
|
||||
/* Full buffer: break the line here rather than dropping input. */
|
||||
if (_stdin_line_len + 1 >= sizeof(_stdin_linebuf)) {
|
||||
_stdin_linebuf[_stdin_line_len++] = '\n';
|
||||
_stdin_echo_char('\n');
|
||||
_stdin_line_ready = 1;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
static int _stdin_fill_line(void) {
|
||||
return _stdin_pump(1);
|
||||
}
|
||||
|
||||
int montauk_stdin_ready(void) {
|
||||
return _stdin_pump(0);
|
||||
}
|
||||
|
||||
int montauk_stdin_wait(void) {
|
||||
return _stdin_pump(1);
|
||||
}
|
||||
|
||||
FILE *fopen(const char *path, const char *mode) {
|
||||
@@ -1730,7 +1782,7 @@ int fgetc(FILE *stream) {
|
||||
}
|
||||
|
||||
if (stream->is_std == 1) {
|
||||
if (_stdin_line_pos >= _stdin_line_len) {
|
||||
if (!_stdin_line_ready || _stdin_line_pos >= _stdin_line_len) {
|
||||
if (!_stdin_fill_line()) {
|
||||
stream->eof = 1;
|
||||
return EOF;
|
||||
@@ -2047,6 +2099,67 @@ int write(int fd, const void *buf, size_t count) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* ftruncate: EXTEND ONLY.
|
||||
*
|
||||
* The kernel has no truncate syscall, so growth is emulated by writing zeros
|
||||
* at the tail. Shrinking cannot be emulated and fails with ENOSYS rather than
|
||||
* silently reporting success -- a caller that believes a file shrank when it
|
||||
* did not is worse off than one told it is unsupported.
|
||||
*/
|
||||
int ftruncate(int fd, long length) {
|
||||
unsigned long size;
|
||||
static const unsigned char zeros[512] = { 0 };
|
||||
|
||||
if (fd < 0 || length < 0) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
size = (unsigned long)_zos_syscall1(SYS_GETSIZE, (long)fd);
|
||||
|
||||
if ((unsigned long)length == size)
|
||||
return 0;
|
||||
|
||||
if ((unsigned long)length < size) {
|
||||
errno = ENOSYS; /* no kernel truncate; cannot shrink */
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (size < (unsigned long)length) {
|
||||
unsigned long chunk = (unsigned long)length - size;
|
||||
int written;
|
||||
if (chunk > sizeof(zeros)) chunk = sizeof(zeros);
|
||||
written = (int)_zos_syscall4(SYS_FWRITE, (long)fd, (long)zeros,
|
||||
(long)size, (long)chunk);
|
||||
if (written <= 0) {
|
||||
errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
size += (unsigned long)written;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Positioned I/O. SYS_READ/SYS_FWRITE already take an explicit file position,
|
||||
* so these pass the caller's offset straight through and never consult or
|
||||
* update _fd_pos -- unlike an lseek/read/lseek-back emulation, the shared file
|
||||
* offset is genuinely left alone.
|
||||
*/
|
||||
ssize_t pread(int fd, void *buf, size_t count, off_t offset) {
|
||||
if (buf == NULL || offset < 0) return -1;
|
||||
return (ssize_t)_zos_syscall4(SYS_READ, (long)fd, (long)buf,
|
||||
(long)offset, (long)count);
|
||||
}
|
||||
|
||||
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset) {
|
||||
if (buf == NULL || offset < 0) return -1;
|
||||
return (ssize_t)_zos_syscall4(SYS_FWRITE, (long)fd, (long)buf,
|
||||
(long)offset, (long)count);
|
||||
}
|
||||
|
||||
int close(int fd) {
|
||||
if (fd >= 0 && fd < _FD_POS_MAX)
|
||||
_fd_pos[fd] = 0;
|
||||
@@ -2255,6 +2368,68 @@ struct dirent *readdir(DIR *dirp) {
|
||||
return &dirp->entry;
|
||||
}
|
||||
|
||||
/*
|
||||
* scandir/alphasort, built on opendir/readdir. The array and every entry are
|
||||
* malloc'd; the caller frees each entry then the array, as POSIX requires.
|
||||
*/
|
||||
int alphasort(const struct dirent **a, const struct dirent **b) {
|
||||
return strcmp((*a)->d_name, (*b)->d_name);
|
||||
}
|
||||
|
||||
int scandir(const char *dirp, struct dirent ***namelist,
|
||||
int (*filter)(const struct dirent *),
|
||||
int (*compar)(const struct dirent **, const struct dirent **)) {
|
||||
DIR *d;
|
||||
struct dirent *ent;
|
||||
struct dirent **list = NULL;
|
||||
size_t count = 0, cap = 0;
|
||||
|
||||
if (dirp == NULL || namelist == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
d = opendir(dirp);
|
||||
if (d == NULL) return -1;
|
||||
|
||||
while ((ent = readdir(d)) != NULL) {
|
||||
struct dirent *copy;
|
||||
|
||||
if (filter != NULL && filter(ent) == 0) continue;
|
||||
|
||||
if (count == cap) {
|
||||
size_t newcap = (cap == 0) ? 16 : cap * 2;
|
||||
struct dirent **grown =
|
||||
(struct dirent **)realloc(list, newcap * sizeof(*list));
|
||||
if (grown == NULL) goto nomem;
|
||||
list = grown;
|
||||
cap = newcap;
|
||||
}
|
||||
|
||||
copy = (struct dirent *)malloc(sizeof(*copy));
|
||||
if (copy == NULL) goto nomem;
|
||||
*copy = *ent;
|
||||
list[count++] = copy;
|
||||
}
|
||||
|
||||
closedir(d);
|
||||
|
||||
if (compar != NULL && count > 1) {
|
||||
qsort(list, count, sizeof(*list),
|
||||
(int (*)(const void *, const void *))compar);
|
||||
}
|
||||
|
||||
*namelist = list;
|
||||
return (int)count;
|
||||
|
||||
nomem:
|
||||
while (count > 0) free(list[--count]);
|
||||
free(list);
|
||||
closedir(d);
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int closedir(DIR *dirp) {
|
||||
if (dirp == NULL) {
|
||||
errno = EINVAL;
|
||||
@@ -3019,6 +3194,19 @@ unsigned int sleep(unsigned int seconds) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sub-second sleeps. The kernel only offers millisecond granularity, so
|
||||
* anything finer rounds UP to 1ms rather than to zero -- a caller asking for a
|
||||
* short delay wants to yield, not to spin.
|
||||
*/
|
||||
int usleep(unsigned long usec) {
|
||||
unsigned long ms = usec / 1000UL;
|
||||
if (ms == 0 && usec != 0)
|
||||
ms = 1;
|
||||
_zos_syscall1(SYS_SLEEP_MS, (long)ms);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ========================================================================
|
||||
math.h: modf and C99 float variants (wrappers over the double
|
||||
implementations; assumed present by hosted libstdc++)
|
||||
|
||||
Binary file not shown.
@@ -233,6 +233,12 @@
|
||||
Block until a printable character is typed.
|
||||
char montauk::getchar();
|
||||
|
||||
.B SYS_GETCHAR_NB (166)
|
||||
Return the next printable character, or 0 when none is
|
||||
pending. Never blocks; key events carrying no ascii are
|
||||
drained rather than left to stall a following getchar.
|
||||
char montauk::getchar_nb();
|
||||
|
||||
.B SYS_INPUT_WAIT (123)
|
||||
Block until the input serial number differs from
|
||||
observedSerial or the timeout elapses; used to sleep
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
# Makefile for DOOM (doomgeneric) on MontaukOS
|
||||
# Copyright (c) 2025 Daniel Hammer
|
||||
|
||||
MAKEFLAGS += -rR
|
||||
.SUFFIXES:
|
||||
|
||||
# ---- Toolchain ----
|
||||
|
||||
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-montauk-
|
||||
ifeq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),)
|
||||
CC = $(error x86_64-montauk toolchain not found. Run ./toolchain/build-montauk-toolchain.sh first)
|
||||
CXX = $(error x86_64-montauk toolchain not found. Run ./toolchain/build-montauk-toolchain.sh first)
|
||||
LD = $(error x86_64-montauk toolchain not found. Run ./toolchain/build-montauk-toolchain.sh first)
|
||||
else
|
||||
CC := $(TOOLCHAIN_PREFIX)gcc
|
||||
CXX := $(TOOLCHAIN_PREFIX)g++
|
||||
LD := $(TOOLCHAIN_PREFIX)gcc
|
||||
endif
|
||||
|
||||
# ---- Paths ----
|
||||
|
||||
DOOM_SRC := ../../../doomgeneric/doomgeneric
|
||||
LIBC_INC := ../../include/libc
|
||||
PROG_INC := ../../include
|
||||
LIBDIR := ../../lib
|
||||
LOADER_LIB := ../../lib/libloader
|
||||
LINK_LD := ../../link.ld
|
||||
BINDIR := ../../bin
|
||||
OBJDIR := obj
|
||||
|
||||
# ---- Compiler flags ----
|
||||
|
||||
CFLAGS := \
|
||||
-std=gnu11 \
|
||||
-g -O2 -pipe \
|
||||
-Wall \
|
||||
-Wno-unused-parameter \
|
||||
-Wno-unused-variable \
|
||||
-Wno-missing-field-initializers \
|
||||
-Wno-sign-compare \
|
||||
-Wno-pointer-sign \
|
||||
-ffreestanding \
|
||||
-fno-stack-protector \
|
||||
-fno-stack-check \
|
||||
-ffunction-sections \
|
||||
-fdata-sections \
|
||||
-msse \
|
||||
-msse2 \
|
||||
-DNORMALUNIX \
|
||||
-DLINUX \
|
||||
-DFEATURE_SOUND \
|
||||
-isystem $(LIBC_INC) \
|
||||
-I . \
|
||||
-I $(PROG_INC) \
|
||||
-I $(DOOM_SRC) \
|
||||
-isystem $(shell $(CC) -print-file-name=include)
|
||||
|
||||
CXXFLAGS := \
|
||||
-std=gnu++20 \
|
||||
-g -O2 -pipe \
|
||||
-Wall \
|
||||
-Wno-unused-parameter \
|
||||
-ffreestanding \
|
||||
-fno-stack-protector \
|
||||
-fno-stack-check \
|
||||
-fno-rtti \
|
||||
-fno-exceptions \
|
||||
-ffunction-sections \
|
||||
-fdata-sections \
|
||||
-msse \
|
||||
-msse2 \
|
||||
-isystem $(LIBC_INC) \
|
||||
-I . \
|
||||
-I $(PROG_INC)
|
||||
|
||||
# ---- Linker flags ----
|
||||
|
||||
LDFLAGS := \
|
||||
-nostdlib \
|
||||
-Wl,--gc-sections \
|
||||
-T $(LINK_LD)
|
||||
|
||||
# ---- DOOM source files (excluding platform-specific ports and sound backends) ----
|
||||
|
||||
DOOM_SRCS := \
|
||||
am_map.c \
|
||||
d_event.c \
|
||||
d_items.c \
|
||||
d_iwad.c \
|
||||
d_loop.c \
|
||||
d_main.c \
|
||||
d_mode.c \
|
||||
d_net.c \
|
||||
doomdef.c \
|
||||
doomgeneric.c \
|
||||
doomstat.c \
|
||||
dstrings.c \
|
||||
dummy.c \
|
||||
f_finale.c \
|
||||
f_wipe.c \
|
||||
g_game.c \
|
||||
gusconf.c \
|
||||
hu_lib.c \
|
||||
hu_stuff.c \
|
||||
i_cdmus.c \
|
||||
i_endoom.c \
|
||||
i_input.c \
|
||||
i_joystick.c \
|
||||
i_scale.c \
|
||||
i_sound.c \
|
||||
i_system.c \
|
||||
i_timer.c \
|
||||
i_video.c \
|
||||
info.c \
|
||||
m_argv.c \
|
||||
m_bbox.c \
|
||||
m_cheat.c \
|
||||
m_config.c \
|
||||
m_controls.c \
|
||||
m_fixed.c \
|
||||
m_menu.c \
|
||||
m_misc.c \
|
||||
m_random.c \
|
||||
memio.c \
|
||||
mus2mid.c \
|
||||
p_ceilng.c \
|
||||
p_doors.c \
|
||||
p_enemy.c \
|
||||
p_floor.c \
|
||||
p_inter.c \
|
||||
p_lights.c \
|
||||
p_map.c \
|
||||
p_maputl.c \
|
||||
p_mobj.c \
|
||||
p_plats.c \
|
||||
p_pspr.c \
|
||||
p_saveg.c \
|
||||
p_setup.c \
|
||||
p_sight.c \
|
||||
p_spec.c \
|
||||
p_switch.c \
|
||||
p_telept.c \
|
||||
p_tick.c \
|
||||
p_user.c \
|
||||
r_bsp.c \
|
||||
r_data.c \
|
||||
r_draw.c \
|
||||
r_main.c \
|
||||
r_plane.c \
|
||||
r_segs.c \
|
||||
r_sky.c \
|
||||
r_things.c \
|
||||
s_sound.c \
|
||||
sha1.c \
|
||||
sounds.c \
|
||||
st_lib.c \
|
||||
st_stuff.c \
|
||||
statdump.c \
|
||||
tables.c \
|
||||
v_video.c \
|
||||
w_checksum.c \
|
||||
w_file.c \
|
||||
w_file_stdc.c \
|
||||
w_main.c \
|
||||
w_wad.c \
|
||||
wi_stuff.c \
|
||||
z_zone.c
|
||||
|
||||
# Local source files
|
||||
LOCAL_SRCS := doomgeneric_montauk.c i_sound_montauk.c
|
||||
LOCAL_CXX_SRCS := doom_wad_check.cpp
|
||||
|
||||
# ---- Object files ----
|
||||
|
||||
DOOM_OBJS := $(addprefix $(OBJDIR)/,$(DOOM_SRCS:.c=.o))
|
||||
LOCAL_OBJS := $(addprefix $(OBJDIR)/,$(LOCAL_SRCS:.c=.o))
|
||||
LOCAL_CXX_OBJS := $(addprefix $(OBJDIR)/,$(LOCAL_CXX_SRCS:.cpp=.o))
|
||||
ALL_OBJS := $(DOOM_OBJS) $(LOCAL_OBJS) $(LOCAL_CXX_OBJS)
|
||||
|
||||
# ---- Target ----
|
||||
|
||||
TARGET := $(BINDIR)/apps/doom/doom.elf
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(ALL_OBJS) $(LINK_LD) Makefile $(LOADER_LIB)/liblibloader.a $(LIBDIR)/libc/liblibc.a
|
||||
mkdir -p $(BINDIR)/apps/doom
|
||||
$(LD) $(CFLAGS) $(LDFLAGS) $(ALL_OBJS) $(LOADER_LIB)/liblibloader.a $(LIBDIR)/libc/liblibc.a -o $@
|
||||
|
||||
# DOOM source files (from doomgeneric directory)
|
||||
$(OBJDIR)/%.o: $(DOOM_SRC)/%.c Makefile
|
||||
mkdir -p $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -MMD -MP -c $< -o $@
|
||||
|
||||
# Local source files
|
||||
$(OBJDIR)/%.o: %.c Makefile
|
||||
mkdir -p $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -MMD -MP -c $< -o $@
|
||||
|
||||
# Local C++ source files
|
||||
$(OBJDIR)/%.o: %.cpp Makefile
|
||||
mkdir -p $(OBJDIR)
|
||||
$(CXX) $(CXXFLAGS) -MMD -MP -c $< -o $@
|
||||
|
||||
clean:
|
||||
rm -rf $(OBJDIR) $(TARGET)
|
||||
|
||||
# Auto-generated header dependency tracking (-MMD -MP).
|
||||
-include $(ALL_OBJS:.o=.d)
|
||||
@@ -1,4 +0,0 @@
|
||||
/* Dummy SDL_mixer.h - not needed for MontaukOS sound backend */
|
||||
#ifndef SDL_MIXER_H
|
||||
#define SDL_MIXER_H
|
||||
#endif
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* doom_wad_check.cpp
|
||||
* Warns the user and exits cleanly if no DOOM WAD is present
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <gui/dialogs.hpp>
|
||||
#include <montauk/syscall.h>
|
||||
#include <stdio.h>
|
||||
|
||||
extern "C" [[noreturn]] void doom_warn_missing_wad(const char* path) {
|
||||
char message[400];
|
||||
snprintf(message, sizeof(message),
|
||||
"No DOOM WAD file was found at %s.\n\n"
|
||||
"MontaukOS cannot ship proprietary DOOM WADs (doom1.wad, doom.wad, "
|
||||
"freedoom1.wad, etc.) on its ISO images due to copyright restrictions.\n\n"
|
||||
"Copy a legally obtained WAD file to that path and relaunch DOOM.",
|
||||
path);
|
||||
|
||||
gui::dialogs::message_box("DOOM", message, gui::dialogs::MESSAGE_BOX_OK);
|
||||
montauk::exit(1);
|
||||
}
|
||||
@@ -1,269 +0,0 @@
|
||||
/*
|
||||
* doomgeneric_montauk.c
|
||||
* DOOM platform implementation for MontaukOS (standalone window server client)
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "doomgeneric.h"
|
||||
#include "doomkeys.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* ---- Raw syscall interface (C versions) ---- */
|
||||
|
||||
static inline long _zos_syscall0(long nr) {
|
||||
long ret;
|
||||
__asm__ volatile("syscall" : "=a"(ret) : "a"(nr)
|
||||
: "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory");
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline long _zos_syscall1(long nr, long a1) {
|
||||
long ret;
|
||||
__asm__ volatile(
|
||||
"mov %[a1], %%rdi\n\t"
|
||||
"syscall"
|
||||
: "=a"(ret)
|
||||
: "a"(nr), [a1] "r"(a1)
|
||||
: "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory");
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline long _zos_syscall2(long nr, long a1, long a2) {
|
||||
long ret;
|
||||
__asm__ volatile(
|
||||
"mov %[a1], %%rdi\n\t"
|
||||
"mov %[a2], %%rsi\n\t"
|
||||
"syscall"
|
||||
: "=a"(ret)
|
||||
: "a"(nr), [a1] "r"(a1), [a2] "r"(a2)
|
||||
: "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory");
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline long _zos_syscall4(long nr, long a1, long a2, long a3, long a4) {
|
||||
long ret;
|
||||
__asm__ volatile(
|
||||
"mov %[a1], %%rdi\n\t"
|
||||
"mov %[a2], %%rsi\n\t"
|
||||
"mov %[a3], %%rdx\n\t"
|
||||
"mov %[a4], %%r10\n\t"
|
||||
"syscall"
|
||||
: "=a"(ret)
|
||||
: "a"(nr), [a1] "r"(a1), [a2] "r"(a2), [a3] "r"(a3), [a4] "r"(a4)
|
||||
: "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Syscall numbers (must match kernel/src/Api/Syscall.hpp) */
|
||||
#define SYS_EXIT 0
|
||||
#define SYS_SLEEP_MS 2
|
||||
#define SYS_PRINT 4
|
||||
#define SYS_GETMILLISECONDS 14
|
||||
#define SYS_WINCREATE 54
|
||||
#define SYS_WINDESTROY 55
|
||||
#define SYS_WINPRESENT 56
|
||||
#define SYS_WINPOLL 57
|
||||
|
||||
/* Window server structs (must match montauk::abi::WinCreateResult and montauk::abi::WinEvent) */
|
||||
struct WinCreateResult {
|
||||
int id; /* -1 on failure */
|
||||
unsigned _pad;
|
||||
unsigned long pixelVa; /* VA of pixel buffer in caller's address space */
|
||||
};
|
||||
|
||||
struct WinEvent {
|
||||
unsigned char type; /* 0=key, 1=mouse, 2=resize, 3=close */
|
||||
unsigned char _pad[3];
|
||||
union {
|
||||
struct {
|
||||
unsigned char scancode;
|
||||
char ascii;
|
||||
unsigned char pressed;
|
||||
unsigned char shift;
|
||||
unsigned char ctrl;
|
||||
unsigned char alt;
|
||||
} key;
|
||||
struct { int x, y, scroll; unsigned char buttons, prev_buttons; } mouse;
|
||||
struct { int w, h; } resize;
|
||||
};
|
||||
};
|
||||
|
||||
/* ---- Window state ---- */
|
||||
|
||||
static int g_winId = -1;
|
||||
static uint32_t* g_pixBuf = 0;
|
||||
|
||||
/* ---- Circular key queue ---- */
|
||||
|
||||
#define KEY_QUEUE_SIZE 64
|
||||
|
||||
struct KeyQueueEntry {
|
||||
int pressed;
|
||||
unsigned char doomkey;
|
||||
};
|
||||
|
||||
static struct KeyQueueEntry g_keyQueue[KEY_QUEUE_SIZE];
|
||||
static int g_keyQueueRead = 0;
|
||||
static int g_keyQueueWrite = 0;
|
||||
|
||||
static void key_queue_push(int pressed, unsigned char doomkey) {
|
||||
int next = (g_keyQueueWrite + 1) % KEY_QUEUE_SIZE;
|
||||
if (next == g_keyQueueRead) return; /* full, drop */
|
||||
g_keyQueue[g_keyQueueWrite].pressed = pressed;
|
||||
g_keyQueue[g_keyQueueWrite].doomkey = doomkey;
|
||||
g_keyQueueWrite = next;
|
||||
}
|
||||
|
||||
static int key_queue_pop(int* pressed, unsigned char* doomkey) {
|
||||
if (g_keyQueueRead == g_keyQueueWrite) return 0;
|
||||
*pressed = g_keyQueue[g_keyQueueRead].pressed;
|
||||
*doomkey = g_keyQueue[g_keyQueueRead].doomkey;
|
||||
g_keyQueueRead = (g_keyQueueRead + 1) % KEY_QUEUE_SIZE;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ---- PS/2 scancode to ASCII table (set 1, unshifted) ---- */
|
||||
|
||||
static const char scancode_to_ascii[128] = {
|
||||
0, 27, '1','2','3','4','5','6','7','8','9','0','-','=','\b',
|
||||
'\t','q','w','e','r','t','y','u','i','o','p','[',']','\n',
|
||||
0, 'a','s','d','f','g','h','j','k','l',';','\'','`',
|
||||
0, '\\','z','x','c','v','b','n','m',',','.','/', 0,
|
||||
'*', 0, ' '
|
||||
};
|
||||
|
||||
/* ---- PS/2 scancode to DOOM key mapping ---- */
|
||||
|
||||
static unsigned char scancode_to_doomkey(unsigned char scancode, char ascii) {
|
||||
switch (scancode) {
|
||||
case 0x48: return KEY_UPARROW;
|
||||
case 0x50: return KEY_DOWNARROW;
|
||||
case 0x4B: return KEY_LEFTARROW;
|
||||
case 0x4D: return KEY_RIGHTARROW;
|
||||
case 0x1C: return KEY_ENTER;
|
||||
case 0x01: return KEY_ESCAPE;
|
||||
case 0x39: return KEY_USE; /* Space = use */
|
||||
case 0x1D: return KEY_FIRE; /* LCtrl = fire */
|
||||
case 0x2A: return KEY_RSHIFT; /* LShift = run */
|
||||
case 0x36: return KEY_RSHIFT; /* RShift = run */
|
||||
case 0x38: return KEY_RALT; /* Alt = strafe */
|
||||
case 0x0E: return KEY_BACKSPACE;
|
||||
case 0x0F: return KEY_TAB;
|
||||
/* F1-F10 */
|
||||
case 0x3B: return KEY_F1;
|
||||
case 0x3C: return KEY_F2;
|
||||
case 0x3D: return KEY_F3;
|
||||
case 0x3E: return KEY_F4;
|
||||
case 0x3F: return KEY_F5;
|
||||
case 0x40: return KEY_F6;
|
||||
case 0x41: return KEY_F7;
|
||||
case 0x42: return KEY_F8;
|
||||
case 0x43: return KEY_F9;
|
||||
case 0x44: return KEY_F10;
|
||||
case 0x57: return KEY_F11;
|
||||
case 0x58: return KEY_F12;
|
||||
/* Equals and minus for screen size */
|
||||
case 0x0D: return KEY_EQUALS;
|
||||
case 0x0C: return KEY_MINUS;
|
||||
default:
|
||||
/* Pass through printable ASCII as lowercase */
|
||||
if (ascii >= 'a' && ascii <= 'z') return (unsigned char)ascii;
|
||||
if (ascii >= '0' && ascii <= '9') return (unsigned char)ascii;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Poll window events and enqueue key events ---- */
|
||||
|
||||
static void poll_keyboard(void) {
|
||||
struct WinEvent evt;
|
||||
while (_zos_syscall2(SYS_WINPOLL, (long)g_winId, (long)&evt) > 0) {
|
||||
if (evt.type == 0) {
|
||||
/* Key event */
|
||||
unsigned char baseSc = evt.key.scancode & 0x7F;
|
||||
|
||||
char ascii = 0;
|
||||
if (baseSc < 128)
|
||||
ascii = scancode_to_ascii[baseSc];
|
||||
|
||||
unsigned char dk = scancode_to_doomkey(baseSc, ascii);
|
||||
if (dk != 0) {
|
||||
key_queue_push(evt.key.pressed ? 1 : 0, dk);
|
||||
}
|
||||
} else if (evt.type == 3) {
|
||||
/* Close event — exit the process */
|
||||
_zos_syscall1(SYS_EXIT, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- DG platform functions ---- */
|
||||
|
||||
void DG_Init(void) {
|
||||
struct WinCreateResult result;
|
||||
_zos_syscall4(SYS_WINCREATE, (long)"DOOM", (long)DOOMGENERIC_RESX,
|
||||
(long)DOOMGENERIC_RESY, (long)&result);
|
||||
|
||||
if (result.id < 0) {
|
||||
_zos_syscall1(SYS_EXIT, 1);
|
||||
}
|
||||
|
||||
g_winId = result.id;
|
||||
g_pixBuf = (uint32_t*)result.pixelVa;
|
||||
}
|
||||
|
||||
void DG_DrawFrame(void) {
|
||||
/* Poll keyboard first */
|
||||
poll_keyboard();
|
||||
|
||||
/* Copy DG_ScreenBuffer into the shared pixel buffer */
|
||||
if (g_pixBuf == 0 || DG_ScreenBuffer == 0) return;
|
||||
|
||||
memcpy(g_pixBuf, DG_ScreenBuffer,
|
||||
DOOMGENERIC_RESX * DOOMGENERIC_RESY * sizeof(uint32_t));
|
||||
|
||||
/* Mark window as dirty so the compositor picks it up */
|
||||
_zos_syscall1(SYS_WINPRESENT, (long)g_winId);
|
||||
}
|
||||
|
||||
void DG_SleepMs(uint32_t ms) {
|
||||
_zos_syscall1(SYS_SLEEP_MS, (long)ms);
|
||||
}
|
||||
|
||||
uint32_t DG_GetTicksMs(void) {
|
||||
return (uint32_t)_zos_syscall0(SYS_GETMILLISECONDS);
|
||||
}
|
||||
|
||||
int DG_GetKey(int* pressed, unsigned char* doomKey) {
|
||||
return key_queue_pop(pressed, doomKey);
|
||||
}
|
||||
|
||||
void DG_SetWindowTitle(const char* title) {
|
||||
(void)title;
|
||||
}
|
||||
|
||||
/* ---- Entry point ---- */
|
||||
|
||||
/* Shows a warning dialog explaining that MontaukOS cannot ship proprietary
|
||||
* DOOM WADs and does not return (implemented in doom_wad_check.cpp, which
|
||||
* links against the shared dialog library). */
|
||||
extern void doom_warn_missing_wad(const char* path);
|
||||
|
||||
void _start(void) {
|
||||
static const char* wadPath = "0:/apps/doom/doom1.wad";
|
||||
|
||||
FILE* wad = fopen(wadPath, "rb");
|
||||
if (!wad) {
|
||||
doom_warn_missing_wad(wadPath);
|
||||
/* unreachable: doom_warn_missing_wad() exits the process */
|
||||
}
|
||||
fclose(wad);
|
||||
|
||||
char *argv[] = { "doom", "-iwad", (char*)wadPath, 0 };
|
||||
doomgeneric_Create(3, argv);
|
||||
for (;;) {
|
||||
doomgeneric_Tick();
|
||||
}
|
||||
}
|
||||
@@ -1,362 +0,0 @@
|
||||
/*
|
||||
* i_sound_montauk.c
|
||||
* DOOM sound effects and music modules for MontaukOS
|
||||
* Implements software mixing of 16 SFX channels into the HDA audio device.
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "deh_str.h"
|
||||
#include "i_sound.h"
|
||||
#include "i_system.h"
|
||||
#include "i_swap.h"
|
||||
#include "m_argv.h"
|
||||
#include "m_misc.h"
|
||||
#include "w_wad.h"
|
||||
#include "z_zone.h"
|
||||
#include "doomtype.h"
|
||||
|
||||
/* ========================================================================
|
||||
Raw syscall interface for audio
|
||||
======================================================================== */
|
||||
|
||||
static inline long _snd_syscall1(long nr, long a1) {
|
||||
long ret;
|
||||
__asm__ volatile(
|
||||
"mov %[a1], %%rdi\n\t"
|
||||
"syscall"
|
||||
: "=a"(ret)
|
||||
: "a"(nr), [a1] "r"(a1)
|
||||
: "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory");
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline long _snd_syscall3(long nr, long a1, long a2, long a3) {
|
||||
long ret;
|
||||
__asm__ volatile(
|
||||
"mov %[a1], %%rdi\n\t"
|
||||
"mov %[a2], %%rsi\n\t"
|
||||
"mov %[a3], %%rdx\n\t"
|
||||
"syscall"
|
||||
: "=a"(ret)
|
||||
: "a"(nr), [a1] "r"(a1), [a2] "r"(a2), [a3] "r"(a3)
|
||||
: "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory");
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define SYS_AUDIOOPEN 80
|
||||
#define SYS_AUDIOCLOSE 81
|
||||
#define SYS_AUDIOWRITE 82
|
||||
|
||||
/* ========================================================================
|
||||
Audio mixing engine
|
||||
======================================================================== */
|
||||
|
||||
/* Required by i_sound.c config binding */
|
||||
int use_libsamplerate = 0;
|
||||
float libsamplerate_scale = 0.65f;
|
||||
|
||||
#define NUM_CHANNELS 16
|
||||
#define MIX_RATE 44100
|
||||
#define MIX_CHANNELS 2
|
||||
#define MIX_BITS 16
|
||||
|
||||
/* Samples per mix frame (~35 fps, slightly oversized for timing jitter) */
|
||||
#define MIX_SAMPLES 1260
|
||||
#define MIX_BUF_BYTES (MIX_SAMPLES * MIX_CHANNELS * (MIX_BITS / 8))
|
||||
|
||||
/* Channel state */
|
||||
struct snd_channel {
|
||||
const uint8_t *data; /* 8-bit unsigned PCM samples */
|
||||
uint32_t length; /* number of samples */
|
||||
uint32_t pos; /* playback position (16.16 fixed-point) */
|
||||
uint32_t step; /* step per output sample (16.16 fixed-point) */
|
||||
int vol_left; /* left volume (0-127) */
|
||||
int vol_right; /* right volume (0-127) */
|
||||
sfxinfo_t *sfxinfo; /* NULL = inactive */
|
||||
};
|
||||
|
||||
static struct snd_channel channels[NUM_CHANNELS];
|
||||
static int audio_handle = -1;
|
||||
static boolean sound_initialized = false;
|
||||
static boolean use_sfx_prefix;
|
||||
|
||||
/* Static mix buffer (stereo interleaved int16_t) */
|
||||
static int16_t mix_buf[MIX_SAMPLES * MIX_CHANNELS];
|
||||
|
||||
/* ========================================================================
|
||||
Sound effect loading (DMX format from WAD lumps)
|
||||
======================================================================== */
|
||||
|
||||
static void GetSfxLumpName(sfxinfo_t *sfx, char *buf, size_t buf_len) {
|
||||
if (sfx->link != NULL)
|
||||
sfx = sfx->link;
|
||||
|
||||
if (use_sfx_prefix)
|
||||
M_snprintf(buf, buf_len, "ds%s", DEH_String(sfx->name));
|
||||
else
|
||||
M_StringCopy(buf, DEH_String(sfx->name), buf_len);
|
||||
}
|
||||
|
||||
/* Parse a DMX sound lump. Returns the raw 8-bit PCM data and fills in
|
||||
samplerate and length. Returns NULL on failure. */
|
||||
static const uint8_t *ParseDmxSound(int lumpnum, int *out_rate, uint32_t *out_len) {
|
||||
unsigned int lumplen = W_LumpLength(lumpnum);
|
||||
const uint8_t *data = W_CacheLumpNum(lumpnum, PU_STATIC);
|
||||
|
||||
if (lumplen < 8 || data[0] != 0x03 || data[1] != 0x00)
|
||||
return NULL;
|
||||
|
||||
int samplerate = (data[3] << 8) | data[2];
|
||||
uint32_t length = (data[7] << 24) | (data[6] << 16) | (data[5] << 8) | data[4];
|
||||
|
||||
if (length > lumplen - 8 || length <= 48)
|
||||
return NULL;
|
||||
|
||||
/* DMX skips first 16 and last 16 bytes of sample data */
|
||||
*out_rate = samplerate;
|
||||
*out_len = length - 32;
|
||||
return data + 16;
|
||||
}
|
||||
|
||||
/* ========================================================================
|
||||
Volume/separation helpers
|
||||
======================================================================== */
|
||||
|
||||
static void SetChannelVolSep(int ch, int vol, int sep) {
|
||||
/* vol: 0-127, sep: 0-254 (0=full left, 127=center, 254=full right) */
|
||||
channels[ch].vol_left = ((254 - sep) * vol) / 127;
|
||||
channels[ch].vol_right = (sep * vol) / 127;
|
||||
}
|
||||
|
||||
/* ========================================================================
|
||||
Software mixer - mix all active channels into mix_buf
|
||||
======================================================================== */
|
||||
|
||||
static void MixChannels(int num_samples) {
|
||||
/* Clear mix buffer */
|
||||
memset(mix_buf, 0, (size_t)(num_samples * MIX_CHANNELS) * sizeof(int16_t));
|
||||
|
||||
for (int ch = 0; ch < NUM_CHANNELS; ch++) {
|
||||
struct snd_channel *c = &channels[ch];
|
||||
if (c->sfxinfo == NULL)
|
||||
continue;
|
||||
|
||||
int16_t *out = mix_buf;
|
||||
|
||||
for (int i = 0; i < num_samples; i++) {
|
||||
uint32_t ipos = c->pos >> 16;
|
||||
|
||||
if (ipos >= c->length) {
|
||||
/* Sound finished */
|
||||
c->sfxinfo = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Convert 8-bit unsigned (0-255, 128=silence) to signed (-128..127)
|
||||
then scale to 16-bit range */
|
||||
int sample = ((int)c->data[ipos] - 128) << 8;
|
||||
|
||||
/* Apply volume and accumulate (additive mixing) */
|
||||
int left = out[i * 2 + 0] + ((sample * c->vol_left) >> 7);
|
||||
int right = out[i * 2 + 1] + ((sample * c->vol_right) >> 7);
|
||||
|
||||
/* Clamp to int16_t range */
|
||||
if (left > 32767) left = 32767;
|
||||
else if (left < -32768) left = -32768;
|
||||
if (right > 32767) right = 32767;
|
||||
else if (right < -32768) right = -32768;
|
||||
|
||||
out[i * 2 + 0] = (int16_t)left;
|
||||
out[i * 2 + 1] = (int16_t)right;
|
||||
|
||||
c->pos += c->step;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ========================================================================
|
||||
sound_module_t implementation
|
||||
======================================================================== */
|
||||
|
||||
static boolean I_Montauk_InitSound(boolean _use_sfx_prefix) {
|
||||
use_sfx_prefix = _use_sfx_prefix;
|
||||
|
||||
for (int i = 0; i < NUM_CHANNELS; i++) {
|
||||
memset(&channels[i], 0, sizeof(channels[i]));
|
||||
}
|
||||
|
||||
audio_handle = (int)_snd_syscall3(SYS_AUDIOOPEN,
|
||||
(long)MIX_RATE, (long)MIX_CHANNELS, (long)MIX_BITS);
|
||||
|
||||
if (audio_handle < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sound_initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void I_Montauk_ShutdownSound(void) {
|
||||
if (!sound_initialized)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < NUM_CHANNELS; i++)
|
||||
channels[i].sfxinfo = NULL;
|
||||
|
||||
if (audio_handle >= 0) {
|
||||
_snd_syscall1(SYS_AUDIOCLOSE, (long)audio_handle);
|
||||
audio_handle = -1;
|
||||
}
|
||||
|
||||
sound_initialized = false;
|
||||
}
|
||||
|
||||
static int I_Montauk_GetSfxLumpNum(sfxinfo_t *sfx) {
|
||||
char namebuf[9];
|
||||
GetSfxLumpName(sfx, namebuf, sizeof(namebuf));
|
||||
return W_GetNumForName(namebuf);
|
||||
}
|
||||
|
||||
static void I_Montauk_UpdateSound(void) {
|
||||
if (!sound_initialized || audio_handle < 0)
|
||||
return;
|
||||
|
||||
/* Mix one frame of audio and submit to the device */
|
||||
MixChannels(MIX_SAMPLES);
|
||||
|
||||
const uint8_t *ptr = (const uint8_t *)mix_buf;
|
||||
int remaining = MIX_BUF_BYTES;
|
||||
|
||||
/* Write as much as the device accepts (non-blocking) */
|
||||
while (remaining > 0) {
|
||||
int written = (int)_snd_syscall3(SYS_AUDIOWRITE,
|
||||
(long)audio_handle, (long)ptr, (long)remaining);
|
||||
if (written <= 0)
|
||||
break;
|
||||
ptr += written;
|
||||
remaining -= written;
|
||||
}
|
||||
}
|
||||
|
||||
static void I_Montauk_UpdateSoundParams(int channel, int vol, int sep) {
|
||||
if (!sound_initialized || channel < 0 || channel >= NUM_CHANNELS)
|
||||
return;
|
||||
if (channels[channel].sfxinfo == NULL)
|
||||
return;
|
||||
SetChannelVolSep(channel, vol, sep);
|
||||
}
|
||||
|
||||
static int I_Montauk_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep) {
|
||||
if (!sound_initialized || channel < 0 || channel >= NUM_CHANNELS)
|
||||
return -1;
|
||||
|
||||
/* Stop any sound already on this channel */
|
||||
channels[channel].sfxinfo = NULL;
|
||||
|
||||
/* Load and parse the sound lump */
|
||||
if (sfxinfo->lumpnum == -1)
|
||||
return -1;
|
||||
|
||||
int samplerate;
|
||||
uint32_t length;
|
||||
const uint8_t *data = ParseDmxSound(sfxinfo->lumpnum, &samplerate, &length);
|
||||
if (data == NULL)
|
||||
return -1;
|
||||
|
||||
/* Set up the channel */
|
||||
channels[channel].data = data;
|
||||
channels[channel].length = length;
|
||||
channels[channel].pos = 0;
|
||||
/* Resampling step: source_rate / MIX_RATE in 16.16 fixed-point */
|
||||
channels[channel].step = ((uint32_t)samplerate << 16) / MIX_RATE;
|
||||
|
||||
SetChannelVolSep(channel, vol, sep);
|
||||
channels[channel].sfxinfo = sfxinfo;
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
static void I_Montauk_StopSound(int channel) {
|
||||
if (!sound_initialized || channel < 0 || channel >= NUM_CHANNELS)
|
||||
return;
|
||||
channels[channel].sfxinfo = NULL;
|
||||
}
|
||||
|
||||
static boolean I_Montauk_SoundIsPlaying(int channel) {
|
||||
if (!sound_initialized || channel < 0 || channel >= NUM_CHANNELS)
|
||||
return false;
|
||||
return channels[channel].sfxinfo != NULL;
|
||||
}
|
||||
|
||||
static void I_Montauk_PrecacheSounds(sfxinfo_t *sounds, int num_sounds) {
|
||||
/* No-op: we load on demand */
|
||||
(void)sounds;
|
||||
(void)num_sounds;
|
||||
}
|
||||
|
||||
static snddevice_t sound_montauk_devices[] = {
|
||||
SNDDEVICE_SB,
|
||||
SNDDEVICE_PAS,
|
||||
SNDDEVICE_GUS,
|
||||
SNDDEVICE_WAVEBLASTER,
|
||||
SNDDEVICE_SOUNDCANVAS,
|
||||
SNDDEVICE_AWE32,
|
||||
};
|
||||
|
||||
sound_module_t DG_sound_module = {
|
||||
sound_montauk_devices,
|
||||
arrlen(sound_montauk_devices),
|
||||
I_Montauk_InitSound,
|
||||
I_Montauk_ShutdownSound,
|
||||
I_Montauk_GetSfxLumpNum,
|
||||
I_Montauk_UpdateSound,
|
||||
I_Montauk_UpdateSoundParams,
|
||||
I_Montauk_StartSound,
|
||||
I_Montauk_StopSound,
|
||||
I_Montauk_SoundIsPlaying,
|
||||
I_Montauk_PrecacheSounds,
|
||||
};
|
||||
|
||||
/* ========================================================================
|
||||
music_module_t implementation (stub - no MIDI synthesis)
|
||||
======================================================================== */
|
||||
|
||||
static boolean I_Montauk_InitMusic(void) { return true; }
|
||||
static void I_Montauk_ShutdownMusic(void) {}
|
||||
static void I_Montauk_SetMusicVolume(int vol) { (void)vol; }
|
||||
static void I_Montauk_PauseMusic(void) {}
|
||||
static void I_Montauk_ResumeMusic(void) {}
|
||||
static void *I_Montauk_RegisterSong(void *data, int len) { (void)data; (void)len; return (void *)1; }
|
||||
static void I_Montauk_UnRegisterSong(void *h) { (void)h; }
|
||||
static void I_Montauk_PlaySong(void *h, boolean loop) { (void)h; (void)loop; }
|
||||
static void I_Montauk_StopSong(void) {}
|
||||
static boolean I_Montauk_MusicIsPlaying(void) { return false; }
|
||||
static void I_Montauk_PollMusic(void) {}
|
||||
|
||||
static snddevice_t music_montauk_devices[] = {
|
||||
SNDDEVICE_SB,
|
||||
SNDDEVICE_ADLIB,
|
||||
SNDDEVICE_GUS,
|
||||
};
|
||||
|
||||
music_module_t DG_music_module = {
|
||||
music_montauk_devices,
|
||||
arrlen(music_montauk_devices),
|
||||
I_Montauk_InitMusic,
|
||||
I_Montauk_ShutdownMusic,
|
||||
I_Montauk_SetMusicVolume,
|
||||
I_Montauk_PauseMusic,
|
||||
I_Montauk_ResumeMusic,
|
||||
I_Montauk_RegisterSong,
|
||||
I_Montauk_UnRegisterSong,
|
||||
I_Montauk_PlaySong,
|
||||
I_Montauk_StopSong,
|
||||
I_Montauk_MusicIsPlaying,
|
||||
I_Montauk_PollMusic,
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
[app]
|
||||
name = "DOOM"
|
||||
binary = "doom.elf"
|
||||
icon = "doom.svg"
|
||||
|
||||
[menu]
|
||||
category = "Games"
|
||||
visible = true
|
||||
@@ -16,7 +16,6 @@ ICON_SRC="$PROJECT_ROOT/programs/gui/icons/Flat-Remix-Blue-Light-darkPanel"
|
||||
# The binary is already built by each app's Makefile into bin/apps/<name>/
|
||||
APPS=(
|
||||
"2048|categories/scalable/2048.svg"
|
||||
"doom|apps/scalable/doom.svg"
|
||||
"spreadsheet|mimetypes/scalable/spreadsheet.svg"
|
||||
"wordprocessor|apps/scalable/libreoffice6.0-writer.svg"
|
||||
"weather|apps/scalable/weather-widget.svg"
|
||||
@@ -54,6 +53,11 @@ for entry in "${APPS[@]}"; do
|
||||
app_dir="$BIN/apps/$name"
|
||||
mkdir -p "$app_dir"
|
||||
|
||||
# This bundle is in-tree. If an out-of-tree port previously claimed the
|
||||
# same name, drop its marker so the release step does not mistake this
|
||||
# for a port and prune it from the ISO.
|
||||
rm -f "$app_dir/.port"
|
||||
|
||||
# Copy manifest
|
||||
manifest="$SRC/$name/manifest.toml"
|
||||
if [ -f "$manifest" ]; then
|
||||
|
||||
+13
-2
@@ -14,7 +14,18 @@ if [ ! -d "$INPUT_DIR" ]; then
|
||||
echo "MontaukOS ramdisk" > "$INPUT_DIR/readme.txt"
|
||||
fi
|
||||
|
||||
# Create USTAR tar archive
|
||||
tar --format=ustar -cf "$OUTPUT_PATH" -C "$INPUT_DIR" .
|
||||
# Create USTAR tar archive.
|
||||
#
|
||||
# -h (dereference) is NOT optional: Fs/Ramdisk.cpp only recognises typeflag
|
||||
# '5' (directory), so a symlink entry unpacks as a zero-length regular file --
|
||||
# silently, with no error anywhere. A staged tree containing symlinks would
|
||||
# ship empty files. Copying the target in is the only faithful representation.
|
||||
tar --format=ustar -h -cf "$OUTPUT_PATH" -C "$INPUT_DIR" .
|
||||
|
||||
if tar -tvf "$OUTPUT_PATH" | grep -q '^l'; then
|
||||
echo "mkramdisk: ERROR: symlink survived into $OUTPUT_PATH (would unpack as an empty file)" >&2
|
||||
tar -tvf "$OUTPUT_PATH" | grep '^l' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "mkramdisk: created $OUTPUT_PATH from $INPUT_DIR ($(wc -c < "$OUTPUT_PATH") bytes)"
|
||||
|
||||
Reference in New Issue
Block a user