diff --git a/.gitmodules b/.gitmodules
index f965af73..b970b9e6 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,3 +4,6 @@
 [submodule "lib/glm"]
 	path = lib/glm
 	url = https://github.com/g-truc/glm.git
+[submodule "lib/stb"]
+	path = lib/stb
+	url = https://github.com/nothings/stb
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 535879b2..7d07c6d6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,6 +3,9 @@
 # This software is released under the MIT License.
 # https://opensource.org/licenses/MIT
 
+# DEBUG
+set(DAWN_BUILDING dawnpokergame)
+
 cmake_minimum_required(VERSION 3.13)
 set(CMAKE_C_STANDARD 99)
 set(CMAKE_C_STANDARD_REQUIRED ON)
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index 2b6b05bd..219e5d11 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -10,4 +10,8 @@ add_subdirectory(glad)
 add_subdirectory(glfw)
 
 # GLM
-add_subdirectory(glm)
\ No newline at end of file
+add_subdirectory(glm)
+
+# STB
+add_library(stb INTERFACE)
+target_include_directories(stb INTERFACE stb)
\ No newline at end of file
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index ed6bd793..c97f4438 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -3,8 +3,11 @@
 # This software is released under the MIT License.
 # https://opensource.org/licenses/MIT
 
+# Include tools
+add_subdirectory(dawntools)
+
 # Change what we are building
-add_subdirectory(dawnpokergame)
+add_subdirectory(${DAWN_BUILDING})
 
 # Check the game project includes the target name
 if(NOT DEFINED DAWN_TARGET_NAME)
diff --git a/src/dawn/CMakeLists.txt b/src/dawn/CMakeLists.txt
index 69bf7cab..17cd2c31 100644
--- a/src/dawn/CMakeLists.txt
+++ b/src/dawn/CMakeLists.txt
@@ -16,5 +16,6 @@ target_include_directories(${DAWN_TARGET_NAME}
 )
 
 # Subdirs
+add_subdirectory(asset)
 add_subdirectory(display)
 add_subdirectory(scene)
\ No newline at end of file
diff --git a/src/dawn/asset/Asset.cpp b/src/dawn/asset/Asset.cpp
new file mode 100644
index 00000000..59766405
--- /dev/null
+++ b/src/dawn/asset/Asset.cpp
@@ -0,0 +1,18 @@
+// Copyright (c) 2022 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#include "AssetManager.hpp"
+
+using namespace Dawn;
+
+Asset::Asset(AssetManager &assetManager, std::string name) :
+  assetManager(assetManager)
+{
+  this->name = name;
+}
+
+Asset::~Asset() {
+  this->loaded = false;
+}
\ No newline at end of file
diff --git a/src/dawn/asset/Asset.hpp b/src/dawn/asset/Asset.hpp
new file mode 100644
index 00000000..400ec41b
--- /dev/null
+++ b/src/dawn/asset/Asset.hpp
@@ -0,0 +1,46 @@
+// Copyright (c) 2022 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#pragma once
+#include "dawnlibs.hpp"
+
+namespace Dawn {
+  class AssetManager;
+  
+  class Asset {
+    public:
+      AssetManager &assetManager;
+      std::string name;
+      uint8_t state = 0x00;
+      bool loaded = false;
+
+      /**
+       * Create an abstract Asset object.
+       * 
+       * @param assetManager Asset manager that this asset belongs to.
+       * @param name Name of the asset.
+       */
+      Asset(AssetManager &assetManager, std::string name);
+
+      /**
+       * Virtual function that will be called by the asset manager on a 
+       * synchronous basis. This will only trigger if the blocks are false and
+       * the loaded is also false.
+       */
+      virtual void updateSync() = 0;
+
+      /**
+       * Virtual function called by the asset manager asynchronously every tick.
+       * This will only trigger if blocks are false and the loaded state is also
+       * false.
+       */
+      virtual void updateAsync() = 0;
+      
+      /**
+       * Dispose the asset item.
+       */
+      virtual ~Asset();
+  };
+}
\ No newline at end of file
diff --git a/src/dawn/asset/AssetLoader.cpp b/src/dawn/asset/AssetLoader.cpp
new file mode 100644
index 00000000..44009c70
--- /dev/null
+++ b/src/dawn/asset/AssetLoader.cpp
@@ -0,0 +1,87 @@
+// Copyright (c) 2022 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#include "AssetLoader.hpp"
+
+using namespace Dawn;
+
+AssetLoader::AssetLoader(std::string fileName) {
+  this->fileName = fileName;
+  this->handle = nullptr;
+}
+
+void AssetLoader::open() {
+  std::string pathFull = DAWN_ASSET_BUILD_PREFIX + this->fileName;
+  this->handle = fopen(pathFull.c_str(), "rb");
+  if(this->handle == NULL || this->handle == nullptr) {
+    throw "Failed to open file handle for " + this->fileName;
+  }
+}
+
+int32_t AssetLoader::close() {
+  int32_t ret = fclose(this->handle);
+  this->handle = nullptr;
+  return ret;
+}
+
+size_t AssetLoader::read(uint8_t *buffer, size_t size) {
+  return fread(buffer, 1, size, this->handle);
+}
+
+int32_t AssetLoader::end() {
+  return fseek(this->handle, 0, SEEK_END);
+}
+
+size_t AssetLoader::skip(size_t n) {
+  return fseek(this->handle, n, SEEK_CUR);
+}
+
+int32_t AssetLoader::rewind() {
+  return fseek(this->handle, 0, SEEK_SET);
+}
+
+size_t AssetLoader::getPosition() {
+  return ftell(this->handle);
+}
+
+size_t AssetLoader::loadRaw(uint8_t **buffer) {
+  size_t length, read;
+
+  // Open a buffer.
+  this->open();
+
+  // Read the count of bytes in the file
+  this->end();
+  length = this->getPosition();
+
+  // Are we only reading the size?
+  if(buffer == nullptr) {
+    this->close();
+    return length;
+  }
+
+  // Reset to start
+  this->rewind();
+
+  // Read the string then close the file handle.
+  *buffer = static_cast<uint8_t *>(malloc(sizeof(uint8_t) * length));
+  read = this->read(*buffer, length);
+  this->close();
+
+  // Did we read successfully?
+  if(read < length) {
+    throw "Failed to read all bytes of " + this->fileName;
+  }
+
+  // Read successfully, return the read bytes.
+  return read;
+}
+
+AssetLoader::~AssetLoader() {
+  if(this->handle != nullptr) {
+    this->close();
+    this->handle = nullptr;
+  }
+}
\ No newline at end of file
diff --git a/src/dawn/asset/AssetLoader.hpp b/src/dawn/asset/AssetLoader.hpp
new file mode 100644
index 00000000..4d8fb20f
--- /dev/null
+++ b/src/dawn/asset/AssetLoader.hpp
@@ -0,0 +1,126 @@
+// Copyright (c) 2022 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#pragma once
+#include "dawnlibs.hpp"
+#include "util/memory.hpp"
+
+namespace Dawn {
+  class AssetLoader {
+    private:
+      std::string fileName;
+      FILE *handle;
+
+    public:
+      /**
+       * Create a new asset loader. Asset Loaders can be used to load data from
+       * a file in a myriad of ways.
+       * 
+       * @param fileName File name of the asset that is to be loaded.
+       */
+      AssetLoader(std::string fileName);
+
+      /**
+       * Platform-centric method to open a file buffer to an asset.
+       * 
+       * @return 0 if success, otherwise for failure.
+       */
+      void open();
+
+      /**
+       * Closes the previously ppened asset.
+       * @return 0 if successful, otherwise false.
+       */
+      int32_t close();
+
+      /**
+       * Read bytes from buffer.
+       * @param buffer Pointer to a ubyte array to buffer data into.
+       * @param size Length of the data buffer (How many bytes to read).
+       * @return The count of bytes read.
+       */
+      size_t read(uint8_t *buffer, size_t size);
+
+      /**
+       * Skip to the end of the buffer, useful to find the length of the buffer.
+       * @return 0 if successful, otherwise false.
+       */
+      int32_t end();
+
+      /**
+       * Method to skip n bytes in the buffer
+       * @param n Count of bytes to skip.
+       * @return 0 if successful, otherwise unsuccessful.
+       */
+      size_t skip(size_t n);
+
+      /**
+       * Rewinds to the start of the asset buffer.
+       * @return 0 if successful, otherwise unsuccessful.
+       */      
+      int32_t rewind();
+
+      
+      /**
+       * Retreive the current byte position within the asset that the head is
+       * at.
+       * @return Position (in bytes) that the current seek is at.
+       */
+      size_t getPosition();
+
+      /**
+       * Loads the entire file into a raw buffer.
+       * @param buffer Pointer to where a pointer to the buffer will be stored.
+       * @return Size of the buffer that was read (in bytes).
+       */
+      size_t loadRaw(uint8_t **buffer);
+
+      /**
+       * Run a callback for each byte within the asset. The callback will
+       * receive each byte individually.
+       * 
+       * @tparam T Type of instance to run callback against.
+       * @param instance Instance of the object to run the callback against.
+       * @param callback Callback method on the class to run the callback for.
+       * @return The count of bytes read.
+       */
+      template<class T>
+      size_t loadBufferedCallback(T *instance, bool (T::*callback)(uint8_t n)) {
+        uint8_t buffer[1024];
+        size_t read, length;
+        int32_t i;
+        bool result;
+
+        // Open the buffer.
+        this->open();
+
+        // Reset length size
+        length = 0;
+
+        // Buffer from input
+        while((read = this->read(buffer, 1024)) != 0) {
+          for(i = 0; i < read; i++) {
+            result = ((*instance).*(callback))(buffer[i]);
+            if(!result) {
+              length += i;
+              break;
+            }
+          }
+          if(!result) break;
+          length += read;
+        }
+
+        // Close the buffer
+        this->close();
+
+        return length;
+      }
+
+      /**
+       * Cleanup the asset loader.
+       */
+      virtual ~AssetLoader();
+  };
+}
\ No newline at end of file
diff --git a/src/dawn/asset/AssetManager.cpp b/src/dawn/asset/AssetManager.cpp
new file mode 100644
index 00000000..906f8f85
--- /dev/null
+++ b/src/dawn/asset/AssetManager.cpp
@@ -0,0 +1,37 @@
+// Copyright (c) 2022 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#include "AssetManager.hpp"
+
+#if !defined(DAWN_ASSET_BUILD_PREFIX)
+  #error Asset Prefix has not been defined.
+#endif
+
+using namespace Dawn;
+
+void AssetManager::init() {
+
+}
+
+void AssetManager::update() {
+  auto it = this->assetsNotLoaded.begin();
+  while(it != this->assetsNotLoaded.end()) {
+    auto asset = it->second;
+    if(asset->loaded) {
+      it = this->assetsNotLoaded.erase(it);
+      continue;
+    }
+
+    asset->updateSync();
+    asset->updateAsync();
+
+    if(asset->loaded) {
+      it = this->assetsNotLoaded.erase(it);
+      continue;
+    }
+
+    ++it;
+  }
+}
\ No newline at end of file
diff --git a/src/dawn/asset/AssetManager.hpp b/src/dawn/asset/AssetManager.hpp
new file mode 100644
index 00000000..25fe5bb7
--- /dev/null
+++ b/src/dawn/asset/AssetManager.hpp
@@ -0,0 +1,38 @@
+// Copyright (c) 2022 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#pragma once
+#include "asset/Asset.hpp"
+
+namespace Dawn {
+  class AssetManager {
+    private:
+      /** List of pointers to assets, mapped by their asset key. */
+      std::map<std::string, std::shared_ptr<Asset>> assets;
+      std::map<std::string, std::shared_ptr<Asset>> assetsNotLoaded;
+
+    public:
+      void init();
+      void update();
+
+      /**
+       * Creates and queue an asset to load.
+       * 
+       * @param name Name of the asset to load.
+       * @return The asset element to be loaded.
+       */
+      template<class T>
+      std::shared_ptr<T> load(std::string name) {
+        auto existing = this->assets.find(name);
+        if(existing != this->assets.end()) {
+          return std::dynamic_pointer_cast<T>(existing->second);
+        }
+        auto asset = std::make_shared<T>(*this, name);
+        this->assets[name] = asset;
+        this->assetsNotLoaded[name] = asset;
+        return asset;
+      }
+  };
+}
\ No newline at end of file
diff --git a/src/dawn/asset/CMakeLists.txt b/src/dawn/asset/CMakeLists.txt
new file mode 100644
index 00000000..057b9c5a
--- /dev/null
+++ b/src/dawn/asset/CMakeLists.txt
@@ -0,0 +1,15 @@
+# Copyright (c) 2022 Dominic Msters
+# 
+# This software is released under the MIT License.
+# https://opensource.org/licenses/MIT
+
+# Sources
+target_sources(${DAWN_TARGET_NAME}
+  PRIVATE
+    Asset.cpp
+    AssetLoader.cpp
+    AssetManager.cpp
+)
+
+# Subdirs
+add_subdirectory(assets)
\ No newline at end of file
diff --git a/src/dawn/asset/assets/CMakeLists.txt b/src/dawn/asset/assets/CMakeLists.txt
new file mode 100644
index 00000000..e646d3b8
--- /dev/null
+++ b/src/dawn/asset/assets/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright (c) 2022 Dominic Msters
+# 
+# This software is released under the MIT License.
+# https://opensource.org/licenses/MIT
+
+# Sources
+target_sources(${DAWN_TARGET_NAME}
+  PRIVATE
+    TextureAsset.cpp
+)
\ No newline at end of file
diff --git a/src/dawn/asset/assets/TextureAsset.cpp b/src/dawn/asset/assets/TextureAsset.cpp
new file mode 100644
index 00000000..b6185ea3
--- /dev/null
+++ b/src/dawn/asset/assets/TextureAsset.cpp
@@ -0,0 +1,62 @@
+// Copyright (c) 2022 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#include "TextureAsset.hpp"
+
+using namespace Dawn;
+
+TextureAsset::TextureAsset(AssetManager &assetManager, std::string name) :
+  Asset(assetManager, name),
+  loader(name + ".texture")
+{
+  this->texture = std::make_shared<Texture>();
+}
+
+void TextureAsset::updateSync() {
+  if(this->state == 0x00 || this->state == 0x01) return;
+
+  this->state = 0x03;
+  this->texture->setSize(this->width, this->height);
+  this->texture->buffer(this->colors);
+  this->state = 0x04;
+  this->loaded = true;
+}
+
+void TextureAsset::updateAsync() {
+  if(this->state != 0x00) return;
+  this->loader.loadRaw(&this->buffer);
+
+  this->state = 0x01;
+
+  // Parse header data.
+  char integer[32];
+  size_t j = 0, i = 0;
+  while(true) {
+    auto c = this->buffer[i++];
+    if(c == '|') {
+      integer[j] = '\0';
+      if(this->width == -1) {
+        this->width = atoi(integer);
+        if(this->width <= 0) throw "Invalid width";
+        j = 0;
+        continue;
+      } else {
+        this->height = atoi(integer);
+        if(this->height <= 0) throw "Invalid height";
+        break;
+      }
+    }
+    integer[j++] = c;
+  }
+
+  this->colors = (struct Color *)((void *)(this->buffer + i));
+  this->state = 0x02;
+}
+
+TextureAsset::~TextureAsset() {
+  if(this->buffer != nullptr) {
+    memoryFree(this->buffer);
+  }
+}
\ No newline at end of file
diff --git a/src/dawn/asset/assets/TextureAsset.hpp b/src/dawn/asset/assets/TextureAsset.hpp
new file mode 100644
index 00000000..641adb6e
--- /dev/null
+++ b/src/dawn/asset/assets/TextureAsset.hpp
@@ -0,0 +1,29 @@
+// Copyright (c) 2022 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#pragma once
+#include "../Asset.hpp"
+#include "../AssetLoader.hpp"
+#include "display/Texture.hpp"
+
+namespace Dawn {
+  class TextureAsset : public Asset {
+    protected:
+      AssetLoader loader;
+      uint8_t *buffer = nullptr;
+      int32_t width = -1, height = -1;
+      struct Color *colors;
+
+    public:
+      std::shared_ptr<Texture> texture;
+
+      TextureAsset(AssetManager &assetManager, std::string name);
+
+      void updateSync() override;
+      void updateAsync() override;
+
+      ~TextureAsset();
+  };
+}
\ No newline at end of file
diff --git a/src/dawn/game/DawnGame.hpp b/src/dawn/game/DawnGame.hpp
index 85ec456d..64956a15 100644
--- a/src/dawn/game/DawnGame.hpp
+++ b/src/dawn/game/DawnGame.hpp
@@ -7,6 +7,7 @@
 #include "dawnlibs.hpp"
 #include "scene/Scene.hpp"
 #include "display/RenderManager.hpp"
+#include "asset/AssetManager.hpp"
 
 #define DAWN_GAME_INIT_RESULT_SUCCESS 0
 #define DAWN_GAME_UPDATE_RESULT_SUCCESS 0
@@ -20,6 +21,7 @@ namespace Dawn {
       std::shared_ptr<Scene> scene;
       DawnHost &host;
       RenderManager renderManager;
+      AssetManager assetManager;
 
       /**
        * Construct a new instance of the DawnGame.
diff --git a/src/dawnopengl/display/shader/Shader.cpp b/src/dawnopengl/display/shader/Shader.cpp
index 5a5d4525..59be3d3f 100644
--- a/src/dawnopengl/display/shader/Shader.cpp
+++ b/src/dawnopengl/display/shader/Shader.cpp
@@ -89,6 +89,10 @@ void Shader::setTexture(
   shaderparameter_t param,
   std::shared_ptr<Texture> texture
 ) {
+  if(texture == nullptr || !texture->isReady()) {
+    this->bindTexture(param, nullptr);
+    return;
+  }
   this->bindTexture(param, texture);
 }
 
diff --git a/src/dawnopengl/display/shader/SimpleTexturedShader.hpp b/src/dawnopengl/display/shader/SimpleTexturedShader.hpp
index eac7b1ef..50396d62 100644
--- a/src/dawnopengl/display/shader/SimpleTexturedShader.hpp
+++ b/src/dawnopengl/display/shader/SimpleTexturedShader.hpp
@@ -30,7 +30,7 @@ namespace Dawn {
       }
       
       void setDefaultParameters(Material &material) override {
-        material.colorValues[this->paramColor] = COLOR_MAGENTA;
+        material.colorValues[this->paramColor] = COLOR_WHITE;
       }
 
       void setGlobalParameters(glm::mat4 proj, glm::mat4 view) override {
@@ -97,7 +97,6 @@ namespace Dawn {
         this->paramHasTexture = this->getParameterByName("u_HasTexture");
 
         this->setBoolean(this->paramHasTexture, false);
-        this->setColor(this->paramColor, COLOR_WHITE);
       }
   };
 }
\ No newline at end of file
diff --git a/src/dawnpokergame/game/CMakeLists.txt b/src/dawnpokergame/game/CMakeLists.txt
index 07f25903..d6f3c4f8 100644
--- a/src/dawnpokergame/game/CMakeLists.txt
+++ b/src/dawnpokergame/game/CMakeLists.txt
@@ -7,4 +7,8 @@
 target_sources(${DAWN_TARGET_NAME}
   PRIVATE
     DawnPokerGame.cpp
-)
\ No newline at end of file
+)
+
+tool_texture(texture_test texture_test.png texture_test)
+
+add_dependencies(${DAWN_TARGET_NAME} texture_test)
\ No newline at end of file
diff --git a/src/dawnpokergame/game/DawnPokerGame.cpp b/src/dawnpokergame/game/DawnPokerGame.cpp
index 05ffeead..ff0964f0 100644
--- a/src/dawnpokergame/game/DawnPokerGame.cpp
+++ b/src/dawnpokergame/game/DawnPokerGame.cpp
@@ -4,7 +4,6 @@
 // https://opensource.org/licenses/MIT
 
 #include "DawnPokerGame.hpp"
-#include "event/Event.hpp"
 
 using namespace Dawn;
 
@@ -15,6 +14,7 @@ DawnGame::DawnGame(DawnHost &host) :
 }
 
 int32_t DawnGame::init() {
+  this->assetManager.init();
   this->renderManager.init();
 
   this->scene = std::make_shared<Scene>(*this);
@@ -35,21 +35,19 @@ int32_t DawnGame::init() {
     0, 0, 0
   );
 
-  auto testTexture = std::make_shared<Texture>();
-  testTexture->setSize(2, 2);
-  struct Color colors[4] = {
-    COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_WHITE
-  };
-  testTexture->buffer(colors);
-  cubeMaterial->textureValues[cubeMaterial->getShader()->getParameterByName("u_Text")] = testTexture;
-
+  auto textureAsset = this->assetManager.load<TextureAsset>("texture_test");
+  cubeMaterial->textureValues[cubeMaterial->getShader()->getParameterByName("u_Text")] = std::shared_ptr<Texture>(textureAsset->texture);
 
   return DAWN_GAME_INIT_RESULT_SUCCESS;
 }
 
 int32_t DawnGame::update(float_t delta) {
+  this->assetManager.update();
+
   if(this->scene != nullptr) this->scene->update();
+
   this->renderManager.update();
+
   return DAWN_GAME_UPDATE_RESULT_SUCCESS;
 }
 
diff --git a/src/dawnpokergame/game/DawnPokerGame.hpp b/src/dawnpokergame/game/DawnPokerGame.hpp
index a346b75c..2b65f68e 100644
--- a/src/dawnpokergame/game/DawnPokerGame.hpp
+++ b/src/dawnpokergame/game/DawnPokerGame.hpp
@@ -7,4 +7,5 @@
 #include "game/DawnGame.hpp"
 #include "scene/components/Components.hpp"
 #include "display/mesh/QuadMesh.hpp"
-#include "display/mesh/TriangleMesh.hpp"
\ No newline at end of file
+#include "display/mesh/TriangleMesh.hpp"
+#include "asset/assets/TextureAsset.hpp"
\ No newline at end of file
diff --git a/src/dawntools/CMakeLists.txt b/src/dawntools/CMakeLists.txt
new file mode 100644
index 00000000..97afa1e4
--- /dev/null
+++ b/src/dawntools/CMakeLists.txt
@@ -0,0 +1,7 @@
+# Copyright (c) 2021 Dominic Msters
+# 
+# This software is released under the MIT License.
+# https://opensource.org/licenses/MIT
+
+add_subdirectory(display)
+add_subdirectory(file)
\ No newline at end of file
diff --git a/src/dawntools/display/CMakeLists.txt b/src/dawntools/display/CMakeLists.txt
new file mode 100644
index 00000000..50da7c0b
--- /dev/null
+++ b/src/dawntools/display/CMakeLists.txt
@@ -0,0 +1,25 @@
+# Copyright (c) 2021 Dominic Msters
+# 
+# This software is released under the MIT License.
+# https://opensource.org/licenses/MIT
+
+add_subdirectory(texturegen)
+add_subdirectory(truetypegen)
+
+# Texture Tool
+function(tool_texture target in out)
+  add_custom_target(${target}
+    COMMAND texturegen "${DAWN_ASSETS_SOURCE_DIR}/${in}" "${DAWN_ASSETS_BUILD_DIR}/${out}"
+    COMMENT "Generating texture ${target} from ${in}"
+    DEPENDS texturegen
+  )
+endfunction()
+
+# TrueType Tool
+function(tool_truetype target in out width height fontSize)
+  add_custom_target(${target}
+    COMMAND truetypegen "${in}" "${DAWN_ASSETS_BUILD_DIR}/${out}" "${width}" "${height}" "${fontSize}"
+    COMMENT "Generating truetype ${target} from ${in}"
+    DEPENDS truetypegen
+  )
+endfunction()
\ No newline at end of file
diff --git a/src/dawntools/display/texturegen/CMakeLists.txt b/src/dawntools/display/texturegen/CMakeLists.txt
new file mode 100644
index 00000000..0567df44
--- /dev/null
+++ b/src/dawntools/display/texturegen/CMakeLists.txt
@@ -0,0 +1,24 @@
+# Copyright (c) 2021 Dominic Msters
+# 
+# This software is released under the MIT License.
+# https://opensource.org/licenses/MIT
+
+# Texture Build Tool
+project(texturegen VERSION 1.0)
+add_executable(texturegen)
+target_sources(texturegen
+  PRIVATE
+    main.c
+    ../../utils/file.c
+    ../../utils/image.c
+)
+target_include_directories(texturegen
+  PUBLIC
+    ${CMAKE_CURRENT_LIST_DIR}/../../
+    ${CMAKE_CURRENT_LIST_DIR}
+)
+target_link_libraries(texturegen
+  PUBLIC
+    ${LIBS_PLATFORM}
+    stb
+)
\ No newline at end of file
diff --git a/src/dawntools/display/texturegen/main.c b/src/dawntools/display/texturegen/main.c
new file mode 100644
index 00000000..dba01e5d
--- /dev/null
+++ b/src/dawntools/display/texturegen/main.c
@@ -0,0 +1,87 @@
+/**
+ * Copyright (c) 2021 Dominic Masters
+ * 
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ */
+
+#include "../../utils/common.h"
+#include "../../utils/file.h"
+#include "../../utils/image.h"
+
+int main(int argc, char *argv[]) {
+  FILE *file;
+  char path[FILENAME_MAX + 1];
+  uint8_t headerBuffer[32];
+  char *in;
+  char *out;
+  int w, h, channels, headerBufferLength;
+  stbi_uc *dataImageRaw, dataIn;
+  float *dataImage;
+  size_t i, len;
+
+  if(argc != 3) {
+    printf("Invalid number of arguments\n");
+    return 1;
+  }
+
+  // Set up strings
+  in = argv[1];
+  out = argv[2];
+  
+  // Normalize slashes
+  fileNormalizeSlashes(in);
+  fileNormalizeSlashes(out);
+  
+  // Check the output doesn't already exist
+  sprintf(path, "%s.texture", out);
+  file = fopen(path, "rb");
+  if(file != NULL) {
+    fclose(file);
+    return 0;
+  }
+
+  // Read in original texture
+  file = fopen(in, "rb");
+  if(file == NULL) {
+    printf("Failed to open file!\n");
+    return 1;
+  }
+  
+  dataImageRaw = stbi_load_from_file(file, &w, &h, &channels, STBI_rgb_alpha);
+  if(dataImageRaw == NULL) {
+    printf("Failed to load input texture!\n");
+    return 1;
+  }
+  fclose(file);
+
+  // Convert to floating points
+  len = STBI_rgb_alpha * w * h;
+  dataImage = malloc(sizeof(float) * len);
+  for(i = 0; i < len; i++) {
+    dataIn = dataImageRaw[i];
+    dataImage[i] = ((float)dataIn) / 255.0f;
+  }
+  stbi_image_free(dataImageRaw);
+
+  // Open output file
+  fileMkdirp(path);
+  file = fopen(path, "wb");
+  if(file == NULL) {
+    printf("Invalid texture file out!\n");
+    return 1;
+  }
+
+  // Write info
+  headerBufferLength = sprintf(headerBuffer, "%i|%i|", w, h);
+  fwrite(headerBuffer, sizeof(uint8_t), headerBufferLength, file);
+
+  // Write texture
+  fwrite(dataImage, sizeof(float), len, file);
+
+  // Cleanup
+  fclose(file);
+  free(dataImage);
+
+  return 0;
+}
\ No newline at end of file
diff --git a/src/dawntools/display/truetypegen/CMakeLists.txt b/src/dawntools/display/truetypegen/CMakeLists.txt
new file mode 100644
index 00000000..9a827edf
--- /dev/null
+++ b/src/dawntools/display/truetypegen/CMakeLists.txt
@@ -0,0 +1,24 @@
+# Copyright (c) 2021 Dominic Msters
+# 
+# This software is released under the MIT License.
+# https://opensource.org/licenses/MIT
+
+# Texture Build Tool
+project(truetypegen VERSION 1.0)
+add_executable(truetypegen)
+target_sources(truetypegen
+  PRIVATE
+    main.c
+    ../../utils/file.c
+    ../../utils/image.c
+)
+target_include_directories(truetypegen
+  PUBLIC
+    ${CMAKE_CURRENT_LIST_DIR}/../../
+    ${CMAKE_CURRENT_LIST_DIR}
+)
+target_link_libraries(truetypegen
+  PUBLIC
+    ${LIBS_PLATFORM}
+    stb
+)
\ No newline at end of file
diff --git a/src/dawntools/display/truetypegen/main.c b/src/dawntools/display/truetypegen/main.c
new file mode 100644
index 00000000..bf06c886
--- /dev/null
+++ b/src/dawntools/display/truetypegen/main.c
@@ -0,0 +1,140 @@
+/**
+ * Copyright (c) 2021 Dominic Masters
+ * 
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ */
+
+#include "../../utils/common.h"
+#include "../../utils/file.h"
+#include "../../utils/image.h"
+#ifndef  STB_TRUETYPE_IMPLEMENTATION
+  #define STB_TRUETYPE_IMPLEMENTATION
+  #include <stb_truetype.h>
+#endif
+
+#define TRUETYPE_FIRST_CHAR 32
+#define TRUETYPE_NUM_CHARS 96
+
+int main(int argc, char *args[]) {
+  FILE *file;
+  char path[FILENAME_MAX + 1];
+  int32_t width, height, fontSize, textureSize;
+
+  /*
+    args0 - PATH
+    args1 - Input Filename (TTF file)
+    args2 - Output Filename
+    args3 - Width of the output texture (Resolution X)
+    args4 - Height of the output texture (Resolution Y)
+    args5 - Font size to draw
+  */
+
+  if(argc != 6) {
+    printf("Invalid number of arguments\n");
+    return 1;
+  }
+
+  char *fileIn = args[1];
+  char *fileOut = args[2];
+  char *strWidth = args[3];
+  char *strHeight = args[4];
+  char *strFontSize = args[5];
+
+  // Normalize slashes
+  fileNormalizeSlashes(fileIn);
+  fileNormalizeSlashes(fileOut);
+  
+  // Check the output doesn't already exist
+  sprintf(path, "%s.truetype", fileOut);
+  file = fopen(path, "rb");
+  if(file != NULL) {
+    fclose(file);
+    return 0;
+  }
+
+  width = atoi(strWidth);
+  if(width <= 0) {
+    printf("Width is invalid.\n");
+    return 1;
+  }
+
+  height = atoi(strHeight);
+  if(height <= 0) {
+    printf("Height is invalid.\n");
+    return 1;
+  }
+
+  fontSize = atoi(strFontSize);
+  if(fontSize <= 0) {
+    printf("Font size is invalid.\n");
+    return 1;
+  }
+  
+  // Read in the TTF data
+  file = fopen(fileIn, "rb");
+  if(file == NULL) {
+    printf("Failed to open input TTF file.\n");
+    return 1;
+  }
+
+  // Seek to end, get length, seek back to start.
+  fseek(file, 0, SEEK_END);
+  size_t fileSize = ftell(file);
+  fseek(file, 0, SEEK_SET);
+
+  // Read in all data
+  char *ttfData = malloc(sizeof(char) * fileSize);
+  size_t readSize = fread(ttfData, 1, fileSize, file);
+  fclose(file);
+  if(readSize < fileSize) {
+    printf("Failed to read all data form TTF\n");
+    return 1;
+  }
+
+  // Create bitmap data. This is a single channel color (alpha).
+  textureSize = width * height;
+  stbi_uc *bitmapData = malloc(sizeof(stbi_uc) * textureSize);
+  stbtt_bakedchar characterData[TRUETYPE_NUM_CHARS];
+
+  // Now parse the TTF itself.
+  stbtt_BakeFontBitmap(
+    ttfData, 0, (float)fontSize, bitmapData,
+    width, height,
+    TRUETYPE_FIRST_CHAR, TRUETYPE_NUM_CHARS,
+    characterData
+  );
+
+  // Prepare output file for writing.
+  sprintf(path, "%s.truetype", fileOut);
+  fileMkdirp(path);
+  file = fopen(path, "wb");
+  if(file == NULL) {
+    printf("Failed to create output TTF file\n");
+    return 1;
+  }
+  
+  // Now prepare output data.
+  char headerBuffer[64];
+  int32_t headerBufferLength = sprintf(
+    headerBuffer, "%i|%i|%i|", width, height, fontSize
+  );
+  fwrite(headerBuffer, sizeof(char), headerBufferLength, file);
+
+  // Write output pixels.
+  float outputPixels[4];
+  for(int32_t i = 0; i < textureSize; i++) {
+    outputPixels[0] = 1.0f;
+    outputPixels[1] = 1.0f;
+    outputPixels[2] = 1.0f;
+    outputPixels[3] = ((float)bitmapData[i]) / 255.0f;
+    fwrite(outputPixels, sizeof(float), 4, file);
+  }
+
+  // Now write output quads data.
+  fwrite(characterData, sizeof(stbtt_bakedchar), TRUETYPE_NUM_CHARS, file);
+  fclose(file);
+  free(bitmapData);
+
+  return 0;
+}
\ No newline at end of file
diff --git a/src/dawntools/file/CMakeLists.txt b/src/dawntools/file/CMakeLists.txt
new file mode 100644
index 00000000..5cd001cb
--- /dev/null
+++ b/src/dawntools/file/CMakeLists.txt
@@ -0,0 +1,27 @@
+# Copyright (c) 2022 Dominic Msters
+# 
+# This software is released under the MIT License.
+# https://opensource.org/licenses/MIT
+
+function(tool_copy target)
+  math(EXPR CARGSN "${ARGC} - 1")
+  set(LOOP_DEPENDENCIES)
+  
+  foreach(index RANGE 1 ${CARGSN} 2)
+    math(EXPR indexnext "${index} + 1")
+    set(LOOP_TARGET "item_${target}_${index}")
+
+    LIST(GET ARGV ${index} from)
+    LIST(GET ARGV ${indexnext} to)
+    LIST(APPEND LOOP_DEPENDENCIES ${LOOP_TARGET})
+    add_custom_command(OUTPUT ${LOOP_TARGET}
+      COMMAND ${CMAKE_COMMAND} -E copy "${from}" "${ASSETS_BUILD_DIR}/${to}"
+      COMMENT "Copying ${from} => ${to}"
+    )
+  endforeach()
+
+  add_custom_target(${target}
+    DEPENDS ${LOOP_DEPENDENCIES}
+    COMMENT "Creating dependency set ${target}"
+  )
+endfunction()
\ No newline at end of file
diff --git a/src/dawntools/utils/common.h b/src/dawntools/utils/common.h
new file mode 100644
index 00000000..af02bd97
--- /dev/null
+++ b/src/dawntools/utils/common.h
@@ -0,0 +1,13 @@
+/**
+ * Copyright (c) 2021 Dominic Masters
+ * 
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ */
+
+#pragma once
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdlib.h>
\ No newline at end of file
diff --git a/src/dawntools/utils/csv.c b/src/dawntools/utils/csv.c
new file mode 100644
index 00000000..394baf3b
--- /dev/null
+++ b/src/dawntools/utils/csv.c
@@ -0,0 +1,115 @@
+/**
+ * Copyright (c) 2022 Dominic Masters
+ * 
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ */
+
+#include "csv.h"
+
+void csvParse(char *string, csv_t *csv) {
+  char c;
+  size_t i, j, length;
+  csvparsestate_t state;
+  int32_t rowCellCount;
+  
+  length = strlen(string);
+  csv->buffer = malloc(sizeof(char) * length * 2);
+  csv->cellCounts = malloc(sizeof(int32_t) * CSV_ROW_COUNT_MAX);
+  csv->rows = malloc(sizeof(char *) * 32 * CSV_ROW_COUNT_MAX);
+
+  i = 0;
+  j = 0;
+  rowCellCount = 0;
+  csv->rowCount = 0;
+  state = CSV_PARSE_STATE_FIND_CELL;
+  while(i < length) {
+    c = string[i++];
+
+    // What are we doing
+    switch(state) {
+      case CSV_PARSE_STATE_FIND_CELL:
+        if(c == '"') {
+          state = CSV_PARSE_STATE_PARSE_CELL_WITH_QUOTES;
+          csv->rows[(csv->rowCount * CSV_ROW_COUNT_MAX) + rowCellCount] = csv->buffer + j;
+          rowCellCount++;
+          continue;
+        } else if(c == '\r' || c == '\n') {
+          // Newline (todo: is this a blank line?)
+          state = CSV_PARSE_STATE_LINE_END;
+          continue;
+        } else if(c == ',') {
+          csv->rows[(csv->rowCount * CSV_ROW_COUNT_MAX) + rowCellCount] = csv->buffer + j;
+          csv->buffer[j++] = '\0';
+          rowCellCount++;
+          continue;
+        } else {
+          state = CSV_PARSE_STATE_PARSE_CELL;
+          csv->rows[(csv->rowCount * CSV_ROW_COUNT_MAX) + rowCellCount] = csv->buffer + j;
+          csv->buffer[j++] = c;
+          rowCellCount++;
+          continue;
+        }
+      
+      case CSV_PARSE_STATE_PARSE_CELL:
+        if(c == '\r' || c == '\n') {
+          state = CSV_PARSE_STATE_LINE_END;
+          csv->buffer[j++] = '\0';
+          continue;
+        } else if(c == ',') {
+          state = CSV_PARSE_STATE_FIND_CELL;
+          csv->buffer[j++] = '\0';
+          continue;
+        }
+        csv->buffer[j++] = c;
+        continue;
+
+      case CSV_PARSE_STATE_PARSE_CELL_WITH_QUOTES:
+        if((c == '\\' && string[i] == '"') || (c == '"' && string[i] == '"')) {
+          // Handle escaped quotes. I normally see [\"] but excel does [""] in
+          // most cases
+          csv->buffer[j++] = '"';
+          i++;
+          continue;
+        } else if(c == '"') {
+          // Handle end of quoted string
+          state = CSV_PARSE_STATE_FIND_CELL;
+          csv->buffer[j++] = '\0';
+          // Because we tend to do [",] at the end of a quoted cell, we do this
+          // to prevent [,,] cases being treated the same
+          if(string[i] == ',') i++;
+          continue;
+        }
+
+        // Normal character.
+        csv->buffer[j++] = c;
+        continue;
+
+      case CSV_PARSE_STATE_LINE_END:
+        // Skip blanks
+        if(c == '\r' || c == '\n') continue;
+        csv->cellCounts[csv->rowCount] = rowCellCount;
+        csv->rowCount++;
+        rowCellCount = 0;
+        state = CSV_PARSE_STATE_FIND_CELL;
+        i--;
+        continue;
+
+      default:
+        printf("Error occured during parse operation.");
+        free(NULL);
+    }
+  }
+  csv->buffer[j++] = '\0';
+
+  if(rowCellCount != 0) {
+    csv->cellCounts[csv->rowCount] = rowCellCount;
+    csv->rowCount++;
+  }
+}
+
+void csvDispose(csv_t *csv) {
+  free(csv->buffer);
+  free(csv->cellCounts);
+  free(csv->rows);
+}
\ No newline at end of file
diff --git a/src/dawntools/utils/csv.h b/src/dawntools/utils/csv.h
new file mode 100644
index 00000000..6c06d754
--- /dev/null
+++ b/src/dawntools/utils/csv.h
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2022 Dominic Masters
+ * 
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ */
+
+#pragma once
+#include "common.h"
+
+#define CSV_ROW_COUNT_MAX 128
+
+typedef enum {
+  CSV_PARSE_STATE_FIND_CELL,//0
+  CSV_PARSE_STATE_PARSE_CELL_WITH_QUOTES,
+  CSV_PARSE_STATE_PARSE_CELL,//2
+  CSV_PARSE_STATE_LINE_END
+} csvparsestate_t;
+
+typedef struct {
+  char *buffer;
+  char **rows;
+  int32_t rowCount;
+  int32_t *cellCounts;
+} csv_t;
+
+void csvParse(char *string, csv_t *csv);
+
+void csvDispose(csv_t *csv);
\ No newline at end of file
diff --git a/src/dawntools/utils/file.c b/src/dawntools/utils/file.c
new file mode 100644
index 00000000..3cb40623
--- /dev/null
+++ b/src/dawntools/utils/file.c
@@ -0,0 +1,195 @@
+/**
+ * Copyright (c) 2021 Dominic Masters
+ * 
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ */
+
+#include "file.h"
+
+void fileNormalizeSlashes(char *string) {
+  char c;
+  int i = 0;
+
+  while(c = string[i++]) {
+    if(c != '\\' && c != '/') continue;
+    string[i-1] = FILE_PATH_SEP;
+  }
+}
+
+void fileMkdirp(char *path) {
+  char buffer[FILENAME_MAX];
+  char c;
+  int i = 0;
+  bool inFile;
+  bool hasMore;
+
+  inFile = false;
+  hasMore = false;
+  while(c = path[i]) {
+    if((c == '\\' || c == '/') && i > 0) {
+      buffer[i] = '\0';
+      fileMkdir(buffer, 0755);
+      inFile = false;
+      hasMore = false;
+      buffer[i] = FILE_PATH_SEP;
+      i++;
+      continue;
+    }
+
+    if(c == '.') inFile = true;
+    hasMore = true;
+    buffer[i] = c;
+    i++;
+  }
+
+  if(!inFile && hasMore) {
+    buffer[i] = '\0';
+    fileMkdir(buffer, 0755);
+  }
+}
+
+size_t assetReadString(FILE *file, char *buffer) {
+  size_t length;
+  fseek(file, 0, SEEK_END);// Seek to the end
+  length = ftell(file);// Get our current position (the end)
+  fseek(file, 0, SEEK_SET);// Reset the seek 
+  if(buffer == NULL) return length;
+  return fread(buffer, 1, length, file);// Read all the bytes
+}
+
+int32_t readAhead(
+  char *bufferIn, int32_t start,
+  char *bufferOut,
+  char *needles, int32_t needleCount
+) {
+  int32_t i = start, k = 0, j;
+  char c;
+  bool needleFound = false;
+
+  if(bufferIn[i] == '\0') return 0;
+
+  while((c = bufferIn[i++]) != '\0') {
+    for(j = 0; j < needleCount; j++) {
+      if(c != needles[j]) continue;
+      needleFound = true;
+    }
+    if(needleFound) break;
+    if(bufferOut != NULL) bufferOut[k] = c;
+    k++;
+  }
+
+  if(bufferOut != NULL) bufferOut[k] = '\0';
+  return k;
+}
+
+int32_t skipAhead(
+  char *bufferIn, int32_t start, 
+  char *needles, int32_t needleCount
+) {
+  char c;
+  int32_t j, k = 0, i = start;
+  bool needleFound;
+
+  while((c = bufferIn[i++]) != '\0') {
+    needleFound = false;
+    for(j = 0; j < needleCount; j++) {
+      if(c != needles[j]) continue;
+      needleFound = true;
+      break;
+    }
+    if(!needleFound) break;
+    k++;
+  }
+  return k;
+}
+
+void fileGetDirectory(char *file, char* buffer) {
+  char *c, *p;
+  int32_t i;
+  p = strrchr(file, FILE_PATH_SEP);
+  c = file;
+  i = 0;
+  do {
+    buffer[i++] = *c;
+  } while(++c < p);
+  buffer[i] = '\0';
+}
+
+bool fileListChildren(
+  char *directory,
+  char *buffer,
+  int32_t *count,
+  uint8_t *types,
+  char **children
+) {
+  #if defined(_MSC_VER)
+    WIN32_FIND_DATA fdFile;
+    HANDLE hFind = NULL;
+    char sPath[2048];
+    int32_t i;
+
+    // Append wildcard
+    sprintf(sPath, "%s\\*.*", directory);
+
+    // Scan first
+    if((hFind = FindFirstFile(sPath, &fdFile)) == INVALID_HANDLE_VALUE) {
+      printf("Path not found: [%s]\n", directory);
+      return false;
+    }
+
+    // Iterate
+    i = 0;
+    do {
+      if(
+        strcmp(fdFile.cFileName, ".") == 0 ||
+        strcmp(fdFile.cFileName, "..") == 0
+      ) continue;
+
+      // Get Full path.
+      sprintf(sPath, "%s\\%s", directory, fdFile.cFileName);
+
+      //Is the entity a File or Folder?
+      if(fdFile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+        types[i] = FILE_CHILD_TYPE_DIR;
+      } else {
+        types[i] = FILE_CHILD_TYPE_FILE;
+      }
+
+      children[i] = buffer + (i * FILE_CHILD_NAME_MAX);
+      strcpy(children[i], fdFile.cFileName);
+      i++;
+    } while(FindNextFile(hFind, &fdFile));
+
+    *count = i;
+    return true;
+  #else
+    struct dirent *de;
+    DIR *dr;
+    int32_t i;
+
+    // Open Dir
+    dr = opendir(directory);
+    if(dr == NULL) {
+      printf("Could not open directory");
+      return false;
+    }
+
+
+    // Iterate
+    i = 0;
+    while ((de = readdir(dr)) != NULL) {
+      // File or folder?
+      if(de->d_type != DT_REG) continue;
+
+      // Copy into child buffer
+      children[i] = buffer + (i * FILE_CHILD_NAME_MAX);
+      strcpy(children[i], de->d_name);
+      i++;
+    }
+    if(closedir(dr)) return false;
+  
+    *count = i;
+    return true;
+  #endif
+}
\ No newline at end of file
diff --git a/src/dawntools/utils/file.h b/src/dawntools/utils/file.h
new file mode 100644
index 00000000..3a6b0f66
--- /dev/null
+++ b/src/dawntools/utils/file.h
@@ -0,0 +1,75 @@
+/**
+ * Copyright (c) 2021 Dominic Masters
+ * 
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ */
+
+#pragma once
+#include "common.h"
+
+#define FILE_CHILD_TYPE_DIR 0x00
+#define FILE_CHILD_TYPE_FILE 0x01
+#define FILE_CHILD_NAME_MAX 512
+#define FILE_CHILD_COUNT_MAX 64
+
+#if defined(_MSC_VER)
+  #include <direct.h>
+  #include <windows.h>
+  #define getcwd _getcwd
+  #define FILE_PATH_SEP '\\'
+  #define fileMkdir(path, perms) _mkdir(path)
+#elif defined(__GNUC__)
+  #include <unistd.h>
+  #include <dirent.h>
+  #include <sys/stat.h>
+  #define FILE_PATH_SEP '/'
+  #define fileMkdir(path, perms) mkdir(path, perms)
+#endif
+
+void fileNormalizeSlashes(char *string);
+
+void fileMkdirp(char *path);
+
+size_t assetReadString(FILE *file, char *buffer);
+
+void fileGetDirectory(char *file, char* buffer);
+
+bool fileListChildren(
+  char *directory,
+  char *buffer,
+  int32_t *count,
+  uint8_t *types,
+  char **children
+);
+
+/**
+ * Reads ahead to the first instance of the given character you provide.
+ * 
+ * @param bufferIn Buffer to scan.
+ * @param start Start position within the buffer to scan from (inclusive).
+ * @param bufferOut Where to write the temporary data that was read ahead.
+ * @param needles Array of characters to scan for.
+ * @param needleCount How many elements are within the needles array.
+ * @return The count of characters skipped.
+ */
+int32_t readAhead(
+  char *bufferIn, int32_t start,
+  char *bufferOut,
+  char *needles, int32_t needleCount
+);
+
+
+/**
+ * Skips any characters found in the needles.
+ * 
+ * @param bufferIn Buffer of chars to read.
+ * @param start Start of the buffer.
+ * @param needles Needles you are trying to skip.
+ * @param needleCount Count of needles in the needles array.
+ * @return The count of chars to skip ahead.
+ */
+int32_t skipAhead(
+  char *bufferIn, int32_t start, 
+  char *needles, int32_t needleCount
+);
\ No newline at end of file
diff --git a/src/dawntools/utils/image.c b/src/dawntools/utils/image.c
new file mode 100644
index 00000000..2a350cc1
--- /dev/null
+++ b/src/dawntools/utils/image.c
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2021 Dominic Masters
+ * 
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ */
+
+#include "image.h"
+
+#ifndef  STB_IMAGE_IMPLEMENTATION
+  #define STB_IMAGE_IMPLEMENTATION
+  #include <stb_image.h>
+#endif
+
+#ifndef  STB_IMAGE_RESIZE_IMPLEMENTATION
+  #define STB_IMAGE_RESIZE_IMPLEMENTATION
+  #include <stb_image_resize.h>
+#endif
+
+#ifndef  STB_IMAGE_WRITE_IMPLEMENTATION
+  #define STB_IMAGE_WRITE_IMPLEMENTATION
+  #include <stb_image_write.h>
+#endif
+
+void imageCopy(
+  uint8_t *source, int32_t sourceWidth, int32_t sourceHeight,
+  uint8_t *dest, int32_t destWidth, int32_t destHeight,
+  int32_t cropX, int32_t cropY, int32_t cropWidth, int32_t cropHeight,
+  int32_t pasteX, int32_t pasteY,
+  int32_t channels
+) {
+  int32_t x, y, c;
+  int32_t absX, absY;
+  int32_t sourceIndex, targetIndex;
+
+  if(cropX == -1) cropX = 0;
+  if(cropY == -1) cropY = 0;
+  if(cropWidth == -1) cropWidth = sourceWidth;
+  if(cropHeight == -1) cropHeight = sourceHeight;
+  if(pasteX == -1) pasteX = 0;
+  if(pasteY == -1) pasteY = 0;
+
+  for(x = cropX; x < cropX + cropWidth; x++) {
+    for(y = cropY; y < cropY + cropHeight; y++) {
+      absX = x - cropX + pasteX;
+      absY = y - cropY + pasteY;
+      if(absX >= destWidth || absY >= destHeight || absX < 0 || absY < 0)continue;
+      targetIndex = absY * destWidth + absX;
+      sourceIndex = y * sourceWidth + x;
+      for(c = 0; c < channels; c++) {
+        dest[(targetIndex*channels) + c] = source[(sourceIndex*channels) + c];
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/dawntools/utils/image.h b/src/dawntools/utils/image.h
new file mode 100644
index 00000000..040d5189
--- /dev/null
+++ b/src/dawntools/utils/image.h
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2021 Dominic Masters
+ * 
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ */
+
+#pragma once
+#include "common.h"
+#include "file.h"
+
+#include <stb_image.h>
+#include <stb_image_resize.h>
+#include <stb_image_write.h>
+
+void imageCopy(
+  uint8_t *source, int32_t sourceWidth, int32_t sourceHeight,
+  uint8_t *dest, int32_t destWidth, int32_t destHeight,
+  int32_t cropX, int32_t cropY, int32_t cropWidth, int32_t cropHeight,
+  int32_t pasteX, int32_t pasteY,
+  int32_t channels
+);
\ No newline at end of file
diff --git a/src/dawntools/utils/xml.c b/src/dawntools/utils/xml.c
new file mode 100644
index 00000000..8c6c43ed
--- /dev/null
+++ b/src/dawntools/utils/xml.c
@@ -0,0 +1,210 @@
+/**
+ * Copyright (c) 2021 Dominic Masters
+ * 
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ */
+
+#include "xml.h"
+
+int32_t xmlLoadChild(xml_t *xml, char *data, int32_t i) {
+  char c;
+  int32_t level = 0;
+  uint8_t doing = XML_DOING_NOTHING;
+  bool insideTag = false;
+  char* buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX);
+  int32_t bufferLength = 0;
+
+  xml->value = NULL;
+  xml->attributeCount = 0;
+
+  xml->children = malloc(sizeof(xml_t) * XML_CHILD_COUNT_MAX);
+  xml->childrenCount = 0;
+
+  while(c = data[i++]) {
+    switch(doing) {
+      case XML_DOING_NOTHING:
+        // Look for either an opening tag (<) or a word for a value.
+        if(c == '>') continue;
+        if(c == '<') {
+          if(insideTag) {
+            i = xmlLoadChild(xml->children + xml->childrenCount++, data, i-1);
+            doing = XML_PARSING_CHILD;
+          } else {
+            doing = XML_PARSING_TAG_NAME;
+            level++;
+            insideTag = true;
+          }
+          continue;
+        }
+
+        if(xmlIsWhitespace(c)) continue;
+        doing = XML_PARSING_VALUE;
+        buffer[bufferLength++] = c;
+        break;
+      
+      case XML_PARSING_TAG_NAME:
+        // Just keep reading until we either hit a space (end of the tag name)
+        // or a closing tag value, either / or >
+        if(xmlIsWhitespace(c) || c == '>' || c == '/') {
+          buffer[bufferLength] = '\0';
+          xml->node = buffer;
+          buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX);
+          bufferLength = 0;
+          if(c == '/') {
+            level--;
+            insideTag = false;
+            doing = XML_PARSING_CLOSE;
+          } else {
+            doing = c == '>' ? XML_DOING_NOTHING : XML_LOOKING_FOR_ATTRIBUTE;
+          }
+          continue;
+        }
+        buffer[bufferLength++] = c;
+        break;
+      
+      case XML_LOOKING_FOR_ATTRIBUTE:
+        // Look until we hit either the end of a tag, or the attribute itself
+        if(xmlIsWhitespace(c) || c == '>' || c == '/' || c == '=') {
+          if(c == '>' || c == '/') {
+            doing = XML_DOING_NOTHING;
+            if(c == '/') {
+              level--;
+              insideTag = false;
+              doing = XML_PARSING_CLOSE;
+            }
+          } else if(c == '=') {
+            doing = XML_LOOKING_FOR_ATTRIBUTE_VALUE;
+          } else {
+            doing = XML_LOOKING_FOR_ATTRIBUTE;
+          }
+
+          if(bufferLength > 0) {
+            buffer[bufferLength] = '\0';
+            xml->attributeNames[xml->attributeCount++] = buffer;
+            xml->attributeDatas[xml->attributeCount] = NULL;
+            buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX);
+            bufferLength = 0;
+          }
+          continue;
+        }
+
+        buffer[bufferLength++] = c;
+        break;
+
+      case XML_LOOKING_FOR_ATTRIBUTE_VALUE:
+        // Keep looking until we find a quote mark
+        if(xmlIsWhitespace(c)) continue;
+        if(c == '>' || c == '/') {
+          doing = XML_DOING_NOTHING;
+          insideTag = false;
+          continue;
+        }
+
+        if(c != '"') continue;
+        doing = XML_PARSING_ATTRIBUTE_VALUE;
+        break;
+
+      case XML_PARSING_ATTRIBUTE_VALUE:
+        // Parse the attribute value until we find a quote mark.
+        if(c == '"') {
+          doing = XML_LOOKING_FOR_ATTRIBUTE;
+          buffer[bufferLength] = '\0';
+          xml->attributeDatas[xml->attributeCount - 1] = buffer;
+          buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX);
+          bufferLength = 0;
+          continue;
+        }
+
+        buffer[bufferLength++] = c;
+        break;
+
+      case XML_PARSING_VALUE:
+        // Keep parsing child until we find a < for an opening/closing tag.
+        if(c == '<') {
+          // In HTML Spec there could be a child here but not in XML spec.
+          doing = XML_PARSING_CLOSE;
+          buffer[bufferLength] = '\0';
+          bufferLength = 0;
+          xml->value = buffer;
+          buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX);
+          continue;
+        }
+
+        buffer[bufferLength++] = c;
+        break;
+        
+      case XML_PARSING_CHILD:
+        if(c == '<') {
+          // Read ahead and confirm this is a close or not
+          if(data[i] == '/') {
+            doing = XML_PARSING_CLOSE;
+            continue;
+          }
+
+          // Likely another child.
+          i = xmlLoadChild(xml->children + xml->childrenCount++, data, i-1);
+        }
+
+        if(xmlIsWhitespace(c)) continue;
+
+        // In HTML Spec there's a chance for there to be a value here, but not
+        // in the XML spec.
+        break;
+
+      case XML_PARSING_CLOSE:
+        // Just keep parsing until the tag closer finishes.
+        if(c != '>') continue;
+        doing = XML_DOING_NOTHING;
+        
+        //TODO: Return index or something?
+        free(buffer);
+        return i;
+
+      default:
+        break;
+    }
+  }
+
+  free(buffer);
+  return i;
+}
+
+void xmlLoad(xml_t *xml, char *data) {
+  xmlLoadChild(xml, data, 0);
+}
+
+void xmlDispose(xml_t *xml) {
+  uint8_t i;
+
+  // Dispose children recursively
+  for(i = 0; i < xml->childrenCount; i++) {
+    xmlDispose(xml->children + i);
+  }
+
+  // Free children array.
+  free(xml->children);
+
+  // Dispose attributes
+  for(i = 0; i < xml->attributeCount; i++) {
+    free(xml->attributeNames[i]);
+    if((xml->attributeDatas + i) != NULL) {
+      free(xml->attributeDatas[i]);
+    }
+  }
+  
+  free(xml->node);
+  if(xml-> value != NULL) free(xml->value);
+}
+
+int16_t xmlGetAttributeByName(xml_t *xml, char *name) {
+  int16_t i;
+  for(i = 0; i < xml->attributeCount; i++) {
+    if(strcmp(xml->attributeNames[i], name) == 0) return i;
+  }
+  return -1;
+}
+
+bool xmlIsWhitespace(char c) {
+  return c == ' ' || c == '\r' || c == '\n' || c == '\t';
+}
\ No newline at end of file
diff --git a/src/dawntools/utils/xml.h b/src/dawntools/utils/xml.h
new file mode 100644
index 00000000..fab84903
--- /dev/null
+++ b/src/dawntools/utils/xml.h
@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2021 Dominic Masters
+ * 
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ */
+
+#pragma once
+#include "common.h"
+#include "file.h"
+
+#define XML_DOING_NOTHING 0x00
+#define XML_PARSING_TAG_NAME 0x01
+#define XML_LOOKING_FOR_ATTRIBUTE 0x02
+#define XML_PARSING_ATTRIBUTE_NAME 0x03
+#define XML_LOOKING_FOR_ATTRIBUTE_VALUE 0x04
+#define XML_PARSING_ATTRIBUTE_VALUE 0x05
+#define XML_PARSING_VALUE 0x06
+#define XML_PARSING_CHILD 0x07
+#define XML_PARSING_CLOSE 0x08
+
+#define XML_TEXT_BUFFER_MAX 256
+#define XML_CHILD_COUNT_MAX 16
+#define XML_ATTRIBUTE_MAX 16
+
+typedef struct _xml_t xml_t;
+
+typedef struct _xml_t {
+  char *node;
+  char *value;
+
+  char *attributeNames[XML_ATTRIBUTE_MAX];
+  char *attributeDatas[XML_ATTRIBUTE_MAX];
+  uint8_t attributeCount;
+
+  xml_t *children;
+  uint8_t childrenCount;
+} xml_t;
+
+/**
+ * Load an XML child from a string buffer.
+ * 
+ * @param xml XML to load.
+ * @param data Data to parse
+ * @param i Character index within the data
+ * @return The index in the data string this XML node ends.
+ */
+int32_t xmlLoadChild(xml_t *xml, char *data, int32_t i);
+
+/**
+ * Load an XML String into an XML memory.
+ * 
+ * @param xml XML to load into.
+ * @param data XML string.
+ */
+void xmlLoad(xml_t *xml, char *data);
+
+/**
+ * Dispose a previously loaded XML.
+ * 
+ * @param xml XML to dispose.
+ */
+void xmlDispose(xml_t *xml);
+
+int16_t xmlGetAttributeByName(xml_t *xml, char *name);
+
+bool xmlIsWhitespace(char c);
\ No newline at end of file
diff --git a/src/dawnwin32/CMakeLists.txt b/src/dawnwin32/CMakeLists.txt
index 4dd3a2f5..99252d61 100644
--- a/src/dawnwin32/CMakeLists.txt
+++ b/src/dawnwin32/CMakeLists.txt
@@ -9,5 +9,11 @@ target_include_directories(${DAWN_TARGET_NAME}
     ${CMAKE_CURRENT_LIST_DIR}
 )
 
+# Platform variables
+target_compile_definitions(${DAWN_TARGET_NAME}
+  PUBLIC
+    DAWN_ASSET_BUILD_PREFIX="../../../assets/"
+)
+
 # Subdirs
 add_subdirectory(host)
\ No newline at end of file