DEADSOFTWARE

kos32: initial port for KolibriOS
authorDeaDDooMER <deaddoomer@deadsoftware.ru>
Mon, 1 Feb 2021 23:56:44 +0000 (02:56 +0300)
committerDeaDDooMER <deaddoomer@deadsoftware.ru>
Mon, 1 Feb 2021 23:56:44 +0000 (02:56 +0300)
src/CMakeLists.txt
src/cmake/kos32-tcc.cmake [new file with mode: 0755]
src/kos32/kos32.h [new file with mode: 0644]
src/kos32/main.c [new file with mode: 0644]

index d91997eb8fa0612fc4d61e2ab1b36ab55fcf6b70..65b419637e68be42f584ccf462518461fa424bbd 100644 (file)
@@ -12,6 +12,7 @@ option(SOUND_DRIVER "Build with selected sound driver" "OpenAL")
 string(TOUPPER "${SYSTEM_DRIVER}" SYSTEM_DRIVER)
 string(COMPARE EQUAL "${SYSTEM_DRIVER}" "SDL" WITH_SDL)
 string(COMPARE EQUAL "${SYSTEM_DRIVER}" "SDL2" WITH_SDL2)
+string(COMPARE EQUAL "${SYSTEM_DRIVER}" "KOS32" WITH_KOS32)
 string(TOUPPER "${RENDER_DRIVER}" RENDER_DRIVER)
 string(COMPARE EQUAL "${RENDER_DRIVER}" "OPENGL" WITH_OPENGL)
 string(COMPARE EQUAL "${RENDER_DRIVER}" "SOFTWARE" WITH_SOFTWARE)
@@ -24,6 +25,7 @@ string(COMPARE EQUAL "${SOUND_DRIVER}" "STUB" WITH_STUBSOUND)
 set(D2D_GAME_ROOT .)
 set(D2D_SDL_ROOT ${D2D_GAME_ROOT}/sdl)
 set(D2D_SDL2_ROOT ${D2D_GAME_ROOT}/sdl2)
+set(D2D_KOS32_ROOT ${D2D_GAME_ROOT}/kos32)
 set(D2D_OPENGL_ROOT ${D2D_GAME_ROOT}/gl)
 set(D2D_SOFTWARE_ROOT ${D2D_GAME_ROOT}/soft)
 set(D2D_STUBRENDER_ROOT ${D2D_GAME_ROOT}/stubren)
@@ -36,6 +38,7 @@ set(D2D_LIBCP866_ROOT ${D2D_GAME_ROOT}/libs/cp866)
 aux_source_directory(${D2D_GAME_ROOT} D2D_GAME_SRC)
 aux_source_directory(${D2D_SDL_ROOT} D2D_SDL_SRC)
 aux_source_directory(${D2D_SDL2_ROOT} D2D_SDL2_SRC)
+aux_source_directory(${D2D_KOS32_ROOT} D2D_KOS32_SRC)
 aux_source_directory(${D2D_OPENGL_ROOT} D2D_OPENGL_SRC)
 aux_source_directory(${D2D_SOFTWARE_ROOT} D2D_SOFTWARE_SRC)
 aux_source_directory(${D2D_STUBRENDER_ROOT} D2D_STUBRENDER_SRC)
@@ -78,8 +81,13 @@ elseif(WITH_SDL2)
     set(D2D_SYSTEM_INCLUDE_DIR "${SDL2_INCLUDE_DIRS}")
     set(D2D_SYSTEM_LIBRARY "${SDL2_LIBRARIES}")
   endif()
+elseif(WITH_KOS32)
+  set(D2D_SYSTEM_SRC "${D2D_KOS32_SRC}")
+  set(D2D_SYSTEM_INCLUDE_DIR "")
+  set(D2D_SYSTEM_LIBRARY "-lck")
+  set(D2D_SYSTEM_LINKFLAGS "")
 else()
-  message(FATAL_ERROR "Select SYSTEM_DRIVER as 'SDL' or 'SDL2'")
+  message(FATAL_ERROR "Select SYSTEM_DRIVER as 'SDL' or 'SDL2' or 'KOS32'")
 endif()
 
 if(WITH_STUBRENDER)
diff --git a/src/cmake/kos32-tcc.cmake b/src/cmake/kos32-tcc.cmake
new file mode 100755 (executable)
index 0000000..92a8594
--- /dev/null
@@ -0,0 +1,17 @@
+# Usage:
+#   cmake -DCMAKE_TOOLCHAIN_FILE=path/to/kos32-tcc.cmake ..
+
+set(CMAKE_SYSTEM_NAME KolibriOS)
+set(TOOLCHAIN_PREFIX kos32)
+
+# cross compilers to use for C, C++ and Fortran
+set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-tcc)
+
+# target environment on the build host system
+#set(CMAKE_FIND_ROOT_PATH /usr/${TOOLCHAIN_PREFIX}/sys-root/kos32)
+
+# modify default behavior of FIND_XXX() commands
+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_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
diff --git a/src/kos32/kos32.h b/src/kos32/kos32.h
new file mode 100644 (file)
index 0000000..9686f12
--- /dev/null
@@ -0,0 +1,380 @@
+#ifndef KOS32_H
+#define KOS32_H
+
+/* --- fn 0 (styles) --- */
+#define KOS32_WIN_STYLE_EMPTY 0
+#define KOS32_WIN_STYLE_SKIN  3
+#define KOS32_WIN_STYLE_FIXED 4
+
+/* --- fn 0 (flags) --- */
+#define KOS32_WIN_FLAG_CAPTION  1
+#define KOS32_WIN_FLAG_RELATIVE 2
+#define KOS32_WIN_FLAG_NOFILL   4
+#define KOS32_WIN_FLAG_GRADIENT 8
+#define KOS32_WIN_FLAG_UNMOVABLE 256
+
+/* --- fn 0 --- */
+static inline void CreateWindow (int x, int y, int w, int h, int style, int flags, int color, int hcolor, const char *title) {
+  int f = (title ? KOS32_WIN_FLAG_CAPTION : 0) | (flags & ~KOS32_WIN_FLAG_CAPTION);
+  __asm__ __volatile__ (
+    "int $0x40"
+    :
+    : "a" (0),
+      "b" (x << 16 | ((w - 1) & 0xffff)),
+      "c" (y << 16 | ((h - 1) & 0xffff)),
+      "d" ((f << 28) | ((style & 0xf) << 24) | (color & 0xffffff)),
+      "S" (((f >> 8) << 24) | (hcolor & 0xffffff)),
+      "D" (title)
+    : "memory"
+  );
+}
+
+/* --- fn 12.1 --- */
+static inline void BeginDraw (void) {
+   __asm__ __volatile__ (
+    "int $0x40"
+    :
+    : "a" (12),
+      "b" (1)
+  );
+}
+
+/* --- fn 12.2 --- */
+static inline void EndDraw (void) {
+   __asm__ __volatile__ (
+    "int $0x40"
+    :
+    : "a" (12),
+      "b" (2)
+  );
+}
+
+/* --- fn 65 --- */
+static inline void PutImageExt (const void *image, int w, int h, int x, int y, int bpp, const void *pal, int offset) {
+  __asm__ __volatile__ (
+    "push %%ebp; movl %6, %%ebp; int $0x40; pop %%ebp"
+    :
+    : "a" (65),
+      "b" (image),
+      "c" (w << 16 | (h & 0xffff)),
+      "d" (x << 16 | (y & 0xffff)),
+      "S" (bpp),
+      "D" (pal),
+      "m" (offset)
+    : "memory", "ebp"
+  );
+}
+
+/* --- fn 18.4 --- */
+static inline int GetIdleCount (void) {
+  int ret;
+  __asm__ __volatile__ (
+    "int $0x40"
+    : "=a" (ret)
+    : "a" (18),
+      "b" (4)
+  );
+  return ret;
+}
+
+/* --- fn 26.9 --- */
+/* result is 1/100 sec */
+static inline int GetTimeCount (void) {
+  int ret;
+  __asm__ __volatile__ (
+    "int $0x40"
+    : "=a" (ret)
+    : "a" (26),
+      "b" (9)
+  );
+  return ret;
+}
+
+/* --- fn 26.10 --- */
+/* result is 1/1000000 sec (nanosec) */
+static inline long GetTimeCountPro (void) {
+  union {
+    struct { int lo, hi; };
+    long pair;
+  } ret;
+  __asm__ __volatile__ (
+    "int $0x40"
+    : "=a" (ret.lo),
+      "=b" (ret.hi)
+    : "a" (26),
+      "b" (10)
+  );
+  return ret.pair;
+}
+
+/* --- fn 40 (flags) --- */
+#define KOS32_EVENT_FLAG_REDRAW         (1 << 0)
+#define KOS32_EVENT_FLAG_KEYBOARD       (1 << 1)
+#define KOS32_EVENT_FLAG_BUTTON         (1 << 2)
+#define KOS32_EVENT_FLAG_RESERVED       (1 << 3)
+#define KOS32_EVENT_FLAG_REDRAW_BG      (1 << 4)
+#define KOS32_EVENT_FLAG_MOUSE          (1 << 5)
+#define KOS32_EVENT_FLAG_IPC            (1 << 6)
+#define KOS32_EVENT_FLAG_NETWORK        (1 << 7)
+#define KOS32_EVENT_FLAG_DEBUG          (1 << 8)
+#define KOS32_EVENT_FLAG_IRQ0           (1 << 15)
+#define KOS32_EVENT_FLAG_IRQ1           (1 << 16)
+#define KOS32_EVENT_FLAG_IRQ2           (1 << 17)
+#define KOS32_EVENT_FLAG_IRQ3           (1 << 18)
+#define KOS32_EVENT_FLAG_IRQ4           (1 << 19)
+#define KOS32_EVENT_FLAG_IRQ5           (1 << 20)
+#define KOS32_EVENT_FLAG_IRQ6           (1 << 21)
+#define KOS32_EVENT_FLAG_IRQ7           (1 << 22)
+#define KOS32_EVENT_FLAG_IRQ8           (1 << 23)
+#define KOS32_EVENT_FLAG_IRQ9           (1 << 24)
+#define KOS32_EVENT_FLAG_IRQ10          (1 << 25)
+#define KOS32_EVENT_FLAG_IRQ11          (1 << 26)
+#define KOS32_EVENT_FLAG_IRQ12          (1 << 27)
+#define KOS32_EVENT_FLAG_IRQ13          (1 << 28)
+#define KOS32_EVENT_FLAG_IRQ14          (1 << 29)
+#define KOS32_EVENT_FLAG_IRQ15          (1 << 30) /* ??? */
+#define KOS32_EVENT_FLAG_MOUSE_NOFOCUS  (1 << 30)
+#define KOS32_EVENT_FLAG_MOUSE_NOACTIVE (1 << 31)
+
+/* --- fn 40 (events) --- */
+#define KOS32_EVENT_NONE           0
+#define KOS32_EVENT_REDRAW         1
+#define KOS32_EVENT_KEYBOARD       2
+#define KOS32_EVENT_BUTTON         3
+#define KOS32_EVENT_RESERVED       4
+#define KOS32_EVENT_REDRAW_BG      5
+#define KOS32_EVENT_MOUSE          6
+#define KOS32_EVENT_IPC            7
+#define KOS32_EVENT_NETWORK        8
+#define KOS32_EVENT_DEBUG          9
+#define KOS32_EVENT_IRQ0           16
+#define KOS32_EVENT_IRQ1           17
+#define KOS32_EVENT_IRQ2           18
+#define KOS32_EVENT_IRQ3           19
+#define KOS32_EVENT_IRQ4           20
+#define KOS32_EVENT_IRQ5           21
+#define KOS32_EVENT_IRQ6           22
+#define KOS32_EVENT_IRQ7           23
+#define KOS32_EVENT_IRQ8           24
+#define KOS32_EVENT_IRQ9           25
+#define KOS32_EVENT_IRQ10          26
+#define KOS32_EVENT_IRQ11          27
+#define KOS32_EVENT_IRQ12          28
+#define KOS32_EVENT_IRQ13          29
+#define KOS32_EVENT_IRQ14          30
+#define KOS32_EVENT_IRQ15          31
+
+/* --- fn 40 --- */
+static inline int SetEventsMask (int mask) {
+  int ret;
+  __asm__ __volatile__ (
+    "int $0x40"
+    : "=a" (ret)
+    : "a" (mask)
+  );
+  return ret;
+}
+
+/* --- fn 11 --- */
+static inline int CheckEvent (void) {
+  int ret;
+  __asm__ __volatile__ (
+    "int $0x40"
+    : "=a" (ret)
+    : "a" (11)
+  );
+  return ret;
+}
+
+/* --- fn 2 --- */
+static inline int GetKey (void) {
+  int ret;
+  __asm__ __volatile__ (
+    "int $0x40"
+    : "=a" (ret)
+    : "a" (2)
+  );
+  return ret;
+}
+
+/* --- fn 17 --- */
+static inline int GetButton (void) {
+  int ret;
+  __asm__ __volatile__ (
+    "int $0x40"
+    : "=a" (ret)
+    : "a" (17)
+  );
+  return ret;
+}
+
+#define KOS32_INPUT_MODE_ASCII 0
+#define KOS32_INPUT_MODE_SCANCODE 1
+
+/* --- fn 66.1 --- */
+static inline void SetInputMode (int mode) {
+  __asm__ __volatile__ (
+    "int $0x40"
+    :
+    : "a" (66),
+      "b" (1),
+      "c" (mode)
+  );
+}
+
+/* --- fn 66.2 --- */
+static inline int GetInputMode (void) {
+  int ret;
+  __asm__ __volatile__ (
+    "int $0x40"
+    : "=a" (ret)
+    : "a" (66),
+      "b" (2)
+  );
+  return ret;
+}
+
+/* --- fn 48.4 --- */
+static inline int GetSkinHeight (void) {
+  int ret;
+  __asm__ __volatile__ (
+    "int $0x40"
+    : "=a" (ret)
+    : "a" (48),
+      "b" (4)
+  );
+  return ret;
+}
+
+#define KOS32_SC_UNKNOWN 0x00
+#define KOS32_SC_ESCAPE  0x01
+#define KOS32_SC_1       0x02
+#define KOS32_SC_2       0x03
+#define KOS32_SC_3       0x04
+#define KOS32_SC_4       0x05
+#define KOS32_SC_5       0x06
+#define KOS32_SC_6       0x07
+#define KOS32_SC_7       0x08
+#define KOS32_SC_8       0x09
+#define KOS32_SC_9       0x0A
+#define KOS32_SC_0       0x0B
+#define KOS32_SC_MINUS   0x0C
+#define KOS32_SC_EQUALS  0x0D
+#define KOS32_SC_BACKSPACE 0x0E
+#define KOS32_SC_TAB     0x0F
+#define KOS32_SC_Q       0x10
+#define KOS32_SC_W       0x11
+#define KOS32_SC_E       0x12
+#define KOS32_SC_R       0x13
+#define KOS32_SC_T       0x14
+#define KOS32_SC_Y       0x15
+#define KOS32_SC_U       0x16
+#define KOS32_SC_I       0x17
+#define KOS32_SC_O       0x18
+#define KOS32_SC_P       0x19
+#define KOS32_SC_LEFTBRACKET 0x1A
+#define KOS32_SC_RIGHTBRACKET 0x1B
+#define KOS32_SC_RETURN  0x1C
+#define KOS32_SC_LCTRL   0x1D
+#define KOS32_SC_A       0x1E
+#define KOS32_SC_S       0x1F
+#define KOS32_SC_D       0x20
+#define KOS32_SC_F       0x21
+#define KOS32_SC_G       0x22
+#define KOS32_SC_H       0x23
+#define KOS32_SC_J       0x24
+#define KOS32_SC_K       0x25
+#define KOS32_SC_L       0x26
+#define KOS32_SC_SEMICOLON 0x27
+#define KOS32_SC_APOSTROPHE 0x28
+#define KOS32_SC_GRAVE   0x29
+#define KOS32_SC_LSHIFT  0x2A
+#define KOS32_SC_BACKSLASH 0x2B
+#define KOS32_SC_Z       0x2C
+#define KOS32_SC_X       0x2D
+#define KOS32_SC_C       0x2E
+#define KOS32_SC_V       0x2F
+#define KOS32_SC_B       0x30
+#define KOS32_SC_N       0x31
+#define KOS32_SC_M       0x32
+#define KOS32_SC_COMMA   0x33
+#define KOS32_SC_PERIOD  0x34
+#define KOS32_SC_SLASH   0x35
+#define KOS32_SC_RSHIFT  0x36
+#define KOS32_SC_KP_MULTIPLY 0x37
+#define KOS32_SC_LALT    0x38
+#define KOS32_SC_SPACE   0x39
+#define KOS32_SC_CAPSLOCK 0x3A
+#define KOS32_SC_F1      0x3B
+#define KOS32_SC_F2      0x3C
+#define KOS32_SC_F3      0x3D
+#define KOS32_SC_F4      0x3E
+#define KOS32_SC_F5      0x3F
+#define KOS32_SC_F6      0x40
+#define KOS32_SC_F7      0x41
+#define KOS32_SC_F8      0x42
+#define KOS32_SC_F9      0x43
+#define KOS32_SC_F10     0x44
+#define KOS32_SC_NUMLOCK 0x45
+#define KOS32_SC_SCROLLLOCK 0x46
+#define KOS32_SC_KP_7    0x47
+#define KOS32_SC_KP_8    0x48
+#define KOS32_SC_KP_9    0x49
+#define KOS32_SC_KP_MINUS 0x4A
+#define KOS32_SC_KP_4    0x4B
+#define KOS32_SC_KP_5    0x4C
+#define KOS32_SC_KP_6    0x4D
+#define KOS32_SC_KP_PLUS 0x4E
+#define KOS32_SC_KP_1    0x4F
+#define KOS32_SC_KP_2    0x50
+#define KOS32_SC_KP_3    0x51
+#define KOS32_SC_KP_0    0x52
+#define KOS32_SC_KP_PERIOD 0x53
+/* ... */
+#define KOS32_SC_F11     0x57
+#define KOS32_SC_F12     0x58
+
+/* extended scancodes */
+#define KOS32_SC_EXTENDED   0xE0 /* starts with this */
+#define KOS32_SC_PREV_TRACK 0x10
+#define KOS32_SC_NEXT_TRACK 0x19
+#define KOS32_SC_KP_ENTER   0x1C
+#define KOS32_SC_RCTRL      0x1D
+#define KOS32_SC_MUTE       0x20
+#define KOS32_SC_CALC       0x21
+#define KOS32_SC_PLAY       0x22
+#define KOS32_SC_STOP       0x24
+#define KOS32_SC_VOLUME_DOWN 0x2E
+#define KOS32_SC_VOLUME_UP  0x30
+#define KOS32_SC_WWW        0x32
+#define KOS32_SC_KP_DIVIDE  0x35
+#define KOS32_SC_RALT       0x38
+#define KOS32_SC_HOME       0x47
+#define KOS32_SC_UP         0x48
+#define KOS32_SC_PAGEUP     0x49
+#define KOS32_SC_LEFT       0x4B
+#define KOS32_SC_RIGHT      0x4D
+#define KOS32_SC_END        0x4F
+#define KOS32_SC_DOWN       0x50
+#define KOS32_SC_PAGEDOWN   0x51
+#define KOS32_SC_INSERT     0x52
+#define KOS32_SC_DELETE     0x53
+#define KOS32_SC_LSUPER     0x5B
+#define KOS32_SC_RSUPER     0x5C
+#define KOS32_SC_APP        0x5D
+#define KOS32_SC_POWER      0x5E
+#define KOS32_SC_SLEEP      0x5F
+#define KOS32_SC_WAKE       0x63
+#define KOS32_SC_WWW_SEARCH 0x65
+#define KOS32_SC_WWW_FAVORITE 0x66
+#define KOS32_SC_WWW_REFRESH 0x67
+#define KOS32_SC_WWW_STOP   0x68
+#define KOS32_SC_WWW_FORWARD 0x69
+#define KOS32_SC_WWW_BACK   0x6A
+#define KOS32_SC_MY_COMPUTER 0x6B
+#define KOS32_SC_EMAIL      0x6C
+#define KOS32_SC_SELECT     0x6D
+
+#define KOS32_SC_EXTENDED_PAUSE 0xE1 /* pause key seq */
+
+#endif /* KOS32_H */
diff --git a/src/kos32/main.c b/src/kos32/main.c
new file mode 100644 (file)
index 0000000..3ccbb5b
--- /dev/null
@@ -0,0 +1,590 @@
+/* Copyright (C) 2020 SovietPony
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License ONLY.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "kos32.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h> // srand exit
+#include <string.h>
+#include <assert.h>
+#include "system.h"
+#include "input.h"
+
+#include "my.h" // fexists
+#include "player.h" // pl1 pl2
+#include "menu.h" // G_keyf
+#include "error.h" // logo
+#include "monster.h" // nomon
+
+#include "files.h" // F_startup F_addwad F_initwads F_allocres
+#include "config.h" // CFG_args CFG_load CFG_save
+#include "args.h" // ARG_parse
+#include "memory.h" // M_startup
+#include "game.h" // G_init G_act
+#include "sound.h" // S_init S_done
+#include "music.h" // S_initmusic S_updatemusic S_donemusic
+#include "render.h" // R_init R_draw R_done
+
+static int ticks = 0;
+static int quit = 0;
+static videomode_size_t wlist[3] = {
+ { 320, 200, 0 },
+ { 640, 400, 0 },
+ { 800, 600, 0 },
+};
+static videomode_t vlist = {
+  3,
+  wlist
+};
+
+static byte *buf = NULL;
+static int buf_w = 0;
+static int buf_h = 0;
+static struct rgb_pal {
+  byte b, g, r, a;
+} rgbpal[256];
+
+static const cfg_t arg[] = {
+  {"file", NULL, Y_FILES},
+  {"cheat", &cheat, Y_SW_ON},
+//  {"vga", &shot_vga, Y_SW_ON},
+//  {"musvol", &mus_vol, Y_WORD},
+  {"mon", &nomon, Y_SW_OFF},
+  {"warp", &_warp, Y_BYTE},
+//  {"config", NULL, cfg_file, Y_STRING},
+  {NULL, NULL, 0} // end
+};
+
+static const cfg_t cfg[] = {
+//  {"screenshot", &shot_vga, Y_SW_ON},
+//  {"music_volume", &mus_vol, Y_WORD},
+//  {"music_random", &music_random, Y_SW_ON},
+//  {"music_time", &music_time, Y_DWORD},
+//  {"music_fade", &music_fade, Y_DWORD},
+  {"pl1_left", &pl1.kl, Y_KEY},
+  {"pl1_right",&pl1.kr, Y_KEY},
+  {"pl1_up", &pl1.ku, Y_KEY},
+  {"pl1_down", &pl1.kd, Y_KEY},
+  {"pl1_jump", &pl1.kj, Y_KEY},
+  {"pl1_fire", &pl1.kf, Y_KEY},
+  {"pl1_next", &pl1.kwr, Y_KEY},
+  {"pl1_prev", &pl1.kwl, Y_KEY},
+  {"pl1_use", &pl1.kp, Y_KEY},
+  {"pl2_left", &pl2.kl, Y_KEY},
+  {"pl2_right", &pl2.kr, Y_KEY},
+  {"pl2_up", &pl2.ku, Y_KEY},
+  {"pl2_down", &pl2.kd, Y_KEY},
+  {"pl2_jump", &pl2.kj, Y_KEY},
+  {"pl2_fire", &pl2.kf, Y_KEY},
+  {"pl2_next", &pl2.kwr, Y_KEY},
+  {"pl2_prev", &pl2.kwl, Y_KEY},
+  {"pl2_use", &pl2.kp, Y_KEY},
+  {NULL, NULL, 0} // end
+};
+
+static void CFG_args (int argc, char **argv) {
+  const cfg_t *list[] = { arg, R_args(), S_args(), MUS_args() };
+  ARG_parse(argc, argv, 4, list);
+}
+
+static void CFG_load (void) {
+  const cfg_t *list[] = { cfg, R_conf(), S_conf(), MUS_conf() };
+  CFG_read_config("default.cfg", 4, list);
+  CFG_read_config("doom2d.cfg", 4, list);
+}
+
+static void CFG_save (void) {
+  const cfg_t *list[] = { cfg, R_conf(), S_conf(), MUS_conf() };
+  CFG_update_config("doom2d.cfg", "doom2d.cfg", 4, list, "generated by doom2d, do not modify");
+}
+
+/* --- error.h --- */
+
+void logo (const char *s, ...) {
+  va_list ap;
+  va_start(ap, s);
+  vprintf(s, ap);
+  va_end(ap);
+//  fflush(stdout);
+}
+
+void logo_gas (int cur, int all) {
+  // stub
+}
+
+void ERR_failinit (char *s, ...) {
+  va_list ap;
+  va_start(ap, s);
+  vprintf(s, ap);
+  va_end(ap);
+  puts("");
+  while (1) ;
+//  exit(1);
+}
+
+void ERR_fatal (char *s, ...) {
+  va_list ap;
+  R_done();
+  MUS_done();
+  S_done();
+  M_shutdown();
+  puts("\nCRITICAL ERROR:");
+  va_start(ap, s);
+  vprintf(s, ap);
+  va_end(ap);
+  puts("");
+  while (1) ;
+//  exit(1);
+}
+
+void ERR_quit (void) {
+  quit = 1;
+}
+
+/* --- system.h --- */
+
+static int Y_resize_window (int w, int h, int fullscreen) {
+  assert(w > 0);
+  assert(h > 0);
+  if (buf != NULL) {
+    return Y_set_videomode_software(w, h, fullscreen);
+  }
+  return 0;
+}
+
+int Y_set_videomode_opengl (int w, int h, int fullscreen) {
+  assert(w > 0);
+  assert(h > 0);
+  // TODO
+  return 0;
+}
+
+static void SetupWindow (int w, int h, const char *title) {
+  int flags = KOS32_WIN_FLAG_CAPTION | KOS32_WIN_FLAG_RELATIVE | KOS32_WIN_FLAG_NOFILL;
+  int skin_h = GetSkinHeight();
+  CreateWindow(0, 0, w + 5*2, h + 5 + skin_h, KOS32_WIN_STYLE_FIXED, flags, 0x000000, 0x000000, title);
+}
+
+int Y_set_videomode_software (int w, int h, int fullscreen) {
+  assert(w > 0);
+  assert(h > 0);
+  int size = w * h;
+  byte *new_buf = malloc(size);
+  if (new_buf != NULL) {
+    Y_unset_videomode();
+    memset(new_buf, 0, size);
+    buf = new_buf;
+    buf_w = w;
+    buf_h = h;
+    BeginDraw();
+    SetupWindow(w, h, "Doom2D (software render)");
+    EndDraw();
+  }
+  return buf != NULL;
+}
+
+void Y_get_videomode (int *w, int *h) {
+  *w = buf_w;
+  *h = buf_h;
+}
+
+int Y_videomode_setted (void) {
+  return buf != NULL;
+}
+
+void Y_unset_videomode (void) {
+  if (buf != NULL) {
+    free(buf);
+    buf = NULL;
+    buf_w = 0;
+    buf_h = 0;
+  }
+}
+
+const videomode_t *Y_get_videomode_list_opengl (int fullscreen) {
+  return &vlist;
+}
+
+const videomode_t *Y_get_videomode_list_software (int fullscreen) {
+  return &vlist;
+}
+
+void Y_set_fullscreen (int yes) {
+  // TODO
+}
+
+int Y_get_fullscreen (void) {
+  // TODO
+  return 0;
+}
+
+void Y_swap_buffers (void) {
+  // TODO
+}
+
+void Y_get_buffer (byte **buf_ref, int *w, int *h, int *pitch) {
+  assert(buf_ref != NULL);
+  assert(w != NULL);
+  assert(h != NULL);
+  assert(pitch != NULL);
+  *buf_ref = buf;
+  *w = buf_w;
+  *h = buf_h;
+  *pitch = buf_w;
+}
+
+void Y_set_vga_palette (byte *vgapal) {
+  int i;
+  byte *p = vgapal;
+  assert(buf != NULL);
+  assert(vgapal != NULL);
+  for (i = 0; i < 256; i++) {
+    rgbpal[i].r = p[0] * 255 / 63;
+    rgbpal[i].g = p[1] * 255 / 63;
+    rgbpal[i].b = p[2] * 255 / 63;
+    p += 3;
+  }
+}
+
+void Y_repaint_rect (int x, int y, int w, int h) {
+  Y_repaint();
+}
+
+void Y_repaint (void) {
+  assert(buf != NULL);
+  BeginDraw();
+  SetupWindow(buf_w, buf_h, NULL);
+  PutImageExt(buf, buf_w, buf_h, 0, 0, 8, rgbpal, 0);
+  EndDraw();
+}
+
+void Y_enable_text_input (void) {
+  SetInputMode(KOS32_INPUT_MODE_ASCII);
+}
+
+void Y_disable_text_input (void) {
+  SetInputMode(KOS32_INPUT_MODE_SCANCODE);
+}
+
+/* --- main --- */
+
+static int scancode_to_key (int scancode) {
+  switch (scancode) {
+    case KOS32_SC_0: return KEY_0;
+    case KOS32_SC_1: return KEY_1;
+    case KOS32_SC_2: return KEY_2;
+    case KOS32_SC_3: return KEY_3;
+    case KOS32_SC_4: return KEY_4;
+    case KOS32_SC_5: return KEY_5;
+    case KOS32_SC_6: return KEY_6;
+    case KOS32_SC_7: return KEY_7;
+    case KOS32_SC_8: return KEY_8;
+    case KOS32_SC_9: return KEY_9;
+    case KOS32_SC_A: return KEY_A;
+    case KOS32_SC_B: return KEY_B;
+    case KOS32_SC_C: return KEY_C;
+    case KOS32_SC_D: return KEY_D;
+    case KOS32_SC_E: return KEY_E;
+    case KOS32_SC_F: return KEY_F;
+    case KOS32_SC_G: return KEY_G;
+    case KOS32_SC_H: return KEY_H;
+    case KOS32_SC_I: return KEY_I;
+    case KOS32_SC_J: return KEY_J;
+    case KOS32_SC_K: return KEY_K;
+    case KOS32_SC_L: return KEY_L;
+    case KOS32_SC_M: return KEY_M;
+    case KOS32_SC_N: return KEY_N;
+    case KOS32_SC_O: return KEY_O;
+    case KOS32_SC_P: return KEY_P;
+    case KOS32_SC_Q: return KEY_Q;
+    case KOS32_SC_R: return KEY_R;
+    case KOS32_SC_S: return KEY_S;
+    case KOS32_SC_T: return KEY_T;
+    case KOS32_SC_U: return KEY_U;
+    case KOS32_SC_V: return KEY_V;
+    case KOS32_SC_W: return KEY_W;
+    case KOS32_SC_X: return KEY_X;
+    case KOS32_SC_Y: return KEY_Y;
+    case KOS32_SC_Z: return KEY_Z;
+    case KOS32_SC_RETURN: return KEY_RETURN;
+    case KOS32_SC_ESCAPE: return KEY_ESCAPE;
+    case KOS32_SC_BACKSPACE: return KEY_BACKSPACE;
+    case KOS32_SC_TAB: return KEY_TAB;
+    case KOS32_SC_SPACE: return KEY_SPACE;
+    case KOS32_SC_MINUS: return KEY_MINUS;
+    case KOS32_SC_EQUALS: return KEY_EQUALS;
+    case KOS32_SC_LEFTBRACKET: return KEY_LEFTBRACKET;
+    case KOS32_SC_RIGHTBRACKET: return KEY_RIGHTBRACKET;
+    case KOS32_SC_BACKSLASH: return KEY_BACKSLASH;
+    case KOS32_SC_SEMICOLON: return KEY_SEMICOLON;
+    case KOS32_SC_APOSTROPHE: return KEY_APOSTROPHE;
+    case KOS32_SC_GRAVE: return KEY_GRAVE;
+    case KOS32_SC_COMMA: return KEY_COMMA;
+    case KOS32_SC_PERIOD: return KEY_PERIOD;
+    case KOS32_SC_SLASH: return KEY_SLASH;
+    case KOS32_SC_CAPSLOCK: return KEY_CAPSLOCK;
+    case KOS32_SC_F1: return KEY_F1;
+    case KOS32_SC_F2: return KEY_F2;
+    case KOS32_SC_F3: return KEY_F3;
+    case KOS32_SC_F4: return KEY_F4;
+    case KOS32_SC_F5: return KEY_F5;
+    case KOS32_SC_F6: return KEY_F6;
+    case KOS32_SC_F7: return KEY_F7;
+    case KOS32_SC_F8: return KEY_F8;
+    case KOS32_SC_F9: return KEY_F9;
+    case KOS32_SC_F10: return KEY_F10;
+    case KOS32_SC_F11: return KEY_F11;
+    case KOS32_SC_F12: return KEY_F12;
+    case KOS32_SC_SCROLLLOCK: return KEY_SCROLLLOCK;
+    case KOS32_SC_NUMLOCK: return KEY_NUMLOCK;
+    case KOS32_SC_KP_MULTIPLY: return KEY_KP_MULTIPLY;
+    case KOS32_SC_KP_MINUS: return KEY_KP_MINUS;
+    case KOS32_SC_KP_PLUS: return KEY_KP_PLUS;
+    case KOS32_SC_KP_0: return KEY_KP_0;
+    case KOS32_SC_KP_1: return KEY_KP_1;
+    case KOS32_SC_KP_2: return KEY_KP_2;
+    case KOS32_SC_KP_3: return KEY_KP_3;
+    case KOS32_SC_KP_4: return KEY_KP_4;
+    case KOS32_SC_KP_5: return KEY_KP_5;
+    case KOS32_SC_KP_6: return KEY_KP_6;
+    case KOS32_SC_KP_7: return KEY_KP_7;
+    case KOS32_SC_KP_8: return KEY_KP_8;
+    case KOS32_SC_KP_9: return KEY_KP_9;
+    case KOS32_SC_KP_PERIOD: return KEY_KP_PERIOD;
+    case KOS32_SC_LCTRL: return KEY_LCTRL;
+    case KOS32_SC_LSHIFT: return KEY_LSHIFT;
+    case KOS32_SC_LALT: return KEY_LALT;
+    default: return KEY_UNKNOWN;
+  }
+}
+
+static int ext_scancode_to_key (int scancode) {
+  switch (scancode) {
+    case KOS32_SC_INSERT: return KEY_INSERT;
+    case KOS32_SC_HOME: return KEY_HOME;
+    case KOS32_SC_PAGEUP: return KEY_PAGEUP;
+    case KOS32_SC_DELETE: return KEY_DELETE;
+    case KOS32_SC_END: return KEY_END;
+    case KOS32_SC_PAGEDOWN: return KEY_PAGEDOWN;
+    case KOS32_SC_RIGHT: return KEY_RIGHT;
+    case KOS32_SC_LEFT: return KEY_LEFT;
+    case KOS32_SC_DOWN: return KEY_DOWN;
+    case KOS32_SC_UP: return KEY_UP;
+    case KOS32_SC_KP_DIVIDE: return KEY_KP_DIVIDE;
+    case KOS32_SC_KP_ENTER: return KEY_KP_ENTER;
+    case KOS32_SC_LSUPER: return KEY_LSUPER;
+    case KOS32_SC_RCTRL: return KEY_RCTRL;
+    case KOS32_SC_RSHIFT: return KEY_RSHIFT;
+    case KOS32_SC_RALT: return KEY_RALT;
+    case KOS32_SC_RSUPER: return KEY_RSUPER;
+    default: return KEY_UNKNOWN;
+  }
+}
+
+static void handle_scancode (int code) {
+  static enum {
+    ST_std, ST_ext,
+    ST_print_down_1, ST_print_down_2,
+    ST_print_up_1, ST_print_up_2,
+    ST_pause_1, ST_pause_2, ST_pause_3, ST_pause_4, ST_pause_5,
+    ST_ok
+  } state = ST_std;
+  int k, down;
+  // logo("scancode >> 0x%x\n", code);
+  switch (state) {
+    case ST_std:
+      if (code == KOS32_SC_EXTENDED) {
+        state = ST_ext;
+      } else if (code == KOS32_SC_EXTENDED_PAUSE) {
+        state = ST_pause_1;
+      } else {
+        state = ST_ok;
+        k = scancode_to_key(code & 0x7f);
+        down = !((code >> 7) & 1);
+      }
+      break;
+    case ST_ext:
+      if (code == 0x2A) {
+        state = ST_print_down_1;
+      } else if (code == 0xB7) {
+        state = ST_print_up_1;
+      } else {
+        state = ST_ok;
+        k = ext_scancode_to_key(code & 0x7f);
+        down = !((code >> 7) & 1);
+      }
+      break;
+    case ST_print_down_1:
+      assert(code == 0xE0);
+      state = ST_print_down_2;
+      break;
+    case ST_print_down_2:
+      assert(code == 0x37);
+      state = ST_ok;
+      k = KEY_PRINTSCREEN;
+      down = 1;
+      break;
+    case ST_print_up_1:
+      assert(code == 0xE0);
+      state = ST_print_up_2;
+      break;
+    case ST_print_up_2:
+      assert(code == 0xAA);
+      state = ST_ok;
+      k = KEY_PRINTSCREEN;
+      down = 0;
+      break;
+    case ST_pause_1:
+      assert(code == 0x1D);
+      state = ST_pause_2;
+      break;
+    case ST_pause_2:
+      assert(code == 0x45);
+      state = ST_pause_3;
+      break;
+    case ST_pause_3:
+      assert(code == 0xE1);
+      state = ST_pause_4;
+      break;
+    case ST_pause_4:
+      assert(code == 0x9D);
+      state = ST_pause_5;
+      break;
+    case ST_pause_5:
+      assert(code == 0xC5);
+      state = ST_ok;
+      k = KEY_PAUSE;
+      down = 1;
+      break;
+    default:
+      ERR_fatal("handle_scancode: invalid state: %i\n", state);
+  }
+  if (state == ST_ok) {
+    state = ST_std;
+    // logo("key: %s (%i, %s)\n", I_key_to_string(k), k, down ? "down" : "up");
+    I_press(k, down);
+    GM_key(k, down);
+    if (k = KEY_PAUSE) {
+      I_press(k, 0);
+      GM_key(k, 0);
+    }
+  }
+}
+
+static void poll_events (void) {
+  int ev, key, button, code, ch, k;
+  while(!!(ev = CheckEvent())) {
+    switch (ev) {
+      case KOS32_EVENT_REDRAW:
+        return; /* force redraw */
+      case KOS32_EVENT_KEYBOARD:
+        key = GetKey();
+        if ((key & 0xff) == 0) {
+          switch (GetInputMode()) {
+            case KOS32_INPUT_MODE_ASCII:
+              ch = (key >> 8) & 0xff;
+              code = (key >> 16) & 0x7f;
+              k = scancode_to_key(code);
+              I_press(k, 1);
+              GM_key(k, 1);
+              GM_input(ch);
+              I_press(k, 0);
+              GM_key(k, 0);
+              break;
+            case KOS32_INPUT_MODE_SCANCODE:
+              code = key >> 8;
+              handle_scancode(code);
+              break;
+          }
+        }
+        break;
+      case KOS32_EVENT_BUTTON:
+        button = GetButton();
+        quit = button == 256; /* close button + left click */
+        break;
+      default:
+        ERR_fatal("poll_events: unhandled event: %i\n", ev);
+    }
+  }
+}
+
+static void step (void) {
+  poll_events();
+  MUS_update();
+  long t = GetTimeCountPro(); /* ns */
+  if (t - ticks > DELAY * 1000000) {
+    ticks = t;
+    G_act();
+  }
+  R_draw();
+}
+
+int main (int argc, char **argv) {
+  char *pw;
+  CFG_args(argc, argv);
+  logo("system: initialize engine\n");
+  SetEventsMask(KOS32_EVENT_FLAG_REDRAW | KOS32_EVENT_FLAG_KEYBOARD | KOS32_EVENT_FLAG_BUTTON);
+  Y_disable_text_input();
+  // Player 1 defaults
+  pl1.ku = KEY_KP_8;
+  pl1.kd = KEY_KP_5;
+  pl1.kl = KEY_KP_4;
+  pl1.kr = KEY_KP_6;
+  pl1.kf = KEY_PAGEDOWN;
+  pl1.kj = KEY_DELETE;
+  pl1.kwl = KEY_HOME;
+  pl1.kwr = KEY_END;
+  pl1.kp = KEY_KP_8;
+  // Player 2 defaults
+  pl2.ku = KEY_E;
+  pl2.kd = KEY_D;
+  pl2.kl = KEY_S;
+  pl2.kr = KEY_F;
+  pl2.kf = KEY_A;
+  pl2.kj = KEY_Q;
+  pl2.kwl = KEY_1;
+  pl2.kwr = KEY_2;
+  pl2.kp = KEY_E;
+  srand(GetIdleCount());
+  F_startup();
+  CFG_load();
+  pw = "doom2d.wad";
+  if (fexists(pw)) {
+    F_addwad(pw);
+  } else {
+    F_addwad("doom2d.wad");
+  }
+  F_initwads();
+  M_startup();
+  F_allocres();
+  S_init();
+  MUS_init();
+  R_init();
+  G_init();
+  ticks = GetTimeCountPro();
+  logo("system: game loop\n");
+  while (!quit) {
+    step();
+  }
+  logo("system: finalize engine\n");
+  CFG_save();
+  R_done();
+  MUS_done();
+  S_done();
+  M_shutdown();
+  logo("system: halt\n");
+  return 0;
+}