<repo_name>nankeen/infos-doom<file_sep>/userland/ls/main.cpp
/* SPDX-License-Identifier: MIT */

#include <libcore.h>

int main(const char *cmdline)
{
    const char *path;
    if (!cmdline || strlen(cmdline) == 0) {
        path = "/usr";
    } else {
        path = cmdline;
    }
    
    HDIR dir = opendir(path, 0);
    if (is_error(dir)) {
        printf("Unable to open directory '%s' for reading.\n", path);
        return 1;
    }
    
    printf("Directory Listing of '%s':\n", path);

    struct dirent de;
    while (readdir(dir, &de)) {
        printf("  %s (%u bytes)\n", de.name, de.size);
    }
    closedir(dir);

    return 0;
}
<file_sep>/run-vnc.sh
#!/bin/sh

TOP=`pwd`
INFOS_DIR=$TOP/infos
INFOS_USER_DIR=$TOP/infos-user
ROOTFS=$INFOS_USER_DIR/bin/rootfs.tar
ISO=$TOP/build/infos.iso
QEMU=qemu-system-x86_64

$QEMU -cdrom $ISO -m 6G -debugcon stdio -hda $ROOTFS -display vnc=localhost:1234 -S -s
<file_sep>/userland/cat/main.cpp
/* SPDX-License-Identifier: MIT */

#include <libcore.h>

int main(const char *cmdline)
{
	if (!cmdline || strlen(cmdline) == 0) {
		return 1;
	}
	
	HFILE file = open(cmdline, 0);
	if (is_error(file)) {
		printf("error: unable to open file '%s' for reading\n", cmdline);
		return 1;
	}
	
	char buffer[64];
	int bytes_read;
	
	do {
		bytes_read = read(file, buffer, sizeof(buffer)-1);
		buffer[bytes_read] = 0;
		
		printf("%s", buffer);
	} while (bytes_read > 0);
	
	close(file);

	return 0;
}
<file_sep>/userland/libcore/src/exec.cpp
/* SPDX-License-Identifier: MIT */
/**
 * MIT License
 * 
 * Copyright (c) 2018 <NAME>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <libcore.h>

HPROC exec(const char *program, const char *args)
{
    return (HPROC)syscall(SYS_EXEC, (unsigned long)program, (unsigned long)args);
}

void wait_proc(HPROC proc)
{
    syscall(SYS_WAIT_PROC, proc);
}

HTHREAD create_thread(ThreadProc tp, void *arg)
{
    return (HTHREAD)syscall(SYS_CREATE_THREAD, (unsigned long)tp, (unsigned long)arg);
}

void stop_thread(HTHREAD thread)
{
    syscall(SYS_STOP_THREAD, thread);
}

void join_thread(HTHREAD thread)
{
    syscall(SYS_JOIN_THREAD, thread);
}

void usleep(unsigned long us)
{
    syscall(SYS_USLEEP, us);
}

void system(const char *cmd)
{
    char prog[64];
    int n = 0;
    while (*cmd && *cmd != ' ' && n < 63) {
        prog[n++] = *cmd++;
    }
    
    prog[n] = 0;
    
    if (*cmd) cmd++;
    
    HPROC pcmd = exec(prog, cmd);
    if (is_error(pcmd)) {
        printf("error: unable to run command %s '%s'\n", prog, cmd);
    } else {
        wait_proc(pcmd);
    }
}
<file_sep>/userland/CMakeLists.txt
function(add_userland_executable)
    set(options)
    set(oneValueArgs NAME)
    set(multiValueArgs SOURCES)
    cmake_parse_arguments(USERLAND_EXEC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

    add_executable(${USERLAND_EXEC_NAME} ${USERLAND_EXEC_SOURCES})
    add_dependencies(${USERLAND_EXEC_NAME} ${NEWLIB_TARGET})

    target_compile_options(${USERLAND_EXEC_NAME} PRIVATE
        $<$<COMPILE_LANGUAGE:C>:${C_COMPILER_FLAGS}>
        $<$<COMPILE_LANGUAGE:CXX>:${CXX_COMPILER_FLAGS}>)
    target_include_directories(${USERLAND_EXEC_NAME} PUBLIC ${NEWLIB_INCLUDES})
    target_link_directories(${USERLAND_EXEC_NAME} PRIVATE ${NEWLIB_LIBS})
    target_link_libraries(${USERLAND_EXEC_NAME} c g m nosys)

    set_property(TARGET ${USERLAND_EXEC_NAME} PROPERTY CXX_STANDARD ${INFOS_CXX_STANDARD})
    set_property(TARGET ${USERLAND_EXEC_NAME} PROPERTY LINK_FLAGS
                 "-O3 -nostdlib -nostdinc -static ${NEWLIB_LIBS}/crt0.o")
endfunction()

function(add_core_executable)
    set(options)
    set(oneValueArgs NAME)
    set(multiValueArgs SOURCES)
    cmake_parse_arguments(USERLAND_EXEC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

    add_executable(${USERLAND_EXEC_NAME} ${USERLAND_EXEC_SOURCES})

    target_compile_options(${USERLAND_EXEC_NAME} PRIVATE
        $<$<COMPILE_LANGUAGE:C>:${C_COMPILER_FLAGS}>
        $<$<COMPILE_LANGUAGE:CXX>:${CXX_COMPILER_FLAGS}>)

    set_property(TARGET ${USERLAND_EXEC_NAME} PROPERTY CXX_STANDARD ${INFOS_CXX_STANDARD})
    set_property(TARGET ${USERLAND_EXEC_NAME} PROPERTY LINK_FLAGS
                 "-O3 -nostdlib -nostdinc -static")
    target_link_libraries(${USERLAND_EXEC_NAME} ${LIBCORE_TARGET})
endfunction()

set(DOOM_TARGET doom)

add_subdirectory(libcore)
add_subdirectory(doom)

add_core_executable(NAME shell SOURCES shell/main.cpp)
add_core_executable(NAME init SOURCES init/main.cpp)
add_core_executable(NAME cat SOURCES cat/main.cpp)
add_core_executable(NAME ls SOURCES ls/main.cpp)
add_core_executable(NAME test-graphics SOURCES test-graphics/main.cpp)

add_userland_executable(NAME test-newlib SOURCES test-newlib/main.cpp)
<file_sep>/userland/libcore/src/printf.cpp
/* SPDX-License-Identifier: MIT */
/**
 * MIT License
 * 
 * Copyright (c) 2018 <NAME>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <libcore.h>

int printf(const char *fmt, ...)
{
    char buffer[0x1000];
    
    int rc;
    va_list args;

    va_start(args, fmt);
    rc = vsnprintf(buffer, sizeof(buffer)-1, fmt, args);
    va_end(args);

    write(__console_handle, buffer, (size_t)rc);
    
    return rc;
}

int sprintf(char *buffer, const char *fmt, ...)
{
    int rc;
    va_list args;

    va_start(args, fmt);
    rc = vsnprintf(buffer, 0x1000, fmt, args);
    va_end(args);

    return rc;
}

static void prepend_to_buffer(char c, char *buffer, int size)
{
    for (int i = size; i > 0; i--) {
        buffer[i] = buffer[i - 1];
    }

    *buffer = c;
}

static int append_num(char *buffer, int size, uint64_t value, int base, bool sgn, int pad, char pad_char)
{
    int n = 0;

    if (pad >= size) {
        pad = size - 1;
    }

    if (sgn && (int64_t)value < 0) {
        prepend_to_buffer('-', buffer, n++);
        value = -value;
    }

    if (value > 0) {
        while (value > 0 && n < size) {
            int digit_value = (int)value % base;

            switch(digit_value) {
            case 0 ... 9:
                prepend_to_buffer('0' + (char)digit_value, buffer, n++);
                break;

            case 10 ... 35:
                prepend_to_buffer('a' + (char)digit_value - 10, buffer, n++);
                break;
            }

            value /= (uint64_t)base;
        }
    } else if (n < size) {
        prepend_to_buffer('0', buffer, n++);
        *buffer = '0';
    }

    pad -= n;
    while (pad > 0 && n < size) {
        prepend_to_buffer(pad_char, buffer, n++);
        pad--;
    }

    return n;
}

static int append_str(char *buffer, int size, const char *text, int pad, char pad_char)
{
    int n = 0;

    while (*text && n < size) {
        *buffer = *text;

        buffer++;
        text++;

        n++;
    }

    while (n < size && n < pad) {
        *buffer++ = pad_char;
        n++;
    }

    return n;
}

int vsnprintf(char *buffer_base, int size, const char *fmt_base, va_list args)
{
    const char *fmt = fmt_base;
    char *buffer = buffer_base;

    // Handle a zero-sized buffer.
    if (size == 0) {
        return 0;
    }

    // Do the printing, while we are still consuming format characters, and
    // haven't exceeded 'size'.
    int count = 0;
    while (*fmt != 0 && count < (size - 1)) {
        if (*fmt == '%') {
            int pad_size = 0, rc;
            char pad_char = ' ';
            int number_size = 4;

retry_format:
            fmt++;

            switch (*fmt) {
            case 0:
                continue;

            case '0':
                if (pad_size > 0) {
                    pad_size *= 10;
                } else {
                    pad_char = '0';
                }
                goto retry_format;

            case '1' ... '9':
                pad_size *= 10;
                pad_size += *fmt - '0';
                goto retry_format;

            case 'd':
            case 'u':
            {
                uint64_t v;

                if (number_size == 8) {
                    if (*fmt == 'u') {
                        v = (uint64_t)va_arg(args, uint64_t);
                    } else {
                        v = (uint64_t)va_arg(args, int64_t);
                    }
                } else {
                    if (*fmt == 'u') {
                        v = (uint64_t)(uint32_t)va_arg(args, uint32_t);
                    } else {
                        v = (uint64_t)(int64_t)(int32_t)va_arg(args, int32_t);
                    }
                }

                rc = append_num(buffer, size - 1 - count, v, 10, *fmt == 'd', pad_size, pad_char);
                count += rc;
                buffer += rc;
                break;
            }

            case 'b':
            case 'x':
            case 'p':
            {
                unsigned long long int v;

                if (number_size == 8 || *fmt == 'p') {
                    v = va_arg(args, unsigned long long int);
                } else {
                    v = (unsigned long long int)va_arg(args, unsigned int);
                }

                if (*fmt == 'p') {
                    rc = append_str(buffer, size - 1 - count, "0x", 0, ' ');
                    count += rc;
                    buffer += rc;
                }

                rc = append_num(buffer, size - 1 - count, v, (*fmt == 'b' ? 2 : 16), false, pad_size, pad_char);
                count += rc;
                buffer += rc;
                break;
            }

            case 'l':
                number_size = 8;
                goto retry_format;

            case 's':
                rc = append_str(buffer, size - 1 - count, va_arg(args, const char *), pad_size, pad_char);
                count += rc;
                buffer += rc;
                break;

            case 'c':
                *buffer = (char)va_arg(args, int);

                buffer++;
                count++;
                break;
                
            default:
                *buffer = *fmt;

                buffer++;
                count++;
                break;
            }

            fmt++;
        } else {
            *buffer = *fmt;

            buffer++;
            fmt++;
            count++;
        }
    }

    // Null-terminate the buffer
    *buffer = 0;
    return count;
}

char getch()
{
    char buffer;
    
    int r = 0;
    while (r < 1) {
        r = read(__console_handle, &buffer, 1);
    }
    
    return buffer;
}
<file_sep>/CMakeLists.txt
cmake_minimum_required(VERSION 3.14)

set(CMAKE_TOOLCHAIN_FILE ${CMAKE_SOURCE_DIR}/toolchain.cmake)

project(infos-doom VERSION 0.0.1 LANGUAGES C CXX ASM)

include(ExternalProject)

set(TARGET_SYSROOT ${CMAKE_BINARY_DIR}/rootfs)
file(MAKE_DIRECTORY ${TARGET_SYSROOT})

# Set compilation flags
set(WARNING_FLAGS -Wall -Wextra -Wshadow)
set(COMMON_COMPILER_FLAGS -fno-builtin -fno-stack-protector ${WARNING_FLAGS})
set(C_COMPILER_FLAGS ${COMMON_COMPILER_FLAGS})
set(CXX_COMPILER_FLAGS ${COMMON_COMPILER_FLAGS} -fno-exceptions -fno-rtti)
set(INFOS_CXX_STANDARD 20)
set(CMAKE_C_STANDARD 11)

# ===================================================
#
#
#              Kernel Target and ISO
#
#
# ===================================================


# kernel target name
set(KERNEL_TARGET infos-kernel)
# kernel output path
set(KERNEL_TARGET_OUT ${CMAKE_CURRENT_SOURCE_DIR}/infos/out/${KERNEL_TARGET})
# libcore target name
set(LIBCORE_TARGET core)

set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${CMAKE_SOURCE_DIR}")

# Define kernel compile target
add_custom_command(OUTPUT ${KERNEL_TARGET_OUT}
                   COMMAND make cc=${CMAKE_C_COMPILER} cxx=${CMAKE_CXX_COMPILER} objcopy=${CMAKE_OBJCOPY} ld=${CMAKE_LINKER} as=${CMAKE_ASM_COMPILER}
                   WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/infos)
add_custom_target(${KERNEL_TARGET} DEPENDS ${KERNEL_TARGET_OUT})

# Define grub ISO target
find_program(GRUB_MKRESCUE grub-mkrescue)
if (NOT GRUB_MKRESCUE)
    message(WARNING "Unable to find `grub-mkrescue`. Bootable ISO image generation will not be available.")
else ()
    file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/isofiles/boot/grub)
    file(COPY ${CMAKE_SOURCE_DIR}/boot/grub.cfg DESTINATION ${CMAKE_BINARY_DIR}/isofiles/boot/grub)
    set(ISO_TARGET grub-bootable-iso)
    set(ISO_PATH ${CMAKE_BINARY_DIR}/infos.iso)
    add_custom_target(${ISO_TARGET} ALL
        COMMAND ${CMAKE_COMMAND} -E copy ${KERNEL_TARGET_OUT} ${CMAKE_BINARY_DIR}/isofiles/boot/
        COMMAND ${GRUB_MKRESCUE} -o ${ISO_PATH} ${CMAKE_BINARY_DIR}/isofiles
        DEPENDS ${KERNEL_TARGET})
endif()

# ===================================================
#
#
#                   Build newlib
#
#
# ===================================================

set(NEWLIB_TRIPLE x86_64-elf-infos CACHE STRING "Newlib target")
set(NEWLIB_TARGET newlib)
set(NEWLIB_PREFIX /usr)
set(NEWLIB_INSTALL ${CMAKE_BINARY_DIR}${NEWLIB_PREFIX}/${NEWLIB_TRIPLE})
set(NEWLIB_INCLUDES ${NEWLIB_INSTALL}/include)
set(NEWLIB_LIBS ${NEWLIB_INSTALL}/lib)
ExternalProject_Add(${NEWLIB_TARGET}
    GIT_REPOSITORY https://github.com/nankeen/infos-newlib
    GIT_TAG newlib-infos
    GIT_SHALLOW true
    UPDATE_COMMAND ""
    # PATCH_COMMAND ${NEWLIB_PATCH}
    BUILD_IN_SOURCE 0
    SOURCE_DIR ${CMAKE_SOURCE_DIR}/newlib
    CONFIGURE_COMMAND
        CFLAGS_FOR_TARGET=-ffreestanding
        ${CMAKE_SOURCE_DIR}/newlib/configure
            --prefix=${NEWLIB_PREFIX}
            --target=${NEWLIB_TRIPLE}
            --disable-multilibs
            INSTALL_COMMAND make DESTDIR=${CMAKE_BINARY_DIR} install
)

# ===================================================
#
#
#              Userspace and rootfs
#
#
# ===================================================

# Add userland targets
add_subdirectory(userland)

# Define rootfs compile target
find_program(TAR_CMD tar)
if (NOT TAR_CMD)
    message (WARNING "Unable to find `tar`. Filesystem generation will not be available.")
else()
    set(FS_TARGET rootfs)
    set(FS_BINS
        $<TARGET_FILE_NAME:init>
        $<TARGET_FILE_NAME:shell>
        $<TARGET_FILE_NAME:cat>
        $<TARGET_FILE_NAME:ls>
        $<TARGET_FILE_NAME:test-graphics>
        $<TARGET_FILE_NAME:test-newlib>
        $<TARGET_FILE_NAME:doom>)
    set(FS_PATHS
        $<TARGET_FILE:init>
        $<TARGET_FILE:shell>
        $<TARGET_FILE:cat>
        $<TARGET_FILE:ls>
        $<TARGET_FILE:test-graphics>
        $<TARGET_FILE:test-newlib>
        $<TARGET_FILE:doom>)
    set(FS_PATH ${TARGET_SYSROOT}.tar)
    file(MAKE_DIRECTORY ${TARGET_SYSROOT}/.savegame)
    add_custom_target(${FS_TARGET} ALL
        COMMAND ${CMAKE_COMMAND} -E copy ${FS_PATHS} ${TARGET_SYSROOT}
        COMMAND ${TAR_CMD} cf ${FS_PATH} -C ${TARGET_SYSROOT} ${FS_BINS} DOOM.WAD .savegame/)
endif()
<file_sep>/userland/doom/src/doomgeneric_infos.c
#include "doomkeys.h"
#include "doomgeneric.h"
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <time.h>
#include <ctype.h>

uint32_t *DG_ScreenBuffer = 0;
static uint64_t start_tick = 0;
static uint32_t *frame_buffer = 0;

typedef unsigned long HANDLE;
typedef HANDLE HFILE;
typedef HANDLE HTHREAD;
typedef HANDLE HPROC;

#define SYS_MMAP            0x15
#define SYS_WAIT_PROC       0x0b
#define SYS_CREATE_THREAD   0x0c
#define SYS_STOP_THREAD     0x0d
#define SYS_JOIN_THREAD     0x0e

#define V_WIDTH     1280
#define V_HEIGHT    768
#define V_STRIDE    V_WIDTH * 4
#define GRAPHICS_START_X ((V_WIDTH - DOOMGENERIC_RESX) / 2)
#define GRAPHICS_START_Y ((V_HEIGHT - DOOMGENERIC_RESY) / 2)

#define KEYQUEUE_SIZE 32

static unsigned short s_KeyQueue[KEYQUEUE_SIZE];
static unsigned int s_KeyQueueWriteIndex = 0;
static unsigned int s_KeyQueueReadIndex = 0;

static uint8_t scancode_map[] = {
    0, KEY_ESCAPE,
    '1', '2', '3', '4', '5', '6',
    '7', '8', '9', '0',

    KEY_MINUS, KEY_EQUALS, KEY_BACKSPACE, KEY_TAB,

    'Q', 'W', 'E', 'R', 'T', 'Y',
    'U', 'I', 'O', 'P', '[', ']',

    KEY_ENTER,
    KEY_FIRE,

    'A', 'S', 'D', 'F', 'G', 'H',
    'J', 'K', 'L', ';', '\'', '`',

    KEY_RSHIFT,
    '\\', 'Z', 'X', 'C', 'V', 'B',
    'N', 'M', KEY_STRAFE_L, KEY_STRAFE_R, '/',

    KEY_RSHIFT,
    '*',
    KEY_LALT,
    KEY_USE,
    KEY_CAPSLOCK,

    KEY_F1,
    KEY_F2,
    KEY_F3,
    KEY_F4,
    KEY_F5,
    KEY_F6,
    KEY_F7,
    KEY_F8,
    KEY_F9,
    KEY_F10,
};

static char getch(HFILE fd)
{
    char buffer;
    
    int r = 0;
    while (r < 1) {
        r = read(fd, &buffer, 1);
    }
    
    return buffer;
}

static unsigned char convertToDoomKey(unsigned int key)
{
    key = tolower(key);
    switch (key)
    {
        case 'a':
            key = KEY_LEFTARROW;
            break;
        case 'd':
            key = KEY_RIGHTARROW;
            break;
        case 'w':
            key = KEY_UPARROW;
            break;
        case 's':
            key = KEY_DOWNARROW;
            break;
    }
    return key;
}

static uint8_t scanCodeToKey(uint8_t scancode)
{
    uint8_t key_scancode = ((uint8_t)scancode) & ~0x80;

    if (key_scancode > sizeof(scancode_map) / sizeof(*scancode_map)) {
        return 0x00;
    }

    return scancode_map[key_scancode];
}

static void addKeyToQueue(int pressed, unsigned int keyCode)
{
    unsigned char key = convertToDoomKey(keyCode);

    unsigned short keyData = (pressed << 8) | key;

    s_KeyQueue[s_KeyQueueWriteIndex] = keyData;
    s_KeyQueueWriteIndex++;
    s_KeyQueueWriteIndex %= KEYQUEUE_SIZE;
}

static void *mmap(void *addr, size_t len, int flags, HFILE fd, size_t offset)
{
    void *ret;
    
    asm volatile("int $0x81" : "=a"(ret) : "a"(SYS_MMAP), "D"(addr), "S"(len), "d"(flags), "c"(fd), "r"(offset));
    
    return ret;
}

static HTHREAD create_thread(void *tp, void *arg)
{
    HTHREAD ret;
    
    asm volatile("int $0x81" : "=a"(ret) : "a"(SYS_CREATE_THREAD), "D"(tp), "S"(arg));
    
    return ret;
}

static void stop_thread(HTHREAD thread)
{
    asm volatile("int $0x81" : : "a"(SYS_STOP_THREAD), "D"(thread));
}

static void handleKeyInput()
{
    char c;
    HFILE kbd_fd = open("/dev/kbd0", 0);
    if (kbd_fd == (HFILE)-1) {
        printf("Can't open keyboard device!\n");
        exit(1);
    }
    while (!should_exit) {
        c = getch(kbd_fd);
        if (c & 0x80) {
            addKeyToQueue(0, scanCodeToKey(c));
        } else {
            addKeyToQueue(1, scanCodeToKey(c));
        }
    }
    stop_thread(-1);
}

void DG_Init()
{
    // Open video device
    HFILE fd = open("/dev/video0", 0);
    if (fd < 0) {
        printf("Could not open /dev/video0\n");
        exit(1);
    }
    frame_buffer = (uint32_t *) mmap((void *)0xdeadbeef, V_STRIDE * V_HEIGHT, 1, fd, 0);

    create_thread((void *)handleKeyInput, NULL);
    start_tick = clock();
}

void DG_DrawFrame()
{
    for (size_t y = 0; y < DOOMGENERIC_RESY; y++) {
        for (size_t x = 0; x < DOOMGENERIC_RESX; x++) {
            frame_buffer[(y + GRAPHICS_START_Y) * V_WIDTH + (x + GRAPHICS_START_X)] = DG_ScreenBuffer[y * DOOMGENERIC_RESX + x];
        }
    }
}

void DG_SleepMs(uint32_t ms)
{
    usleep(ms * 1000);
}

uint32_t DG_GetTicksMs()
{
    return (clock() - start_tick) / 1e6;
}

int DG_GetKey(int* pressed, unsigned char* doom_key)
{
    if (s_KeyQueueReadIndex == s_KeyQueueWriteIndex){
        //key queue is empty
        return 0;
    } else {
        unsigned short keyData = s_KeyQueue[s_KeyQueueReadIndex];
        s_KeyQueueReadIndex++;
        s_KeyQueueReadIndex %= KEYQUEUE_SIZE;

        *pressed = keyData >> 8;
        *doom_key = keyData & 0xFF;

        return 1;
    }

    return 0;
}

void DG_SetWindowTitle(const char *title)
{
    printf("Title: %s\n", title);
}
<file_sep>/userland/init/main.cpp
/* SPDX-License-Identifier: MIT */

#include <libcore.h>

int main(const char *)
{
	printf("Welcome to InfOS!\nStarting the system...\n");
	
	while (true) {
		HPROC shell_proc = exec("/usr/shell", NULL);
		if (is_error(shell_proc)) {
			printf("Error: Unable to launch shell");
			return 1;
		}

		wait_proc(shell_proc);

		printf("SHELL TERMINATED!  PRESS ENTER TO RESTART\n");
		while (getch() != '\n');
	}
	
	return 0;
}
<file_sep>/userland/doom/src/doomgeneric.c
#include "doomgeneric.h"

int should_exit = 0;

void dg_Create()
{
    DG_ScreenBuffer = (uint32_t *)malloc(DOOMGENERIC_RESX * DOOMGENERIC_RESY * 4);

    DG_Init();
}
<file_sep>/userland/test-newlib/main.cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int, const char *)
{
	char *allocated = (char *)malloc(0x20000);
	printf("Allocated buffer at: %p\n", allocated);
	fflush(stdout);
	*(allocated+0x20000) = 0;
	free(allocated);
	allocated = NULL;
    return 0;
}
<file_sep>/userland/libcore/src/io.cpp
/* SPDX-License-Identifier: MIT */
/**
 * MIT License
 * 
 * Copyright (c) 2018 <NAME>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <libcore.h>

HFILE open(const char *file, int flags)
{
    return (HFILE)syscall(SYS_OPEN, (unsigned long)file, (unsigned long)flags);
}

void close(HFILE file)
{
    syscall(SYS_CLOSE, (unsigned long)file);
}

int read(HFILE file, char *buffer, size_t size)
{
    return (int)syscall(SYS_READ, (unsigned long)file, (unsigned long)buffer, (unsigned long)size);
}

int write(HFILE file, const char *buffer, size_t size)
{
    return (int)syscall(SYS_WRITE, (unsigned long)file, (unsigned long)buffer, (unsigned long)size);
}

HDIR opendir(const char *path, int flags)
{
    return (HDIR)syscall(SYS_OPENDIR, (unsigned long)path, (unsigned long)flags);
}

int readdir(HDIR dir, struct dirent *de)
{
    return (int)syscall(SYS_READDIR, (unsigned long)dir, (unsigned long)de);
}

void closedir(HDIR dir)
{
    syscall(SYS_CLOSEDIR, (unsigned long)dir);
}

int get_time_of_day(struct tod *t)
{
    return (int)syscall(SYS_GET_TOD, (unsigned long)t);
}
<file_sep>/README.md
<h1 align="center">Doom on InfOS</h1>
<p align="center">
    <img alt="GitHub top language" src="https://img.shields.io/github/languages/top/nankeen/infos-doom?style=for-the-badge">
    <img alt="GitHub last commit" src="https://img.shields.io/github/last-commit/nankeen/infos-doom?style=for-the-badge">
    <img alt="GitHub issues" src="https://img.shields.io/github/issues/nankeen/infos-doom?style=for-the-badge">
</p>

> InfOS is the Informatics research operating system, designed specifically for the UG3 Operating Systems course.

This project aims to port Doom over to [InfOS](https://github.com/tspink/infos), the main goal is to extend InfOS with user space graphics.

### Status

Before we can attempt to the port, the kernel must be extended.
The two main hurdles as of 12 Mar 2021 are:

1. Kernel currently uses VGA text mode and does not have framebuffer graphics.
2. User space memory allocation is not supported.

## Gallery

![image](https://user-images.githubusercontent.com/6895854/112836924-1f9e6a80-9093-11eb-8af8-8bdf3102ec5b.png)

### Tasks

- [x] Add multiboot framebuffer graphics support.
- [x] Add memory mapped IO and user space page allocation with `mmap`.
- [x] Add framebuffer console output and font rendering.
- [x] Add user space memory allocator.
- [x] Implement frame drawing function in Doom.
- [x] Implement player input functions in Doom.
- [ ] Add mouse device support (optional).

## Build

The InfOS project builds with a Makefile.
This project builds the kernel with the original Makefile but uses CMake for other binaries.
To build and generate the a bootable ISO, you would also need GRUB.

### Requirements

Install the dependencies on Ubuntu (ninja is optional)

```bash
sudo apt install grub2 xorriso build-essential ninja-build
```

### Toolchain setup

This project would compile userspace binaries with a different compiler toolchain that needs setup.
Follow https://wiki.osdev.org/GCC_Cross-Compiler to build this, we need to name it `x86_64-elf-infos-*`.

On MacOS you can install `x86_64-elf-gcc` with Homebrew.

```bash
brew install x86_64-elf-gcc
```

Create a directory to store the toolchain, make sure that it is in your path.
This is a quick hack to link the compiler with the correct name.

```bash
mkdir $HOME/opt/bin
for elf_bin in /usr/local/bin/x86_64-elf-*
    do ln $elf_bin ~/opt/bin/x86_64-elf-infos-${elf_bin##*x86_64-elf-}
done
export PATH="$HOME/opt/bin:$PATH"
```

### Build with CMake

This might not be able to build the kernel due to an issue with the toolchain, so if it fails, try compiling the kernel first with `make -C infos/`.

```bash
mkdir build
cd build
cmake -G Ninja ..
cmake --build . --target grub-bootable-iso rootfs
```

### Run
```bash
./run.sh
```
<file_sep>/userland/doom/README.md
# doomgeneric
The purpose of doomgeneric is to make porting Doom easier.
Of course Doom is already portable but with doomgeneric it is possible with just a few functions.
The limitation is there is no sound!

To try it you will need a WAD file (game data). If you don't own the game, shareware version is freely available (doom1.wad).

# porting
Create a file named doomgeneric_yourplatform.c and just implement these functions to suit your platform.
* DG_Init
* DG_DrawFrame
* DG_SleepMs
* DG_GetTicksMs
* DG_GetKey

|Functions            |Description|
|---------------------|-----------|
|DG_Init              |Initialize your platfrom (create window, framebuffer, etc...).
|DG_DrawFrame         |Frame is ready in DG_ScreenBuffer. Copy it to your platform's screen.
|DG_SleepMs           |Sleep in milliseconds.
|DG_GetTicksMs        |The ticks passed since launch in milliseconds.
|DG_GetKey            |Provide keyboard events.
|DG_SetWindowTitle    |Not required. This is for setting the window title as Doom sets this from WAD file.

# platforms
I have ported to Windows, X11, and Soso. Just look at (doomgeneric_win.c or doomgeneric_xlib.c).

Note that X11 port is not efficient since it generates pixmap by XDrawPoint. It can be further improved by using X11 extensions.

## SDL

![SDL](screenshots/sdl.png)

## Windows
![Windows](screenshots/windows.png)

## X11 - Ubuntu
![Ubuntu](screenshots/ubuntu.png)

## X11 - FreeBSD
![FreeBSD](screenshots/freebsd.png)
<file_sep>/userland/test-graphics/main.cpp
#include <libcore.h>

#define V_WIDTH     1280
#define V_HEIGHT    768
#define V_STRIDE    V_WIDTH * 4

void *init_framebuffer()
{
    // Open video device
    HFILE fd = open("/dev/video0", 0);
    if (is_error(fd)) {
        printf("Could not open /dev/video0\n");
        exit(1);
    }

    return mmap((void *)0xdeadbeef, V_STRIDE * V_HEIGHT, 1, fd, 0);
}

int main(const char *)
{
    // Test userland frame graphics
    uint32_t *frame_buffer = (uint32_t *)init_framebuffer();
    if ((intptr_t)frame_buffer == -1) {
        printf("MMAP error: %p\n", frame_buffer);
        exit(1);
    }
    printf("Mapped frame buffer at %p\n", frame_buffer);
    for (size_t i = 0; i < V_WIDTH * V_HEIGHT; i++) {
        frame_buffer[i] = -1;
    }
    getch();
    return 0;
}
<file_sep>/toolchain.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_VERSION 1)
set(CMAKE_SYSTEM_PROCESSOR x86_64)

set(TOOLCHAIN_PREFIX x86_64-elf)

set(CMAKE_AR                    ${TOOLCHAIN_PREFIX}-ar)
set(CMAKE_ASM_COMPILER          ${TOOLCHAIN_PREFIX}-as)
set(CMAKE_C_COMPILER            ${TOOLCHAIN_PREFIX}-gcc)
set(CMAKE_CXX_COMPILER          ${TOOLCHAIN_PREFIX}-g++)
set(CMAKE_LINKER                ${TOOLCHAIN_PREFIX}-ld)
set(CMAKE_OBJCOPY               ${TOOLCHAIN_PREFIX}-objcopy)
set(CMAKE_RANLIB                ${TOOLCHAIN_PREFIX}-ranlib)
set(CMAKE_SIZE                  ${TOOLCHAIN_PREFIX}-size)
set(CMAKE_STRIP                 ${TOOLCHAIN_PREFIX}-strip)

set(CMAKE_TRY_COMPILE_TARGET_TYPE "STATIC_LIBRARY")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_CROSS_COMPILING TRUE)
<file_sep>/userland/doom/CMakeLists.txt
if (NOT DEFINED DOOM_TARGET)
    set(DOOM_TARGET doom)
    message (WARNING "DOOM_TARGET was not set by project root. Defaulting to '${DOOM_TARGET}'.")
    set(DOOM_TARGET ${DOOM_TARGET} PARENT_SCOPE)
endif ()

file(GLOB_RECURSE SOURCES RELATIVE ${CMAKE_CURRENT_LIST_DIR} "*.c")

add_executable(${DOOM_TARGET} ${SOURCES})
add_dependencies(${DOOM_TARGET} ${NEWLIB_TARGET})

set_property(TARGET ${DOOM_TARGET} PROPERTY LINK_FLAGS
             "-Wl,--gc-sections -nostdlib -nostdinc -static ${NEWLIB_LIBS}/crt0.o")

target_include_directories(${DOOM_TARGET} PRIVATE ${CMAKE_CURRENT_LIST_DIR}/src ${NEWLIB_INCLUDES})
target_link_directories(${DOOM_TARGET} PRIVATE ${NEWLIB_LIBS})
target_link_libraries(${DOOM_TARGET} c g m nosys)

set(DOOM_C_COMPILER_FLAGS -ggdb3 -D_DEFAULT_SOURCE)
target_compile_options(${DOOM_TARGET} PRIVATE $<$<COMPILE_LANGUAGE:C>:${DOOM_C_COMPILER_FLAGS}>)
<file_sep>/userland/libcore/src/mmio.cpp
#include <libcore.h>


void *mmap(void *addr, size_t len, int flags, HFILE fd, off_t offset)
{
    return (void *)syscall(SYS_MMAP, (unsigned long)addr, len, (unsigned long)flags, fd, offset);
}
<file_sep>/userland/libcore/include/libcore.h
/* SPDX-License-Identifier: MIT */
/**
 * MIT License
 * 
 * Copyright (c) 2018 <NAME>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#pragma once

typedef unsigned char           uint8_t;
typedef unsigned short          uint16_t;
typedef unsigned int            uint32_t;
typedef unsigned long long int  uint64_t;

typedef signed char             int8_t;
typedef signed short            int16_t;
typedef signed int              int32_t;
typedef signed long long int    int64_t;

typedef unsigned long   size_t;
typedef unsigned long   off_t;
typedef unsigned long   uintptr_t;
typedef signed long     intptr_t;

typedef signed long int     intmax_t;
typedef unsigned long int   uintmax_t;

#define ARRAY_SIZE(_arr) (sizeof(_arr) / sizeof(_arr[0]))

#define SYS_NOP             0x00
#define SYS_YIELD           0x01
#define SYS_EXIT            0x02
#define SYS_OPEN            0x03
#define SYS_CLOSE           0x04
#define SYS_READ            0x05
#define SYS_WRITE           0x06
#define SYS_OPENDIR         0x07
#define SYS_READDIR         0x08
#define SYS_CLOSEDIR        0x09
#define SYS_EXEC            0x0a
#define SYS_WAIT_PROC       0x0b
#define SYS_CREATE_THREAD   0x0c
#define SYS_STOP_THREAD     0x0d
#define SYS_JOIN_THREAD     0x0e
#define SYS_USLEEP          0x0f
#define SYS_GET_TOD         0x10
#define SYS_MMAP            0x15

typedef unsigned long HANDLE;
typedef HANDLE HFILE;
typedef HANDLE HDIR;
typedef HANDLE HPROC;
typedef HANDLE HTHREAD;

#define HTHREAD_SELF ((HTHREAD)-1)

static inline bool is_error(HANDLE h) { return h == (HANDLE)-1; }

extern unsigned long syscall(uint32_t nr);
extern unsigned long syscall(uint32_t nr, unsigned long a1);
extern unsigned long syscall(uint32_t nr, unsigned long a1, unsigned long a2);
extern unsigned long syscall(uint32_t nr, unsigned long a1, unsigned long a2, unsigned long a3);
extern unsigned long syscall(uint32_t nr, unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5);

extern void exit(int exit_code) __attribute__((noreturn));

extern HFILE open(const char *filename, int flags);
extern int read(HFILE file, char *buffer, size_t size);
extern int write(HFILE file, const char *buffer, size_t size);
extern void close(HFILE file);
extern void *mmap(void *addr, size_t len, int flags, HFILE fd, off_t offset);

struct dirent
{
    char name[64];
    unsigned int size;
    int flags;
};

extern HDIR opendir(const char *path, int flags);
extern int readdir(HDIR dir, struct dirent *de);
extern void closedir(HDIR dir);

extern HPROC exec(const char *filename, const char *args);
extern void wait_proc(HPROC proc);
extern void system(const char *command);

typedef void (*ThreadProc)(void *);
extern HTHREAD create_thread(ThreadProc tp, void *arg);
extern void stop_thread(HTHREAD thread);
extern void join_thread(HTHREAD thread);
extern void usleep(unsigned long us);

struct tod {
    unsigned short seconds, minutes, hours, day_of_month, month, year;
};

extern int get_time_of_day(struct tod *t);

#define va_start(v,l)   __builtin_va_start(v,l)
#define va_end(v)       __builtin_va_end(v)
#define va_arg(v,l)     __builtin_va_arg(v,l)

typedef __builtin_va_list __gnuc_va_list;
typedef __gnuc_va_list va_list;

extern int snprintf(char *buffer, int size, const char *fmt, ...);
extern int printf(const char *fmt, ...);
extern int sprintf(char *buffer, const char *fmt, ...);
extern int vsnprintf(char *buffer, int size, const char *fmt, va_list args);

extern int strcmp(const char *l, const char *r);
extern int strlen(const char *str);

extern char getch();

extern HFILE __console_handle;

#define NULL 0
<file_sep>/userland/shell/main.cpp
#include <libcore.h>

int main(const char *)
{
    printf("This is the InfOS shell.  Path resolution is not-yet-implemented, so you\n"
            "must type the command exactly, e.g. try typing: /usr/ls.\n\n");
    
    while (true) {
        printf("$ ");

        char command_buffer[128];
        size_t n = 0;

        while (n < sizeof command_buffer - 1) {
            char c = getch();
            if (c == 0) continue;
            if (c == '\n') break;
            if (c == '\b') {
                if (n > 0) {
                    command_buffer[--n] = 0;
                    printf("\b");
                }
            } else {
                command_buffer[n++] = c;
                printf("%c", c);
            }
        }

        printf("\n");
        if (n == 0) continue;
        
        command_buffer[n] = 0;
        
        if (strcmp("exit", command_buffer) == 0) break;
        system(command_buffer);
    }
    
    return 0;
}
<file_sep>/userland/libcore/CMakeLists.txt
if (NOT DEFINED LIBCORE_TARGET)
    set(LIBCORE_TARGET libcore)
    message (WARNING "LIBCORE_TARGET was not set by project root. Defaulting to '${LIBCORE_TARGET}'.")
    set(LIBCORE_TARGET ${LIBCORE_TARGET} PARENT_SCOPE)
endif ()

file(GLOB_RECURSE SOURCES RELATIVE ${CMAKE_CURRENT_LIST_DIR} "*.cpp")

add_library(${LIBCORE_TARGET} STATIC ${SOURCES})
target_include_directories(${LIBCORE_TARGET} PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include)
set_property(TARGET ${LIBCORE_TARGET} PROPERTY CXX_STANDARD ${INFOS_CXX_STANDARD})
set(LIBCORE_C_COMPILER_FLAGS ${C_COMPILER_FLAGS} -Wno-unused-function)
set(LIBCORE_CXX_COMPILER_FLAGS ${CXX_COMPILER_FLAGS} -Wno-unused-function)
target_compile_options(${LIBCORE_TARGET} PRIVATE
    $<$<COMPILE_LANGUAGE:C>:${LIBCORE_C_COMPILER_FLAGS}>
    $<$<COMPILE_LANGUAGE:CXX>:${LIBCORE_CXX_COMPILER_FLAGS}>)
<file_sep>/run.sh
#!/bin/sh

TOP=`pwd`
ROOTFS=$TOP/build/rootfs.tar
ISO=$TOP/build/infos.iso
QEMU=qemu-system-x86_64

$QEMU -cdrom $ISO -m 6G -debugcon stdio -hda $ROOTFS -gdb tcp:0.0.0.0:1234
