commit 11b0825dd77182992237c4e189c5a7fdec73cc95
Author: Mikołaj Lenczewski <mblenczewski@gmail.com>
Date: Sun, 28 Apr 2024 22:42:13 +0000
Initial commit
Diffstat:
17 files changed, 827 insertions(+), 0 deletions(-)
diff --git a/.editorconfig b/.editorconfig
@@ -0,0 +1,21 @@
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+charset = utf-8
+
+guidelines = 80, 120, 160
+
+[*.{c,h}]
+indent_style = tab
+indent_size = 8
+
+[*.{sh}]
+indent_style = tab
+indent_size = 8
+
+[*.{md,txt}]
+indent_style = space
+indent_size = 2
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,4 @@
+bin/
+opt/
+
+**/.*.swp
diff --git a/.gitmodules b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "thirdparty/msvc-wine"]
+ path = thirdparty/msvc-wine
+ url = https://github.com/mstorsjo/msvc-wine.git
diff --git a/LICENSE b/LICENSE
@@ -0,0 +1,18 @@
+The MIT-Zero License
+
+Copyright (c) 2024 Mikołaj Lenczewski <mblenczewski@gmail.com>
+
+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.
+
+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.
diff --git a/README b/README
@@ -0,0 +1,39 @@
+starfield
+==============================================================================
+A simple game, built for linux and cross-compiled for windows.
+
+starfield: Dependencies
+------------------------------------------------------------------------------
+Our expected build environment is linux, with the following dependencies:
+```text
+- clang
+```
+
+When building for linux, our dependency list is:
+```text
+```
+
+When building for windows, our dependency list is:
+```text
+- python3 (build-time dependency)
+- msitools (build-time dependency)
+- perl (build-time dependency)
+```
+
+starfield: Building
+------------------------------------------------------------------------------
+To clean any built artefacts, run:
+```sh
+$ ./clean.sh
+```
+
+To build for linux, run:
+```sh
+$ ./build_linux.sh
+```
+
+To build for windows, run:
+```sh
+$ ./setup_win.sh # ensure this has been ran at least once
+$ ./build_win.sh
+```
diff --git a/build_linux.sh b/build_linux.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+CC="clang"
+
+WARNINGS="-Wall -Wextra -Wpedantic -Werror"
+
+CFLAGS="-std=c11 -O0 -g"
+CPPFLAGS="-DSTARFIELD_DEBUG -Icommon"
+
+set -ex
+
+mkdir -p bin
+
+$CC -shared -o bin/starfield.so starfield/starfield.c \
+ $WARNINGS $CFLAGS $CPPFLAGS -nostdlib
+
+# TODO: how to build a static binary that supports loading starfield.so?
+$CC -o bin/linux_starfield platform/linux_starfield.c \
+ $WARNINGS $CFLAGS $CPPFLAGS
diff --git a/build_win.sh b/build_win.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+# sets up windows sdk variables, make sure to run `setup_win.sh` beforehand
+BIN="$(realpath ./opt/bin/x64)" . ./thirdparty/msvc-wine/msvcenv-native.sh
+
+CC="clang --target=x86_64-pc-windows-msvc"
+
+WARNINGS="-Wall -Wextra -Wpedantic -Werror"
+
+CFLAGS="-std=c11 -O0 -g -gcodeview"
+CPPFLAGS="-DSTARFIELD_DEBUG -Icommon"
+LDFLAGS="-fuse-ld=lld -Wl,/debug:full,/pdb:"
+
+EXPORTS="
+ -Wl,/export:starfield_init
+ -Wl,/export:starfield_update
+ -Wl,/export:starfield_render
+ -Wl,/export:starfield_sample
+ -Wl,/export:starfield_free
+"
+
+# TODO: remove -lksuser and -luuid ?
+WIN_LIBS="-luser32 -lgdi32 -lwinmm -lksuser -luuid"
+
+set -ex
+
+mkdir -p bin
+
+$CC -shared -o bin/starfield.dll starfield/starfield.c \
+ $WARNINGS $CFLAGS $CPPFLAGS $LDFLAGS $EXPORTS
+
+$CC -static -o bin/win_starfield.exe platform/win_starfield.c \
+ $WARNINGS $CFLAGS $CPPFLAGS $LDFLAGS $WIN_LIBS -Wl,/subsystem:windows
diff --git a/clean.sh b/clean.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+set -ex
+
+rm -rf bin
diff --git a/common/starfield_api.h b/common/starfield_api.h
@@ -0,0 +1,226 @@
+#ifndef STARFIELD_API_H
+#define STARFIELD_API_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#include <inttypes.h>
+#include <limits.h>
+#include <stdalign.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+typedef int32_t b32;
+
+typedef uint8_t u8;
+typedef uint16_t u16;
+typedef uint32_t u32;
+typedef uint64_t u64;
+
+typedef int8_t s8;
+typedef int16_t s16;
+typedef int32_t s32;
+typedef int64_t s64;
+
+typedef float f32;
+typedef double f64;
+
+typedef unsigned char c8;
+
+struct str_t {
+ c8 *ptr;
+ u64 len;
+};
+
+#define KiB (1024ULL)
+#define MiB (1024 * KiB)
+#define GiB (1024 * MiB)
+#define TiB (1024 * GiB)
+
+#define ARRLEN(arr) (sizeof (arr) / sizeof (arr)[0])
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
+#define TESTBITS(v, mask) (((v) & (mask)) == (mask))
+
+#define IS_ALIGNED(v, align) (((v) & ~((align) - 1)) == 0)
+
+#define ALIGN_PREV(v, align) ((v) & ~((align) - 1))
+#define ALIGN_NEXT(v, align) ALIGN_PREV((v) + (align) - 1, (align))
+
+#ifdef STARFIELD_DEBUG
+ #define ASSERT(cond) if (!(cond)) { *((volatile u8 *) 0) = 0; }
+#else
+ #define ASSERT(cond)
+#endif
+
+#define global extern
+#define internal static
+#define func_local static
+
+/* platform api definitions */
+
+#ifdef STARFIELD_DEBUG
+
+struct DEBUG_platform_read_file_result {
+ void *ptr;
+ u64 len;
+};
+
+#define DEBUG_PLATFORM_READ_FILE(name) \
+ struct DEBUG_platform_read_file_result name(char const *filename)
+
+#define DEBUG_PLATFORM_WRITE_FILE(name) \
+ b32 name(char const *filename, void *buf, u64 len)
+
+#define DEBUG_PLATFORM_FREE_FILE_MEMORY(name) \
+ void name(void *ptr)
+
+typedef DEBUG_PLATFORM_READ_FILE(DEBUG_platform_api_read_file_t);
+typedef DEBUG_PLATFORM_WRITE_FILE(DEBUG_platform_api_write_file_t);
+typedef DEBUG_PLATFORM_FREE_FILE_MEMORY(DEBUG_platform_api_free_file_memory_t);
+
+#endif
+
+struct platform_api {
+#ifdef STARFIELD_DEBUG
+ DEBUG_platform_api_read_file_t *DEBUG_read_file;
+ DEBUG_platform_api_write_file_t *DEBUG_write_file;
+ DEBUG_platform_api_free_file_memory_t *DEBUG_free_file_memory;
+#endif
+};
+
+/* starfield api definitions */
+
+#define STARFIELD_DATADIR "data"
+#define STARFIELD_TEMPDIR "temp"
+
+struct starfield_thread;
+
+struct starfield_arena {
+ void *ptr;
+ u64 cap, len;
+};
+
+struct starfield_memory {
+ struct starfield_arena permanent, temporary;
+};
+
+struct starfield_button_input {
+ u32 half_transition_count;
+ b32 was_down;
+};
+
+enum starfield_mouse_button {
+ STARFIELD_MOUSE_L,
+ STARFIELD_MOUSE_R,
+ STARFIELD_MOUSE_M,
+
+ STARFIELD_MOUSE_X,
+ STARFIELD_MOUSE_Y,
+
+ _STARFIELD_MOUSE_BUTTON_COUNT,
+};
+
+struct starfield_mouse_input {
+ s16 x, y, z;
+ struct starfield_button_input buttons[_STARFIELD_MOUSE_BUTTON_COUNT];
+};
+
+enum starfield_keyboard_button {
+ STARFIELD_KEYBOARD_ESC,
+
+ STARFIELD_KEYBOARD_A,
+ STARFIELD_KEYBOARD_D,
+ STARFIELD_KEYBOARD_W,
+ STARFIELD_KEYBOARD_S,
+ STARFIELD_KEYBOARD_SPACE,
+ STARFIELD_KEYBOARD_SHIFT,
+ STARFIELD_KEYBOARD_CTRL,
+
+ STARFIELD_KEYBOARD_Q,
+ STARFIELD_KEYBOARD_E,
+
+ STARFIELD_KEYBOARD_J,
+ STARFIELD_KEYBOARD_L,
+ STARFIELD_KEYBOARD_I,
+ STARFIELD_KEYBOARD_K,
+
+ _STARFIELD_KEYBOARD_BUTTON_COUNT,
+};
+
+struct starfield_keyboard_input {
+ struct starfield_button_input buttons[_STARFIELD_KEYBOARD_BUTTON_COUNT];
+};
+
+struct starfield_input {
+ struct starfield_mouse_input mouse;
+ struct starfield_keyboard_input keyboard;
+};
+
+struct starfield_video_buffer {
+ void *buffer;
+ u32 width, height;
+ u32 pitch;
+};
+
+struct starfield_audio_buffer {
+ void *buffer;
+ u32 sample_rate;
+ u32 channels;
+ u32 expected_samples;
+};
+
+#define STARFIELD_INIT(name) \
+ void name(struct starfield_thread *thread, \
+ struct starfield_memory *memory, \
+ struct platform_api *platform)
+
+#define STARFIELD_UPDATE(name) \
+ void name(struct starfield_thread *thread, \
+ struct starfield_memory *memory, \
+ struct platform_api *platform, \
+ struct starfield_input *input, \
+ f32 dt)
+
+#define STARFIELD_RENDER(name) \
+ void name(struct starfield_thread *thread, \
+ struct starfield_memory *memory, \
+ struct platform_api *platform, \
+ struct starfield_video_buffer *video, \
+ f32 dt)
+
+#define STARFIELD_SAMPLE(name) \
+ void name(struct starfield_thread *thread, \
+ struct starfield_memory *memory, \
+ struct platform_api *platform, \
+ struct starfield_audio_buffer *audio, \
+ f32 dt)
+
+#define STARFIELD_FREE(name) \
+ void name(struct starfield_thread *thread, \
+ struct starfield_memory *memory, \
+ struct platform_api *platform)
+
+typedef STARFIELD_INIT(starfield_api_init_t);
+typedef STARFIELD_UPDATE(starfield_api_update_t);
+typedef STARFIELD_RENDER(starfield_api_render_t);
+typedef STARFIELD_SAMPLE(starfield_api_sample_t);
+typedef STARFIELD_FREE(starfield_api_free_t);
+
+struct starfield_api {
+ starfield_api_init_t *init;
+ starfield_api_update_t *update;
+ starfield_api_render_t *render;
+ starfield_api_sample_t *sample;
+ starfield_api_free_t *free;
+};
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* STARFIELD_API_H */
diff --git a/platform/linux_starfield.c b/platform/linux_starfield.c
@@ -0,0 +1,167 @@
+#include "linux_starfield.h"
+
+internal struct linux_starfield_state state;
+
+int
+main(int argc, char **argv, char **envp)
+{
+ /* TODO: parse out commandline arguments for debug behaviour? */
+ (void) argc;
+ (void) envp;
+
+ struct platform_api platform_api = load_platform_api();
+
+ struct starfield_thread thread = {
+ .id = 0,
+ };
+
+ struct starfield_memory memory;
+ if (!init_starfield_memory(256 * MiB, 256 * MiB, &memory)) {
+ fprintf(stderr, "Failed to allocate starfield memory\n");
+ _exit(1);
+ }
+
+ /* NOTE: we expect the library to be alongside our platform-specific
+ * executable. the unix loader does not search this path by default,
+ * so we build the absolute path to the library here as a workaround
+ */
+ char starfield_library_path[PATH_MAX];
+ strcpy(starfield_library_path, argv[0]);
+ strcpy(strrchr(starfield_library_path, '/'), "/starfield.so");
+
+ struct starfield_api starfield_api;
+ if (!load_starfield_api(starfield_library_path, &starfield_api)) {
+ fprintf(stderr, "Failed to load starfield library: %s\n", starfield_library_path);
+ _exit(1);
+ }
+
+ starfield_api.init(&thread, &memory, &platform_api);
+
+ state.running = 1;
+ while (state.running) {
+ f32 dt = 0;
+
+ struct starfield_input input = {
+ 0,
+ };
+
+ starfield_api.update(&thread, &memory, &platform_api, &input, dt);
+
+ struct starfield_video_buffer video = {
+ 0,
+ };
+
+ starfield_api.render(&thread, &memory, &platform_api, &video, dt);
+
+ struct starfield_audio_buffer audio = {
+ 0,
+ };
+
+ starfield_api.sample(&thread, &memory, &platform_api, &audio, dt);
+
+ sleep(1);
+ }
+
+ starfield_api.free(&thread, &memory, &platform_api);
+
+ _exit(0);
+}
+
+b32
+init_starfield_memory(u64 permanent_memory_cap, u64 temporary_memory_cap,
+ struct starfield_memory *memory)
+{
+#if STARFIELD_DEBUG
+ void *base_addr = (void *) (2 * TiB);
+#else
+ void *base_addr = NULL;
+#endif
+
+ u64 len = permanent_memory_cap + temporary_memory_cap;
+
+ /* TODO: add hugetlb support? seems to require a kernel commandline arg */
+ void *ptr = mmap(base_addr, len,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
+ -1, 0);
+
+ if (ptr == MAP_FAILED) return false;
+
+ /* TODO: can we make this faster? can we avoid this entirely? */
+ memset(base_addr, 0, len);
+
+ memory->permanent.ptr = ptr;
+ memory->permanent.cap = permanent_memory_cap;
+ memory->permanent.len = 0;
+
+ memory->temporary.ptr = (u8 *) ptr + permanent_memory_cap;
+ memory->temporary.cap = temporary_memory_cap;
+ memory->temporary.len = 0;
+
+ return true;
+}
+
+b32
+load_starfield_api(char const *filename, struct starfield_api *api)
+{
+ void *library = dlopen(filename, RTLD_LAZY);
+ if (!library) return false;
+
+ if (!(api->init = (starfield_api_init_t *) dlsym(library, "starfield_init")))
+ return false;
+
+ if (!(api->update = (starfield_api_update_t *) dlsym(library, "starfield_update")))
+ return false;
+
+ if (!(api->render = (starfield_api_render_t *) dlsym(library, "starfield_render")))
+ return false;
+
+ if (!(api->sample = (starfield_api_sample_t *) dlsym(library, "starfield_sample")))
+ return false;
+
+ if (!(api->free = (starfield_api_free_t *) dlsym(library, "starfield_free")))
+ return false;
+
+ return true;
+}
+
+/* platform implementation
+ * ===========================================================================
+ */
+
+#ifdef STARFIELD_DEBUG
+
+DEBUG_PLATFORM_READ_FILE(DEBUG_platform_read_file)
+{
+ (void) filename;
+
+ return (struct DEBUG_platform_read_file_result) {0};
+}
+
+DEBUG_PLATFORM_WRITE_FILE(DEBUG_platform_write_file)
+{
+ (void) filename;
+ (void) buf;
+ (void) len;
+
+ return true;
+}
+
+DEBUG_PLATFORM_FREE_FILE_MEMORY(DEBUG_platform_free_file_memory)
+{
+ (void) ptr;
+}
+
+#endif
+
+struct platform_api
+load_platform_api(void)
+{
+ return (struct platform_api) {
+#ifdef STARFIELD_DEBUG
+ .DEBUG_read_file = DEBUG_platform_read_file,
+ .DEBUG_write_file = DEBUG_platform_write_file,
+ .DEBUG_free_file_memory = DEBUG_platform_free_file_memory,
+#endif
+ };
+}
diff --git a/platform/linux_starfield.h b/platform/linux_starfield.h
@@ -0,0 +1,34 @@
+#ifndef LINUX_STARFIELD_H
+#define LINUX_STARFIELD_H
+
+#define _XOPEN_SOURCE 700
+
+#include "starfield_api.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/mman.h>
+
+#include <dlfcn.h>
+
+struct starfield_thread {
+ u32 id;
+};
+
+struct linux_starfield_state {
+ b32 running;
+};
+
+struct platform_api
+load_platform_api(void);
+
+b32
+init_starfield_memory(u64 permanent_memory_cap, u64 temporary_memory_cap,
+ struct starfield_memory *memory);
+
+b32
+load_starfield_api(char const *filename, struct starfield_api *api);
+
+#endif /* LINUX_STARFIELD_H */
diff --git a/platform/win_starfield.c b/platform/win_starfield.c
@@ -0,0 +1,165 @@
+#include "win_starfield.h"
+
+internal struct win_starfield_state state;
+
+int WINAPI
+WinMain(HINSTANCE instance, HINSTANCE prev_instance, LPSTR cmdline, int cmdshow)
+{
+ (void) instance;
+ (void) prev_instance;
+ (void) cmdline;
+ (void) cmdshow;
+
+ struct platform_api platform_api = load_platform_api();
+
+ struct starfield_thread thread = {
+ .id = 0,
+ };
+
+ struct starfield_memory memory;
+ if (!init_starfield_memory(256 * MiB, 256 * MiB, &memory)) {
+ fprintf(stderr, "Failed to allocate starfield memory\n");
+ _exit(1);
+ }
+
+ /* NOTE: the windows dll loader checks the parent directory of our
+ * instance when loading libraries, so no need to do anything fancy
+ */
+ char starfield_library_path[] = "starfield.dll";
+
+ struct starfield_api starfield_api;
+ if (!load_starfield_api(starfield_library_path, &starfield_api)) {
+ fprintf(stderr, "Failed to load starfield library: %s\n", starfield_library_path);
+ _exit(1);
+ }
+
+ starfield_api.init(&thread, &memory, &platform_api);
+
+ state.running = 1;
+ while (state.running) {
+ f32 dt = 0;
+
+ struct starfield_input input = {
+ 0,
+ };
+
+ starfield_api.update(&thread, &memory, &platform_api, &input, dt);
+
+ struct starfield_video_buffer video = {
+ 0,
+ };
+
+ starfield_api.render(&thread, &memory, &platform_api, &video, dt);
+
+ struct starfield_audio_buffer audio = {
+ 0,
+ };
+
+ starfield_api.sample(&thread, &memory, &platform_api, &audio, dt);
+
+ Sleep(1000);
+ }
+
+ starfield_api.free(&thread, &memory, &platform_api);
+
+ _exit(0);
+}
+
+b32
+init_starfield_memory(u64 permanent_memory_cap, u64 temporary_memory_cap,
+ struct starfield_memory *memory)
+{
+#if STARFIELD_DEBUG
+ void *base_addr = (void *) (2 * TiB);
+#else
+ void *base_addr = NULL;
+#endif
+
+ u64 len = permanent_memory_cap + temporary_memory_cap;
+
+ /* TODO: add hugetlb support?
+ * https://learn.microsoft.com/en-us/windows/win32/memory/large-page-support
+ */
+ void *ptr = VirtualAlloc(base_addr, len, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
+
+ if (!ptr) return false;
+
+ /* NOTE: windows guarantees that the memory returned by VirtualAlloc
+ * is zero-ed, so no need to memset it here
+ */
+
+ memory->permanent.ptr = ptr;
+ memory->permanent.cap = permanent_memory_cap;
+ memory->permanent.len = 0;
+
+ memory->temporary.ptr = (u8 *) ptr + permanent_memory_cap;
+ memory->temporary.cap = temporary_memory_cap;
+ memory->temporary.len = 0;
+
+ return true;
+}
+
+b32
+load_starfield_api(char const *filename, struct starfield_api *api)
+{
+ HMODULE library = LoadLibraryA(filename);
+ if (!library) return false;
+
+ if (!(api->init = (starfield_api_init_t *) GetProcAddress(library, "starfield_init")))
+ return false;
+
+ if (!(api->update = (starfield_api_update_t *) GetProcAddress(library, "starfield_update")))
+ return false;
+
+ if (!(api->render = (starfield_api_render_t *) GetProcAddress(library, "starfield_render")))
+ return false;
+
+ if (!(api->sample = (starfield_api_sample_t *) GetProcAddress(library, "starfield_sample")))
+ return false;
+
+ if (!(api->free = (starfield_api_free_t *) GetProcAddress(library, "starfield_free")))
+ return false;
+
+ return true;
+}
+
+/* platform implementation
+ * ===========================================================================
+ */
+
+#ifdef STARFIELD_DEBUG
+
+DEBUG_PLATFORM_READ_FILE(DEBUG_platform_read_file)
+{
+ (void) filename;
+
+ return (struct DEBUG_platform_read_file_result) {0};
+}
+
+DEBUG_PLATFORM_WRITE_FILE(DEBUG_platform_write_file)
+{
+ (void) filename;
+ (void) buf;
+ (void) len;
+
+ return false;
+}
+
+DEBUG_PLATFORM_FREE_FILE_MEMORY(DEBUG_platform_free_file_memory)
+{
+ (void) ptr;
+}
+
+#endif
+
+struct platform_api
+load_platform_api(void)
+{
+ return (struct platform_api) {
+#ifdef STARFIELD_DEBUG
+ .DEBUG_read_file = DEBUG_platform_read_file,
+ .DEBUG_write_file = DEBUG_platform_write_file,
+ .DEBUG_free_file_memory = DEBUG_platform_free_file_memory,
+#endif
+ };
+}
diff --git a/platform/win_starfield.h b/platform/win_starfield.h
@@ -0,0 +1,28 @@
+#ifndef WIN_STARFIELD_H
+#define WIN_STARFIELD_H
+
+#include "starfield_api.h"
+
+#include <stdio.h>
+
+#include <windows.h>
+
+struct starfield_thread {
+ u32 id;
+};
+
+struct win_starfield_state {
+ b32 running;
+};
+
+struct platform_api
+load_platform_api(void);
+
+b32
+init_starfield_memory(u64 permanent_memory_cap, u64 temporary_memory_cap,
+ struct starfield_memory *memory);
+
+b32
+load_starfield_api(char const *filename, struct starfield_api *api);
+
+#endif /* WIN_STARFIELD_H */
diff --git a/setup_win.sh b/setup_win.sh
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+set -ex
+
+git submodule update --init --recursive
+
+# TODO: switch from vsdownload.py to simplified script
+
+mkdir -p opt
+python3 ./thirdparty/msvc-wine/vsdownload.py --dest opt
+./thirdparty/msvc-wine/install.sh opt
diff --git a/starfield/starfield.c b/starfield/starfield.c
@@ -0,0 +1,47 @@
+#include "starfield.h"
+
+global
+STARFIELD_INIT(starfield_init)
+{
+ (void) thread;
+ (void) memory;
+ (void) platform;
+}
+
+global
+STARFIELD_UPDATE(starfield_update)
+{
+ (void) thread;
+ (void) memory;
+ (void) platform;
+ (void) input;
+ (void) dt;
+}
+
+global
+STARFIELD_RENDER(starfield_render)
+{
+ (void) thread;
+ (void) memory;
+ (void) platform;
+ (void) video;
+ (void) dt;
+}
+
+global
+STARFIELD_SAMPLE(starfield_sample)
+{
+ (void) thread;
+ (void) memory;
+ (void) platform;
+ (void) audio;
+ (void) dt;
+}
+
+global
+STARFIELD_FREE(starfield_free)
+{
+ (void) thread;
+ (void) memory;
+ (void) platform;
+}
diff --git a/starfield/starfield.h b/starfield/starfield.h
@@ -0,0 +1,6 @@
+#ifndef STARFIELD_H
+#define STARFIELD_H
+
+#include "starfield_api.h"
+
+#endif /* STARFIELD_H */
diff --git a/thirdparty/msvc-wine b/thirdparty/msvc-wine
@@ -0,0 +1 @@
+Subproject commit 2055e1b2a67868605479a4b0d747d1e58d2a8679