Category: Projects

Most stripped-down version of Chromium

I am curious how far Chromium can be stripped down for embedded applications. The natural thing to do, of course, is to not use HTML for embedded applications. But I wonder: can one strip down Chromium to its bare minimum? No video or audio, no WebGL, no MIDI, no CSS 3D animations, no WebSockets, no profiling, no extensions, no developer console – basically nothing except the layout engine, a JS engine, and a basic renderer. The target is to minimize binary size and memory footprint while still drawing from an actively maintained code base, as HTML 4 engines are not really maintained anymore, and many modern websites do not render properly on HTML 4 engines.

Just an idle thought. No action is needed.

Using OpenPGP for video games

Cryptography seems to be all the rage these days: it is a method to strongly prove the occurrence of an event, and no one can question the universal truth of mathematics. The blockchain hype merely serves as witness to this trend toward cryptographic verification.

I see cryptography as a topic long avoided by mainstream software developers due to its core functionality being backed by pure math, a field which software developers are either not fond of or not competent in (or both). It is often perceived as a “black box” which ought not to be touched or recreated, lest one’s application be infested with thousands of security issues. However, cryptography is not purely math, and today, well-tested abstractions exist to make common cryptographic applications understandable and implementable.

Online video games tend to be backed by a central or master server, which places two main liabilities on the part of the maintainer of the game:

  • the responsibility of the maintainer to secure personal information within the server (such as email addresses and passwords) and to report security breaches; and
  • the regular maintenance of the server, which is essential to maintaining the ability for players to use the game.

However, as time progresses, the maintainer of a game is more likely to renege on these responsibilities for economic reasons, either causing the player’s experience to be significantly degraded or rendering the game entirely unplayable.

However, replacing a master server is not the main focus of this writing; rather, I wish to discuss an important issue in online games: who to trust.

I wish we could trust everyone – however, when a game gets sufficiently large, it becomes statistically likely for a player to decide to cheat or flood a server to attempt to break the game experience – in which case now there is one player we cannot trust.

With the rising popularity of virtual private networks and proxies, anonymity is king. It is nearly impossible to uniquely identify a player without prompting for a particular set of credentials by a master server. Even then, it is easy for a banned player to create new credentials and begin cheating or spamming again – the natural response is to increase the amount of personal information prompted by the central server, but this likewise increases the liability held by the owner.

One approach to mitigate this problem is by employing a cryptosystem specifically designed to solve the problem of trust – in our case, who should be allowed into a server, and who should not. OpenPGP is one such well-established system that uses a decentralized web of trust to systematically determine who can be trusted, without the liability of a centralized server or unintentionally restricting legitimate users from playing (such as banning an excessively large IP range, or requiring users to provide personal information that violates some users’ privacy).

The reason OpenPGP lacks adoption is primarily because of its unintuitive nature and its dependence on everyone to use the system in order for its individual users to benefit from it (a collective action problem). However, the limited domain in which OpenPGP would operate allows it to be enforced behind the veil of abstraction. Users will never need to know what “keys” or “signing” are – they only know that they have an identity that they can optionally secure with a password. They can also “like” other users. If their identity becomes compromised, they can choose to “destroy” it forever. Behind the scenes, keys have a six-month expiration date that is automatically renewed simply by playing the game.

On the server side, the server operates an extended whitelist based off a basic whitelist that lists primary identities that are fully trusted. Identities that are (indirectly) trusted by those primary identities may also be allowed to join the server. After the authentication succeeds, the server can reliably recognize the identity of a player, useful if the player has a specific rank or level on that server.

If all servers are whitelisted, then how can new players join? An optional centralized server can automatically grant new players trust temporarily, which allows them to join “newbie” servers and gain trust until they find themselves allowed into other servers. While this disincentivizes curiosity, this incentivizes playing in the same server as others, as well as social integration into the community. Alternatively, new players can also request trust through a side channel, such as a community chat server or forum outside of the game.

If a player has lost the trust of the community by breaking a major rule or through social engineering, other players can revoke trust just as easily as they imparted it: the OpenPGP system allows for signature revocation.

In short, an ideal implementation of public key-based infrastructure in video games would be seamless to users, eliminate the costly upkeep of a strong centralized server, and encourage regular social activity.

Using JS for Qt logic

Sorry, but I’m going to have to take sides on this one: Electron sucks. At their core, they convert a program into an embedded web browser with somewhat looser restrictions on interfacing with the host system, only to make it possible for web developers to develop for the desktop without learning a new language. The benefits of Electron are heavily biased toward developers and not the end-user, which is really the target of the entire user experience.

The problem is that HTML5 makes for an abstract machine that is far too abstract, and the more layers of abstraction that sit between the software and the hardware, the more overhead there will exist from translation and just-in-time compilation. The result is a web browser that uses 120 MB of shared memory and 200 MB of resident memory per tab.

For a web browser, though, abstraction is good. It makes development easy without sacrificing user experience for what is essentially a meta-program. But for any other desktop application with a specific defined functionality, this abstraction is excessive.

This is generally why I like Qt: because it is one of the only libraries left that continues to support native desktop and embedded application development without sacrificing performance. The one problem with it, however, is that performance requires the use of C++, and most people do not know how to work C++. Moreover, it requires double the work if the program is also being targeted for the web.

There does exist QML, which removes most of the C++ and exposes a nice declarative syntax that combines both layout and logic into a single file. However, it has two significant problems: first, it adds even more cruft to the output program, and custom functionality still requires interfacing with C++ code, which can get a little difficult.

Qt’s main Achilles’ heel for a long time has been targeting the web. There are various experimental solutions available, but none of them are stable or fast enough to do the job yet.

I’ve been coming up with an idea. Qt exposes its V4 JavaScript engine (a JIT engine) for use in traditional C++ desktop programs. What I could do is the following:

  • Write JS code that both the browser and desktop clients share in common, and then make calls to some abstract interface.
  • Implement the interface uniquely for each respective platform.

For instance, the wiring for most UI code can be written in C++, which then exposes properties and calls events in JS-land. Heck, Qt already does most of that work for us with meta-objects.

How do I maintain the strong contract of an interface? You need a little strong typing, don’t you? Of course, of course – we can always slap in TypeScript, which, naturally, compiles to standards-compliant JavaScript.

The one problem is supporting promises in the JS code that gets run, which mostly relies on the capabilities of the V4 engine. I think they support promises, but it does not seem well documented. Based on this post about invoking async C++ functions asynchronously, I think that I need to write callback-based functions on the C++ side and then promisify the functions when connecting between the JS interface and the C++ side. That shouldn’t be too hard.

Note that important new features for QtJsEngine, such as ES6, were only added in Qt 5.12. This might complicate distribution for Linux (since Qt continues to lag behind in Debian and Ubuntu), but we’ll get there when we get there – it is like thinking about tripping on a rock at the summit of a mountain when we are still at home base.

Reverse engineering Wargroove

So far, I think I have spent a total of 8 hours working on a reverse engineering project to mod Wargroove. I thought it would be trivial, but it has been a little more convoluted than I expected due to my inexperience in reverse engineering. The end goal is to make an Advance Wars total conversion mod.

Wargroove is based on the Halley engine. It is a well-designed C++14 game engine – arguably one of the best-designed game engines I have seen to date (and that’s saying something). The CMake build process is straightforward, and most libraries can be statically linked without hassle. The engine also encourages scripting using Lua, which means that how much of the game that can be modded might be much greater than I originally anticipated (which was just image resources).

I was able to hack up Halley’s asset pack inspector to try to extract Wargroove’s assets, and lo and behold, it listed out all of the files in each pack. But there was one problem: the files it extracts were all gibberish. Naturally, all of the asset packs are encrypted using AES. That means I have to find the IV and a decryption key, which is just a string.

The IV is easy to find: it’s in the header of the asset pack. However, the key is not so easy to find: you have to take a deep dive into the executable to find it.

I’ve done reverse engineering before as part of a university course, and of course a little bit in my spare time using Radare. I thought this would be relatively easy: find the code that corresponds to the decrypt function, then find cross-references to it, tracing upward until I can find a reference to some kind of string. But while I have come close, I have not been able to find the string through static analysis.

All right, so let’s try dynamic analysis. The problem with dynamic analysis, however, is that it’s difficult to hook into the executable right on game startup due to its dependency on Steam, so I can’t put a breakpoint on the decrypt function. Instead, however, I intentionally caused an error on load time to trigger an error message, which gives me a chance to hook a debugger and inspect the memory.

My first attempt to cause an error was by modifying what I thought was a decryption key and then watching the game try to decrypt without success. I’m basically looking for a string that is at least 16 characters long, and I found some good candidates that were close to some game initialization-related strings. However, none of the keys that I modified caused the game to crash.

My second, fail-safe attempt, was to simply rename one of the .dat files, which surely caused the game to fail to run.

Yet still no dice. None of the references in the stack pointed to anything that looked like a string, except for the error message in the dialog box. It was almost as if the error message that was produced overwrote the decryption key that I needed, which doesn’t seem like it’s supposed to happen.

After a while, I considered inspecting the network to see if the decryption key was being received from a server. But the game makes zero network communication except in the multiplayer and user-generated content (UGC) modes, and besides, getting a decryption key from a server would imply always-online DRM.

While IDA has given many clues, it does not provide any useful cross-references for the supposed decryption keys that I found. I don’t believe there is anything devious going on here – the developers would not take so much time to obfuscate a single string – so I’m going to give those strings a hard pass and keep looking.

I think I will focus my efforts on finding a way to hook into the executable right at start time, so that I can lay a breakpoint right at the decrypt function, which will give me the most accurate stack trace. I think this is possible if I hook into the Steam client and then watch for child process creation.

None of this is easy, though, when you are using Steam on Wine and launching Wargroove with an old version of Proton, in OpenGL mode, with the –no-intro flag, and then simultaneously launching x64dbg from a terminal with the same Wine prefix as Wargroove, with WINEESYNC set to 1. Yikes! I should probably do the debugging on my Windows machine.

At times, I feel like hitting my head against the wall, but I have confidence that I will find the key eventually so that I can move forward.


Unfortunately, after another six hours sunk into the project, I haven’t been able to make much headway. The Steam library is interfering with the debug hook on my Windows machine. Using the Image File Execution Options in the Windows registry, I can immediately hook a debugger on process startup; however, the interruption from hooking x96dbg causes the Steam API to fail initialization with a cryptic error code, and therefore halts the entire game from starting up.

Thinking that maybe the interference was solely an x96dbg issue, I tried Cheat Engine instead. I was originally hesitant about using Cheat Engine due to my unfamiliarity with it, but nevertheless it does not seem to support just-in-time debugging since elevation is required. Moreover, on automatically detecting the process and manually pausing it on startup, upon hooking any debugger, the game crashes.

Heck, when the engine fails to load an asset pack, it even prints a stack trace in the message box! The problem is that the stack trace string seems to overwrite the decryption key that I need.

I think what I need to do is patch a breakpoint into the executable by way of an INT 3.

Okay, well, that still didn’t work. I’ll have to try again later.


5/7: Darn it! Someone beat me to it. They created a closed-source tool in Java called ModPacker. Let me find out what the decryption key ultimately came out to be:

+Ohzep4z06NuKguNbFRz3w==

The tool uses only the first 16 characters of this Base64-encoded string.

I am not even sure what process was taken to find this string. It humiliates me that I was not able to find this string, but for someone else, the process seemed effortless.

I do not feel worthy of having this decryption key, and the only gripe I have against this ModPacker tool is that it is closed-source and reinvents the wheel in how it uses its own implementation of the Halley packer and unpacker tools instead of using the official tools.

On the versioning of large files

I’m such an idealist. It’s become clear that I can’t settle for anything other than the most perfect, elegant, long-term solution for software.

That reality has become painfully apparent trying to find a good way to track the assets on Attorney Online in a consistent, efficient manner. I just want to track its history for myself, not to put the content itself online or anything like that (since putting it online might place me further at risk for a copyright strike).

Well, that eliminates Git LFS as a choice. Git LFS eliminates the ability to make truly local repositories, as it requires the LFS server to be in a remote location (over SSH or HTTPS). It’s not like Git LFS is even very robust, anyway – the only reason people use it is because GitHub (now the second center of the Internet, apparently) developed it and marketed it so hard that they were basically able to buy (or should I say, sell?) their way to get it into the Git source tree.

Git-annex, on the other hand, seems promising, if I could figure out how to delete the remote that I accidentally botched. There’s not a whole lot of documentation on it, save the manpages and the forums, most posts of which are entirely unanswered. What’s more, GitLab dropped support of git-annex a year ago, citing lack of use. Oh well, it lets me do what I wanted to do: store the large files wherever I want.

I could also sidestep these issues by using Mercurial. But that would be almost as bad as using bare Git – the only difference would be that Mercurial tries to diff binary files, but I’d still probably have to download the entire repository todo en un cantazo.

I was also investigating some experimental designs, such as IPFS. IPFS is interesting because it’s very much a viable successor to BitTorrent, and it’s conservative enough to use a DHT instead of the Ethereum blockchain. The blockchain is seen as some kind of holy grail for computer science, but it’s still bound under the CAP theorem. It just so happens to sidestep the issues stipulated by the CAP theorem in convenient ways. Now, don’t get me wrong, my personal reservation for Ethereum is that I didn’t invest in it last year (before I went to Japan, I told myself, “Invest in Ethereum!!”, and guess what, I didn’t), and it seems that its advocates are people who did invest in it and consequently became filthy rich from it, so they come off as a little pretentious to me. But that’s enough ranting.

IPFS supports versioning, but there is no native interface for it. I think it would be a very interesting research subject to investigate a massively distributed versioning file system. Imagine a Git that supports large files, but there’s one remote – IPFS – and all the objects are scattered throughout the network…

Well, in the meantime, I’ll try out git-annex, well aware that it is not a perfect solution.

Does pyqtdeploy even work?

I know nobody is going to read this terrible blog to find this, but still, I’m moderately frustrated in trying to find a decent workflow to deploy a small, single-executable, Python-based Qt application.

Even on Windows using C++, it was not so easy to build statically until I found the Qt static libraries on the MinGW/MSYS2 repository – then building statically became a magical experience.

So far, the only deployment tools that promise to deploy a Python Qt program as a single executable are PyInstaller and pyqtdeploy.

PyInstaller works by freezing everything, creating an archive inside the executable with the minimum number of modules necessary to run, invoking UPX on these modules, and then when the program is run, it extracts everything to a temporary folder and runs the actual program from there. As such, startup times seem to be around 3-5 seconds, and the size of the executable is about 30 MB.

pyqtdeploy works by freezing your code, turning it into a Qt project with some pyqtdeploy-specific code, and then compiling that code as if it were a C++-style project, so you could compile a static version of Qt against this generated code.

But in order to use pyqtdeploy, you need to have the libraries at hand for linking:

LIBS += -lQtCore
LIBS += -lQtGui
LIBS += -lpython36
LIBS += -lsip

There’s no way around it – you must build Python and the other dependencies from scratch, and this could take a long time.

I have also encountered strange errors such as SOLE_AUTHENTICATION_SERVICE being undefined in the Windows API headers.

I mean, I suppose pyqtdeploy works, but is this even a trajectory worth going? What would be the pre-UPX size of such an executable – 25 MB, perhaps? That would put it on par with the AO executable.

I might as well write the launcher in C++, or switch to Tkinter.

A humanitarian mission for mesh networking

After Hurricane Maria, I was invited to a Slack group in Puerto Rico to offer my programming expertise for anyone who needed it. After beginning to comprehend the magnitude of the communications problem, I scoured for ways to set up long-distance mesh networking – no, not mobile apps like FireChat that rely on short-distance Wi-Fi or Bluetooth to establish limited local communications – rather, ways to post and find information across the entire island, with relays that could connect through the limited submarine cables to the outside Internet as a gateway for government agencies and worried relatives.

During the three weeks in my interest of this project (but powerlessness in doing anything, as I was taking classes), I investigated present technologies (such as 802.11s), as well as capabilities of router firmware, theoretical ranges of high-gain antennas, and other existing projects.

I saw Project Loon, but never expected much of it. The project must have taken a great deal of effort to take off, but unfortunately, it seemed to have a high cost with little return. Essentially, balloons were sent from some point on Earth and then led by high-altitude winds to cross Puerto Rico for a few hours, eventually to land at some location in the United States. Despite this effort, I found very few reports of actual reception from a Project Loon balloon.

Meanwhile, someone in the mesh networking Slack channel informed me that they were working with a professor at A&M to implement a mesh network from a paper that was already written. While I ultimately never saw the implementation of this mesh network, I felt put down by my naivete, but accepting that my plans were undeveloped and unexecutable, I moved on with the semester. Surely, mobile carriers must have had all hands on deck to reestablish cell phone coverage as quickly as possible, which is certainly the best long-term solution to the issue.

However, many places other than Puerto Rico remain in dire need of communications infrastructure, in towns and villages that for-profit carriers have no interest in placing coverage in. Moreover, there are islands at risk of becoming incommunicable in case of a hurricane.

I am looking to start a humanitarian mission to set up a mesh network. I find that there are three major characteristics to a theoretical successful mesh network: resilience, reach, and time to deploy.

A mesh network that is not resilient is flimsy: one failed node, perhaps bad weather or even vandalism, should not render all of the other nodes useless. Rather, the network should continue operating internally until connection can be reestablished with other nodes, or the situation can be avoided entirely by providing connections with other node, or even wormholing across the mesh network via cellular data.

A mesh network that does not reach does not have any users to bear load from, and thus becomes a functionally useless work of modern art. No, your users will not install an app from the app store – besides, with what Internet? – or buy a $50 pen-sized repeater from you. They want to get close to a hotspot – perhaps a few blocks away in Ponce – and let relatives all the way in Rio Piedras know that they are safe. And to maximize reach, of course, you need high-gain antennas to make 10-to-15-mile hops between backbone nodes that carry most of the traffic, which then distribute the traffic to subsidiary nodes down near town centers using omnidirectional antennas.

A mesh network that takes too long to deploy will not find much use in times of disaster. Cellular companies work quickly to restore coverage – a mesh network simply cannot beat cell coverage once it has been reestablished. First responders will bring satellite phones, and chances of switching to an entirely new communication system will dwindle as the weeks pass as volunteer workflows solidify.

How do I wish to achieve these mesh networking goals?

  • Resilience – use Elixir and Erlang/OTP to build fault-tolerant systems and web servers that can shape traffic to accommodate both real-time and non-real-time demands. For instance, there could be both voice and text coming through a narrow link, which could be as low as 20 Mbps. There may also be an indirect route to the Internet, but there may not be enough bandwidth to allow all users to be routed to the Internet. Moreover, decentralized data structures exist that can be split and merged, in case new nodes are added or nodes become split in an emergency, with possible delayed communication between nodes due to an unreliable data link.
  • Reach – allow users to reach the access point via conventional Wi-Fi or cellular radio, and connect via web browser. Nodes use omnidirectional antennas for distribution and high-gain antennas to form a backbone that can span dozens of miles.
  • Time to deploy – use off-the-shelf consumer hardware and allow flexibility in choice of hardware. Make the specs open for anyone to build a node if desired. Pipeline the production of such nodes with a price tag of less than $400 per node.

I imagine that the mesh network will predominantly serve a disaster-oriented social network with various key features:

  • Safety check – when and where did this person report that they were okay or needed assistance?
  • Posts – both public and private
  • Maps – locations that are open for business, distress calls, closed roads, etc.
  • Real-time chat (text and voice)
  • Full interaction with the outside world via Internet relays
  • Limited routing to specific websites on the open Internet, if available (e.g. Wikipedia)

One issue with this idea, I suppose, is the prerequisite of having a fully decentralized social network, which has yet to be developed. But we cannot wait until the next big disaster to begin creating resilient mesh networks. We must begin experimenting very soon.

Threading in AC

Last time I read about threading, I read that “even experts have issues with threading.” Either that’s not very encouraging, or I’m an expert for even trying.

There are a bunch of threads and event loops in AC, and the problem of how to deal with them is inevitable. Here is an executive summary of the primary threads:

  • UI thread (managed by Qt)
    • Uses asyncio event loop, but some documentation encourages me to wrap it with QEventLoop for some unspecified reason. So far, it’s working well without using QEventLoop.
    • Core runs on the same thread using a QPygletWidget, which I assume separates resources from the main UI thread since it is OpenGL.
      • Uses QTimer for calling draw and update timers
      • Uses Pyglet’s own event loop for coordinating events within the core
  • Network thread (QThread)
    • Uses asyncio event loop, but it uses asyncio futures and ad-hoc Qt signals to communicate with the UI thread.
    • Main client handler is written using asyncio.Protocol with an async/await reactor pattern, but I want to see if I can import a Node-style event emitter library, since I was going that route anyway with the code I have written.

My fear is that the network threads will all get splintered into one thread per character session, and that Pyglet instances on the UI thread will clash, resulting in me splintering all of the Pyglet instances into their own threads. If left unchecked, I could end up with a dozen threads and a dozen event loops.

Then, we have the possibility of asset worker threads for downloading. The issue with this is possible clashing when updating the SQLite local asset repository.

The only way to properly manage all of these threads is to take my time writing clean code. I cannot rush to write code that “works” because of the risk of dozens of race conditions that bubble up, not to mention the technical debt that I incur. Still, I should not need to use a single lock if I design this correctly, due to the GIL.

Gearing up

It’s time to start work on Animated Chatroom. It is a monumental project; the largest project to date that I have ever desired to undertake.

My resources are somewhat scarce, but it could be worse. The two resources which I am in great scarcity of are developers (human resources) and energy (something which tends to be inversely proportional to time). The developers I seek are either not competent enough to produce modular code, or they live in a very different time zone that complicates any coordinative effort. My energy is drained from playing with my brother or doing real-life tasks which I have been postponing for too long, such as cleaning some things up.

There is another question that compounds a desire to do everything other than work on Animated Chatroom: where do I even start?

Well, let’s see what Torvalds has to say about his success:

Nobody should start to undertake a large project. You start with a small trivial project, and you should never expect it to get large. If you do, you’ll just overdesign and generally think it is more important than it likely is at that stage. Or worse, you might be scared away by the sheer size of the work you envision. So start small, and think about the details. Don’t think about some big picture and fancy design. If it doesn’t solve some fairly immediate need, it’s almost certainly over-designed. And don’t expect people to jump in and help you. That’s not how these things work. You need to get something half-way useful first, and then others will say “hey, that almost works for me”, and they’ll get involved in the project.

Okay, Benevolent Dictator Linus…

You start with a small trivial project, and you should never expect it to get large. If you do, you’ll just overdesign and generally think it is more important than it likely is at that stage.

All right, so we started with a small trivial project. It was called Attorney Online 2. It was good. And then it tanked because of poor design. I want Animated Chatroom to not go through that pain again.

Or worse, you might be scared away by the sheer size of the work you envision.

Which I am.All right, so what features do we not need? Let’s cut nodes until we get something less overwhelming.

Better. That’s almost the bare minimum that I need.

In list format:

  1. Core animation engine.
  2. Asset loader.
  3. Basic network.
  4. Basic UI.

That’s all, I guess I don’t care about anything else right now. So let’s cut it down even further.

Okay. So version 0.1 will barely have a UI. It’s just figuring out how stuff should work.

It’s clear that VNVM is at the center of this entire project. If I can design VNVM correctly, then this project has a chance; otherwise, a poor execution will lead to a shaky foundation.

The Visual Novel Virtual Machine

What is the purpose of the Visual Novel Virtual Machine project? The purpose is to bring sequences of animations and dialogue sequences, the bread and butter of visual novels, to a portable environment. From reverse engineering performed by others, it turns out that major visual novels also use a bytecode to control dialogue and game events. In the VNVM world, this is called VNASM (Visual Novel Assembly).

Within characters, emotes are simply small bits of VNASM, which are then called by the parent game (which also runs in the VNVM). Recording a game is just a matter of storing what code was emitted and when. The point is that essentially all VNASM is compiled or emitted by a front-end, rendering it unnecessary to understand VNASM to write a character. (But, it would kinda be nice to be able to inline it, wouldn’t it?)

This makes VNVM satisfactory for both scripted and network environments. In a network situation, where the execution is left open, there is a simple wait loop that awaits the next instruction from the network buffer. New clients simply retrieve the full execution state of the VNVM to get going. The server controls what kinds of commands can be sent to it; most likely, an in-character chat will look something like this as a request made to the server:

{ emote: "thinking", message: "Hmm... \p{2} I wonder if I can get this to work right..." }

The \p marker denotes a pause of 2 seconds, which the server parses out to emit a delay of 2 seconds (of course, limiting the number to a reasonable amount). The server then pushes the reference to the character who wants to talk, as well as the message to be said, and calls char_0b7aa8::thinking where 0b7aa8 is the character’s ID. This denotes that the named subroutine thinking is located in a segment of VNVM code named char_0b7aa8.

More to follow later.

Rethinking Attorney Online assets

It seems fairly obvious that FanatSors was not expecting a complex level of gameplay when he released the Delphi-made Attorney Online back in 2012.

Yet AO still exists, with about 150-200 daily players who frequent the couple dozen servers, with the most popular being /aog/’s Attorney Online Vidya. OmniTroid’s Qt-based, open-source AO2 client is now the de facto client, touting “advanced” features such as color, Unicode support, and parametrized preanimations. Likewise, the open-source tsuserver3 and esoteric-but-still-open-source serverD are the two choices of hosting for AO. Today, however, the legends of FanatSors and OmniTroid have faded away.

https://i.imgur.com/V30YtuL.png
An overview of the Attorney Online family.

Most players and case-writers are regularly impacted by the technical limits and quirks of the engine. Configuration of each character is all done in a single INI file, defining each emote as an octothorpe-delimited sequence of animations to be played. Each animation refers to a GIF file prefixed by an (a) or (b); that is, the format and naming scheme must be precise in the file system level.

This is not the main challenge, however. The challenge is managing assets.

Every server asks their users to download an archive, spanning up to 7 GB in size, containing character sprites, music tracks, backgrounds, evidence images, and sound effects that may be needed during gameplay. Assets are only identified by their folder name; this is the only unique identifier attached to an asset. These are the problems with using an internal name as the sole identifier:

  • Two servers may offer different content, but under the same internal name, causing a hard clash. Content could be isolated per-server, but this causes a serious redundancy problem.
  • Two servers may offer the same content, but under different internal names. This causes excessive redundancy.
  • Two servers may offer just about the same content, with a small difference. In this case, there is no hierarchy established as to which one is derived from the other one.

Upon requesting a character list for my proposed new standard base, nuVanilla (and receiving a monumental list!), it felt that the amount of dimensions that the assets needed to be examined in were too many to use a conventional spreadsheet for, so I opted for a full-blown database. My choice was split between MySQL/MariaDB and PostgreSQL, but I remembered that I wanted to learn Postgres, as the performance and versatility claimed to be far greater than MariaDB could offer.

One immediate issue is the sheer number of many-to-many relationships that manifest, causing an effective decoupling of many columns:

  • Multiple packs can include the same asset.
  • The same asset could be under different internal names.
  • Multiple assets can have the same internal name.
  • Multiple assets can represent the same character.
  • Assets of the same character can come from different games.
  • Assets can be in different formats, such as 256×192 or even 1280×720 (yes, some people resize their sprites to match their theme’s viewport size).

How do I represent uniqueness of assets, then? I can’t even hash the char.ini, because the char.ini contains the internal name of the asset. What’s more, there is no standard way to hash multiple files at a time; in this case, I would want to hash all of the emote images at the same time. (For now, however, I am hashing the char.ini until I find an adequate solution.)

One solution would be to give every asset a UUID. This would, in theory, add an additional layer of “uniqueness” into each asset. However, this still does not resolve the original problem: two assets with the same content but different internal names would still be detected as “different” upon submission, since the hash of each char.ini would be different. And this would compound a new problem on top of the old one: modifiers of an asset would be burdened with updating the UUID of the asset they are editing; forgetting to update it could only cause an error when uploading it to some centralized database.

What modifications can be done to an asset?

  • A small correction to frame data – minimal change
  • Emote additions – significant change
  • Internal name change – minimal change
  • SFX name change – minimal change

Three out of four of these changes are minimal changes. Thus, it would not make sense to consider them completely different assets. We can try to establish a hierarchy of assets, to see which asset succeeded the other, but that is no substitute for a diff. The data stored remains redundant.

Therefore, I can conclude Attorney Online assets cannot be accurately uniquely identified for management, and attempting to set up a database to manage them would take me nowhere.

I should then refocus my efforts to designing asset structure in Animated Chatroom.

Each asset would have a definition file (such as char.json), which would state the name of the character, its ancestors, and a reference to the sprite file. The format of the definition file would probably be JSON, while the sprite file would then be written in something like Spritelang.

The asset is then bundled using tar and signed using GPG. This verifies the identity of the packager of the asset (for increased trust, the packager’s key can be cross-signed by a responsible admin, who in turned is cross-signed by the Animated Chatroom Root Key. All of these keys can be uploaded onto a general-purpose key server, like pgp.mit.edu. The signature of a package need not be specifically signed by the AC official root key; just a key that is cross-signed by someone on the keychain. The absence of a signature does not mean that the asset contains malicious content and therefore cannot be trusted; rather, the purpose of the signature is to assure that the contents of an asset have not been modified, and to seal the credits of an asset. After the tar has been signed, the tar can then be compressed in a desired format such as xz (which is basically 7-Zip but using a byte stream as opposed to an embedded archive). In this case, the unique identifier of the asset is the key ID of the GPG signature. This is the strongest possible hash: not only is the data factored in, but also the identity of the individual who authored the asset.

Now that we have established a strong, unique identifier to our data, we need to solve the data redundancy problem.

Children of ancestors use an incremental tar file, which minimizes the content stored in the child. Even deleted content can be tracked if incremental tars are applied correctly. We may also employ a similar scheme as the Docker Image Specification (version 1), but it’s clear that someone did not read into tar’s ability to do incremental archiving out of the box. (“NOTE: For this reason, it is not possible to create an image root filesystem which contains a file or directory with a name beginning with .wh.. ” It requires little thinking to realize that this is a tremendously absurd limitation, just to add the ability of identifying deleted objects. If anything, deleted files should have been put in a separate file, for the sake of not introducing an artificial limitation to the file system.)

Incremental versioning has a major implication for authoring assets: authored assets are immutable. No, sir, the Animated Chatroom authoring tool will not allow you to modify an asset that has already been successfully packaged and signed, unless you choose one of the following options:

  • Create an asset that is a child of the asset you want to modify. This is not a favorable option if you have not published the parent asset. However, the authoring tool will set the hierarchy up for you.
  • Create a new asset, derived from the data of the parent asset. There is no hierarchy established, it’s just a hard copy of the parent asset. This is favorable only when the parent asset has not been published.

Acquiring modified versions of an asset would be simple under this system:

  • The desired version of the asset is downloaded and unzipped. (We don’t need to untar the entire asset yet.)
  • The signature is verified, and a warning is displayed if the signature is invalid.
  • The definition file is untarred and checked for an ancestor. If an ancestor exists, a recursive download request is made on the ancestor.
  • The asset contents are untarred on the target folder.

Asset servers for Animated Chatroom web clients can establish this hierarchy – without the expected redundancy! – by using symbolic links to represent files that are identical to the parent.

Finally, we can track what assets we have downloaded and what asset repositories we are currently using, by storing local data in an SQLite database file.

Instead of a name-based character list, servers use character IDs to disambiguate between different versions of the same character. A server can then offer download sources for specific characters, such as if a character was made “in-house,” so to speak.

What are the improvements over this design over the old design devised by FanatSors?

  • Authorship is immutable. This is important mostly for original content: repository owners will refuse to publish assets that refuse to identify the original creator of content. However, in the case of ripped content, it is desirable to preserve information about who ripped it, but ultimately, it is all copyrighted by the publisher of the game (Capcom, Chunsoft, etc.).
  • Downloading is automatic. Under the old system, players were too lazy to download zip files to play on new servers, and server owners were too lazy to compose zip files for players to download every time new content is suggested. Now, server admins need only do a graphical lookup of the assets they want to add on the server, confirm the additions, and the new content is immediately requested for download by clients, all seamlessly and in the background.
  • Name clashing is no more, since we established that internal names are no good as a unique identifier.
  • Asset content is deduplicated (to the best of the ability of this system).
  • Asset management is decentralized. I don’t own the database – in fact, nobody does. You can host part of the repertoire of Animated Chatroom content, but you can never host all of it.
    • On a similar note, this makes Animated Chatroom effectively immune to cease-and-desist notifications. I can take down the offending content on my servers, but due to technical restrictions, I cannot be responsible for the content hosted by other servers. The cease-and-desist notifications would have to be sent to each offending server.

This concludes an overview of the proposed design of asset management in Animated Chatroom. I hope you found this design enlightening for any future adventures in software development.