diff --git a/AbeLauncher/AbeLauncher.vcxproj b/AbeLauncher/AbeLauncher.vcxproj
new file mode 100644
index 0000000..9daec6c
--- /dev/null
+++ b/AbeLauncher/AbeLauncher.vcxproj
@@ -0,0 +1,166 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 16.0
+ Win32Proj
+ {dcbbb50c-24c2-4fd0-8bc4-e531e0bfa079}
+ AbeLauncher
+ 10.0
+ ReliveLauncher
+
+
+
+ Application
+ true
+ v142
+ Unicode
+
+
+ Application
+ false
+ v142
+ true
+ Unicode
+
+
+ Application
+ true
+ v142
+ Unicode
+
+
+ Application
+ false
+ v142
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+ false
+
+
+ true
+
+
+ false
+
+
+
+ Level3
+ true
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions);_CRT_SECURE_NO_WARNINGS
+ true
+ D:\Code\Git\ScarlettEngine\build\SDL2\SDL2-2.0.8\include;%(AdditionalIncludeDirectories)
+
+
+ Console
+ true
+ D:\Code\Git\ScarlettEngine\build\SDL2\SDL2-2.0.8\lib\x86;%(AdditionalLibraryDirectories)
+ SDL2main.lib;SDL2.lib;%(AdditionalDependencies)
+
+
+
+
+ Level3
+ true
+ true
+ true
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);_CRT_SECURE_NO_WARNINGS
+ true
+ D:\Code\Git\ScarlettEngine\build\SDL2\SDL2-2.0.8\include;%(AdditionalIncludeDirectories)
+
+
+ Console
+ true
+ true
+ true
+ D:\Code\Git\ScarlettEngine\build\SDL2\SDL2-2.0.8\lib\x86;%(AdditionalLibraryDirectories)
+ SDL2main.lib;SDL2.lib;%(AdditionalDependencies)
+
+
+
+
+ Level3
+ true
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions);_CRT_SECURE_NO_WARNINGS
+ true
+ D:\Code\Git\ScarlettEngine\build\SDL2\SDL2-2.0.8\include;%(AdditionalIncludeDirectories)
+
+
+ Console
+ true
+ D:\Code\Git\ScarlettEngine\build\SDL2\SDL2-2.0.8\lib\x64;%(AdditionalLibraryDirectories)
+ SDL2main.lib;SDL2.lib;%(AdditionalDependencies)
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions);_CRT_SECURE_NO_WARNINGS
+ true
+ D:\Code\Git\ScarlettEngine\build\SDL2\SDL2-2.0.8\include;%(AdditionalIncludeDirectories)
+
+
+ Console
+ true
+ true
+ true
+ D:\Code\Git\ScarlettEngine\build\SDL2\SDL2-2.0.8\lib\x64;%(AdditionalLibraryDirectories)
+ SDL2main.lib;SDL2.lib;%(AdditionalDependencies)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/AbeLauncher/AbeLauncher.vcxproj.filters b/AbeLauncher/AbeLauncher.vcxproj.filters
new file mode 100644
index 0000000..9e30ac1
--- /dev/null
+++ b/AbeLauncher/AbeLauncher.vcxproj.filters
@@ -0,0 +1,36 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+
\ No newline at end of file
diff --git a/AbeLauncher/Launcher.cpp b/AbeLauncher/Launcher.cpp
new file mode 100644
index 0000000..d7ec27b
--- /dev/null
+++ b/AbeLauncher/Launcher.cpp
@@ -0,0 +1,257 @@
+#include "Launcher.hpp"
+#include
+#include
+
+#define CONTENT_DIR "data/"
+
+enum class GameType : int32_t
+{
+ eAo = 0,
+ eAe = 1,
+};
+
+std::vector LoadMusicFile(const char* filename)
+{
+ // open the file:
+ std::ifstream file(filename, std::ios::binary);
+
+ if (!file.is_open()) {
+ return std::vector();
+ }
+
+ // read the data:
+ return std::vector((std::istreambuf_iterator(file)),
+ std::istreambuf_iterator());
+}
+
+inline float lerp(float a, float b, float f)
+{
+ return a + f * (b - a);
+}
+
+inline MenuSampleStereo sampleLerp(MenuSampleStereo from, MenuSampleStereo to, float t)
+{
+ return { static_cast(lerp(from.Left, to.Left, t)),static_cast(lerp(from.Right, to.Right, t)) };
+}
+
+void Launcher::RenderAudio(MenuSampleStereo* pDstSamples, int lengthSamples)
+{
+ static int sampleIndex = 0;
+
+ const MenuSampleStereo* pSoundDataAo = reinterpret_cast(m_MenuMusicAo.data());
+ const MenuSampleStereo* pSoundDataAe = reinterpret_cast(m_MenuMusicAe.data());
+
+ for (int i = 0; i < lengthSamples; i++)
+ {
+
+ pDstSamples[i] = sampleLerp(pSoundDataAo[sampleIndex], pSoundDataAe[sampleIndex], m_SelectionSmooth);
+
+ sampleIndex++;
+
+ if (sampleIndex >= m_MenuMusicAo.size() / sizeof(MenuSampleStereo))
+ {
+ sampleIndex = 0;
+ }
+ }
+}
+
+void Launcher::SetState(LauncherState state)
+{
+ m_State = state;
+}
+
+void Launcher::SetProgress(float v)
+{
+ m_LoadProgress = v;
+}
+
+void Launcher::SetProgressMessage(std::string message)
+{
+ m_LoadMessage = message;
+}
+
+void Launcher::DrawGameTile(SDL_Rect region, Texture* bgTex, Texture* logoTex, float bgOffX, float darkness)
+{
+ SDL_RenderSetClipRect(m_pRenderer, ®ion);
+
+ int maxLogoSize = static_cast(m_ScreenHeight / 1.5f);
+
+ int scaledLogoX = region.w;
+ if (scaledLogoX > maxLogoSize)
+ scaledLogoX = maxLogoSize;
+ if (scaledLogoX < m_ScreenWidth * 0.2f)
+ scaledLogoX = static_cast(m_ScreenWidth * 0.2f);
+
+ int scaledLogoY = static_cast(scaledLogoX * (static_cast(logoTex->m_Height) / logoTex->m_Width));
+
+ DrawTexture(bgTex, { bgOffX , static_cast(region.y) }, { static_cast(m_ScreenWidth), static_cast(m_ScreenHeight) }, { 0,0 });
+ DrawTexture(logoTex, { (region.x + (region.w / 2.0f)) , region.y + (m_ScreenHeight / 2.0f) }, { static_cast(scaledLogoX), static_cast(scaledLogoY) });
+
+ SDL_SetRenderDrawColor(m_pRenderer, 0, 0, 0, static_cast(darkness * 190));
+ SDL_RenderFillRect(m_pRenderer, ®ion);
+}
+
+void Launcher::DrawText(STBTTF_Font* font, std::string text, Vec2 position, uint8_t r, uint8_t g, uint8_t b, uint8_t a, TextAlign align)
+{
+ float height = 0;
+ const float textWidth = STBTTF_MeasureText(font, text.c_str(), &height);
+
+ float alignment = 0;
+ switch (align)
+ {
+ case TextAlign_Centre:
+ alignment = textWidth / 2;
+ break;
+ case TextAlign_Right:
+ alignment = textWidth;
+ break;
+ }
+
+ SDL_SetRenderDrawColor(m_pRenderer, 0, 0, 0, a);
+ STBTTF_RenderText(m_pRenderer, font, (position.x + 2) - alignment, (position.y + height) + 2, text.c_str());
+ SDL_SetRenderDrawColor(m_pRenderer, r, g, b, a);
+ STBTTF_RenderText(m_pRenderer, font, position.x - alignment, (position.y + height), text.c_str());
+}
+
+void Launcher::DrawProgressBar(Vec2 pos, Vec2 size, float percent)
+{
+ const int padding = 2;
+ SDL_Rect dst = { static_cast(pos.x) ,static_cast(pos.y) ,static_cast(size.x) ,static_cast(size.y) };
+ SDL_SetRenderDrawColor(m_pRenderer, 0, 0, 0, 255);
+ SDL_RenderFillRect(m_pRenderer, &dst);
+
+ dst.x += padding;
+ dst.y += padding;
+ dst.w -= padding * 2;
+ dst.h -= padding * 2;
+
+ SDL_SetRenderDrawColor(m_pRenderer, 103 / 3, 44 / 3, 146 / 3, 255);
+ SDL_RenderFillRect(m_pRenderer, &dst);
+
+ dst.w = static_cast(dst.w * percent);
+
+ SDL_SetRenderDrawColor(m_pRenderer, 103, 44, 146, 255);
+ SDL_RenderFillRect(m_pRenderer, &dst);
+}
+
+static std::thread workerThread;
+
+void Launcher::StartGame()
+{
+ m_State = LauncherState_GameSelected;
+
+ // Dummy thread to simulate how loading/converting will work.
+ workerThread = std::thread([this] {
+ SetProgressMessage("Starting Conversion...");
+ SDL_Delay(2000);
+
+ for (int i = 0; i < 1000; i++)
+ {
+ SetProgressMessage("Converting File " + std::to_string(i));
+ SetProgress(i / 1000.0f);
+ SDL_Delay(20);
+ }
+
+ SetProgressMessage("Done!");
+ });
+}
+
+void SoundEngineCallback(void* userdata, Uint8* stream, int32_t len)
+{
+ memset(stream, 0, len);
+
+ const int sampleCount = len / sizeof(MenuSampleStereo);
+ reinterpret_cast(userdata)->RenderAudio(reinterpret_cast(stream), sampleCount);
+}
+
+void Launcher::Initialize()
+{
+ m_MenuMusicAo = LoadMusicFile(CONTENT_DIR "ao.dat");
+ m_MenuMusicAe = LoadMusicFile(CONTENT_DIR "ae.dat");
+
+ InitAudio(this, SoundEngineCallback);
+
+ m_TexBG1 = LoadTexture(CONTENT_DIR "bg1.png");
+ m_TexBG2 = LoadTexture(CONTENT_DIR "bg2.png");
+ m_TexLogoAo = LoadTexture(CONTENT_DIR "logo1.png");
+ m_TexLogoAe = LoadTexture(CONTENT_DIR "logo2.png");
+
+ m_FontSmall = STBTTF_OpenFont(m_pRenderer, CONTENT_DIR "oddfont.ttf", 32);
+ m_FontMedium = STBTTF_OpenFont(m_pRenderer, CONTENT_DIR "oddfont.ttf", 64);
+ m_FontLarge = STBTTF_OpenFont(m_pRenderer, CONTENT_DIR "oddfont.ttf", 128);
+}
+
+void Launcher::OnEvent(SDL_Event& pEvent)
+{
+ if (m_State == LauncherState_GameSelection)
+ {
+ if (pEvent.type == SDL_KEYDOWN)
+ {
+ if (pEvent.key.keysym.scancode == SDL_SCANCODE_LEFT)
+ m_SelectionIndex = 0;
+ else if (pEvent.key.keysym.scancode == SDL_SCANCODE_RIGHT)
+ m_SelectionIndex = 1;
+ if (pEvent.key.keysym.scancode == SDL_SCANCODE_SPACE || pEvent.key.keysym.scancode == SDL_SCANCODE_RETURN)
+ StartGame();
+ }
+ else if (pEvent.type == SDL_CONTROLLERBUTTONDOWN)
+ {
+ if (pEvent.cbutton.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT)
+ m_SelectionIndex = 0;
+ else if (pEvent.cbutton.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT)
+ m_SelectionIndex = 1;
+ else if (pEvent.cbutton.button == SDL_CONTROLLER_BUTTON_A)
+ StartGame();
+ }
+ }
+}
+
+void Launcher::Update(float td)
+{
+}
+
+void Launcher::Draw(float td, SDL_Rect region)
+{
+ SDL_SetRenderDrawColor(m_pRenderer, 255, 0, 0, 255);
+ SDL_RenderClear(m_pRenderer);
+ SDL_SetRenderDrawBlendMode(m_pRenderer, SDL_BLENDMODE_BLEND);
+
+ m_SelectionSmooth = lerp(m_SelectionSmooth, static_cast(m_SelectionIndex), 0.1f * td);
+ float selectionCurtained = 0;
+ float curtainTarget = 0.2f + (m_SelectionIndex * 0.6f);
+
+ static float clipSmooth = 0;
+ clipSmooth = lerp(clipSmooth, (m_State == LauncherState_GameSelected) ? m_SelectionIndex : curtainTarget, 0.1f * td);
+
+ int clipXPos = static_cast(m_ScreenWidth * clipSmooth);
+
+ const SDL_Rect regions[2] = {
+ { 0, 0, m_ScreenWidth - clipXPos, m_ScreenHeight },
+ { m_ScreenWidth - clipXPos, 0, m_ScreenWidth - (m_ScreenWidth - clipXPos), m_ScreenHeight }
+ };
+
+
+ DrawGameTile(regions[0], m_TexBG1, m_TexLogoAo, m_SelectionSmooth * -(m_ScreenWidth / 6.0f), m_SelectionSmooth);
+ DrawGameTile(regions[1], m_TexBG2, m_TexLogoAe, (1.0f - m_SelectionSmooth) * (m_ScreenWidth / 6.0f), (1.0f - m_SelectionSmooth));
+
+ SDL_RenderSetClipRect(m_pRenderer, ®ions[m_SelectionIndex]);
+
+ STBTTF_Font* progressFont = m_FontMedium;
+
+ if (m_State == LauncherState_GameSelected)
+ {
+ std::string text = m_LoadMessage;
+
+ DrawProgressBar({ regions[m_SelectionIndex].x + (regions[m_SelectionIndex].w * 0.2f), regions[m_SelectionIndex].h / 1.1f }, { regions[m_SelectionIndex].w * 0.6f, 10.0f }, m_LoadProgress);
+ DrawText(progressFont, text, { regions[m_SelectionIndex].x + (regions[m_SelectionIndex].w / 2.0f), regions[m_SelectionIndex].h / 1.2f }, 255, 255, 255, 255, TextAlign_Centre);
+ }
+
+ SDL_RenderSetClipRect(m_pRenderer, nullptr);
+}
+
+void Launcher::Shutdown()
+{
+ STBTTF_CloseFont(m_FontSmall);
+ STBTTF_CloseFont(m_FontMedium);
+ STBTTF_CloseFont(m_FontLarge);
+}
diff --git a/AbeLauncher/Launcher.hpp b/AbeLauncher/Launcher.hpp
new file mode 100644
index 0000000..926ed4f
--- /dev/null
+++ b/AbeLauncher/Launcher.hpp
@@ -0,0 +1,66 @@
+#pragma once
+
+#include "SDLApp.hpp"
+
+#include "stbttf.h"
+
+enum TextAlign : uint8_t
+{
+ TextAlign_Left = 0,
+ TextAlign_Centre = 1,
+ TextAlign_Right = 2,
+};
+
+enum LauncherState : uint8_t
+{
+ LauncherState_GameSelection = 0,
+ LauncherState_GameSelected = 1,
+};
+
+class UIGameSelector : public UIElement
+{
+
+};
+
+class Launcher : public SDLApp
+{
+public:
+ virtual void Initialize() override;
+ virtual void OnEvent(SDL_Event& pEvent) override;
+ virtual void Update(float td) override;
+ virtual void Draw(float td, SDL_Rect region) override;
+ virtual void Shutdown() override;
+
+ int m_SelectionIndex = 0;
+ float m_SelectionSmooth = 0;
+
+ void RenderAudio(MenuSampleStereo* pDstSamples, int lengthSamples);
+
+ void SetState(LauncherState state);
+ void SetProgress(float v);
+ void SetProgressMessage(std::string message);
+
+private:
+ void DrawGameTile(SDL_Rect region, Texture* bgTex, Texture* logoTex, float bgOffX, float darkness);
+ void DrawText(STBTTF_Font* font, std::string text, Vec2 position, uint8_t r, uint8_t g, uint8_t b, uint8_t a, TextAlign align);
+ void DrawProgressBar(Vec2 pos, Vec2 size, float percent /* from 0 - 1*/);
+
+ std::vector m_MenuMusicAo;
+ std::vector m_MenuMusicAe;
+
+ Texture* m_TexBG1 = nullptr;
+ Texture* m_TexBG2 = nullptr;
+ Texture* m_TexLogoAo = nullptr;
+ Texture* m_TexLogoAe = nullptr;
+
+ STBTTF_Font* m_FontSmall = nullptr;
+ STBTTF_Font* m_FontMedium = nullptr;
+ STBTTF_Font* m_FontLarge = nullptr;
+
+ float m_LoadProgress = 0.0f;
+ std::string m_LoadMessage = "";
+
+ void StartGame();
+
+ LauncherState m_State = LauncherState_GameSelection;
+};
\ No newline at end of file
diff --git a/AbeLauncher/SDLApp.cpp b/AbeLauncher/SDLApp.cpp
new file mode 100644
index 0000000..c1b692f
--- /dev/null
+++ b/AbeLauncher/SDLApp.cpp
@@ -0,0 +1,196 @@
+#include "SDLApp.hpp"
+
+#define STB_IMAGE_IMPLEMENTATION
+#include "stb/stb_image.h"
+
+#define STB_RECT_PACK_IMPLEMENTATION
+#define STB_TRUETYPE_IMPLEMENTATION
+#define STBTTF_IMPLEMENTATION
+#include "stbttf.h"
+
+SDLApp::SDLApp()
+{
+ g_AppInstance = this;
+}
+
+void SDLApp::Run()
+{
+ SDL_Init(SDL_INIT_EVERYTHING);
+
+ m_ScreenWidth = 1280;
+ m_ScreenHeight = 720;
+
+ m_pWindow = SDL_CreateWindow("Select Game",
+ SDL_WINDOWPOS_CENTERED,
+ SDL_WINDOWPOS_CENTERED,
+ m_ScreenWidth,
+ m_ScreenHeight,
+ 0);
+ if (m_pWindow == nullptr)
+ {
+ ALIVE_FATAL("Failed to create GAME SELECT Window");
+ }
+
+ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2");
+
+ m_pRenderer = SDL_CreateRenderer(m_pWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
+ if (m_pRenderer == nullptr)
+ {
+ ALIVE_FATAL("Failed to create GAME SELECT Renderer");
+ }
+
+ m_LastFrameTick = SDL_GetTicks();
+
+ m_pController = GetGameController();
+
+ Initialize();
+
+ while (m_AppRunning)
+ {
+ SDL_GetWindowSize(m_pWindow, &m_ScreenWidth, &m_ScreenHeight);
+
+ int newTick = SDL_GetTicks();
+
+ float frameDelta = ((newTick - m_LastFrameTick) / 1000.0f) * 60.0f;
+
+ if (m_pController == nullptr)
+ m_pController = GetGameController();
+
+ // Get the next event
+ SDL_Event event;
+ while (SDL_PollEvent(&event))
+ {
+ if (event.type == SDL_QUIT)
+ {
+ m_AppRunning = false;
+ }
+
+ if (m_pController == nullptr)
+ m_pController = GetGameController();
+
+ OnEvent(event);
+ }
+
+ UpdateTree(frameDelta);
+
+ int w = 0;
+ int h = 0;
+ SDL_GetWindowSize(m_pWindow, &w, &h);
+ DrawTree(frameDelta, { 0, 0, w, h });
+
+ SDL_RenderPresent(m_pRenderer);
+
+ m_LastFrameTick = newTick;
+ }
+
+ Shutdown();
+
+ for (auto& t : m_LoadedTextures)
+ {
+ SDL_DestroyTexture(t.second->m_Texture);
+ delete t.second;
+ }
+
+ m_LoadedTextures.clear();
+
+ SDL_DestroyRenderer(m_pRenderer);
+ SDL_DestroyWindow(m_pWindow);
+
+ SDL_PauseAudio(1);
+ SDL_CloseAudio();
+
+ if (m_pController != nullptr)
+ {
+ SDL_GameControllerClose(m_pController);
+ }
+}
+
+void SDLApp::Stop()
+{
+ m_AppRunning = false;
+}
+
+void UIElement::UpdateTree(float td)
+{
+ Update(td);
+
+ for (auto& child : m_Children)
+ {
+ child->UpdateTree(td);
+ }
+}
+
+void UIElement::DrawTree(float td, SDL_Rect region)
+{
+ Draw(td, region);
+
+ for (auto& child : m_Children)
+ {
+ const SDL_Rect dst = { region.x + child->m_Transform.x, region.y + child->m_Transform.y, child->m_Transform.w, child->m_Transform.h };
+ child->DrawTree(td, dst);
+ }
+}
+
+void SDLApp::InitAudio(void* userData, SDL_AudioCallback callback)
+{
+ if (!SDL_InitSubSystem(SDL_INIT_AUDIO))
+ {
+ m_AudioDeviceSpec.callback = callback;
+ m_AudioDeviceSpec.format = AUDIO_S16;
+ m_AudioDeviceSpec.channels = 2;
+ m_AudioDeviceSpec.freq = 44100;
+ m_AudioDeviceSpec.samples = 2048;
+ m_AudioDeviceSpec.userdata = userData;
+
+ if (SDL_OpenAudio(&m_AudioDeviceSpec, NULL) >= 0)
+ {
+ SDL_PauseAudio(0);
+ }
+ }
+}
+
+
+SDL_GameController* SDLApp::GetGameController()
+{
+ for (int i = 0; i < SDL_NumJoysticks(); ++i) {
+ if (SDL_IsGameController(i)) {
+ SDL_GameController* controller = NULL;
+ controller = SDL_GameControllerOpen(i);
+ return controller;
+ }
+ }
+
+ return nullptr;
+}
+
+Texture* SDLApp::LoadTexture(std::string path)
+{
+ FILE* fh = fopen(path.c_str(), "rb");
+
+ int32_t x = 0, y = 0;
+ int32_t comp = 0;
+ const uint8_t* data = stbi_load_from_file(fh, &x, &y, &comp, 4);
+ SDL_Texture* texture = SDL_CreateTexture(m_pRenderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STATIC, x, y);
+
+ SDL_UpdateTexture(texture, NULL, data, x * 4);
+ stbi_image_free((void*)data);
+ SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
+ fclose(fh);
+
+ Texture * tex = new Texture(texture);
+ m_LoadedTextures[path] = tex;
+
+ return tex;
+}
+
+void SDLApp::DrawTexture(Texture* pTexture, Vec2 position, Vec2 size, Vec2 centre)
+{
+ SDL_Rect dst = { position.x - (size.x * centre.x), position.y - (size.y * centre.y), size.x, size.y};
+ SDL_RenderCopy(m_pRenderer, pTexture->m_Texture, nullptr, &dst);
+}
+
+Texture::Texture(SDL_Texture* texture)
+{
+ m_Texture = texture;
+ SDL_QueryTexture(texture, nullptr, nullptr, &m_Width, &m_Height);
+}
\ No newline at end of file
diff --git a/AbeLauncher/SDLApp.hpp b/AbeLauncher/SDLApp.hpp
new file mode 100644
index 0000000..54e76d6
--- /dev/null
+++ b/AbeLauncher/SDLApp.hpp
@@ -0,0 +1,89 @@
+#pragma once
+
+#include
+#include
+#include
+#include