Victoria
Victoria is the ‘Whatever, I do What I want` .unitypackage file importer. For too long have we been forced to bow to the folder structures as imposed upon us by package authors. With Victoria you can do whatever you want. Choose what to import, and where with a simple drag and drop system. Victoria also allows for previewing files and search for specifics before importing to make absolutely sure its whatever you want.
I’ve had quite some fun building this thing. Lets dig in!
Rationale
While working on some unannounced personal project (hopefully soon released, right…), I needed to get meta-data from `.unitypackage` files. I knew already that `.unitypackage` files are just some glorified GZIP based archives. The packages have a specific layout, which we will describe later, and thus some parsing was required. The meta-data I needed to know was the size and number of files.
After having written a simple extractor to get the meta-data I needed, I got sucked in and started some true yak shaving and explore how to improve the experience of importing `.unitypackage` files. If you don’t know what yak shaving is: “Yak shaving is a term in software development and general productivity describing the phenomenon where completing a simple task requires a seemingly endless chain of smaller, often unrelated prerequisite tasks. It refers to engaging in meaningless or tedious activities that have no obvious relationship to the primary goal but are perceived as necessary to troubleshoot or enable progress on a larger problem.“ The term officially comes from the TV cartoon “Ren & Stimpy”. Yeah, nostalgia!
But to get back to my little project here: I’ve always been annoyed by the way importing a unitypackage works be default. Every single time I need to import some random package from the AssetStore for example. I’m forced to import the assets in the location the author encoded in the package structure. When done importing, I move the assets to the location I want and subsequently I need to run a `git status` command to check whether I did not forget anything. So, yes, that sucks.
Once I had the basics for reading package contents going the yak shaving began and I started to work on a fully fledged importer. I learned quite a lot from writing this thing, specifically how .unitypackages are structured, some edge cases in the Unity UI-Toolkit event system, and generally refreshed my knowledge a bit working with UI-Toolkit. Working with UI-Toolkit is just such a nice refresher over working with the UGUI stack. Just really great.
Oh, and the name of this silly project comes from an oldshcool meme originating from this video: https://www.youtube.com/watch?v=IkUf6q3cyfE Just a true, internet classic, haha… Later, South Park also made a fun episode about it starring Cartman, of course. Check it out here: https://www.youtube.com/watch?v=IpF9O0R873I
So, with Victoria you can be a rebel and import `.unitypackage` source files anywhere you want!
DevLog
So this all started when I was working on a different project. I wont go into the other project just yet since it deserves its own blog. Suffice to say that for some unexplainable reason I needed meta-data about `.unitypackage` file contents like the number of files and the exported size.
I already knew that `unitypackage` files are just gzipped files and some of my earlier endeavors proved that the file structure is rather odd. However, this time I wanted to dig in a bit deeper to be able to get the meta-data I needed. I searched github for some existing unitypackage extractor libraries and stumbled upon this thing: https://github.com/gered/extractunitypackage.
Its a simply python library to extract packages and seemed simple enough to rewrite in C#. I mean, its only a single file, how hard could it be… So, I did what any good software engineer would do nowadays: Ask AI to rewrite this bad boy from Python to C# and… it totally failed! Haha. I asked Copilot to rewrite it since well it was free and I did not have a Claude subscription at the time. Maybe that was the mistake. But after a few attempts the code still did not work and I had to, as Uncle Bob would say: ‘fiddle it into working’. Once I had everything working I started to explore how to create a custom importer.
But before I talk about writing the importer lets quickly discuss the structure of `.unitypackage` files: A .unitypackage file is a gzipped tar archive (.tar.gz under the hood). When extracted, it contains a flat list of directories, one per asset, each named after the asset’s GUID:
package.unitypackage (gzip’d tar)
● ├── {guid}/
│ ├── asset ← the raw file content (binary or text)
│ ├── asset.meta ← Unity .meta file (GUID, importer settings)
│ ├── pathname ← plain text file with the original project path
│ └── preview.png ← optional thumbnail (used in the import dialog)
├── {guid}/
│ ├── asset
│ ├── asset.meta
│ └── pathname
└── …
Key Points
- No folder hierarchy in the archive itself, the original path is encoded in the pathname file, not the directory structure.
- asset may be absent for folder entries (Unity folders are just .meta + pathname with no content).
- asset.meta is the Unity meta file as it existed in the source project, carrying the GUID and all importer settings (texture type, compression, etc.).
- preview.png is Unity-generated; not all assets have one (scripts and folders typically don’t).
As you can see, once you understand the structure of a `.unitypackage` an algorithm to extract a package is pretty straightforward. It’s just a single loop which gathers the necessary files per directory. The extractor in Victoria is just a single while loop which appends data to a class each cycle.
With that our of the way; let’s get back to the importer. The first obvious step to do was to create a UI representation of the parsed data. Unity3D has some TreeView (https://docs.unity3d.com/6000.0/Documentation/ScriptReference/UIElements.TreeView.html) which I gave a try but in the end decided to move off of due to lack of control over all the nodes. Since I had some specific requirements regarding drag and dropping I was constantly fighting the Unity TreeView.
If there is one thing that I have learned over my software engineering career is to never, ever fight your framework because its the root cause of many bugs, tech debt and messy code.
And thus I needed to roll my own tree structure. Luckily, a connected, acyclic undirected graph is a well understood data-structure and one I’ve written many times over in my career. Thus I embarked on the path to write me a little folder-file tree renderer.
Writing this thing was not particularly difficult yet the rendering and event-handling could be quite tricky. For example; I wanted to implement focus logic in the tree to allow scrolling with the arrow keys on the keyboard (or, whenever selecting a search result, the node would be in the middle of the scroll-view), but I couldn’t not force the scroll-offset of the scroll-view immediately, I needed to use the `<VisualElement>.schedule` function to execute that logic 100ms later to allow for proper redrawing. And well, it feels rather a hack than an actual fix. But it does work.
I then started working on the previewer. Since Unity has specific files in the .unitypackage files for previewing I though I should support that too. I think I went even a step further by adding preview of text and audio files. With Victoria you can read any text file or play any audio before importing. The audio preview is particularly hacky since it more or less just extracts the audio file and writes it to disk. Then I simply use Unity’s www requests to play them. I figured this was the easiest way since I would not have to deal with any codecs. :hackerman:
Once I had the tree-view and preview rendering working nicely, including the key-binds for navigation I started designing an import flow. First I was thinking to replicate what Unity shows to you by default. A single tree-view with checkboxes for the assets you want imported. I figured to implement the same yet allow for the target root directory to be changed.
But that didn’t sit well and at some point it clicked and I came up with the idea of representing the target folder(s) as another tree structure. And, then allow the user to simply drag and drop de files into the target folder. I still included a checkbox to allow for fine-grained filtering.
And thus I smacked in a splitview and render another tree-view of the Unity project on the right side. And now, my carefully curated events did not work properly anymore. Another ‘big’ problem was that I wanted to reuse logic from my previous tree rendering for the destination tree but it created a mess since the user would be able to drag the destination folders around too. That didn’t work… The destination file-tree in the unity project is fixed.
So I needed to create a few new abstractions and shuffle some logic around to properly implement the behaviour I wanted. That proved to be a bit more work than I anticipated but in the end it turned out pretty ok.
After the rendering of both views was done and I could drag and dorp files and folders around from the import to the destination view I needed to write the actual import. I created the concept of an ‘import manifest’ to track which files and folders were dropped on which parent in the destination tree. Again, pretty straightforward except that the user can create a custom import tree under the destination tree e.g. drop one import folder under a destination folder and then yet another import folder under the previously imported folder. This creates a somewhat recursive import which was the only difficult edge-case to look out for.
With the importing done I started to fine-tune the logic and architecture a bit in order to make sure the code was testable. I have to admit; this is the first personal project in a long time where I did not use a TDD approach. And I dearly noticed… In the end I cleaned up the code enough and forced my AI coding tool to write me some tests. It did a fairly good job. At least, good enough for me to trust my own production code. But, I have to say, I will not go at it without TDD again. Especially when I know what software to write.
After instructing Claude which tests to write and going back and forth a bit I started implementing a runtime import feature. Because? Why not!? The idea was that yes, you can import `.unitypackages` at run-time. But Victoria is limited to importing only. Once files are imported, you’re on your own. You will need to implement or add custom libraries to handle importing of the files. For some files Unity already allows importing at run-time e.g. regular media like textures, audio and video. But for other more bespoke files like 3D-Models, Skyboxes or animation clips and whatever else, you need to write a custom importer. Or, just use AssetBundles… 😛
I asked Claude for its implementation plan for this little feature and it created a really shitty plan. It suggested to create this MonoBehaviour dependency nightmare for the runtime importer which I turned down very hard. As you can observe from the current implementation, most of the code unity agnostic and only relies on the UIToolkit being available for the rendering. The user can just Add the VictoriaElement to an existing VisualElement and that’s it. No need for MonoBehaviour nonsense… But its rather expected that Claude suggested this spaghetti of MonoBehaviours since it will be most of its training data related to Unity. Sad but true.
I ended up implementing the feature myself and again instructed Claude to write tests. This went fairly smooth, except for the fact that runtime UI-Toolkit UI does not have access to stylesheets from the editor 😀 This was a major problem because all of the sudden all the styling of the UI was inaccessible. By default, Unity loads the editor stylesheet when you attach your VisualElement to an EditorWindow. But for runtime components you will need to create one and style it accordingly.
And here is where Claude shines. I told Claude about this problem and it very happily generated me the necessary stylesheet. I instructed Claude to read my source code, list all the components and features I implemented and then extract the necessary styling from the Unity editor stylesheet and cobble up a stylesheet I could use at run-time. This proved to work pretty nicely. Great stuff, it saved me a lot of time 🙂 Exactly the shitty use-case for an AI to handle.
With the runtime importing done I wanted to focus on the last remaining items: Documentation and CI/CD workflows. I noticed before that my CI workflows were not running properly. I used some yml files from older projects but they didn’t run properly. So, I went back and forth with Claude to fix all of them again. The files I used had some work-arounds added in order to fix some of the shortcomings of the GameCI project. If you don’t know GameCI, go check it out! I feel like using AI for these CI/CD tasks is really great. It saved me from a lot of trial and error since AI has a much better ‘understanding’ of the options available in the popular actions.
I also needed to update the doxygen file but that was very simple, no AI required there.
And with that done.., well that’s it. That’s Victoria. A fun little side-project. A custom`.unitypackage` importer with both editor-and-run-time import just because we can.