From 4dd4cb045c070b8ec9822ff1b7435fe9a43abffd Mon Sep 17 00:00:00 2001
From: Dominic Masters <dominic@domsplace.com>
Date: Mon, 9 Oct 2023 13:16:52 -0500
Subject: [PATCH] Added libarchive support to Dawn.

---
 .gitmodules                                   |   3 +
 CMakeLists.txt                                |   2 +-
 assets/games/liminal/prefabs/ButtonPrefab.xml |  35 +--
 assets/games/liminal/prefabs/VNTextbox.xml    |   8 +-
 assets/games/liminal/scenes/CMakeLists.txt    |   1 +
 assets/games/liminal/scenes/SceneMainMenu.xml |  53 +++++
 lib/CMakeLists.txt                            |   3 +
 lib/libarchive                                |   1 +
 src/CMakeLists.txt                            |   3 +
 src/dawn/CMakeLists.txt                       |   1 +
 src/dawn/asset/AssetLoader.cpp                | 203 ++++++++++++------
 src/dawn/asset/AssetLoader.hpp                | 117 +++++++---
 src/dawn/asset/AssetManager.cpp               |   4 -
 src/dawn/asset/assets/LanguageAsset.cpp       |  29 ++-
 src/dawn/asset/assets/LanguageAsset.hpp       |   9 +-
 src/dawn/asset/assets/TextureAsset.cpp        |   6 +-
 src/dawn/asset/assets/TilesetAsset.cpp        |   6 +-
 src/dawn/asset/assets/TrueTypeAsset.cpp       |   2 -
 src/dawn/display/RenderPipeline.cpp           |   9 +
 src/dawn/display/shader/ShaderPass.hpp        |   3 +
 src/dawn/scene/components/ui/UIComponent.cpp  |  54 ++++-
 src/dawn/scene/components/ui/UIComponent.hpp  |  18 ++
 src/dawn/scene/components/ui/UIEmpty.cpp      |   4 +-
 src/dawn/scene/components/ui/text/UILabel.cpp |   3 +-
 src/dawn/scene/components/ui/text/UILabel.hpp |  10 +
 src/dawnliminal/CMakeLists.txt                |   1 -
 src/dawnliminal/game/LiminalGame.cpp          |   4 +-
 src/dawnlinux64/CMakeLists.txt                |   2 +-
 src/dawnshared/display/Color.cpp              |  18 ++
 src/dawnshared/util/parser/TypeParsers.hpp    |  13 ++
 src/dawntools/CMakeLists.txt                  |   1 +
 src/dawntools/assetstool/CMakeLists.txt       |  13 ++
 src/dawntools/assetstool/assetstool.py        |  60 ++++++
 src/dawntools/texturetool/CMakeLists.txt      |   2 +-
 src/dawntools/truetypetool/CMakeLists.txt     |   2 +-
 .../util/generator/SceneItemGenerator.cpp     |   8 +
 src/dawntools/util/parser/SceneItemParser.cpp |  12 ++
 src/dawntools/util/parser/SceneItemParser.hpp |   2 +
 38 files changed, 567 insertions(+), 158 deletions(-)
 create mode 100644 assets/games/liminal/scenes/SceneMainMenu.xml
 create mode 160000 lib/libarchive
 create mode 100644 src/dawntools/assetstool/CMakeLists.txt
 create mode 100755 src/dawntools/assetstool/assetstool.py

diff --git a/.gitmodules b/.gitmodules
index fb40d0ce..f943cf3f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -19,3 +19,6 @@
 [submodule "lib/freetype"]
 	path = lib/freetype
 	url = https://gitlab.freedesktop.org/freetype/freetype.git
+[submodule "lib/libarchive"]
+	path = lib/libarchive
+	url = https://github.com/libarchive/libarchive
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5849193d..d72d5a0a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -16,8 +16,8 @@ set(DAWN_CACHE_TARGET "dawn-target")
 # Set Common Build Variables
 set(DAWN_ROOT_DIR "${CMAKE_SOURCE_DIR}")
 set(DAWN_BUILD_DIR "${CMAKE_BINARY_DIR}")
-set(DAWN_TOOLS_DIR "${DAWN_ROOT_DIR}/tools")
 set(DAWN_SOURCES_DIR "${DAWN_ROOT_DIR}/src")
+set(DAWN_TOOLS_DIR "${DAWN_SOURCES_DIR}/dawntools")
 set(DAWN_ASSETS_SOURCE_DIR "${DAWN_ROOT_DIR}/assets")
 set(DAWN_ASSETS_BUILD_DIR "${DAWN_BUILD_DIR}/assets")
 set(DAWN_GENERATED_DIR "${DAWN_BUILD_DIR}/generated")
diff --git a/assets/games/liminal/prefabs/ButtonPrefab.xml b/assets/games/liminal/prefabs/ButtonPrefab.xml
index b87ad686..371a276a 100644
--- a/assets/games/liminal/prefabs/ButtonPrefab.xml
+++ b/assets/games/liminal/prefabs/ButtonPrefab.xml
@@ -8,11 +8,11 @@
   <asset type="texture" name="button_background_inactive" /> -->
   <asset type="texture" name="button_background" />
   <asset type="texture" name="button_hover_element" />
-  <asset type="texture" name="button_wings_down" />
-  <asset type="texture" name="button_wings_inactive" />
-  <asset type="texture" name="button_wings_open" />
+  <asset type="texture" name="button_wings_down" ref="wingsDown" />
+  <asset type="texture" name="button_wings_inactive" ref="wingsInactive" />
+  <asset type="texture" name="button_wings_open" ref="wingsOpen" />
 
-  <UIEmpty alignment="64, 128, 400, 64" ref="uiItem" />
+  <UIEmpty ref="uiItem" alignment="0, 0, 128, 32" />
   <UISimpleMenuItem ref="menuItem" />
 
   <!-- Background -->
@@ -20,7 +20,7 @@
     <UIImage
       ref="backgroundLeft"
       texture="button_background"
-      color="white"
+      color="red"
       alignment="0, 0, 47, 0"
       alignX="left"
       alignY="stretch"
@@ -33,7 +33,7 @@
     <UIImage
       ref="backgroundMiddle"
       texture="button_background"
-      color="white"
+      color="red"
       alignment="47, 0, 47, 0"
       alignX="stretch"
       alignY="stretch"
@@ -47,7 +47,7 @@
     <UIImage
       ref="backgroundRight"
       texture="button_background"
-      color="white"
+      color="red"
       alignment="47, 0, 0, 0"
       alignX="right"
       alignY="stretch"
@@ -71,9 +71,9 @@
   <!-- Left Wing -->
   <item>
     <UIImage
-      texture="button_wings_open"
+      texture="button_wings_down"
       ref="wingsLeft"
-      alignment="-80, -54, 134.15, 151.3"
+      alignment="-40, -27, 67.075, 75.65"
       alignX="UI_COMPONENT_ALIGN_START"
       alignY="UI_COMPONENT_ALIGN_START"
       uvs="0, 0, 0.5, 1"
@@ -83,9 +83,9 @@
   <!-- Right Wing -->
   <item>
     <UIImage
-      texture="button_wings_open"
+      texture="button_wings_down"
       ref="wingsRight"
-      alignment="134.15, -54, -80, 151.3"
+      alignment="67, -27, -40, 75"
       alignX="UI_COMPONENT_ALIGN_END"
       alignY="UI_COMPONENT_ALIGN_START"
       uvs="0.5, 0, 1, 1"
@@ -97,10 +97,11 @@
     <UISimpleLabel
       ref="label"
       font="font_main"
-      size="32"
-      alignment="0, 12, 0, 12"
-      alignX="center"
+      size="16"
+      alignment="0, 6, 0, 6"
+      alignX="stretch"
       alignY="stretch"
+      textAlign="center"
     >
       Hello Button.
     </UISimpleLabel>
@@ -109,10 +110,16 @@
   <code type="init">
     useEvent([&amp;]{
       hoverDeocration->color = COLOR_WHITE;
+      backgroundLeft->color = backgroundMiddle->color = backgroundRight->color = COLOR_BLUE;
+      wingsLeft->texture = &amp;wingsOpen->texture;
+      wingsRight->texture = &amp;wingsOpen->texture;
     }, menuItem->eventHoveredOn);
 
     useEvent([&amp;]{
       hoverDeocration->color = COLOR_TRANSPARENT;
+      backgroundLeft->color = backgroundMiddle->color = backgroundRight->color = COLOR_RED;
+      wingsLeft->texture = &amp;wingsDown->texture;
+      wingsRight->texture = &amp;wingsDown->texture;
     }, menuItem->eventHoveredOff);
   </code>
 </prefab>
\ No newline at end of file
diff --git a/assets/games/liminal/prefabs/VNTextbox.xml b/assets/games/liminal/prefabs/VNTextbox.xml
index d8a0ec8f..9410df5c 100644
--- a/assets/games/liminal/prefabs/VNTextbox.xml
+++ b/assets/games/liminal/prefabs/VNTextbox.xml
@@ -28,10 +28,10 @@
   <UISimpleMenu />
 
   <!-- <item ref="button0" prefab="prefabs/Button" menuX="0" menuY="0" /> -->
-  <item ref="button0" alignment="0, 0, 32, 32" prefab="prefabs/Button" menuX="0" menuY="0" />
-  <item ref="button1" alignment="180, 0, 32, 32" prefab="prefabs/Button" menuX="1" menuY="0" />
-  <item ref="button2" alignment="0, 180, 32, 32" prefab="prefabs/Button" menuX="0" menuY="1" />
-  <item ref="button3" alignment="180, 180, 32, 32" prefab="prefabs/Button" menuX="1" menuY="1" />
+  <item ref="button0" alignment="0, 0, 128, 32" prefab="prefabs/Button" menuX="0" menuY="0" />
+  <item ref="button1" alignment="192, 0, 128, 32" prefab="prefabs/Button" menuX="1" menuY="0" />
+  <item ref="button2" alignment="0, 180, 128, 32" prefab="prefabs/Button" menuX="0" menuY="1" />
+  <item ref="button3" alignment="192, 180, 128, 32" prefab="prefabs/Button" menuX="1" menuY="1" />
   
   <!-- <item prefab="Button" ref="button0" alignment="0, 0, 0, 0" menuX="0" menuY="0" />
   <item prefab="Button" ref="button1" alignment="180, 0, 0, 0" menuX="1" menuY="0" />
diff --git a/assets/games/liminal/scenes/CMakeLists.txt b/assets/games/liminal/scenes/CMakeLists.txt
index e302b637..c0212b30 100644
--- a/assets/games/liminal/scenes/CMakeLists.txt
+++ b/assets/games/liminal/scenes/CMakeLists.txt
@@ -5,6 +5,7 @@
 
 tool_scene(${CMAKE_CURRENT_LIST_DIR}/SceneStandard.xml)
 tool_scene(${CMAKE_CURRENT_LIST_DIR}/SceneMonologue.xml)
+tool_scene(${CMAKE_CURRENT_LIST_DIR}/SceneMainMenu.xml)
 tool_vnscene(${CMAKE_CURRENT_LIST_DIR}/SceneInitial.xml)
 
 include("${CMAKE_CURRENT_LIST_DIR}/test/CMakeLists.txt")
diff --git a/assets/games/liminal/scenes/SceneMainMenu.xml b/assets/games/liminal/scenes/SceneMainMenu.xml
new file mode 100644
index 00000000..00161348
--- /dev/null
+++ b/assets/games/liminal/scenes/SceneMainMenu.xml
@@ -0,0 +1,53 @@
+<scene name="SceneMainMenu">
+  <!-- Camera -->
+  <item>
+    <Camera ref="camera" />
+  </item>
+
+  <!-- UI -->
+  <item>
+    <UICanvas ref="canvas" camera="camera" />
+
+    <!-- Menu -->
+    <item>
+      <UIMenuController />
+      <UISimpleMenu />
+
+      <UIEmpty alignment="0, 0, 0, 0" alignX="center" alignY="center" />
+
+      <item>
+        <UIImage alignment="0, 0, 32, 32" alignX="left" alignY="top" color="red" />
+      </item>
+
+      <item>
+        <UIImage alignment="0, 0, 8, 8" alignX="center" alignY="center" color="blue" />
+      </item>
+
+      <item
+        ref="button0"
+        alignment="-400, 100, 128, 32"
+        prefab="prefabs/Button"
+        alignX="middle"
+        menuX="0"
+        menuY="0"
+      />
+      <item
+        ref="button1"
+        alignment="0, 100, 128, 32"
+        prefab="prefabs/Button"
+        alignX="middle"
+        menuX="1"
+        menuY="0"
+      />
+      <item
+        ref="button2"
+        alignment="400, 100, 128, 32"
+        prefab="prefabs/Button"
+        alignX="middle"
+        menuX="2"
+        menuY="0"
+      />
+    </item>
+  </item>
+
+</scene>
\ No newline at end of file
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index 4c1f567d..e805855a 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -25,6 +25,9 @@ target_include_directories(stb INTERFACE stb)
 # FreeType
 add_subdirectory(freetype)
 
+# LibArchive
+add_subdirectory(libarchive)
+
 # OpenAL
 if(DAWN_TARGET_OPENAL)
   set(LIBTYPE "STATIC")
diff --git a/lib/libarchive b/lib/libarchive
new file mode 160000
index 00000000..2ba3d927
--- /dev/null
+++ b/lib/libarchive
@@ -0,0 +1 @@
+Subproject commit 2ba3d92706384be14cd376734f3f7ebe5648591e
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index b1328f4c..4e1b9a3a 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -71,4 +71,7 @@ if(DEFINED DAWN_TARGET_NAME)
   else()
     add_dependencies(${DAWN_TARGET_NAME} ${DAWN_TARGET_DEPENDENCIES_LAST})
   endif()
+
+  # Compress the game assets.
+  add_dependencies(${DAWN_TARGET_NAME} dawnassets)
 endif()
\ No newline at end of file
diff --git a/src/dawn/CMakeLists.txt b/src/dawn/CMakeLists.txt
index 5f90aa66..1be141bc 100644
--- a/src/dawn/CMakeLists.txt
+++ b/src/dawn/CMakeLists.txt
@@ -9,6 +9,7 @@ target_link_libraries(${DAWN_TARGET_NAME}
     glm
     stb
     freetype
+    archive_static
 )
 
 # Includes
diff --git a/src/dawn/asset/AssetLoader.cpp b/src/dawn/asset/AssetLoader.cpp
index 2772b13b..4e90945e 100644
--- a/src/dawn/asset/AssetLoader.cpp
+++ b/src/dawn/asset/AssetLoader.cpp
@@ -4,91 +4,169 @@
 // https://opensource.org/licenses/MIT
 
 #include "AssetLoader.hpp"
+#include "util/mathutils.hpp"
 
 using namespace Dawn;
 
+ssize_t assetLoaderArchiveRead(
+  struct archive *archive,
+  void *d,
+  const void **buffer
+) {
+  assertNotNull(archive, "assetArchiveRead: Archive is NULL!");
+  assertNotNull(d, "assetArchiveRead: Data is NULL!");
+  assertNotNull(buffer, "assetArchiveRead: Buffer is NULL!");
+  AssetLoader *loader = (AssetLoader*)d;
+
+  *buffer = loader->buffer;
+  size_t read = fread(
+    loader->buffer, 1, ASSET_LOADER_BUFFER_SIZE, loader->assetArchiveFile
+  );
+  if(ferror(loader->assetArchiveFile)) return ARCHIVE_FATAL;
+  return read;
+}
+
+int64_t assetLoaderArchiveSeek(
+  struct archive *archive,
+  void *d,
+  int64_t offset,
+  int32_t whence
+) {
+  assertNotNull(archive, "assetArchiveSeek: Archive is NULL!");
+  assertNotNull(d, "assetArchiveSeek: Data is NULL!");
+  assertTrue(offset > 0, "assetArchiveSeek: Offset must be greater than 0!");
+  AssetLoader *loader = (AssetLoader*)d;
+  int32_t ret = fseek(loader->assetArchiveFile, offset, whence);
+  assertTrue(ret == 0, "assetArchiveSeek: Failed to seek!");
+  return ftell(loader->assetArchiveFile);
+}
+
+int32_t assetLoaderArchiveOpen(struct archive *a, void *d) {
+  assertNotNull(a, "assetArchiveOpen: Archive is NULL!");
+  assertNotNull(d, "assetArchiveOpen: Data is NULL!");
+  AssetLoader *loader = (AssetLoader*)d;
+
+  int32_t ret = fseek(loader->assetArchiveFile, 0, SEEK_SET);
+  assertTrue(ret == 0, "assetArchiveOpen: Failed to seek to start of file!");
+  return ARCHIVE_OK;
+}
+
+int32_t assetLoaderArchiveClose(struct archive *a, void *d) {
+  assertNotNull(a, "assetArchiveClose: Archive is NULL!");
+  assertNotNull(d, "assetArchiveClose: Data is NULL!");
+  return assetLoaderArchiveOpen(a, d);
+}
+
 AssetLoader::AssetLoader(std::string fileName) {
   assertTrue(fileName.size() > 0, "AssetLoader::AssetLoader: fileName must be greater than 0");
-
   this->fileName = fileName;
-  this->handle = nullptr;
 }
 
 void AssetLoader::open() {
-  assertNull(this->handle, "AssetLoader::open: File is already open");
-  std::string pathFull = DAWN_ASSET_BUILD_PREFIX + this->fileName;
-  this->handle = fopen(pathFull.c_str(), "rb");
-  assertNotNull(this->handle, "AssetLoader::open: Failed to open file " + pathFull);
+  assertNull(this->assetArchiveFile, "AssetLoader::open: File is already open");
+  assertNull(this->assetArchive, "AssetLoader::open: Archive is already open");
+  assertNull(this->assetArchiveEntry, "AssetLoader::open: Entry is already open");
+
+  this->assetArchiveFile = fopen(DAWN_ASSET_LOCATION, "rb");
+  assertNotNull(this->assetArchiveFile, "AssetLoader::open: Failed to open file " + std::string(DAWN_ASSET_LOCATION));
+
+  // Open archive reader
+  assetArchive = archive_read_new();
+  assertNotNull(assetArchive, "AssetLoader::open: Failed to create archive reader");
+
+  // Set up the reader
+  archive_read_support_format_tar(assetArchive);
+
+  // Open reader
+  archive_read_set_open_callback(assetArchive, &assetLoaderArchiveOpen);
+  archive_read_set_read_callback(assetArchive, &assetLoaderArchiveRead);
+  archive_read_set_seek_callback(assetArchive, &assetLoaderArchiveSeek);
+  archive_read_set_close_callback(assetArchive, &assetLoaderArchiveClose);
+  archive_read_set_callback_data(assetArchive, this);
+
+  int32_t ret =  archive_read_open1(assetArchive);
+  assertTrue(ret == ARCHIVE_OK, "AssetLoader::open: Failed to open archive!");
+  position = 0;
+
+  // Iterate over each file to find the one for this asset loader.
+  while(archive_read_next_header(assetArchive, &assetArchiveEntry) == ARCHIVE_OK) {
+    const char_t *headerFile = (char_t*)archive_entry_pathname(assetArchiveEntry);
+    if(std::string(headerFile) == this->fileName) return;
+    int32_t ret = archive_read_data_skip(assetArchive);
+    assertTrue(ret == ARCHIVE_OK, "AssetLoader::open: Failed to skip data!");
+  }
+
+  assertUnreachable("AssetLoader::open: Failed to find file!");
 }
 
 int32_t AssetLoader::close() {
-  assertNotNull(this->handle, "AssetLoader::close: File is not open");
-  int32_t ret = fclose(this->handle);
-  this->handle = nullptr;
-  return ret;
+  assertNotNull(this->assetArchiveFile, "AssetLoader::close: File is NULL");
+  assertNotNull(this->assetArchive, "AssetLoader::close: Archive is NULL!");
+  assertNotNull(this->assetArchiveEntry, "AssetLoader::close: Entry is NULL!");
+  
+  // Close the archive
+  int32_t ret = archive_read_free(this->assetArchive);
+  assertTrue(ret == ARCHIVE_OK, "AssetLoader::close: Failed to close archive!");
+
+  this->assetArchive = nullptr;
+  this->assetArchiveEntry = nullptr;
+
+  // Close the file
+  int32_t res = fclose(this->assetArchiveFile);
+  this->assetArchiveFile = nullptr;
+  return res;
 }
 
 size_t AssetLoader::read(uint8_t *buffer, size_t size) {
-  assertNotNull(buffer, "AssetLoader::read: buffer must not be null");
-  assertTrue(size > 0, "AssetLoader::read: size must be greater than 0");
-  assertNotNull(this->handle, "AssetLoader::read: File is not open");
-  return fread(buffer, 1, size, this->handle);
+  assertNotNull(buffer, "assetArchiveRead: Buffer is NULL!");
+  assertTrue(size > 0, "assetArchiveRead: Size must be greater than 0!");
+  assertNotNull(this->assetArchive, "assetRead: Archive is NULL!");
+  assertNotNull(this->assetArchiveEntry, "assetRead: Entry is NULL!");
+
+  ssize_t read = archive_read_data(this->assetArchive, buffer, size);
+  this->position += read;
+  
+  if(read == ARCHIVE_FATAL) {
+    assertUnreachable(archive_error_string(this->assetArchive));
+  }
+  
+  assertTrue(read != ARCHIVE_RETRY, "assetRead: Failed to read data (RETRY)!");
+  assertTrue(read != ARCHIVE_WARN, "assetRead: Failed to read data (WARN)!");
+
+  return read;
 }
 
-int32_t AssetLoader::end() {
-  assertNotNull(this->handle, "AssetLoader::end: File is not open");
-  return fseek(this->handle, 0, SEEK_END);
+size_t AssetLoader::getSize() {
+  assertTrue(this->assetArchiveEntry != nullptr, "AssetLoader::getSize: Entry is NULL!");
+  assertTrue(archive_entry_size_is_set(assetArchiveEntry), "assetGetSize: Entry size is not set!");
+  return archive_entry_size(assetArchiveEntry);
 }
 
 size_t AssetLoader::skip(size_t n) {
-  assertTrue(n > 0, "AssetLoader::skip: n must be greater than 0");
-  assertNotNull(this->handle, "AssetLoader::skip: File is not open");
-  return fseek(this->handle, n, SEEK_CUR);
+  assertTrue(n >= 0, "AssetLoader::skip: Byte count must be greater than 0.");
+
+  uint8_t dumpBuffer[ASSET_LOADER_BUFFER_SIZE];
+  size_t skipped = 0;
+  size_t n2, n3;
+  while(n != 0) {
+    n2 = mathMin<size_t>(n, ASSET_LOADER_BUFFER_SIZE);
+    n3 = this->read(dumpBuffer, n2);
+    assertTrue(n3 == n2, "AssetLoader::skip: Failed to skip bytes!");
+    n -= n3;
+  }
+
+  return skipped;
 }
 
-int32_t AssetLoader::rewind() {
-  assertNotNull(this->handle, "AssetLoader::rewind: File is not open");
-  return fseek(this->handle, 0, SEEK_SET);
+void AssetLoader::rewind() {
+  // TODO: See if I can optimize this
+  this->close();
+  this->open();
 }
 
 size_t AssetLoader::getPosition() {
-  assertNotNull(this->handle, "AssetLoader::getPosition: File is not open");
-  return ftell(this->handle);
-}
-
-size_t AssetLoader::loadRaw(uint8_t **buffer) {
-  size_t length, read;
-
-  assertNotNull(buffer, "AssetLoader::loadRaw: buffer must not be null");
-
-  // 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 *>(memoryAllocate(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;
+  assertNotNull(this->assetArchiveFile, "AssetLoader::getPosition: File is not open!");
+  return this->position;
 }
 
 size_t AssetLoader::setPosition(size_t position) {
@@ -98,8 +176,5 @@ size_t AssetLoader::setPosition(size_t position) {
 }
 
 AssetLoader::~AssetLoader() {
-  if(this->handle != nullptr) {
-    this->close();
-    this->handle = nullptr;
-  }
+  if(this->assetArchiveFile != nullptr) this->close();
 }
\ No newline at end of file
diff --git a/src/dawn/asset/AssetLoader.hpp b/src/dawn/asset/AssetLoader.hpp
index c3b5d4b8..6cb820f5 100644
--- a/src/dawn/asset/AssetLoader.hpp
+++ b/src/dawn/asset/AssetLoader.hpp
@@ -4,17 +4,81 @@
 // https://opensource.org/licenses/MIT
 
 #pragma once
+extern "C" {
+  #include <archive.h>
+  #include <archive_entry.h>
+}
 #include "dawnlibs.hpp"
 #include "assert/assert.hpp"
 #include "util/memory.hpp"
 
+#define ASSET_LOADER_BUFFER_SIZE 32768
+
+#if !defined(DAWN_ASSET_LOCATION)
+  #error Asset Archive Location has not been defined.
+#endif
+
+/**
+ * Method invoked by the libarchive internals to read bytes from the archive
+ * file pointer.
+ * 
+ * @param archive Archive requesting the read. 
+ * @param data Data pointer passed to the archive.
+ * @param buffer Pointer to where the buffer pointer should be stored.
+ * @return Count of bytes read.
+ */
+ssize_t assetLoaderArchiveRead(
+  struct archive *archive,
+  void *data,
+  const void **buffer
+);
+
+/**
+ * Method invoked by the libarchive internals to seek the archive file pointer.
+ * 
+ * @param archive Archive requesting the seek.
+ * @param data Data pointer passed to the archive.
+ * @param offset Offset to seek to.
+ * @param whence Whence to seek from.
+ * @return The new offset.
+ */
+int64_t assetLoaderArchiveSeek(
+  struct archive *archive,
+  void *data,
+  int64_t offset,
+  int32_t whence
+);
+
+/**
+ * Method invoked by the libarchive internals to open the archive file pointer.
+ * 
+ * @param archive Archive requesting the open.
+ * @param data Data pointer passed to the archive.
+ * @return 0 if success, otherwise for failure.
+ */
+int32_t assetLoaderArchiveOpen(struct archive *a, void *data);
+
+/**
+ * Method invoked by the libarchive internals to close the archive file pointer.
+ * 
+ * @param archive Archive requesting the close.
+ * @param data Data pointer passed to the archive.
+ * @return 0 if success, otherwise for failure.
+ */
+int32_t assetLoaderArchiveClose(struct archive *a, void *data);
+
 namespace Dawn {
   class AssetLoader {
     private:
       std::string fileName;
-      FILE *handle;
+      struct archive *assetArchive = nullptr;
+      struct archive_entry *assetArchiveEntry = nullptr;
+      size_t position;
 
     public:
+      uint8_t buffer[ASSET_LOADER_BUFFER_SIZE];
+      FILE *assetArchiveFile = nullptr;
+
       /**
        * Create a new asset loader. Asset Loaders can be used to load data from
        * a file in a myriad of ways.
@@ -45,46 +109,39 @@ namespace Dawn {
       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.
+       * Get the size of the asset.
+       * @return The size of the asset in bytes.
        */
-      int32_t end();
+      size_t getSize();
 
       /**
-       * Method to skip n bytes in the buffer
-       * @param n Count of bytes to skip.
-       * @return 0 if successful, otherwise unsuccessful.
+       * Skips the read head forward to a given position.
+       * 
+       * @param n Count of bytes to progress the read head by.
+       * @return Count of bytes progressed.
        */
       size_t skip(size_t n);
 
       /**
-       * Rewinds to the start of the asset buffer.
-       * @return 0 if successful, otherwise unsuccessful.
-       */      
-      int32_t rewind();
-      
+       * Rewind the read head to the beginning of the file.
+       */
+      void rewind();
+
       /**
-       * Retreive the current byte position within the asset that the head is
-       * at.
-       * @return Position (in bytes) that the current seek is at.
+       * Sets the absolute position of the read head within the buffer of the
+       * file.
+       * 
+       * @param absolutePosition Absolute position to set the read head to.
+       */
+      size_t setPosition(size_t absolutePosition);
+
+      /**
+       * Returns the current position of the read head.
+       * 
+       * @return The current read head position.
        */
       size_t getPosition();
 
-      /**
-       * Sets the position of the file read head.
-       * 
-       * @param position Position to set to.
-       * @return How many bytes were skipped / rewound.
-       */
-      size_t setPosition(size_t position);
-
-      /**
-       * 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.
diff --git a/src/dawn/asset/AssetManager.cpp b/src/dawn/asset/AssetManager.cpp
index a2288948..8fc7f750 100644
--- a/src/dawn/asset/AssetManager.cpp
+++ b/src/dawn/asset/AssetManager.cpp
@@ -5,10 +5,6 @@
 
 #include "AssetManager.hpp"
 
-#if !defined(DAWN_ASSET_BUILD_PREFIX)
-  #error Asset Prefix has not been defined.
-#endif
-
 using namespace Dawn;
 
 void AssetManager::init() {
diff --git a/src/dawn/asset/assets/LanguageAsset.cpp b/src/dawn/asset/assets/LanguageAsset.cpp
index e277fb51..3c4ee765 100644
--- a/src/dawn/asset/assets/LanguageAsset.cpp
+++ b/src/dawn/asset/assets/LanguageAsset.cpp
@@ -19,22 +19,20 @@ void LanguageAsset::updateSync() {
 }
 
 void LanguageAsset::updateAsync() {
-  assertTrue(this->state == 0x00, "LanguageAsset::updateAsync: State must be 0x00");
+  assertTrue(
+    this->state == LANGUAGE_ASSET_LOAD_STATE_NOT_LOADING,
+    "LanguageAsset::updateAsync: State must be NOT_LOADING"
+  );
 
   // Open Asset
-  this->state = 0x01;
+  this->state = LANGUAGE_ASSET_LOAD_STATE_OPENING;
   this->loader.open();
 
   // Get Length
-  this->state = 0x02;
-  this->loader.end();
-  this->state = 0x03;
-  size_t len = this->loader.getPosition();
-  this->state = 0x04;
-  this->loader.rewind();
+  size_t len = this->loader.getSize();
 
   // Create buffer
-  this->state = 0x05;
+  this->state =  LANGUAGE_ASSET_LOAD_STATE_CREATING_BUFFER;
   size_t position = 0;
 
   // Loop over CSV
@@ -55,23 +53,22 @@ void LanguageAsset::updateAsync() {
 
     // Prepare for next string.
     position = position + (size_t)(valueEnd - buffer + 1);
-    this->loader.rewind();
-    this->loader.skip(position);
+    this->loader.setPosition(position);
 
     // Store strings.
     std::string key((char *)keyStart);
     this->values[key] = value;
   }
 
-  this->state = 0x06;
-  this->loader.rewind();
-
-  this->state = 0x07;
+  this->state = LANGUAGE_ASSET_LOAD_STATE_READY_TO_READ;
   this->loaded = true;
 }
 
 std::string LanguageAsset::getValue(std::string key) {
-  assertTrue(this->state == 0x07, "LanguageAsset::getValue: State must be 0x07");
+  assertTrue(
+    this->state == LANGUAGE_ASSET_LOAD_STATE_READY_TO_READ,
+    "LanguageAsset::getValue: State must be LANGUAGE_ASSET_LOAD_STATE_READY_TO_READ"
+  );
   assertMapHasKey(this->values, key, "LanguageAsset::getValue: Key does not exist");
   assertTrue(this->values[key].begin >= 0 && this->values[key].length > 0, "LanguageAsset::getValue: Value is invalid");
 
diff --git a/src/dawn/asset/assets/LanguageAsset.hpp b/src/dawn/asset/assets/LanguageAsset.hpp
index 7fda9a3a..72aaaef5 100644
--- a/src/dawn/asset/assets/LanguageAsset.hpp
+++ b/src/dawn/asset/assets/LanguageAsset.hpp
@@ -13,10 +13,17 @@ namespace Dawn {
     size_t length;
   };
 
+  enum LangageAssetLoadState {
+    LANGUAGE_ASSET_LOAD_STATE_NOT_LOADING,
+    LANGUAGE_ASSET_LOAD_STATE_OPENING,
+    LANGUAGE_ASSET_LOAD_STATE_CREATING_BUFFER,
+    LANGUAGE_ASSET_LOAD_STATE_READY_TO_READ
+  };
+
   class LanguageAsset : public Asset {
     protected:
       AssetLoader loader;
-      uint8_t state = 0x00;
+      enum LangageAssetLoadState state = LANGUAGE_ASSET_LOAD_STATE_NOT_LOADING;
       std::map<std::string, struct AssetLanguageValue> values;
       uint8_t buffer[1024];
 
diff --git a/src/dawn/asset/assets/TextureAsset.cpp b/src/dawn/asset/assets/TextureAsset.cpp
index 79180e7f..69c17a40 100644
--- a/src/dawn/asset/assets/TextureAsset.cpp
+++ b/src/dawn/asset/assets/TextureAsset.cpp
@@ -37,7 +37,11 @@ void TextureAsset::updateSync() {
 void TextureAsset::updateAsync() {
   if(this->state != 0x00) return;
   this->state = 0x01;
-  this->loader.loadRaw(&this->buffer);
+  std::cout << "Update texture tool" << std::endl;
+  this->loader.open();
+  this->buffer = (uint8_t*)memoryAllocate(this->loader.getSize());
+  this->loader.read(this->buffer, this->loader.getSize());
+  this->loader.close();
   this->state = 0x02;
 
   // Parse header data.
diff --git a/src/dawn/asset/assets/TilesetAsset.cpp b/src/dawn/asset/assets/TilesetAsset.cpp
index ad96c160..3c475f81 100644
--- a/src/dawn/asset/assets/TilesetAsset.cpp
+++ b/src/dawn/asset/assets/TilesetAsset.cpp
@@ -19,11 +19,13 @@ void TilesetAsset::updateSync() {
 }
 
 void TilesetAsset::updateAsync() {
-  uint8_t *buffer;
   assertTrue(this->state == 0x00, "TilesetAsset::updateAsync: Initial state should be 0x00");
 
   this->state = 0x01;
-  this->loader.loadRaw(&buffer);
+  this->loader.open();
+  uint8_t *buffer = (uint8_t*)memoryAllocate(this->loader.getSize());
+  this->loader.read(buffer, this->loader.getSize());
+  this->loader.close();
   this->state = 0x02;
 
   char *strCurrent = (char *)buffer;
diff --git a/src/dawn/asset/assets/TrueTypeAsset.cpp b/src/dawn/asset/assets/TrueTypeAsset.cpp
index 32194c09..65261230 100644
--- a/src/dawn/asset/assets/TrueTypeAsset.cpp
+++ b/src/dawn/asset/assets/TrueTypeAsset.cpp
@@ -103,7 +103,6 @@ void TrueTypeAsset::updateAsync() {
     struct TrueTypeAssetStyle style;
 
     // Buffer
-    this->loader.rewind();
     this->loader.setPosition(styleListBegin);
     read = this->loader.read(buffer, 32);
     assertTrue(read == 32, "TrueTypeAsset::updateAsync: Could not read variant");
@@ -116,7 +115,6 @@ void TrueTypeAsset::updateAsync() {
     styleListBegin += i + 1;
     
     // Buffer
-    this->loader.rewind();
     this->loader.setPosition(styleListBegin);
     read = this->loader.read(buffer, 32);
     assertTrue(read == 32, "TrueTypeAsset::updateAsync: Could not read variant style");
diff --git a/src/dawn/display/RenderPipeline.cpp b/src/dawn/display/RenderPipeline.cpp
index 8e156588..7f33a1c2 100644
--- a/src/dawn/display/RenderPipeline.cpp
+++ b/src/dawn/display/RenderPipeline.cpp
@@ -143,12 +143,21 @@ void RenderPipeline::renderSceneCamera(Scene *scene, Camera *camera) {
     }
   #endif
 
+  // Inject index into each item
+  itPassItem = shaderPassItems.begin();
+  while(itPassItem != shaderPassItems.end()) {
+    itPassItem->index = itPassItem;
+    ++itPassItem;
+  }
+
   // Now we've queued everything, let's sort the rendering queue by the priority
   std::sort(
     shaderPassItems.begin(),
     shaderPassItems.end(),
     [](struct ShaderPassItem &a, struct ShaderPassItem  &b) {
       if(a.priority == b.priority) {
+        // Compare indexes if w is same.
+        if(a.w == b.w) return a.index < b.index;
         return a.w < b.w;
       }
       return a.priority < b.priority;
diff --git a/src/dawn/display/shader/ShaderPass.hpp b/src/dawn/display/shader/ShaderPass.hpp
index 4943ffc1..6ff85a12 100644
--- a/src/dawn/display/shader/ShaderPass.hpp
+++ b/src/dawn/display/shader/ShaderPass.hpp
@@ -8,9 +8,12 @@
 #include "display/mesh/Mesh.hpp"
 
 namespace Dawn {
+  struct ShaderPassItem;
+
   struct ShaderPassItem {
     Shader *shader = nullptr;
     int32_t priority = 0;
+    std::vector<struct ShaderPassItem>::iterator index;
 
     Mesh *mesh;
     int32_t start = 0;
diff --git a/src/dawn/scene/components/ui/UIComponent.cpp b/src/dawn/scene/components/ui/UIComponent.cpp
index 5c0e74be..b22411a8 100644
--- a/src/dawn/scene/components/ui/UIComponent.cpp
+++ b/src/dawn/scene/components/ui/UIComponent.cpp
@@ -71,17 +71,6 @@ void UIComponent::updateAlignment() {
       glm::vec2(align[0], align[2])
     );
   } else {
-    UIComponent::calculateDimensions(
-      this->alignY,
-      this->alignUnitTop,
-      this->alignUnitBottom,
-      &translate.y,
-      &this->height,
-      parentInnerHeight,
-      this->getContentHeight(),
-      this->width,
-      glm::vec2(align[1], align[3])
-    );
     UIComponent::calculateDimensions(
       this->alignX,
       this->alignUnitLeft,
@@ -93,6 +82,17 @@ void UIComponent::updateAlignment() {
       this->height,
       glm::vec2(align[0], align[2])
     );
+    UIComponent::calculateDimensions(
+      this->alignY,
+      this->alignUnitTop,
+      this->alignUnitBottom,
+      &translate.y,
+      &this->height,
+      parentInnerHeight,
+      this->getContentHeight(),
+      this->width,
+      glm::vec2(align[1], align[3])
+    );
   }
 
 
@@ -104,6 +104,38 @@ void UIComponent::updateAlignment() {
   this->eventAlignmentUpdated.invoke();
 }
 
+float_t UIComponent::getWidthFromAlignment() {
+  switch(this->alignX) {
+    case UI_COMPONENT_ALIGN_STRETCH:
+    case UI_COMPONENT_ALIGN_START:
+    case UI_COMPONENT_ALIGN_MIDDLE:
+      return alignment._realValue[2];
+
+    case UI_COMPONENT_ALIGN_END:
+      return alignment._realValue[0];
+
+    default:
+      assertUnreachable("UIComponent::getWidthFromAlignment: Unknown alignment");
+      return -1;
+  }
+}
+
+float_t UIComponent::getHeightFromAlignment() {
+  switch(this->alignY) {
+    case UI_COMPONENT_ALIGN_STRETCH:
+    case UI_COMPONENT_ALIGN_START:
+    case UI_COMPONENT_ALIGN_MIDDLE:
+      return alignment._realValue[3];
+
+    case UI_COMPONENT_ALIGN_END:
+      return alignment._realValue[1];
+
+    default:
+      assertUnreachable("UIComponent::getWidthFromAlignment: Unknown alignment");
+      return -1;
+  }
+}
+
 float_t UIComponent::calculateAlignmentValue(
   float_t alignmentValue,
   float_t parentSize,
diff --git a/src/dawn/scene/components/ui/UIComponent.hpp b/src/dawn/scene/components/ui/UIComponent.hpp
index e43fe6aa..37c87c6f 100644
--- a/src/dawn/scene/components/ui/UIComponent.hpp
+++ b/src/dawn/scene/components/ui/UIComponent.hpp
@@ -44,6 +44,24 @@ namespace Dawn {
        * Internal method to update the alignment of this item.
        */
       virtual void updateAlignment();
+
+      /**
+       * Helper function used for UI components that intend to use whatever the
+       * dimensions that are set within the alignment parameter are for their
+       * width.
+       * 
+       * @return The width as defined in the alignment. 
+       */
+      float_t getWidthFromAlignment();
+
+      /**
+       * Helper function used for UI components that intend to use whatever the
+       * dimensions that are set within the alignment parameter are for their
+       * height.
+       * 
+       * @return The height as defined in the alignment. 
+       */
+      float_t getHeightFromAlignment();
       
     public:
       StateProperty<bool_t> alignmentNeedsUpdating;
diff --git a/src/dawn/scene/components/ui/UIEmpty.cpp b/src/dawn/scene/components/ui/UIEmpty.cpp
index 4342c4d7..875e7a67 100644
--- a/src/dawn/scene/components/ui/UIEmpty.cpp
+++ b/src/dawn/scene/components/ui/UIEmpty.cpp
@@ -10,10 +10,10 @@ using namespace Dawn;
 UIEmpty::UIEmpty(SceneItem *item) : UIComponent(item) { }
 
 float_t UIEmpty::getContentWidth() {
-  return this->getWidth();
+  return this->getWidthFromAlignment();
 }
 float_t UIEmpty::getContentHeight() {
-  return this->getHeight();
+  return this->getHeightFromAlignment();
 }
 float_t UIEmpty::getChildOffsetX() { return 0.0f; }
 float_t UIEmpty::getChildOffsetY() { return 0.0f; }
\ No newline at end of file
diff --git a/src/dawn/scene/components/ui/text/UILabel.cpp b/src/dawn/scene/components/ui/text/UILabel.cpp
index 6c211bcc..6f9107ab 100644
--- a/src/dawn/scene/components/ui/text/UILabel.cpp
+++ b/src/dawn/scene/components/ui/text/UILabel.cpp
@@ -10,7 +10,8 @@ using namespace Dawn;
 
 UILabel::UILabel(SceneItem *item) :
   UIComponentRenderable(item),
-  lineHeight(1.0f)
+  lineHeight(1.0f),
+  textAlign(UI_LABEL_TEXT_ALIGN_LEFT)
 {
 
 }
diff --git a/src/dawn/scene/components/ui/text/UILabel.hpp b/src/dawn/scene/components/ui/text/UILabel.hpp
index 1db2a1dc..0e881fcc 100644
--- a/src/dawn/scene/components/ui/text/UILabel.hpp
+++ b/src/dawn/scene/components/ui/text/UILabel.hpp
@@ -43,6 +43,13 @@ namespace Dawn {
     UI_LABEL_VERTICAL_ALIGN_BOTTOM
   };
 
+  enum UILabelTextAlign {
+    UI_LABEL_TEXT_ALIGN_LEFT,
+    UI_LABEL_TEXT_ALIGN_CENTER,
+    UI_LABEL_TEXT_ALIGN_RIGHT
+    // TODO: Add justify
+  };
+
   class UILabel : public UIComponentRenderable {
     private:
       Mesh mesh;
@@ -68,6 +75,9 @@ namespace Dawn {
       // @optional
       StateProperty<float_t> lineHeight;
 
+      // @optional
+      StateProperty<enum UILabelTextAlign> textAlign;
+
       UILabel(SceneItem *item);
 
       void onStart() override;
diff --git a/src/dawnliminal/CMakeLists.txt b/src/dawnliminal/CMakeLists.txt
index 704d7567..ef104a51 100644
--- a/src/dawnliminal/CMakeLists.txt
+++ b/src/dawnliminal/CMakeLists.txt
@@ -17,5 +17,4 @@ add_subdirectory(game)
 add_subdirectory(save)
 
 # Assets
-set(LIMINAL_ASSETS_DIR )
 include("${DAWN_ASSETS_SOURCE_DIR}/games/liminal/CMakeLists.txt")
\ No newline at end of file
diff --git a/src/dawnliminal/game/LiminalGame.cpp b/src/dawnliminal/game/LiminalGame.cpp
index 01b01942..eaaa4856 100644
--- a/src/dawnliminal/game/LiminalGame.cpp
+++ b/src/dawnliminal/game/LiminalGame.cpp
@@ -5,11 +5,13 @@
 
 #include "game/DawnGame.hpp"
 #include "vnscenes/SceneInitial.hpp"
+#include "scenes/SceneMainMenu.hpp"
 #include "scenes/HelloWorldScene.hpp"
 
 using namespace Dawn;
 
 Scene * Dawn::dawnGameGetInitialScene(DawnGame *game) {
-  return new SceneInitial(game);
+  // return new SceneInitial(game);
   // return new HelloWorldScene(game);
+  return new SceneMainMenu(game);
 }
\ No newline at end of file
diff --git a/src/dawnlinux64/CMakeLists.txt b/src/dawnlinux64/CMakeLists.txt
index 2ae0f125..3f339a6a 100644
--- a/src/dawnlinux64/CMakeLists.txt
+++ b/src/dawnlinux64/CMakeLists.txt
@@ -18,7 +18,7 @@ target_include_directories(${DAWN_TARGET_NAME}
 # Platform variables
 target_compile_definitions(${DAWN_TARGET_NAME}
   PUBLIC
-    DAWN_ASSET_BUILD_PREFIX="../../assets/"
+    DAWN_ASSET_LOCATION="../../assets.tar"
 )
 
 # Subdirs
diff --git a/src/dawnshared/display/Color.cpp b/src/dawnshared/display/Color.cpp
index 64304b0b..36b58d5f 100644
--- a/src/dawnshared/display/Color.cpp
+++ b/src/dawnshared/display/Color.cpp
@@ -38,6 +38,24 @@ struct Color Color::fromString(std::string str) {
   }
 
   // Hex code?
+  if(lower[0] == '#') {
+    // Remove the hash
+    lower = lower.substr(1);
+
+    // Convert to RGB
+    if(lower.length() == 3) {
+      // Convert to 6 digit hex
+      lower = lower[0] + lower[0] + lower[1] + lower[1] + lower[2] + lower[2];
+    }
+
+    // Convert to RGB
+    return {
+      (float_t)std::stoi(lower.substr(0, 2), nullptr, 16) / 255.0f,
+      (float_t)std::stoi(lower.substr(2, 2), nullptr, 16) / 255.0f,
+      (float_t)std::stoi(lower.substr(4, 2), nullptr, 16) / 255.0f,
+      1.0f
+    };
+  }
 
   // Split by comma 
   auto splitByComma = stringSplit(str, ",");
diff --git a/src/dawnshared/util/parser/TypeParsers.hpp b/src/dawnshared/util/parser/TypeParsers.hpp
index c518ef55..122675dc 100644
--- a/src/dawnshared/util/parser/TypeParsers.hpp
+++ b/src/dawnshared/util/parser/TypeParsers.hpp
@@ -139,6 +139,7 @@ namespace Dawn {
     if(v.find("middle") != std::string::npos) return "UI_COMPONENT_ALIGN_MIDDLE";
     if(v.find("end") != std::string::npos) return "UI_COMPONENT_ALIGN_END";
     *error = "Invalid UIComponentAlign value: " + v;
+    return "";
   }
 
   static inline std::string uiComponentAlignUnitParser(std::string v, std::string *error) {
@@ -147,6 +148,16 @@ namespace Dawn {
     if(v.find("percent") != std::string::npos) return "UI_COMPONENT_ALIGN_UNIT_PERCENT";
     if(v.find("ratio") != std::string::npos) return "UI_COMPONENT_ALIGN_UNIT_RATIO";
     *error = "Invalid UIComponentAlignUnit value: " + v;
+    return "";
+  }
+
+  static inline std::string uiLabelTextAlignParser(std::string v, std::string *error) {
+    v = stringToLowercase(v);
+    if(v.find("left") != std::string::npos) return "UI_LABEL_TEXT_ALIGN_LEFT";
+    if(v.find("center") != std::string::npos) return "UI_LABEL_TEXT_ALIGN_CENTER";
+    if(v.find("right") != std::string::npos) return "UI_LABEL_TEXT_ALIGN_RIGHT";
+    *error = "Invalid UILabelTextAlign value: " + v;
+    return "";
   }
 
   static inline std::function<std::string(std::string, std::string*)> parserFromTypeName(std::string type) {
@@ -176,6 +187,8 @@ namespace Dawn {
         parser = uiComponentAlignParser;
       } else if(type.ends_with("UIComponentAlignUnit")) {
         parser = uiComponentAlignUnitParser;
+      } else if(type.ends_with("UILabelTextAlign")) {
+        parser = uiLabelTextAlignParser;
       } else {
         parser = rawParser;
       }
diff --git a/src/dawntools/CMakeLists.txt b/src/dawntools/CMakeLists.txt
index 106d19e3..7c5af684 100644
--- a/src/dawntools/CMakeLists.txt
+++ b/src/dawntools/CMakeLists.txt
@@ -22,6 +22,7 @@ include(util/parser/CMakeLists.txt)
 include(util/generator/CMakeLists.txt)
 
 # Tools
+add_subdirectory(assetstool)
 add_subdirectory(prefabtool)
 add_subdirectory(scenetool)
 add_subdirectory(texturetool)
diff --git a/src/dawntools/assetstool/CMakeLists.txt b/src/dawntools/assetstool/CMakeLists.txt
new file mode 100644
index 00000000..2cd1a33f
--- /dev/null
+++ b/src/dawntools/assetstool/CMakeLists.txt
@@ -0,0 +1,13 @@
+# Copyright (c) 2023 Dominic Masters
+# 
+# This software is released under the MIT License.
+# https://opensource.org/licenses/MIT
+
+add_custom_target(dawnassets
+  COMMAND ${DAWN_TOOLS_DIR}/assetstool/assetstool.py
+    --input=${DAWN_ASSETS_BUILD_DIR}
+    --output=${DAWN_BUILD_DIR}/assets.tar
+  COMMENT "Bundling assets..."
+    USES_TERMINAL
+    DEPENDS ${DAWN_ASSETS}
+)
\ No newline at end of file
diff --git a/src/dawntools/assetstool/assetstool.py b/src/dawntools/assetstool/assetstool.py
new file mode 100755
index 00000000..9db833e2
--- /dev/null
+++ b/src/dawntools/assetstool/assetstool.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+# Copyright (c) 2023 Dominic Masters
+# 
+# This software is released under the MIT License.
+# https://opensource.org/licenses/MIT
+
+import os
+import tarfile
+import argparse
+
+# Args
+parser = argparse.ArgumentParser(description='Bundles all assets into the internal archive format.')
+parser.add_argument('-i', '--input');
+parser.add_argument('-o', '--output');
+args = parser.parse_args()
+
+# Ensure the directory for the output path exists
+if not os.path.exists(os.path.dirname(args.output)):
+  os.makedirs(os.path.dirname(args.output))
+
+# Create a ZIP archive and add the specified directory
+# archive = tarfile.open(args.output, 'w:bz2') # BZ2 Compression
+
+# Does the archive already exist?
+filesInArchive = []
+
+if os.path.exists(args.output):
+  # Yes, open it
+  archive = tarfile.open(args.output, 'r:')
+
+  # Get all the files in the archive
+  for member in archive.getmembers():
+    filesInArchive.append(member.name)
+
+  archive.close()
+
+  # Open archive for appending.
+  archive = tarfile.open(args.output, 'a:')
+else:
+  # No, create it
+  archive = tarfile.open(args.output, 'w:')
+
+# Add all files in the input directory
+for foldername, subfolders, filenames in os.walk(args.input):
+  for filename in filenames:
+
+    # Is the file already in the archive?
+    absolute_path = os.path.join(foldername, filename)
+    relative_path = os.path.relpath(absolute_path, args.input)
+
+    if relative_path in filesInArchive:
+      # Yes, skip it
+      continue
+
+    # No, add it
+    print(f"Archiving asset {filename}...")
+    archive.add(absolute_path, arcname=relative_path)
+
+# Close the archive
+archive.close()
\ No newline at end of file
diff --git a/src/dawntools/texturetool/CMakeLists.txt b/src/dawntools/texturetool/CMakeLists.txt
index 8dc5874b..530a1924 100644
--- a/src/dawntools/texturetool/CMakeLists.txt
+++ b/src/dawntools/texturetool/CMakeLists.txt
@@ -87,5 +87,5 @@ function(tool_texture target)
     COMMENT "Generating texture ${target} from ${FILE}"
     DEPENDS ${DEPS}
   )
-  add_dependencies(${DAWN_TARGET_NAME} ${target})
+  add_dependencies(dawnassets ${target})
 endfunction()
\ No newline at end of file
diff --git a/src/dawntools/truetypetool/CMakeLists.txt b/src/dawntools/truetypetool/CMakeLists.txt
index 4a3bda50..e5ddc74c 100644
--- a/src/dawntools/truetypetool/CMakeLists.txt
+++ b/src/dawntools/truetypetool/CMakeLists.txt
@@ -63,5 +63,5 @@ function(tool_truetype target)
     COMMENT "Generating truetype"
     DEPENDS ${DEPS}
   )
-  add_dependencies(${DAWN_TARGET_NAME} ${target})
+  add_dependencies(dawnassets ${target})
 endfunction()
\ No newline at end of file
diff --git a/src/dawntools/util/generator/SceneItemGenerator.cpp b/src/dawntools/util/generator/SceneItemGenerator.cpp
index 3f7154f6..c857bc7a 100644
--- a/src/dawntools/util/generator/SceneItemGenerator.cpp
+++ b/src/dawntools/util/generator/SceneItemGenerator.cpp
@@ -139,6 +139,14 @@ void SceneItemGenerator::generate(
     line(initBody, name + "->uiItem->alignment = " + item->alignment + ";", "");
   }
 
+  if(item->alignX.size() > 0) {
+    line(initBody, name + "->uiItem->alignX = " + item->alignX + ";", "");
+  }
+
+  if(item->alignY.size() > 0) {
+    line(initBody, name + "->uiItem->alignY = " + item->alignY + ";", "");
+  }
+
   if(item->menuX.size() > 0) {
     line(initBody, name + "->menuItem->menuX = " + item->menuX + ";", "");
   }
diff --git a/src/dawntools/util/parser/SceneItemParser.cpp b/src/dawntools/util/parser/SceneItemParser.cpp
index b995d20d..0dd77cf0 100644
--- a/src/dawntools/util/parser/SceneItemParser.cpp
+++ b/src/dawntools/util/parser/SceneItemParser.cpp
@@ -19,6 +19,8 @@ std::map<std::string, std::string> SceneItemParser::getOptionalAttributes() {
     { "scale", "" },
     { "prefab", "" },
     { "alignment", "" },
+    { "alignX", "" },
+    { "aignY", "" },
     { "menuX", "" },
     { "menuY", "" }
   };
@@ -42,6 +44,16 @@ int32_t SceneItemParser::onParse(
     if(error->size() > 0) return 1;
   }
 
+  if(values["alignX"].size() > 0) {
+    out->alignX = uiComponentAlignParser(values["alignX"], error);
+    if(error->size() > 0) return 1;
+  }
+
+  if(values["alignY"].size() > 0) {
+    out->alignY = uiComponentAlignParser(values["alignY"], error);
+    if(error->size() > 0) return 1;
+  }
+
   if(values["menuX"].size() > 0) {
     out->menuX = intParser(values["menuX"], error);
     if(error->size() > 0) return 1;
diff --git a/src/dawntools/util/parser/SceneItemParser.hpp b/src/dawntools/util/parser/SceneItemParser.hpp
index c6da961f..534796a0 100644
--- a/src/dawntools/util/parser/SceneItemParser.hpp
+++ b/src/dawntools/util/parser/SceneItemParser.hpp
@@ -25,6 +25,8 @@ namespace Dawn {
     std::string ref;
     std::string position;
     std::string alignment;
+    std::string alignX;
+    std::string alignY;
     std::string lookAtPosition;
     std::string lookAtTarget;
     std::string scale;