diff --git a/.gitignore b/.gitignore
index 0447b8b..ed7a539 100644
--- a/.gitignore
+++ b/.gitignore
@@ -114,3 +114,108 @@ dmypy.json
# Pyre type checker
.pyre/
+
+### PyCharm ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### PyCharm Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
+
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
+
+# Sonarlint plugin
+# https://plugins.jetbrains.com/plugin/7973-sonarlint
+.idea/**/sonarlint/
+
+# SonarQube Plugin
+# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
+.idea/**/sonarIssues.xml
+
+# Markdown Navigator plugin
+# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
+.idea/**/markdown-navigator.xml
+.idea/**/markdown-navigator-enh.xml
+.idea/**/markdown-navigator/
+
+# Cache file creation bug
+# See https://youtrack.jetbrains.com/issue/JBR-2257
+.idea/$CACHE_FILE$
+
+# CodeStream plugin
+# https://plugins.jetbrains.com/plugin/12206-codestream
+.idea/codestream.xml
+
+/output
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..5c98b42
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,2 @@
+# Default ignored files
+/workspace.xml
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/live-a-png.iml b/.idea/live-a-png.iml
new file mode 100644
index 0000000..74d515a
--- /dev/null
+++ b/.idea/live-a-png.iml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..6522620
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..36c862f
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index a20e2b8..cd95621 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,7 @@ Edit those files to populate the correct values.
## Run the code
+Edit `main.py` to set the directory of the extracted asset bundles.
Run `main.py` in the terminal to start the application.
## Development: Making changes
diff --git a/image_assembler.py b/image_assembler.py
new file mode 100644
index 0000000..fd58245
--- /dev/null
+++ b/image_assembler.py
@@ -0,0 +1,44 @@
+import math
+from PIL import Image
+
+
+class ImageAssembler:
+ def __init__(self, metadata, atlas: Image, cell_size: int, padding: int):
+ self.atlas = atlas
+ self.atlas_width_cells = int(atlas.size[0] / cell_size)
+ self.atlas_height_cells = int(atlas.size[1] / cell_size)
+
+ self.cell_size = cell_size
+ self.padding = padding
+ self.metadata = metadata
+
+ self.output = Image.new('RGBA', (metadata['width'], metadata['height']), (255, 255, 255, 0))
+
+ # TODO double check this logic
+ self.output_width_cells = math.ceil(float(metadata['width']) / cell_size)
+
+ def atlas_get_cell(self, cell_no: int):
+ cell_x_pos = cell_no % self.atlas_width_cells
+ cell_y_pos = self.atlas_height_cells - math.floor(cell_no / self.atlas_width_cells) - 1 # From top
+ return self.atlas.crop((
+ cell_x_pos * self.cell_size,
+ cell_y_pos * self.cell_size,
+ (cell_x_pos + 1) * self.cell_size,
+ (cell_y_pos + 1) * self.cell_size
+ ))
+
+ def paint(self):
+ for i in range(len(self.metadata['cellIndexList'])):
+ source_cell_number = self.metadata['cellIndexList'][i]
+ source_cell = self.atlas_get_cell(source_cell_number)
+
+ output_x_pos = i % self.output_width_cells
+ output_y_pos = math.floor(i / self.output_width_cells) # From bottom
+ self.output.paste(source_cell, (
+ output_x_pos * (self.cell_size - (2*self.padding)),
+ self.output.size[1] - self.cell_size - (output_y_pos * (self.cell_size - (2*self.padding)))
+ ))
+
+ def get_output(self):
+ return self.output
+
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..470c0a7
--- /dev/null
+++ b/main.py
@@ -0,0 +1,31 @@
+import json
+from os import PathLike
+from pathlib import Path
+from PIL import Image
+
+from image_assembler import ImageAssembler
+
+json_dir = Path('C:\\extracts\\MonoBehaviour')
+atlas_dir = Path('C:\\extracts\\Texture2D')
+output_dir = Path('./output')
+
+
+def assemble_json(json_file: PathLike):
+ with open(json_file) as f:
+ metadata = json.load(f)
+
+ for image in metadata['textureDataList']:
+ # TODO try-catch Image.open
+ atlas = Image.open(atlas_dir / (image['atlasName'] + '.png'))
+ assembler = ImageAssembler(image, atlas, metadata['cellSize'], metadata['padding'])
+ assembler.paint()
+ output = assembler.get_output()
+
+ output.save(output_dir / (image['name'] + '.png'), 'PNG')
+
+
+if __name__ == '__main__':
+ for item in json_dir.iterdir():
+ if item.is_file() and (str(item)).endswith('.json'):
+ print(item)
+ assemble_json(item)
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..334e961
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1 @@
+Pillow==7.2.0