diff --git a/assets/games/liminal/VNTextbox.xml b/assets/games/liminal/VNTextbox.xml
new file mode 100644
index 00000000..64e88d98
--- /dev/null
+++ b/assets/games/liminal/VNTextbox.xml
@@ -0,0 +1,4 @@
+<prefab name="VNTextbox" type="">
+  <asset type="truetype" name="font_main" />
+  <UILabel text="Hello UI" font="font_main" fontSize="32" />
+</prefab>
\ No newline at end of file
diff --git a/src/dawn/scene/components/ui/UIComponent.cpp b/src/dawn/scene/components/ui/UIComponent.cpp
index 739bf0bf..8ac8a331 100644
--- a/src/dawn/scene/components/ui/UIComponent.cpp
+++ b/src/dawn/scene/components/ui/UIComponent.cpp
@@ -19,7 +19,7 @@ UIComponent::UIComponent(SceneItem *item) :
 
 UIComponentDimensional * UIComponent::getParentDimensional() {
   auto parent = this->transform->getParent();
-  if(!parent) return nullptr;
+  if(parent == nullptr) return nullptr;
   auto dimensional = parent->item->getComponent<UIComponentDimensional>();
   assertNotNull(dimensional);
   return dimensional;
diff --git a/src/dawnliminal/CMakeLists.txt b/src/dawnliminal/CMakeLists.txt
index b78b1e1c..773059b1 100644
--- a/src/dawnliminal/CMakeLists.txt
+++ b/src/dawnliminal/CMakeLists.txt
@@ -18,4 +18,7 @@ add_subdirectory(save)
 
 # Assets
 set(LIMINAL_ASSETS_DIR ${DAWN_ASSETS_DIR}/games/liminal)
-tool_vnscene(${LIMINAL_ASSETS_DIR}/test.xml)
\ No newline at end of file
+tool_vnscene(${LIMINAL_ASSETS_DIR}/test.xml)
+
+tool_prefab(${LIMINAL_ASSETS_DIR}/VNTextbox.xml)
+tool_truetype(font_main ${DAWN_ASSETS_DIR}/ark-pixel.ttf)
\ No newline at end of file
diff --git a/src/dawnliminal/game/LiminalGame.cpp b/src/dawnliminal/game/LiminalGame.cpp
index 7bec3cab..4c28a4cb 100644
--- a/src/dawnliminal/game/LiminalGame.cpp
+++ b/src/dawnliminal/game/LiminalGame.cpp
@@ -10,6 +10,6 @@
 using namespace Dawn;
 
 Scene * Dawn::dawnGameGetInitialScene(DawnGame *game) {
-  // return new HelloWorldScene(game);
-  return new TestScene(game);
+  return new HelloWorldScene(game);
+  // return new TestScene(game);
 }
\ No newline at end of file
diff --git a/src/dawnliminal/scenes/HelloWorldScene.hpp b/src/dawnliminal/scenes/HelloWorldScene.hpp
index 6daa3b5e..fe34516c 100644
--- a/src/dawnliminal/scenes/HelloWorldScene.hpp
+++ b/src/dawnliminal/scenes/HelloWorldScene.hpp
@@ -7,14 +7,7 @@
 #include "scene/Scene.hpp"
 #include "scene/components/display/Camera.hpp"
 #include "prefabs/SimpleSpinningCubePrefab.hpp"
-#include "games/vn/components/VNManager.hpp"
-#include "games/vn/events/VNDummyEvent.hpp"
-#include "games/vn/events/VNTextEvent.hpp"
-#include "games/vn/events/VNPositionEvent.hpp"
-#include "games/vn/events/VNSetEvent.hpp"
-#include "games/vn/events/VNChoiceEvent.hpp"
-#include "games/vn/events/VNParallelEvent.hpp"
-#include "games/vn/events/VNWaitEvent.hpp"
+#include "prefabs/VNTextbox.hpp"
 
 namespace Dawn {
   class HelloWorldScene : public Scene {
@@ -33,52 +26,15 @@ namespace Dawn {
 
         auto cube = SimpleSpinningCubePrefab::create(this);
 
-        auto vnItem = this->createSceneItem();
-        auto vnManager = vnItem->addComponent<VNManager>();
-
-        auto eventTest = vnManager->createEvent<VNDummyEvent>();
-
-        auto positionEvent = vnManager->createEvent<VNPositionEvent>();
-        positionEvent->to.x = 2.0f;
-        positionEvent->item = cube;
-        positionEvent->duration = 3.0f;
-
-        auto vnTextEvent = vnManager->createEvent<VNTextEvent>();
-        vnTextEvent->text = "Hello World!";
-
-        auto setPropertyEvent = vnManager->createEvent<VNSetEvent<int32_t>>();
-        setPropertyEvent->modifies = &test;
-        setPropertyEvent->to = 10;
-
-        auto choiceEvent = vnManager->createEvent<VNChoiceEvent>();
-        choiceEvent->text = "Choice?";
-        choiceEvent->choices["state0"] = "State 0";
-        choiceEvent->choices["state1"] = "State 1";
-        choiceEvent->choices["state2"] = "State 2";
-        choiceEvent->choices["state3"] = "State 3";
-
-        auto parallelEvent = vnManager->createEvent<VNParallelEvent>();
-        auto wait0 = vnManager->createEvent<VNWaitEvent>();
-        wait0->duration = 1.0f;
-        parallelEvent->events.push_back(wait0);
-
-        auto wait1 = vnManager->createEvent<VNWaitEvent>();
-        wait1->duration = 3.0f;
-        parallelEvent->events.push_back(wait1);
-
-        eventTest
-          ->then(parallelEvent)
-          ->then(positionEvent)
-          // ->then(vnTextEvent)
-          // ->then(setPropertyEvent)
-          // ->then(choiceEvent)
-        ;
-        vnManager->setEvent(eventTest);
+        auto textbox = VNTextbox::create(this);
+        textbox->transform.setParent(canvas->transform);
       }
       
       std::vector<Asset*> getRequiredAssets() override {
         auto assMan = &this->game->assetManager;
         std::vector<Asset*> assets;
+        vectorAppend(&assets, SimpleSpinningCubePrefab::prefabAssets(assMan));
+        vectorAppend(&assets, VNTextbox::prefabAssets(assMan));
         return assets;
       }
 
diff --git a/src/dawntools/CMakeLists.txt b/src/dawntools/CMakeLists.txt
index 7f4bff00..99b0091e 100644
--- a/src/dawntools/CMakeLists.txt
+++ b/src/dawntools/CMakeLists.txt
@@ -21,4 +21,5 @@ include(util/CMakeLists.txt)
 # Tools
 add_subdirectory(prefabtool)
 add_subdirectory(texturetool)
+add_subdirectory(truetypetool)
 add_subdirectory(vnscenetool)
\ No newline at end of file
diff --git a/src/dawntools/prefabtool/PrefabAssetParser.cpp b/src/dawntools/prefabtool/PrefabAssetParser.cpp
index c94b9c41..586cc0cf 100644
--- a/src/dawntools/prefabtool/PrefabAssetParser.cpp
+++ b/src/dawntools/prefabtool/PrefabAssetParser.cpp
@@ -25,6 +25,8 @@ int32_t PrefabAssetParser::onParse(
   
   if(values["type"] == "texture") {
     out->type = PREFAB_ASSET_TYPE_TEXTURE;
+  } else if(values["type"] == "truetype") {
+    out->type = PREFAB_ASSET_TYPE_TRUETYPE_FONT;
   } else {
     *error = "Unknown asset type '" + values["type"] + "'";
     return 1;
diff --git a/src/dawntools/prefabtool/PrefabAssetParser.hpp b/src/dawntools/prefabtool/PrefabAssetParser.hpp
index c2c57e77..b263bb1e 100644
--- a/src/dawntools/prefabtool/PrefabAssetParser.hpp
+++ b/src/dawntools/prefabtool/PrefabAssetParser.hpp
@@ -8,7 +8,8 @@
 
 namespace Dawn {
   enum PrefabAssetType {
-    PREFAB_ASSET_TYPE_TEXTURE
+    PREFAB_ASSET_TYPE_TEXTURE,
+    PREFAB_ASSET_TYPE_TRUETYPE_FONT
   };
 
   struct PrefabAsset {
diff --git a/src/dawntools/prefabtool/PrefabGen.cpp b/src/dawntools/prefabtool/PrefabGen.cpp
index 78af3edb..6b42a244 100644
--- a/src/dawntools/prefabtool/PrefabGen.cpp
+++ b/src/dawntools/prefabtool/PrefabGen.cpp
@@ -44,6 +44,11 @@ void PrefabGen::generate(
         assetMap[a.fileName] = "&" + a.usageName + "->texture";
         break;
 
+      case PREFAB_ASSET_TYPE_TRUETYPE_FONT:
+        assetType = "TrueTypeAsset";
+        assetMap[a.fileName] = "&" + a.usageName + "->font";
+        break;
+
       default:
         assertUnreachable();
     }
diff --git a/src/dawntools/truetypetool/CMakeLists.txt b/src/dawntools/truetypetool/CMakeLists.txt
new file mode 100644
index 00000000..0efff173
--- /dev/null
+++ b/src/dawntools/truetypetool/CMakeLists.txt
@@ -0,0 +1,51 @@
+# Copyright (c) 2021 Dominic Msters
+# 
+# This software is released under the MIT License.
+# https://opensource.org/licenses/MIT
+
+project(truetypetool VERSION 2.0)
+add_executable(truetypetool)
+
+target_sources(truetypetool
+  PRIVATE
+    ${DAWN_SHARED_SOURCES}
+    ${DAWN_TOOL_SOURCES}
+    TrueTypeTool.cpp
+)
+
+target_include_directories(truetypetool
+  PUBLIC
+    ${DAWN_SHARED_INCLUDES}
+    ${DAWN_TOOL_INCLUDES}
+    ${CMAKE_CURRENT_LIST_DIR}
+)
+
+# Definitions
+target_compile_definitions(truetypetool
+  PUBLIC
+    ${DAWN_SHARED_DEFINITIONS}
+    DAWN_TOOL_INSTANCE=TrueTypeTool
+    DAWN_TOOL_HEADER="TrueTypeTool.hpp"
+)
+
+# Libraries
+target_link_libraries(truetypetool
+  PUBLIC
+    ${DAWN_BUILD_HOST_LIBS}
+    stb
+)
+
+# Tool Function
+function(tool_truetype target in)
+  set(DEPS "")
+  if(DAWN_BUILD_TOOLS)
+    set(DEPS truetypetool)
+  endif()
+
+  add_custom_target(${target}
+    COMMAND truetypetool --input="${DAWN_ASSETS_SOURCE_DIR}/${in}" --output="${DAWN_ASSETS_BUILD_DIR}/${target}"
+    COMMENT "Generating truetype font ${target} from ${in}"
+    DEPENDS ${DEPS}
+  )
+  add_dependencies(${DAWN_TARGET_NAME} ${target})
+endfunction()
\ No newline at end of file
diff --git a/src/dawntools/truetypetool/TrueTypeTool.cpp b/src/dawntools/truetypetool/TrueTypeTool.cpp
new file mode 100644
index 00000000..39dcfa5d
--- /dev/null
+++ b/src/dawntools/truetypetool/TrueTypeTool.cpp
@@ -0,0 +1,75 @@
+// Copyright (c) 2023 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#include "TrueTypeTool.hpp"
+#ifndef  STB_TRUETYPE_IMPLEMENTATION
+  #define STB_TRUETYPE_IMPLEMENTATION
+  #include <stb_truetype.h>
+#endif
+
+using namespace Dawn;
+
+std::vector<std::string> TrueTypeTool::getRequiredFlags() {
+  return { "input", "output" };
+}
+
+int32_t TrueTypeTool::start() {
+  File fileIn(flags["input"]);
+  char *ttfData = nullptr;
+  if(!fileIn.readToBuffer(&ttfData)) {
+    std::cout << "Failed to read file to buffer!" << std::endl;
+    return 1;
+  }
+
+  // Create bitmap data. This is a single channel color (alpha).
+  size_t width = 1024;
+  size_t height = 1024;
+  float_t fontSize = 128.0f;
+  size_t textureSize = width * height;
+  stbi_uc *bitmapData = new stbi_uc[textureSize];
+  stbtt_bakedchar characterData[TRUETYPE_NUM_CHARS];
+
+  // Now parse the TTF itself.
+  stbtt_BakeFontBitmap(
+    (uint8_t*)ttfData, 0, (float)fontSize, bitmapData,
+    width, height,
+    TRUETYPE_FIRST_CHAR, TRUETYPE_NUM_CHARS,
+    characterData
+  );
+
+  // Prepare output file for writing.
+  File fileOut(flags["output"] + ".truetype");
+  if(!fileOut.mkdirp()) {
+    free(ttfData);
+    delete bitmapData;
+    std::cout << "Failed to create output directory!" << std::endl;
+    return 1;
+  }
+  if(!fileOut.open(FILE_MODE_WRITE)) {
+    free(ttfData);
+    delete bitmapData;
+    std::cout << "Failed to open output file for writing!" << std::endl;
+    return 1;
+  }
+
+  // Write data
+  fileOut.writeString(std::to_string(width) + "|" + std::to_string(height) + "|" + std::to_string(fontSize) + "|");
+  uint8_t pixels[4];
+  for(size_t i = 0; i < textureSize; i++) {
+    pixels[0] = 255;
+    pixels[1] = 255;
+    pixels[2] = 255;
+    pixels[3] = bitmapData[i];
+    fileOut.writeRaw((char*)pixels, 4);
+  }
+
+  // Write quads
+  fileOut.writeRaw((char*)characterData, sizeof(stbtt_bakedchar) * TRUETYPE_NUM_CHARS);
+  free(ttfData);
+  delete bitmapData;
+  fileOut.close();
+  
+  return 0;
+}
\ No newline at end of file
diff --git a/src/dawntools/truetypetool/TrueTypeTool.hpp b/src/dawntools/truetypetool/TrueTypeTool.hpp
new file mode 100644
index 00000000..2c35530e
--- /dev/null
+++ b/src/dawntools/truetypetool/TrueTypeTool.hpp
@@ -0,0 +1,22 @@
+// Copyright (c) 2023 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#pragma once
+#include "util/DawnTool.hpp"
+#include "util/File.hpp"
+#include "util/Image.hpp"
+
+#define TRUETYPE_FIRST_CHAR 32
+#define TRUETYPE_NUM_CHARS 96
+
+namespace Dawn {
+  class TrueTypeTool : public DawnTool {
+    protected:
+      std::vector<std::string> getRequiredFlags() override;
+
+    public:
+      int32_t start();
+  };
+}
\ No newline at end of file
diff --git a/src/dawntools/util/File.cpp b/src/dawntools/util/File.cpp
index 56df6443..496097fa 100644
--- a/src/dawntools/util/File.cpp
+++ b/src/dawntools/util/File.cpp
@@ -166,18 +166,26 @@ size_t File::readAhead(char *buffer, size_t max, char needle) {
   return -1;
 }
 
-bool_t File::writeString(std::string in) {
+size_t File::readToBuffer(char **buffer) {
   if(!this->isOpen()) {
-    if(!this->open(FILE_MODE_WRITE)) return false;
+    if(!this->open(FILE_MODE_READ)) return 0;
   }
+  assertTrue(this->mode == FILE_MODE_READ);
+
+  if((*buffer) == nullptr) *buffer = (char*)malloc(this->length);
+  fseek(this->file, 0, SEEK_SET);
+  auto l = fread((*buffer), sizeof(char), this->length, this->file);
+  return l;
+}
+
+bool_t File::writeString(std::string in) {
+  if(!this->isOpen() && !this->open(FILE_MODE_WRITE)) return false;
   assertTrue(this->mode == FILE_MODE_WRITE);
   return this->writeRaw((char *)in.c_str(), in.size()) && this->length == in.size();
 }
 
 bool_t File::writeRaw(char *data, size_t len) {
-  if(!this->isOpen()) {
-    if(!this->open(FILE_MODE_WRITE)) return false;
-  }
+  if(!this->isOpen() && !this->open(FILE_MODE_WRITE)) return false;
   assertTrue(this->mode == FILE_MODE_WRITE);
   this->length = fwrite(data, sizeof(char_t), len, this->file);
   return true;
diff --git a/src/dawntools/util/File.hpp b/src/dawntools/util/File.hpp
index d92add27..0ff97515 100644
--- a/src/dawntools/util/File.hpp
+++ b/src/dawntools/util/File.hpp
@@ -109,6 +109,15 @@ namespace Dawn {
         char needle
       );
 
+      /**
+       * Reads the contents of this file into a given buffer. If buffer is null
+       * then the buffer will be allocated and returned.
+       * 
+       * @param buffer Pointer to buffer to read to.
+       * @return The size of the read data.
+       */
+      size_t readToBuffer(char **buffer);
+
       /**
        * Writes the entire contents of a string to a file.
        *