<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Moral Recordings</title><link href="/" rel="alternate"/><link href="/feeds/all.atom.xml" rel="self"/><id>/</id><updated>2026-05-27T00:00:00+08:00</updated><entry><title>Making the Plagiarism Machine Write Code Still Counts as Plagiarism</title><link href="/writing/2026/05/27/plagiarism_machine/" rel="alternate"/><published>2026-05-27T00:00:00+08:00</published><updated>2026-05-27T00:00:00+08:00</updated><author><name>Scott</name></author><id>tag:None,2026-05-27:/writing/2026/05/27/plagiarism_machine/</id><summary type="html">&lt;img src="/writing/2026/05/27/plagiarism_machine/blunt_analogy.jpg" alt="A stylized halftone image of a meat mincer, based on a photograph from Ebay seller retrometropolous66 named &amp;quot;VINTAGE RETRO SPONG  CAST ALLOY HAND MEAT GRINDER MINCER MADE IN ENGLAND&amp;quot;." class="letterbox" title="blunt_analogy.jpg"/&gt; &lt;p&gt;I have been struggling to put into words my frustration with the open source community in failing to meet the present moment.&lt;/p&gt;
&lt;p&gt;In case you've missed the last three years: AI labs have strip-mined the internet, robbing creators of their work and selling it back to the public as a machine designed to crush labour. Thanks to their relentless lobbying of businesses and governments, there is immense pressure on workers to offload their cognition onto AI products backed by a Large Language Model, on the false pretense that the model is a reliable source of knowledge.&lt;/p&gt;
&lt;p&gt;(For avoidance of doubt, a reliable source of knowledge doesn't habitually make fraudulent citations, and doesn't invent answers then spout them off with 100% confidence. These behaviours are referred to as hallucination; a problem which researchers from OpenAI and Anthropic admit is &lt;a class="reference external" href="https://transformer-circuits.pub/2025/attribution-graphs/biology.html#dives-hallucinations"&gt;intrinsic of LLMs&lt;/a&gt; and &lt;a class="reference external" href="https://arxiv.org/abs/2509.04664"&gt;essentially not fixable&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;It is no surprise that AI's biggest success story to date has been in computer programming, a field where a lot of people have no professional standards and are allergic to solidarity. A lot of attention has been paid to new projects that have embraced vibe coding; usually for their oafish levels of complexity, repetition and serious design flaws. Less attention has been paid to established open-source projects, where in an attempt to avoid corporate pressure or alienating key contributors, &lt;em&gt;some&lt;/em&gt; level of LLM-driven development has been accepted.&lt;/p&gt;
&lt;p&gt;Everywhere I look, there are big projects opening the door to LLM-generated code submissions. The Linux kernel. systemd. Kubernetes. Firefox. Python. Rust. Even ScummVM, a project I contribute to, has added such a policy. All perhaps motivated by different reasons, but collectively they provide a broad social license to ignore all ethical concerns and normalise the presence of LLM code generation.&lt;/p&gt;
&lt;p&gt;I wish I was shocked at the lack of a visible resistance movement in this space as there has been with writing and visual arts, but that would be in denial of the extreme free-market libertarianism that has dominated software/the tech sector for the last 50 years. There has always been a level of dry rot visible from space, and now we get to watch part of the house collapse from its own weight.&lt;/p&gt;
</summary><content type="html">&lt;img src="/writing/2026/05/27/plagiarism_machine/blunt_analogy.jpg" alt="A stylized halftone image of a meat mincer, based on a photograph from Ebay seller retrometropolous66 named &amp;quot;VINTAGE RETRO SPONG  CAST ALLOY HAND MEAT GRINDER MINCER MADE IN ENGLAND&amp;quot;." class="letterbox" title="blunt_analogy.jpg"/&gt; &lt;p&gt;I have been struggling to put into words my frustration with the open source community in failing to meet the present moment.&lt;/p&gt;
&lt;p&gt;In case you've missed the last three years: AI labs have strip-mined the internet, robbing creators of their work and selling it back to the public as a machine designed to crush labour. Thanks to their relentless lobbying of businesses and governments, there is immense pressure on workers to offload their cognition onto AI products backed by a Large Language Model, on the false pretense that the model is a reliable source of knowledge.&lt;/p&gt;
&lt;p&gt;(For avoidance of doubt, a reliable source of knowledge doesn't habitually make fraudulent citations, and doesn't invent answers then spout them off with 100% confidence. These behaviours are referred to as hallucination; a problem which researchers from OpenAI and Anthropic admit is &lt;a class="reference external" href="https://transformer-circuits.pub/2025/attribution-graphs/biology.html#dives-hallucinations"&gt;intrinsic of LLMs&lt;/a&gt; and &lt;a class="reference external" href="https://arxiv.org/abs/2509.04664"&gt;essentially not fixable&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;It is no surprise that AI's biggest success story to date has been in computer programming, a field where a lot of people have no professional standards and are allergic to solidarity. A lot of attention has been paid to new projects that have embraced vibe coding; usually for their oafish levels of complexity, repetition and serious design flaws. Less attention has been paid to established open-source projects, where in an attempt to avoid corporate pressure or alienating key contributors, &lt;em&gt;some&lt;/em&gt; level of LLM-driven development has been accepted.&lt;/p&gt;
&lt;p&gt;Everywhere I look, there are big projects opening the door to LLM-generated code submissions. The Linux kernel. systemd. Kubernetes. Firefox. Python. Rust. Even ScummVM, a project I contribute to, has added such a policy. All perhaps motivated by different reasons, but collectively they provide a broad social license to ignore all ethical concerns and normalise the presence of LLM code generation.&lt;/p&gt;
&lt;p&gt;I wish I was shocked at the lack of a visible resistance movement in this space as there has been with writing and visual arts, but that would be in denial of the extreme free-market libertarianism that has dominated software/the tech sector for the last 50 years. There has always been a level of dry rot visible from space, and now we get to watch part of the house collapse from its own weight.&lt;/p&gt;

&lt;hr class="docutils" /&gt;
&lt;p&gt;There are plenty of reasons to despise and oppose the frontier AI labs. For example, the cartoonish level of waste found at every stage of LLM technology. From the billions of investor cash set alight for training and inference and &lt;a class="reference external" href="https://redmondmag.com/blogs/generationai/2025/12/microsoft-is-sitting-on-a-pile-of-unused-gpus.aspx"&gt;warehouses of unused GPUs&lt;/a&gt;, all the way to unhinged multi-agent cack factories like Steve Yegge's Gas Town, a project which openly boasts &lt;a class="reference external" href="https://steve-yegge.medium.com/welcome-to-gas-town-4f25ee16dd04"&gt;&amp;quot;you won't like [it] if you ever have to think, even for a moment, about where money comes from&amp;quot;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Other good reasons: the constant AI lab doom-mongering about &lt;a class="reference external" href="https://www.techpolicy.press/the-myth-of-agi/"&gt;sentience&lt;/a&gt; and &lt;a class="reference external" href="https://www.forbes.com/sites/joemckendrick/2026/04/27/at-least-18-of-jobs-face-major-ai-risk-openai-economist-predicts/"&gt;mass unemployment&lt;/a&gt; to goose their valuation and land more marks, the &lt;a class="reference external" href="https://www.nbcnews.com/tech/tech-news/experts-warn-collapse-trust-online-ai-deepfakes-venezuela-rcna252472"&gt;worldwide breakdown of trust and consensus reality&lt;/a&gt; as their slop permeates everything, the end of discoverability as &lt;a class="reference external" href="https://www.404media.co/google-search-really-has-gotten-worse-researchers-find/"&gt;the internet drowns&lt;/a&gt;, and their willingness to sell their tech for accelerating &lt;a class="reference external" href="https://apnews.com/article/israel-palestinians-ai-weapons-430f6f15aab420806163558732726ad9"&gt;mass surveillance and genocide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In addition, the AI labs have been so good at &lt;a class="reference external" href="https://www.wheresyoured.at/ai-is-too-expensive/#anthropic-and-openai-need-to-make-or-raise-more-than-125-trillion-in-the-next-four-years"&gt;subsidising demand&lt;/a&gt; that the major cloud providers have committed hundreds of billions toward new datacenter spending. These datacenter projects are best known for siphoning &lt;a class="reference external" href="https://www.404media.co/an-incomplete-list-of-successful-anti-data-center-legislation/"&gt;massive amounts of water and energy away from communities&lt;/a&gt;, creating &lt;a class="reference external" href="https://www.selc.org/news/xai-built-an-illegal-power-plant-to-power-its-data-center/"&gt;atmospheric and noise pollution&lt;/a&gt;, inflating the worldwide cost of essential parts like &lt;a class="reference external" href="https://nand-research.com/memory-nand-flash-crisis-may-2026-update/"&gt;memory chips&lt;/a&gt;, and adding a &lt;a class="reference external" href="https://finance.yahoo.com/markets/options/articles/hyperscaler-debt-flood-brings-derivatives-190000196.html"&gt;destabilising amount of construction debt&lt;/a&gt; to the global financial system.&lt;/p&gt;
&lt;p&gt;And of course, the legal question is still open on whether training an LLM on copyright-infringing works is itself an act of infringement, or whether LLM-generated works are copyrightable at all. Early rulings by the US Copyright Office have stated that &lt;a class="reference external" href="https://apnews.com/article/ai-copyright-office-artificial-intelligence-363f1c537eb86b624bf5e81bed70d459"&gt;works where &amp;quot;the expressive elements are determined by a machine&amp;quot; are not eligible for copyright&lt;/a&gt;, and lobbyists are desperately trying to get new laws to firm up their position before the wave of lawsuits fully breaks.&lt;/p&gt;
&lt;p&gt;All of these are important and justified grounds for opposing the onslaught of AI companies into every facet of life, and have been written about by people more eloquent and better-read than myself.&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;p&gt;To keep things brief, let's talk about the ethics of using any large language model trained on all published open-source code to generate new code. Just that case! Not provide advice on punctuation and spelling, not work as a version of American Fuzzy Lop that costs 20 grand a throw, not the mostly-transformative use cases that are wheeled out to justify &lt;em&gt;all&lt;/em&gt; LLM usage being good. New code.&lt;/p&gt;
&lt;p&gt;We know on a fundamental level that frontier models are a laundromat for existing content, and AI labs have focused on randomising the output enough to make direct plagiarism claims harder to prove. We know that the frontier model companies go to extreme lengths to take everything, including material clearly licensed or marked by the author as not for use in LLM training. Even the so-called &amp;quot;open weight&amp;quot; LLMs, the golden boy of bikeshedded ethical LLM theory, have opaque training sets stuffed to the gunnels with scraped content that fails the same ethical test. We know that training a personal model with just stuff you wrote is physically not enough data to get a workable result, and plenty of charlatans will sell you this false promise and fake it with a frontier model.&lt;/p&gt;
&lt;p&gt;My view is that generating code wholesale with a frontier LLM, then sticking your own name and copyright notice on it, is a massive slap in the face and should be treated with contempt, regardless of how easy or expected it is. Hundreds of thousands of developers contribute to the commons, often on their own dime, in exchange for attribution and making the world a better place. Working for exposure! And you can't even do that all of a sudden? That's now too hard? Spare me.&lt;/p&gt;
&lt;p&gt;Having the LLM generate a merge request wholesale for an existing project combines the above with a second slap in the face. There's plenty of debate about how many lines of unvetted changes still counts as polite to throw at a maintainer. I would start at 0. Why should a maintainer bother to read a changeset that you couldn't be bothered to write, and most likely won't be able to answer follow-up questions on?&lt;/p&gt;
&lt;p&gt;As a maintainer of ScummVM's Macromedia Director support, where the aim is to recreate the precise ordering and edge-cases of the original engine, the number of lines changed doesn't even matter. It could be one line! The work is in the bench-testing that has been done to confirm how the original engine does it, and incorporating it in a way that doesn't break other games or other versions of Director. In this context, LLM generation would be a pretty strong signifier that no thought was put into the manner or consequence of these changes outside of getting a specific game to work, which is against the project's goal of universal compatibility.&lt;/p&gt;
&lt;p&gt;For what it's worth, it is possible to both loathe LLM-driven development, and &lt;em&gt;not&lt;/em&gt; get mad at non-developers using them in a personal capacity to solve a code problem. They are a tiny fraction of the overall usage. My ire is firmly directed at the self-described software engineers talking up their love of scabwork.&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;p&gt;But here we are. A lot of prominent developers decided they want to slam on the treat button ad nauseam and still call it engineering, and the united front is dead before it ever had a chance to form. That's life. My fault for expecting better.&lt;/p&gt;
&lt;p&gt;I feel for every single person who has had their future ripped out from under them, and I want no part of it. The overwhelming urge has been to wind up all my coding projects and move to a hobby which is harder to ruin with slop.&lt;/p&gt;
&lt;p&gt;Still, we've been here before. This isn't the first time a technology has been hyped to the world with the promise of improved productivity, then prove to be a disappointing (and now load-bearing!) albatross that drinks money. Nor will it be the last! Just look at the number of businesses still welded to Oracle Forms; management paid top dollar for that asbestos and by God it's going to &lt;em&gt;stay&lt;/em&gt; in those walls until they collapse.&lt;/p&gt;
&lt;p&gt;Maybe this is a flash in the pan and developers will regain their senses once LLM pricing is dragged kicking and screaming toward what it costs to run. Maybe the trauma of the oil crisis smashing into the impending debt collapse will be enough to spark a new renaissance, and we will collectively rediscover the joy of what is only possible through human labour.  But until then, all we can do is look at the comments.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;quot;I have never been so productive&amp;quot;&lt;/em&gt; they say, drunk on an indecipherable colossus they've spent $5000 to build, marveling at all the time they used to waste writing their own words of intimacy or comfort. &lt;em&gt;&amp;quot;Claude writes much better code than I ever did&amp;quot;&lt;/em&gt; they say, face still notionally human but with a deadness behind the eyes, long ago having smothered any dreams of self-improvement or professional responsibility. &lt;em&gt;&amp;quot;You obviously haven't tried the latest model&amp;quot;&lt;/em&gt; they say, turgid with excitement for the heat death of everything, celebrating as the sky is blotted out by their poisonous exhaust.&lt;/p&gt;
</content><category term="Devlog"/><category term="open source"/><category term="llm"/><category term="plagiarism"/></entry><entry><title>Can you complete the Oregon Trail if you wait at a river for 14272 years: A study</title><link href="/writing/2025/01/11/waiting_for_oregon/" rel="alternate"/><published>2025-01-11T00:00:00+08:00</published><updated>2025-01-11T00:00:00+08:00</updated><author><name>Scott</name></author><id>tag:None,2025-01-11:/writing/2025/01/11/waiting_for_oregon/</id><summary type="html">&lt;img src="/writing/2025/01/11/waiting_for_oregon/header.png" alt="A screenshot from the main gameplay view of Oregon Trail, showing a covered wagon drawn by oxen near a river, and cheerfully informing you that Zeke has a broken leg and will die soon." class="letterbox" title="Goddamnit Zeke I turn my head for ONE SECOND"/&gt; &lt;p&gt;If you're into retro computing, you probably know about Oregon Trail; a simulation of the hardships faced by a group of colonists in 1848 as they travel by covered wagon from Independence Missouri to the Willamette Valley in Oregon. The game was wildly successful in the US education market, with the various editions selling 65 million copies. What you probably &lt;em&gt;don't&lt;/em&gt; know is the game's great untold secret.&lt;/p&gt;
&lt;p&gt;Two years ago, Twitch streamer &lt;a class="reference external" href="https://www.twitch.tv/albrot"&gt;albrot&lt;/a&gt; discovered a bug in the code for crossing rivers. One of the options is to &amp;quot;wait to see if conditions improve&amp;quot;; waiting a day will consume food but &lt;em&gt;not&lt;/em&gt; recalculate any health conditions, granting your party immortality.&lt;/p&gt;
&lt;p&gt;From this conceit the Oregon Trail Time Machine was born; a multiday livestream of the game as the party waits for conditions to improve at the final Snake River crossing until the year 10000, to see if the withered travellers can make it to the ruins of ancient Oregon. The &lt;a class="reference external" href="https://www.twitch.tv/videos/1738411118?t=01h04m40s"&gt;first attempt ended in tragedy&lt;/a&gt;; no matter what albrot tried, the party would succumb to disease and die almost immediately.&lt;/p&gt;
&lt;p&gt;A couple of days before New Years Eve 2025, albrot reached out and asked if I knew anything about Apple II hacking. In reality the answer was no, I knew three things about the Apple II:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;It has a MOS 6502 processor&lt;/li&gt;
&lt;li&gt;It was popular in the US educational market&lt;/li&gt;
&lt;li&gt;Something something Carmen Sandiego something Prince of Persia?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But all old computers are basically the same right? Specialist knowledge is for cowards.&lt;/p&gt;
</summary><content type="html">&lt;img src="/writing/2025/01/11/waiting_for_oregon/header.png" alt="A screenshot from the main gameplay view of Oregon Trail, showing a covered wagon drawn by oxen near a river, and cheerfully informing you that Zeke has a broken leg and will die soon." class="letterbox" title="Goddamnit Zeke I turn my head for ONE SECOND"/&gt; &lt;p&gt;If you're into retro computing, you probably know about Oregon Trail; a simulation of the hardships faced by a group of colonists in 1848 as they travel by covered wagon from Independence Missouri to the Willamette Valley in Oregon. The game was wildly successful in the US education market, with the various editions selling 65 million copies. What you probably &lt;em&gt;don't&lt;/em&gt; know is the game's great untold secret.&lt;/p&gt;
&lt;p&gt;Two years ago, Twitch streamer &lt;a class="reference external" href="https://www.twitch.tv/albrot"&gt;albrot&lt;/a&gt; discovered a bug in the code for crossing rivers. One of the options is to &amp;quot;wait to see if conditions improve&amp;quot;; waiting a day will consume food but &lt;em&gt;not&lt;/em&gt; recalculate any health conditions, granting your party immortality.&lt;/p&gt;
&lt;p&gt;From this conceit the Oregon Trail Time Machine was born; a multiday livestream of the game as the party waits for conditions to improve at the final Snake River crossing until the year 10000, to see if the withered travellers can make it to the ruins of ancient Oregon. The &lt;a class="reference external" href="https://www.twitch.tv/videos/1738411118?t=01h04m40s"&gt;first attempt ended in tragedy&lt;/a&gt;; no matter what albrot tried, the party would succumb to disease and die almost immediately.&lt;/p&gt;
&lt;p&gt;A couple of days before New Years Eve 2025, albrot reached out and asked if I knew anything about Apple II hacking. In reality the answer was no, I knew three things about the Apple II:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;It has a MOS 6502 processor&lt;/li&gt;
&lt;li&gt;It was popular in the US educational market&lt;/li&gt;
&lt;li&gt;Something something Carmen Sandiego something Prince of Persia?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But all old computers are basically the same right? Specialist knowledge is for cowards.&lt;/p&gt;

&lt;div class="section" id="where-to-start"&gt;
&lt;h2&gt;Where to start&lt;/h2&gt;
&lt;p&gt;First I loaded Oregon Trail into MAME's Apple II emulator. MAME is an emulator that was originally targeted at arcade hardware, however it has support for hundreds of game consoles and home computer systems thanks to merging with the MESS project. It has one of the nicest debuggers I've ever used, with plenty of documentation, which is why I strongly recommend it for reversing work. You will need a copy of the Apple II ROMs, listed in the MAME ROM index as &lt;tt class="docutils literal"&gt;apple2e.zip&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;We are using the most common disk image of the game (aka &amp;quot;peperony and chease&amp;quot; edition). To open the game:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;mame&lt;span class="w"&gt; &lt;/span&gt;apple2e&lt;span class="w"&gt; &lt;/span&gt;-debug&lt;span class="w"&gt; &lt;/span&gt;-flop1&lt;span class="w"&gt; &lt;/span&gt;oregonmod_a.do
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This will open a separate Debug window and pause execution until you start running the machine (F5). In the game window you can activate the MAME menu keys by pressing Scroll Lock; useful ones to know are TAB for the main menu, F6 for saving a state, F7 for loading a state.&lt;/p&gt;
&lt;p&gt;This was my thinking for how to get a first toehold into what was going on. As you travel, the game gives your party one of four health ratings:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;good&lt;/li&gt;
&lt;li&gt;fair&lt;/li&gt;
&lt;li&gt;poor&lt;/li&gt;
&lt;li&gt;very poor&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It follows that the game code must read the text string &amp;quot;poor&amp;quot; from somewhere in memory so it can be printed on the screen, and this code would be attached to the routine that determines how sick everyone is. So I sent the party off from Independence, Missouri with 1 ox, no food and no clothes. As soon as we were on the road, I made a save state. I then called &amp;quot;Step Into&amp;quot; in the debugger (F11) which paused execution, and made a dump of the Apple II's memory to a file with the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;save&lt;span class="w"&gt; &lt;/span&gt;/tmp/mem_before,0,0x10000
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I loaded this dump into a hex editor and searched for the word &amp;quot;poor&amp;quot;, which I found at address 0xA6AE. Then in the debugger I added a watchpoint for any code attempting to read the first byte of &amp;quot;poor&amp;quot;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;wpset&lt;span class="w"&gt; &lt;/span&gt;0xA6AE,1,r
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As expected, once my party became emaciated enough the debugger halted execution at the following instruction:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;DB47&lt;span class="w"&gt;  &lt;/span&gt;LDA&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$5&lt;/span&gt;E&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;Y
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The Apple II CPU is a 6502, which has a nice &lt;a class="reference external" href="http://6502.org/users/obelisk/6502/reference.html"&gt;instruction reference&lt;/a&gt; here. Looking it up, we can see that LDA basically means &amp;quot;load a byte from the address stored at 0x005E into register A&amp;quot;. Because the 6502 is a very basic 8-bit CPU, you are limited in how you access the 16-bit address space. Generally speaking you have the choice of using a fixed offset (up to 16-bit, baked in the instruction) plus whatever 8-bit value is in a register, or using a Zero Page pointer. The above instruction is using a Zero Page address; for this mode you have to write a 16-bit pointer containing the location you want to somewhere in the first 0x100 bytes of memory, then use an instruction which references that pointer.&lt;/p&gt;
&lt;p&gt;So we know that the pointer to the text describing the player's health is at 0x005E, which we can confirm by opening a memory window in the debugger and see AE A6 at address 0x005E (the 6502 is little endian, so addresses are stored backwards). It's fair to say that this is just for updating the display, and not for checking how ill everyone is.&lt;/p&gt;
&lt;p&gt;We need to find the code that sets this status, so I added another watchpoint for any writes to the pointer at 0x005E:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;wpset&lt;span class="w"&gt; &lt;/span&gt;0x005E,1,w,wpdata&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;0xAE
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After reload the save state, the game stopped in another area:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;E630&lt;span class="w"&gt;  &lt;/span&gt;STX&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$5&lt;/span&gt;E&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;mem&lt;span class="o"&gt;[&lt;/span&gt;0x005E&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;X
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I started using the debugger's Step Out feature (Shift+F11) to move up the call stack and see what was calling what. From further poking around it became apparent that we were in some sort of generic high level text printing function. Then a penny dropped, and I started wondering about the memory layout of the Apple II. A &lt;a class="reference external" href="http://apple2.guidero.us/doku.php/mg_notes/general/mem_map"&gt;memory map&lt;/a&gt; shows the D000–F7FF range to be the part of the ROM containing the BASIC interpreter. Oh.&lt;/p&gt;
&lt;p&gt;In fact there's a &lt;a class="reference external" href="https://6502disassembly.com/a2-rom/Applesoft.html#SymFREFAC"&gt;very helpful annotated Apple II ROM disassembly&lt;/a&gt;, showing that function is indeed a high-level print function for text.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="well-that-just-got-complicated"&gt;
&lt;h2&gt;Well that just got complicated&lt;/h2&gt;
&lt;p&gt;So 1985 Oregon Trail is written in &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Applesoft_BASIC"&gt;Applesoft BASIC&lt;/a&gt;, and the program is stored as some sort of bytecode. My heart sank a little at this news; 6502 assembly is cumbersome to understand at the best of times, and throwing a BASIC virtual machine on top of that makes live debugging even worse. I didn't have time to hack together a way to trace the BASIC code as it executed.&lt;/p&gt;
&lt;p&gt;However this did open up a new angle of attack. A BASIC program uses variables, which are stored in a known location in a known format. What if we let the game play, then kept an eye on the debugger's live memory view to see what variables change over time?&lt;/p&gt;
&lt;p&gt;The good news is that Applesoft BASIC keeps the original names for each variable; a critical clue. The downside is that those names are (at most) two letters long. Also the memory is managed dynamically, so variables will move around as the program chugs on.&lt;/p&gt;
&lt;p&gt;From poking around the ROM disassembly, the pointer to the variable store is at address 0x0069 of the Zero Page (usually 0x9902), followed by a pointer to the array store, and a pointer to all of the string data. Each variable is 7 bytes long; 2 bytes for the name (using the high bits of each to encode type), and 5 bytes data. The default numeric type in Applesoft BASIC is a cursed 40-bit floating point format, with an 8-bit exponent and a 31-bit mantissa.&lt;/p&gt;
&lt;p&gt;After a bunch of test runs I found a variable called H, which increased in line with my health status getting worse. Even better, setting the H data to all zeros reset the status back to &amp;quot;good&amp;quot;, after which it seemed to decay back to &amp;quot;very poor&amp;quot; at the previous rate. There was also a H1 array that seemed to keep track of party members that had died (0 for alive, -1 for dead), and a H2 array which seemed to keep track of whatever exciting disease each party member had. Easy! Open and shut!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="did-we-make-it"&gt;
&lt;h2&gt;Did we make it?&lt;/h2&gt;
&lt;p&gt;New Years Eve 2025 had arrived, and with it attempt 2 to reach the barren wastelands of future Oregon. This time, the time machine stopped at 16120 AD.&lt;/p&gt;
&lt;img src="/writing/2025/01/11/waiting_for_oregon/rip.jpg" alt="A screenshot of albrot&amp;#x27;s second Oregon Trail Time Machine attempt, some 5 days after the stream began. There is a tombstone that reads &amp;quot;Here lies ALBROT&amp;quot;. The streamer is facepalming." class="" title="RIP albrot, what was he even doing up on the roof"/&gt; &lt;p&gt;A dismal failure. Even when zeroing out the memory, every day H would reset to 139, dooming the party to a short-but-agonizing fate. Cheating death is harder than expected.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="we-can-still-save-this"&gt;
&lt;h2&gt;We can still save this&lt;/h2&gt;
&lt;p&gt;Several days later, I tried writing a scrappy decompiler for the Applesoft BASIC bytecode. From past experience I was worried this would be real complex, but in the mother of all lucky breaks the &amp;quot;bytecode&amp;quot; is the original program text with certain keywords replaced with 1-byte tokens. After nicking the list of tokens from the Apple II ROM disassembly I had a half-decent decompiler after a few goes.&lt;/p&gt;
&lt;p&gt;(This ultimately turned out to be unnecessary. If I had looked more closely at the options for &lt;a class="reference external" href="https://ciderpress2.com/"&gt;CiderPress II&lt;/a&gt;, I would've seen that you can use the &amp;quot;&lt;a class="reference external" href="https://github.com/fadden/CiderPress2/blob/main/docs/Manual-cp2.md#importip"&gt;import&lt;/a&gt;&amp;quot; and &amp;quot;&lt;a class="reference external" href="https://github.com/fadden/CiderPress2/blob/main/docs/Manual-cp2.md#exportxp"&gt;export&lt;/a&gt;&amp;quot; commands to convert Applesoft BASIC files to and from plain text).&lt;/p&gt;
&lt;p&gt;So of &lt;em&gt;course&lt;/em&gt; we had to look for the code that calculates the player's health. Here it is, from &lt;tt class="docutils literal"&gt;OREGON TRAIL&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="il"&gt;3200&lt;/span&gt;
&lt;span class="kd"&gt;LET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;H0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;C0&lt;/span&gt;
&lt;span class="kr"&gt;FOR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;L&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;C0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;C4&lt;/span&gt;
&lt;span class="kr"&gt;IF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;H1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vg"&gt;L&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;C0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;THEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;H2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vg"&gt;L&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;H2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vg"&gt;L&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;C1&lt;/span&gt;
&lt;span class="kd"&gt;LET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;H0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;H0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;C1&lt;/span&gt;
&lt;span class="kr"&gt;IF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;H2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vg"&gt;L&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;C1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;THEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;H1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vg"&gt;L&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;C0&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="il"&gt;3205&lt;/span&gt;
&lt;span class="kr"&gt;NEXT&lt;/span&gt;
&lt;span class="kr"&gt;IF&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;RND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vg"&gt;C1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;P5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;THEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;TM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;QT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;INT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;RND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vg"&gt;C1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="il"&gt;41&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;LET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;PP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;RND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vg"&gt;C1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;QP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;LET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;W&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;INT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="vg"&gt;TM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="il"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="il"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;LET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;TM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;W&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="il"&gt;3206&lt;/span&gt;
&lt;span class="kd"&gt;LET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;TR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;C0&lt;/span&gt;
&lt;span class="kd"&gt;LET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;TS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;C0&lt;/span&gt;
&lt;span class="kr"&gt;IF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;PP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;THEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;Z&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;RND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vg"&gt;C1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;.3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;LET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;W&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="il"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;Z&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;Z&lt;/span&gt;
&lt;span class="kd"&gt;LET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;TR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;.2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;.6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;Z&lt;/span&gt;
&lt;span class="kr"&gt;IF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;TM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;C2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;THEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;W&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;W&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;C1&lt;/span&gt;
&lt;span class="kd"&gt;LET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;TS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="il"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;TR&lt;/span&gt;
&lt;span class="kd"&gt;LET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;TR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;C0&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="il"&gt;3207&lt;/span&gt;
&lt;span class="kd"&gt;LET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;ZT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;TM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;C3&lt;/span&gt;
&lt;span class="kr"&gt;IF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;ZT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;C0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;THEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;ZT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;C2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;TM&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="il"&gt;3210&lt;/span&gt;
&lt;span class="kd"&gt;LET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;ZC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="il"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;TM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;TM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;OP&lt;/span&gt;
&lt;span class="kr"&gt;IF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;ZC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;C0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;THEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;ZC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;C0&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="il"&gt;3215&lt;/span&gt;
&lt;span class="kd"&gt;LET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;X&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vg"&gt;ZC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;P5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;LET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;Y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vg"&gt;PF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;C0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;LET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;ZF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;F0&lt;/span&gt;
&lt;span class="kr"&gt;IF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;Y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;THEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;ZF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="il"&gt;8&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="il"&gt;3220&lt;/span&gt;
&lt;span class="kd"&gt;LET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;ZP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vg"&gt;W&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="il"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vg"&gt;W&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="il"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;P&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;P&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="il"&gt;3225&lt;/span&gt;
&lt;span class="kd"&gt;LET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;Z&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;FS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;P5&lt;/span&gt;
&lt;span class="kr"&gt;IF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;X&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;OR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;Y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;THEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;Z&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;FS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;.8&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="il"&gt;3230&lt;/span&gt;
&lt;span class="kd"&gt;LET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;FS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;Z&lt;/span&gt;
&lt;span class="kd"&gt;LET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;H&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;.9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;H&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;ZT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;ZC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;ZF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;ZP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;FS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;H0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;HR&lt;/span&gt;
&lt;span class="kd"&gt;LET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;PF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;PF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;FC&lt;/span&gt;
&lt;span class="kr"&gt;IF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;PF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;C0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;THEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;PF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;C0&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="il"&gt;3235&lt;/span&gt;
&lt;span class="kr"&gt;IF&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="ow"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;W1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vg"&gt;H&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="il"&gt;139&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;THEN&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;GOSUB&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;10300&lt;/span&gt;
&lt;span class="vg"&gt;INVERSE&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Constants:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;C0&lt;/strong&gt;: 0.0&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C1&lt;/strong&gt;: 1.0&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C2&lt;/strong&gt;: 2.0&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C3&lt;/strong&gt;: 3.0&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C4&lt;/strong&gt;: 4.0&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;P5&lt;/strong&gt;: 0.5&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Inputs:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;NP&lt;/strong&gt;: number of party members alive.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;P&lt;/strong&gt;: pace. 1: steady, 2: strenuous, 3: gruelling&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PF&lt;/strong&gt;: pounds of food left. Copied over from I(8).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;R&lt;/strong&gt;: rations. 1: filling, 2: meagre, 3: bare-bones&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;W&lt;/strong&gt;: weather. 0: very cold, 1: cold, 2: cool, 3: warm, 4: hot, 5: very hot, 6: rainy, 7: snowy, 8: very rainy, 9: very snowy&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Calculated factors:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;FC&lt;/strong&gt;: pounds of food consumed. &lt;tt class="docutils literal"&gt;FC = NP * (4 - R)&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ZC&lt;/strong&gt;: clothing misery factor. &lt;tt class="docutils literal"&gt;ZC = 5 - W * 2 - (clothes/NP)&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ZF&lt;/strong&gt;: food misery factor. &lt;tt class="docutils literal"&gt;ZF = 2 * (R - 1)&lt;/tt&gt;, or &lt;tt class="docutils literal"&gt;= 8&lt;/tt&gt; if you have no food left&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ZP&lt;/strong&gt;: pace misery factor. &lt;tt class="docutils literal"&gt;ZP = (W &amp;gt; 5) + (W &amp;gt; 7) + P + P&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ZT&lt;/strong&gt;: temperature misery factor. &lt;tt class="docutils literal"&gt;ZT = 0&lt;/tt&gt; if W is warm or cool, otherwise the number of steps away from warm or cool.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FS&lt;/strong&gt;: food starvation factor. &lt;tt class="docutils literal"&gt;FS = FS + 0.8&lt;/tt&gt; if there's no food or if ZC &amp;gt; 0.5, otherwise &lt;tt class="docutils literal"&gt;FS = FS * 0.5&lt;/tt&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;H0&lt;/strong&gt;: seems to be &lt;tt class="docutils literal"&gt;H0 = 5.0&lt;/tt&gt; at calc time?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HR&lt;/strong&gt;: hardship factor. &lt;tt class="docutils literal"&gt;HR = 10&lt;/tt&gt; if the trail is rough, &lt;tt class="docutils literal"&gt;HR = 20&lt;/tt&gt; if someone just died, 20% chance of &lt;tt class="docutils literal"&gt;HR = 20&lt;/tt&gt; if the water is bad, 10% chance of &lt;tt class="docutils literal"&gt;HR = 10&lt;/tt&gt; if there's very little water&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;H&lt;/strong&gt;: party health score. 0-34 is good, 35-69 is fair, 70-104 is poor, 105-139 is very poor. Capped at 139. &lt;tt class="docutils literal"&gt;H = .9 * H + ZT + ZC + ZF + ZP + FS + H0 + HR&lt;/tt&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The inputs were figured out by checking what text the game prints on the screen for those variables; e.g. W is used to fetch a string from an array W$ that looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;0xa0ee&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;5780250001000a&lt;span class="w"&gt;  &lt;/span&gt;W$&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;0xa0f8&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;09efa6&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;b&lt;span class="s1"&gt;&amp;#39;very cold&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;0xa0fb&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;04eba6&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;b&lt;span class="s1"&gt;&amp;#39;cold&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;0xa0fe&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;04e7a6&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;b&lt;span class="s1"&gt;&amp;#39;cool&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;0xa101&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;04e3a6&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;b&lt;span class="s1"&gt;&amp;#39;warm&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;0xa104&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;03e0a6&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;b&lt;span class="s1"&gt;&amp;#39;hot&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;0xa107&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;08d8a6&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;b&lt;span class="s1"&gt;&amp;#39;very hot&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;0xa10a&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;05d3a6&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;b&lt;span class="s1"&gt;&amp;#39;rainy&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;0xa10d&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;05cea6&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;b&lt;span class="s1"&gt;&amp;#39;snowy&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;0xa110&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;0ac4a6&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;b&lt;span class="s1"&gt;&amp;#39;very rainy&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;0xa113&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;0abaa6&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;b&lt;span class="s1"&gt;&amp;#39;very snowy&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The health factors are all conjecture based on the inputs, but I think they make sense. We have the formula for H, and there's only one misery factor which would be affected by waiting 14272 years: the food starvation factor. In case you're wondering what that looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;0x9bb0&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;4653967e4f4234&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;FS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4166608&lt;/span&gt;.55078125
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Yikes. We can see that every day after crossing Snake River, H would increase by at least 4.1 million, and then be rounded down to the worst possible health score of 139. It would take 15 days of doing nothing but sitting and eating to bring the food starvation factor down to 127, by which point everyone in the party would be dead.&lt;/p&gt;
&lt;p&gt;But finally, FINALLY we know how to distort reality enough to get this train back on the rails. The party is back to full health, all that's left is to get past the final fork at The Dalles and...&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="did-we-make-it-to-oregon"&gt;
&lt;h2&gt;Did we make it to Oregon???&lt;/h2&gt;
&lt;img src="/writing/2025/01/11/waiting_for_oregon/error53.png" alt="A screenshot of an error screen. The text reads &amp;quot;Error 53 at line #50050 in Oregon Trail. Please report this error to MECC.&amp;quot;" class="" title="This only happened because I threw out the receipt"/&gt; &lt;p&gt;Piss. I couldn't find a technical contact for MECC, so I dug around and saw line 50050 was in TRADE.LIB; namely the code responsible for drawing the inventory. Error 53 is the Apple II error code for &lt;tt class="docutils literal"&gt;GS/OS: parameter out of range&lt;/tt&gt;. But what could possibly be wrong with the inventory? It was fine on the road!&lt;/p&gt;
&lt;p&gt;Oh. Wait.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;0x9d29&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;49802200010009&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;I$&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;0x9d33&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;000000&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;b&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;0x9d36&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;0512a9&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;b&lt;span class="s1"&gt;&amp;#39;Wagon&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;0x9d39&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;040ea9&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;b&lt;span class="s1"&gt;&amp;#39;oxen&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;0x9d3c&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;10fea8&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;b&lt;span class="s1"&gt;&amp;#39;sets of clothing&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;0x9d3f&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;07f7a8&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;b&lt;span class="s1"&gt;&amp;#39;bullets&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;0x9d42&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;0ceba8&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;b&lt;span class="s1"&gt;&amp;#39;wagon wheels&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;0x9d45&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;0be0a8&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;b&lt;span class="s1"&gt;&amp;#39;wagon axles&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;0x9d48&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;0dd3a8&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;b&lt;span class="s1"&gt;&amp;#39;wagon tongues&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;0x9d4b&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;0ec5a8&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;b&lt;span class="s1"&gt;&amp;#39;pounds of food&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;0x9d4b&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;49003400010009&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;I&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;0x9d57&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;ffffffffff&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;-1.7014118342085515e+38
&lt;span class="o"&gt;[&lt;/span&gt;0x9d5c&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;ffffffffff&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;-1.7014118342085515e+38
&lt;span class="o"&gt;[&lt;/span&gt;0x9d61&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;8310000000&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.5
&lt;span class="o"&gt;[&lt;/span&gt;0x9d66&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;8528000000&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;21&lt;/span&gt;.0
&lt;span class="o"&gt;[&lt;/span&gt;0x9d6b&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;8a09000000&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;548&lt;/span&gt;.0
&lt;span class="o"&gt;[&lt;/span&gt;0x9d70&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;8100000000&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.0
&lt;span class="o"&gt;[&lt;/span&gt;0x9d75&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;8100000000&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.0
&lt;span class="o"&gt;[&lt;/span&gt;0x9d7a&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;8100000000&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.0
&lt;span class="o"&gt;[&lt;/span&gt;0x9d7f&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;8200000000&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.0
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The game was kind enough to gift me -170 billion billion billion billion wagons.&lt;/p&gt;
&lt;p&gt;Actually this was a red herring. Line 50050 is also on Side B inside END.LIB, aka. the screen before the crash (choosing between floating down the Columbia River or taking the Barlow Toll Road).&lt;/p&gt;
&lt;p&gt;And guess what.&lt;/p&gt;
&lt;p&gt;It reads the date:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# 50050&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;0x023d&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;POKE&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;900&lt;/span&gt;,NP
&lt;span class="o"&gt;[&lt;/span&gt;0x0245&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;POKE&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;901&lt;/span&gt;,AY&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1800&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;0x0252&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;POKE&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;902&lt;/span&gt;,AM
&lt;span class="o"&gt;[&lt;/span&gt;0x025a&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;POKE&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;903&lt;/span&gt;,AD
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;AY&lt;/tt&gt; is the variable containing the year, and the POKE instruction tries to shove the value of &lt;tt class="docutils literal"&gt;AY - 1800&lt;/tt&gt; into &lt;em&gt;a single byte&lt;/em&gt; of the regular Apple II memory, causing a crash as it is much larger than the maximum of 255.&lt;/p&gt;
&lt;p&gt;These POKEs seem to be how Oregon Trail saves key information when it switches between BASIC programs. Most of the game is handled by the main program &amp;quot;OREGON TRAIL&amp;quot; (written by John Krenz), which loads in modules like &amp;quot;RIVER.LIB&amp;quot; and keeps access to all the variables. Whenever a new main program is loaded in, all the BASIC variables are lost except for whatever data you happened to copy to regular memory with POKE. At the end of the game you have the option of floating down the river, which uses a new main program &amp;quot;FLOAT&amp;quot; (written by Steven Splinter) before switching again to the &amp;quot;WIN&amp;quot; program, so it follows that the game would need to use the POKEd numbers.&lt;/p&gt;
&lt;p&gt;What happens if we mod the BASIC code in memory to say something else?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;...
POKE&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;901&lt;/span&gt;,255
...
&lt;/pre&gt;&lt;/div&gt;
&lt;img src="/writing/2025/01/11/waiting_for_oregon/badending.png" alt="A screenshot of the ending screen of Oregon Trail, showing the beautiful Willamette Valley, and the utterly incorrect year of 18255." class="" title="Oh no."/&gt; &lt;p&gt;Finally we have a definitive answer about whether this whole ordeal is possible. Unlike &lt;em&gt;every other progress screen in the game&lt;/em&gt;, the ending screen has the year hardcoded to start with &amp;quot;18&amp;quot;, followed by the printed text of whatever &lt;tt class="docutils literal"&gt;PEEK 901&lt;/tt&gt; is. The maximum number of years you can take without crashing the game is 207, and any attempts longer than 51 years will end in the wrong century.&lt;/p&gt;
&lt;p&gt;Really, there wasn't much point going on; it felt like further trickery went against the spirit of the challenge. Maybe there's closure enough in knowing that if you somehow avoid starvation for 142 centuries, the game dicks you at the last possible moment by expecting the year to be sensible.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="then-again-we-re-98-of-the-way-there"&gt;
&lt;h2&gt;Then again we're 98% of the way there&lt;/h2&gt;
&lt;p&gt;I lied. &lt;a class="reference external" href="https://moral.net.au/misc/oregon_sideb_mod.do"&gt;Here's&lt;/a&gt; a improved version of Oregon Trail side B with the following quality-of-life changes:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Waiting by a river for conditions to improve resets the food starvation factor to 0.&lt;/li&gt;
&lt;li&gt;The final sequence uses two bytes to store the year.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Many thanks to the CiderPress II team for making it easy to load replacement code onto the floppy.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusions"&gt;
&lt;h2&gt;Conclusions&lt;/h2&gt;
&lt;img src="/writing/2025/01/11/waiting_for_oregon/success.png" alt="A screenshot of the ending screen of Oregon Trail, showing the beautiful Willamette Valley, and the correct end year of 16120." class="" title="Nature has reclaimed post-civilisation Oregon"/&gt; &lt;p&gt;Applesoft BASIC is majestically slow, but thanks to 40-bit floating point and standard commands for drawing and text manipulation, Oregon Trail works much better than I could have expected after running the simulation for 14272 years. The final screen being broken is a bit of a letdown, but understandable given how unlikely it is that you'd take 14272 years to finish the game.&lt;/p&gt;
&lt;p&gt;Honestly, one of my aims has been to create a reverse engineering approach that will work for any system, regardless of knowledge or experience, and give you the basic steps for how to understand and modify it. This was a bit of a test run of this, and I think it could be considered a success. Eventually I'll write it down properly, but hopefully this effort will give you some ideas for your next reversing project.&lt;/p&gt;
&lt;p&gt;And albrot? Well, he was able to fulfill his dream and become the &lt;a class="reference external" href="https://www.twitch.tv/albrot/clip/MushyFlirtyPorcupineOhMyDog-r1PPasYUcpDPTW4e"&gt;first person to survive for 15000 years on the Oregon Trail&lt;/a&gt;. A well deserved victory.&lt;/p&gt;
&lt;p&gt;If you're interested in the tools I wrote for this work, the Applesoft BASIC decompiler and variable scraper I wrote are available &lt;a class="reference external" href="https://github.com/moralrecordings/applesoft-basic-decompiler"&gt;here&lt;/a&gt;. Happy trails!&lt;/p&gt;
&lt;/div&gt;
</content><category term="Devlog"/><category term="oregon trail"/><category term="reversing"/><category term="obsolete"/></entry><entry><title>Show Your Working: Making Powerline Ethernet faster</title><link href="/writing/2024/02/25/powerether/" rel="alternate"/><published>2024-02-25T00:00:00+08:00</published><updated>2024-02-25T00:00:00+08:00</updated><author><name>Scott</name></author><id>tag:None,2024-02-25:/writing/2024/02/25/powerether/</id><summary type="html">&lt;img src="/writing/2024/02/25/powerether/powerboard.jpg" alt="An Australian powerboard with 12 electrical sockets. The leftmost socket is occupied by a massive Netgear Powerline Ethernet adapter, complete with trailing Cat5e cable. Six of the other sockets are occupied with electrical plugs of different styles." class="letterbox" title="It&amp;#x27;s only the guilty who concern themselves with clearing their names."/&gt; &lt;p&gt;Having wired networking is nice! The current iterations of Wi-Fi are admittedly very fast, but I feel a bit iffy trusting that the signal will &lt;em&gt;always&lt;/em&gt; work at a good latency, when there's rogue slabs of concrete and surprise microwave sources to deal with.&lt;/p&gt;
&lt;p&gt;My house has fiber internet which terminates at the wireless router I keep under my TV. To enable mission-critical house activities (e.g. streaming from my studio to Twitch/Diode Zone, using Steam Link from the couch), I have some Powerline Ethernet adapters which link the router to my studio PC upstairs. And for the past several years, they have been indistinguishable from magic; somehow they turn 1970s home electrical wiring into Cat 5e!&lt;/p&gt;
&lt;p&gt;Until the rubber finally met the road; I tried to watch a Blu-Ray on my Home Theatre PC, streamed from my Studio PC, and it had big buffering pauses every 60 seconds. An early speed test I did showed the link speed for streaming the Blu-Ray was only &lt;strong&gt;24 Mbps&lt;/strong&gt;! But for some reason, downloading things from the studio PC was absolutely fine! At this point I realised I knew very little about how the magic boxes worked, or how I would go about isolating the problem.&lt;/p&gt;
</summary><content type="html">&lt;img src="/writing/2024/02/25/powerether/powerboard.jpg" alt="An Australian powerboard with 12 electrical sockets. The leftmost socket is occupied by a massive Netgear Powerline Ethernet adapter, complete with trailing Cat5e cable. Six of the other sockets are occupied with electrical plugs of different styles." class="letterbox" title="It&amp;#x27;s only the guilty who concern themselves with clearing their names."/&gt; &lt;p&gt;Having wired networking is nice! The current iterations of Wi-Fi are admittedly very fast, but I feel a bit iffy trusting that the signal will &lt;em&gt;always&lt;/em&gt; work at a good latency, when there's rogue slabs of concrete and surprise microwave sources to deal with.&lt;/p&gt;
&lt;p&gt;My house has fiber internet which terminates at the wireless router I keep under my TV. To enable mission-critical house activities (e.g. streaming from my studio to Twitch/Diode Zone, using Steam Link from the couch), I have some Powerline Ethernet adapters which link the router to my studio PC upstairs. And for the past several years, they have been indistinguishable from magic; somehow they turn 1970s home electrical wiring into Cat 5e!&lt;/p&gt;
&lt;p&gt;Until the rubber finally met the road; I tried to watch a Blu-Ray on my Home Theatre PC, streamed from my Studio PC, and it had big buffering pauses every 60 seconds. An early speed test I did showed the link speed for streaming the Blu-Ray was only &lt;strong&gt;24 Mbps&lt;/strong&gt;! But for some reason, downloading things from the studio PC was absolutely fine! At this point I realised I knew very little about how the magic boxes worked, or how I would go about isolating the problem.&lt;/p&gt;

&lt;div class="section" id="what-is-powerline-ethernet"&gt;
&lt;h2&gt;What is Powerline Ethernet?&lt;/h2&gt;
&lt;p&gt;The Powerline Ethernet adapters I have are a pair of &lt;a class="reference external" href="https://www.netgear.com/au/home/wired/powerline/pl1000/"&gt;Netgear PL1000 extenders&lt;/a&gt;, which (amusingly) offer &amp;quot;up to Gigabit speeds&amp;quot;. These adapters are based on the HomePlug AV2 standard (later rolled into IEEE 1901), which uses the live-neutral and live-ground pairs of your house's mains wiring as communication channels. Which is to say, the transmitter in the adapter generates a radio-band AC signal and sends it into the wiring with a coupling capacitor, allowing it to overlay the thumping great 50 Hz wave of the mains current. The receiver in the adapter filters out all the non-radio AC frequencies and tries to decipher a working signal from what's left. As for other mains devices, they should be filtering their power and ignore this as a little bit of line noise.&lt;/p&gt;
&lt;p&gt;The trick that makes this possible is our friend &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Orthogonal_frequency-division_multiplexing"&gt;Orthogonal Frequency-Division Multiplexing&lt;/a&gt;; basically if you have an analogue medium as a channel, you can cram multiple digital signals into that channel using carefully overlapped frequency bands and fancy error correction. For HomePlug AV2 the frequency bands in use range from 1.6 MHz to 86 MHz; you might recognise these numbers as &amp;quot;the end of AM radio&amp;quot; and &amp;quot;the start of FM radio&amp;quot;, or if you are an Australian RF engineer &amp;quot;the end of getting sued by Macquarie Media&amp;quot; and &amp;quot;the start of getting sued by Southern Cross Austereo&amp;quot;.&lt;/p&gt;
&lt;p&gt;All well and good, but how do we test performance? Ethernet is a protocol running on top of this physical channel layer. None of the losses or error correction are exposed as packet level messages; all we get is the apparent link speed being slower or faster.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="testing-powerline-ethernet"&gt;
&lt;h2&gt;Testing Powerline Ethernet&lt;/h2&gt;
&lt;p&gt;After a bit of trial and error, I used a little tool called &lt;a class="reference external" href="https://iperf.fr/"&gt;iperf3&lt;/a&gt; to measure the maximum bandwidth on the link, with various combinations of things plugged in. Early on it became apparent that the speed in one direction was determined by the amount of noise at the &lt;strong&gt;receiver&lt;/strong&gt; end; so in this case, something on my router's powerboard was dumping out enough RF slop to slow down the Blu-Ray data streaming from my studio PC. But which thing, and by how much? Let's find out!&lt;/p&gt;
&lt;div class="section" id="is-it-the-surge-protector"&gt;
&lt;h3&gt;Is it the surge protector?&lt;/h3&gt;
&lt;p&gt;One of the many axioms about Powerline Ethernet that frequently comes up is &amp;quot;for the love of God never run it through a surge protector&amp;quot;. I assume this advice comes from a place where single-plug wall outlets are banned and lightning doesn't exist. Given that there's still a signal getting through, my gut feeling was to downgrade this advice to &amp;quot;never run it through a &lt;em&gt;crap&lt;/em&gt; surge protector&amp;quot;. But we can do some tests to prove the actual impact, even if we intend to ignore it. Here are speed tests for the bare outlets with/without a surge protector inline; both surge protectors are recent model HPM 12-socket powerboards, with the powerline adapter in the socket closest to the cable.&lt;/p&gt;
&lt;table border="1" class="docutils align-center"&gt;
&lt;caption&gt;Speed tests with a surge protector&lt;/caption&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;Router surge protector&lt;/th&gt;
&lt;th class="head"&gt;Studio surge protector&lt;/th&gt;
&lt;th class="head"&gt;Router -&amp;gt; Studio speed&lt;/th&gt;
&lt;th class="head"&gt;Studio -&amp;gt; Router speed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;196 Mbits/s&lt;/td&gt;
&lt;td&gt;141 Mbits/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;186 Mbits/s&lt;/td&gt;
&lt;td&gt;135 Mbits/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;186 Mbits/s&lt;/td&gt;
&lt;td&gt;144 Mbits/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;168 Mbits/s&lt;/td&gt;
&lt;td&gt;131 Mbits/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;So it's about a 10% speed hit. Sounds good to me! It's almost as if the designers knew that powerline communication was an electrical standard and designed their filtering circuits to cope.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="is-it-one-of-my-appliances"&gt;
&lt;h3&gt;Is it one of my appliances?&lt;/h3&gt;
&lt;p&gt;Now to test the impact of individual devices. My house is wired entirely with single-plug outlets (why would you do this???), so the intention is to keep using powerboards on both ends. The baseline setup is:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;my full studio setup online and unchanged&lt;/li&gt;
&lt;li&gt;the Powerline Ethernet adapter, an ASUS router and a fiber NTD plugged into the router-side powerboard&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As the issue was happening at the router-side receiver, I decided to try plugging in combinations of the devices that make up my home theatre system.&lt;/p&gt;
&lt;table border="1" class="docutils align-center"&gt;
&lt;caption&gt;Speed tests with combinations of appliances&lt;/caption&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;Amp&lt;/th&gt;
&lt;th class="head"&gt;TV&lt;/th&gt;
&lt;th class="head"&gt;MiSTer&lt;/th&gt;
&lt;th class="head"&gt;PC&lt;/th&gt;
&lt;th class="head"&gt;Router -&amp;gt; Studio speed&lt;/th&gt;
&lt;th class="head"&gt;Studio -&amp;gt; Router speed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;108 Mbit/s&lt;/td&gt;
&lt;td&gt;147 Mbits/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;108 Mbit/s&lt;/td&gt;
&lt;td&gt;114 Mbits/s standby, 93.1 Mbits/s on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;107 Mbit/s&lt;/td&gt;
&lt;td&gt;122 Mbits/s standby, 108 Mbits/s on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;109 Mbit/s&lt;/td&gt;
&lt;td&gt;142 Mbits/s standby, 112 Mbits/s on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;103 Mbit/s&lt;/td&gt;
&lt;td&gt;104 Mbits/s standby, 38 Mbit/s on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;105 Mbit/s&lt;/td&gt;
&lt;td&gt;91 Mbit/s standby, 40 Mbit/s on&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;There it is! My problem was that I wanted some sound to go with my Blu-Ray, unaware that my digital amplifier has a side-gig of spewing out RF pollution all day.&lt;/p&gt;
&lt;img src="/writing/2024/02/25/powerether/guilty.jpg" alt="A unbranded 24V 4A DC power supply. It has several worthless certification symbols on it." class="" title="GUILTY! GUIIIILTY! Jail for 100 years!!!"/&gt; &lt;p&gt;The class-D amp in question came with this unbranded 24V 4A DC power brick. Like all mystery power bricks it is adorned with The Runes: CE certification, the C-Tick mark for ACMA approval, and an approval number that suggests TUV certified it. I could find none of these numbers in the &lt;a class="reference external" href="https://equipment.erac.gov.au/Public/"&gt;public certification database&lt;/a&gt;, but we can at least be at ease that whoever sold this power brick owned a sticker printer.&lt;/p&gt;
&lt;p&gt;Does replacing this with a legit Australian-certified power supply fix the issue? I got a replacement from Altronics; notably it is heavier, has bigger capacitors and features a ferrite choke on the lead. My enthusiasm for knowledge did not extend to cracking open the sealed cases to compare circuit boards, but filtering components are usually the first things that get stripped for cost reasons as end-users tend not to notice.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Connecting&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;host&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.1.4,&lt;span class="w"&gt; &lt;/span&gt;port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5201&lt;/span&gt;
Reverse&lt;span class="w"&gt; &lt;/span&gt;mode,&lt;span class="w"&gt; &lt;/span&gt;remote&lt;span class="w"&gt; &lt;/span&gt;host&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.1.4&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;sending
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.1.8&lt;span class="w"&gt; &lt;/span&gt;port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;53286&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;connected&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.1.4&lt;span class="w"&gt; &lt;/span&gt;port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5201&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ID&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Interval&lt;span class="w"&gt;           &lt;/span&gt;Transfer&lt;span class="w"&gt;     &lt;/span&gt;Bitrate
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.00-1.00&lt;span class="w"&gt;   &lt;/span&gt;sec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;.25&lt;span class="w"&gt; &lt;/span&gt;MBytes&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;52&lt;/span&gt;.4&lt;span class="w"&gt; &lt;/span&gt;Mbits/sec
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.00-2.00&lt;span class="w"&gt;   &lt;/span&gt;sec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;.50&lt;span class="w"&gt; &lt;/span&gt;MBytes&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;62&lt;/span&gt;.9&lt;span class="w"&gt; &lt;/span&gt;Mbits/sec
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.00-3.00&lt;span class="w"&gt;   &lt;/span&gt;sec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;.75&lt;span class="w"&gt; &lt;/span&gt;MBytes&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;73&lt;/span&gt;.4&lt;span class="w"&gt; &lt;/span&gt;Mbits/sec
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.00-4.00&lt;span class="w"&gt;   &lt;/span&gt;sec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;.12&lt;span class="w"&gt; &lt;/span&gt;MBytes&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;76&lt;/span&gt;.5&lt;span class="w"&gt; &lt;/span&gt;Mbits/sec
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.00-5.00&lt;span class="w"&gt;   &lt;/span&gt;sec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;.38&lt;span class="w"&gt; &lt;/span&gt;MBytes&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;61&lt;/span&gt;.9&lt;span class="w"&gt; &lt;/span&gt;Mbits/sec
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;.00-6.00&lt;span class="w"&gt;   &lt;/span&gt;sec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;.88&lt;span class="w"&gt; &lt;/span&gt;MBytes&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;57&lt;/span&gt;.7&lt;span class="w"&gt; &lt;/span&gt;Mbits/sec
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;.00-7.00&lt;span class="w"&gt;   &lt;/span&gt;sec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;.12&lt;span class="w"&gt; &lt;/span&gt;MBytes&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;59&lt;/span&gt;.8&lt;span class="w"&gt; &lt;/span&gt;Mbits/sec
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;.00-8.00&lt;span class="w"&gt;   &lt;/span&gt;sec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;.25&lt;span class="w"&gt; &lt;/span&gt;MBytes&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;52&lt;/span&gt;.4&lt;span class="w"&gt; &lt;/span&gt;Mbits/sec
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;.00-9.00&lt;span class="w"&gt;   &lt;/span&gt;sec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;.12&lt;span class="w"&gt; &lt;/span&gt;MBytes&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;59&lt;/span&gt;.8&lt;span class="w"&gt; &lt;/span&gt;Mbits/sec
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;.00-10.00&lt;span class="w"&gt;  &lt;/span&gt;sec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;.75&lt;span class="w"&gt; &lt;/span&gt;MBytes&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;65&lt;/span&gt;.0&lt;span class="w"&gt; &lt;/span&gt;Mbits/sec
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.00-11.00&lt;span class="w"&gt;  &lt;/span&gt;sec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;.25&lt;span class="w"&gt; &lt;/span&gt;MBytes&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;60&lt;/span&gt;.8&lt;span class="w"&gt; &lt;/span&gt;Mbits/sec
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;.00-12.00&lt;span class="w"&gt;  &lt;/span&gt;sec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;.75&lt;span class="w"&gt; &lt;/span&gt;MBytes&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;65&lt;/span&gt;.0&lt;span class="w"&gt; &lt;/span&gt;Mbits/sec
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;.00-13.00&lt;span class="w"&gt;  &lt;/span&gt;sec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;.12&lt;span class="w"&gt; &lt;/span&gt;MBytes&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;76&lt;/span&gt;.5&lt;span class="w"&gt; &lt;/span&gt;Mbits/sec
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;.00-14.00&lt;span class="w"&gt;  &lt;/span&gt;sec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;.38&lt;span class="w"&gt; &lt;/span&gt;MBytes&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;78&lt;/span&gt;.6&lt;span class="w"&gt; &lt;/span&gt;Mbits/sec
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;.00-15.00&lt;span class="w"&gt;  &lt;/span&gt;sec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;.12&lt;span class="w"&gt; &lt;/span&gt;MBytes&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;76&lt;/span&gt;.5&lt;span class="w"&gt; &lt;/span&gt;Mbits/sec
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;.00-16.00&lt;span class="w"&gt;  &lt;/span&gt;sec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;.88&lt;span class="w"&gt; &lt;/span&gt;MBytes&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;74&lt;/span&gt;.5&lt;span class="w"&gt; &lt;/span&gt;Mbits/sec
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;16&lt;/span&gt;.00-17.00&lt;span class="w"&gt;  &lt;/span&gt;sec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;.25&lt;span class="w"&gt; &lt;/span&gt;MBytes&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;77&lt;/span&gt;.6&lt;span class="w"&gt; &lt;/span&gt;Mbits/sec
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;.00-18.00&lt;span class="w"&gt;  &lt;/span&gt;sec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;.62&lt;span class="w"&gt; &lt;/span&gt;MBytes&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;72&lt;/span&gt;.3&lt;span class="w"&gt; &lt;/span&gt;Mbits/sec
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;.00-19.00&lt;span class="w"&gt;  &lt;/span&gt;sec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;.75&lt;span class="w"&gt; &lt;/span&gt;MBytes&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;73&lt;/span&gt;.4&lt;span class="w"&gt; &lt;/span&gt;Mbits/sec
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;.00-20.00&lt;span class="w"&gt;  &lt;/span&gt;sec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;.62&lt;span class="w"&gt; &lt;/span&gt;MBytes&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;72&lt;/span&gt;.3&lt;span class="w"&gt; &lt;/span&gt;Mbits/sec
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;.00-21.00&lt;span class="w"&gt;  &lt;/span&gt;sec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;.50&lt;span class="w"&gt; &lt;/span&gt;MBytes&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;71&lt;/span&gt;.3&lt;span class="w"&gt; &lt;/span&gt;Mbits/sec
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Much better. Interestingly you can see what looks to be the adaptive error correction optimising for the new noise profile.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusions"&gt;
&lt;h2&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;So, lessons learned:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The receiving speed of Powerline Ethernet is determined by the amount of RF noise at the receiving end.&lt;/li&gt;
&lt;li&gt;Recent surge protectors and powerboards are fine.&lt;/li&gt;
&lt;li&gt;Plugging in devices at the receiving end will nibble away at the speed but not by much, IF they use a certified power supply that doesn't cut corners!&lt;/li&gt;
&lt;li&gt;If you're experiencing weird or asymmetrical drops in link speed, try unplugging all of the power supplies to isolate the problem.&lt;/li&gt;
&lt;li&gt;Consider replacing no-name DC power bricks with certified ones, or adding a ferrite choke to the lead.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Very special thanks to &lt;a class="reference external" href="https://fosstodon.org/&amp;#64;adonm"&gt;&amp;#64;adonm&lt;/a&gt; for identifying the source of the problem in one guess.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Devlog"/><category term="show your working"/><category term="electricity"/><category term="networking"/></entry><entry><title>Show Your Working: Forcing Linux to print at actual size</title><link href="/writing/2023/05/19/printer_actual_size/" rel="alternate"/><published>2023-05-19T00:00:00+08:00</published><updated>2023-05-19T00:00:00+08:00</updated><author><name>Scott</name></author><id>tag:None,2023-05-19:/writing/2023/05/19/printer_actual_size/</id><summary type="html">&lt;img src="/writing/2023/05/19/printer_actual_size/94mm.jpg" alt="A piece of A4 paper with a box labelled &amp;quot;100mm x 100mm box&amp;quot;. A digital caliper shows the box to be 94mm long." class="letterbox" title="94mm. 94mm!!!!!"/&gt; &lt;p&gt;One day I decided I've had enough of computers and wanted to do literally anything else. Specifically, in that moment I wanted to print out some sewing patterns. Here is a 100mm test square I printed on my printer. Except it's only 94mm. 94mm!!!&lt;/p&gt;
&lt;p&gt;Not going to lie, searching the internet for useful information has become real bleak lately. There's plenty of advice floating around to &amp;quot;Open the PDF in Acrobat and select &amp;quot;Actual size&amp;quot; when printing&amp;quot;, but no advice about the Linux equivalent. To be honest the coward's way out would be to dig out the sacrificial Windows laptop, find a print driver and use Acrobat Reader, but I figured other people might like to know how to fix it, so here you go!&lt;/p&gt;
</summary><content type="html">&lt;img src="/writing/2023/05/19/printer_actual_size/94mm.jpg" alt="A piece of A4 paper with a box labelled &amp;quot;100mm x 100mm box&amp;quot;. A digital caliper shows the box to be 94mm long." class="letterbox" title="94mm. 94mm!!!!!"/&gt; &lt;p&gt;One day I decided I've had enough of computers and wanted to do literally anything else. Specifically, in that moment I wanted to print out some sewing patterns. Here is a 100mm test square I printed on my printer. Except it's only 94mm. 94mm!!!&lt;/p&gt;
&lt;p&gt;Not going to lie, searching the internet for useful information has become real bleak lately. There's plenty of advice floating around to &amp;quot;Open the PDF in Acrobat and select &amp;quot;Actual size&amp;quot; when printing&amp;quot;, but no advice about the Linux equivalent. To be honest the coward's way out would be to dig out the sacrificial Windows laptop, find a print driver and use Acrobat Reader, but I figured other people might like to know how to fix it, so here you go!&lt;/p&gt;

&lt;p&gt;In order to figure out what's going on, let's have a bit of a recap of the Linux printing stack and the problem space in general. Printing under Linux is handled by CUPS, a userspace daemon responsible for connecting to printer hardware and feeding it the right slurry of bits. In this context, a printer &amp;quot;driver&amp;quot; is a &lt;tt class="docutils literal"&gt;PrinterName.ppd&lt;/tt&gt; file (PostScript Printer Description), containing a bunch of information about the hardware, what features are supported, what slurry types to feed it, and so forth. Seperate to that, there is a &lt;tt class="docutils literal"&gt;printers.conf&lt;/tt&gt; file listing each active printer and how it is connected; locally (LPT, USB) or via the network (IPP, JetDirect, SMB).&lt;/p&gt;
&lt;p&gt;Now for the longest time, all of this configuration was manual. There were large collections of PPDs (such as foomatic) containing definitions for thousands of different printer models, usually not the one in front of you. More often than not you'd have to settle for one of the generic drivers, which produce a slurry that a lot of printers eat such as PCL6. If you're really unlucky the printer would need a proprietary driver; this would take the form of a PPD file plus a binary blob executable responsible for generating special Brand-Name Printer Slurry.&lt;/p&gt;
&lt;p&gt;But here we are, with the Dark Age of Printing long behind us. In 2013 the Printer Working Group developed the IPP Everywhere standard, which Apple (amongst others) decided was the future for printing from mobile devices, and so has become a standard for consumer printers. Finally, network printers could enunciate exactly the type of slurry they would like and remove the need for drivers once and for all! You no longer had to suffer like your printing ancestors did.&lt;/p&gt;
&lt;p&gt;Or so the theory goes. The printer I'm using is a Fuji Xerox DocuPrint P265 over IPP. For printing I'm using &lt;tt class="docutils literal"&gt;lpr&lt;/tt&gt;, the CUPS-provided tool for sending files to the print spooler, with print-scaling set to none. In this scaling mode, content should be centered to fit on the page, with no resizing. This means e.g. an A4 sized document printed on A4 paper should be perfectly to scale and have about 5mm chopped off by the hardware page margins. When printing from an application such as Evince, the correct setting for &amp;quot;actual size&amp;quot; printing is Page Handling -&amp;gt; Page Scaling -&amp;gt; None.&lt;/p&gt;
&lt;p&gt;First I tried printing &lt;a class="reference external" href="/writing/2023/05/19/printer_actual_size/100mm.pdf"&gt;a PDF&lt;/a&gt; with a document size of exactly 100mm x 100mm.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;lpr&lt;span class="w"&gt; &lt;/span&gt;100mm.pdf&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;print-scaling&lt;span class="o"&gt;=&lt;/span&gt;none
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The result was a square in the center of the page with a 94mm edge.&lt;/p&gt;
&lt;p&gt;I tried printing &lt;a class="reference external" href="/writing/2023/05/19/printer_actual_size/100mma4.pdf"&gt;another PDF&lt;/a&gt; in A4 with a box at the edge of the page, along with a 100mm square in the center. This produced an interesting tell; the square printed with a 94mm edge, but the page box showed up too! This implies that something was flat-out ignoring the page margin of the printer hardware. And yet surely the IPP standard would account for that!&lt;/p&gt;
&lt;p&gt;After a bit more digging I discovered the &lt;tt class="docutils literal"&gt;driverless&lt;/tt&gt; command bundled with CUPS, which connects to an IPP printer and generates a PPD file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;driverless&lt;span class="w"&gt; &lt;/span&gt;ipp://192.168.1.2/ipp/print
*PPD-Adobe:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;4.3&amp;quot;&lt;/span&gt;
*FormatVersion:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;4.3&amp;quot;&lt;/span&gt;
*FileVersion:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;1.28.17&amp;quot;&lt;/span&gt;
*LanguageVersion:&lt;span class="w"&gt; &lt;/span&gt;English
*LanguageEncoding:&lt;span class="w"&gt; &lt;/span&gt;ISOLatin1
*PSVersion:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(3010.000) 0&amp;quot;&lt;/span&gt;
*LanguageLevel:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;3&amp;quot;&lt;/span&gt;
*FileSystem:&lt;span class="w"&gt; &lt;/span&gt;False
*PCFileName:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;drvless.ppd&amp;quot;&lt;/span&gt;
*Manufacturer:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;FX&amp;quot;&lt;/span&gt;
*ModelName:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;FX DocuPrint P265 dw&amp;quot;&lt;/span&gt;
*Product:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(FX DocuPrint P265 dw)&amp;quot;&lt;/span&gt;
*NickName:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;FX DocuPrint P265 dw, driverless, cups-filters 1.28.17&amp;quot;&lt;/span&gt;
...
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This all looks very sensible, let's see what it has to say about paper dimensions.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;*DefaultPageSize:&lt;span class="w"&gt; &lt;/span&gt;A4
*PageSize&lt;span class="w"&gt; &lt;/span&gt;A4/A4:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;&amp;lt;/PageSize[595.275590551181 841.889763779528]&amp;gt;&amp;gt;setpagedevice&amp;quot;&lt;/span&gt;
*DefaultPageRegion:&lt;span class="w"&gt; &lt;/span&gt;A4
*PageRegion&lt;span class="w"&gt; &lt;/span&gt;A4/A4:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;&amp;lt;/PageSize[595.275590551181 841.889763779528]&amp;gt;&amp;gt;setpagedevice&amp;quot;&lt;/span&gt;
*DefaultImageableArea:&lt;span class="w"&gt; &lt;/span&gt;A4
*ImageableArea&lt;span class="w"&gt; &lt;/span&gt;A4:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;12.245669291339 12.245669291339 583.029921259842 829.644094488189&amp;quot;&lt;/span&gt;
*DefaultPaperDimension:&lt;span class="w"&gt; &lt;/span&gt;A4
*PaperDimension&lt;span class="w"&gt; &lt;/span&gt;A4:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;595.275590551181 841.889763779528&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Yep, there's a margin. It's in weird printer units but there's definitely about 5mm carved out from the edge of the full page. What gives?&lt;/p&gt;
&lt;p&gt;Oh wait, CUPS makes its own cache of the PPD doesn't it. Maybe mine is slightly di&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;/etc/cups/ppd/FujiXerox.ppd
*PPD-Adobe:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;4.3&amp;quot;&lt;/span&gt;
*%%%%&lt;span class="w"&gt; &lt;/span&gt;PPD&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Generic&lt;span class="w"&gt; &lt;/span&gt;IPP&lt;span class="w"&gt; &lt;/span&gt;Everywhere&lt;span class="w"&gt; &lt;/span&gt;Printer&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;CUPS.
*%%%%&lt;span class="w"&gt; &lt;/span&gt;Created&lt;span class="w"&gt; &lt;/span&gt;by&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;CUPS&lt;span class="w"&gt; &lt;/span&gt;PPD&lt;span class="w"&gt; &lt;/span&gt;Compiler&lt;span class="w"&gt; &lt;/span&gt;CUPS&lt;span class="w"&gt; &lt;/span&gt;v2.3.3op2.
*%&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;c&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2014&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;OpenPrinting
*FormatVersion:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;4.3&amp;quot;&lt;/span&gt;
*FileVersion:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;1.0&amp;quot;&lt;/span&gt;
*LanguageVersion:&lt;span class="w"&gt; &lt;/span&gt;English
*LanguageEncoding:&lt;span class="w"&gt; &lt;/span&gt;ISOLatin1
*PCFileName:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pwgrast.ppd&amp;quot;&lt;/span&gt;
*Product:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(Generic IPP Everywhere Printer)&amp;quot;&lt;/span&gt;
*Manufacturer:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Generic&amp;quot;&lt;/span&gt;
*ModelName:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Generic IPP Everywhere Printer&amp;quot;&lt;/span&gt;
*ShortNickName:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Generic IPP Everywhere Printer&amp;quot;&lt;/span&gt;
*NickName:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Generic IPP Everywhere Printer&amp;quot;&lt;/span&gt;
*PSVersion:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(3010.000) 0&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Oh. Oh no.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;*DefaultPageSize:&lt;span class="w"&gt; &lt;/span&gt;A4
*PageSize&lt;span class="w"&gt; &lt;/span&gt;A4/A4:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;&amp;lt;/PageSize[595.28 841.89]/ImagingBBox null&amp;gt;&amp;gt;setpagedevice&amp;quot;&lt;/span&gt;
*DefaultPageRegion:&lt;span class="w"&gt; &lt;/span&gt;A4
*PageRegion&lt;span class="w"&gt; &lt;/span&gt;A4/A4:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;&amp;lt;/PageSize[595.28 841.89]/ImagingBBox null&amp;gt;&amp;gt;setpagedevice&amp;quot;&lt;/span&gt;
*DefaultImageableArea:&lt;span class="w"&gt; &lt;/span&gt;A4
*ImageableArea&lt;span class="w"&gt; &lt;/span&gt;A4/A4:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0 0 595.280029296875 841.890014648438&amp;quot;&lt;/span&gt;
*DefaultPaperDimension:&lt;span class="w"&gt; &lt;/span&gt;A4
*PaperDimension&lt;span class="w"&gt; &lt;/span&gt;A4/A4:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;595.280029296875 841.890014648438&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The short version is that (CUPS/GNOME Settings/whatever I used to set it up) has gone down some other, more sinister route to create a PPD file for my driverless printer. One which doesn't involve page margins. But it does have about four times as many exotic media types, on the offchance that I might need to print a swan.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;su
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/etc/cups/ppd
$&lt;span class="w"&gt; &lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;stop&lt;span class="w"&gt; &lt;/span&gt;cups
$&lt;span class="w"&gt; &lt;/span&gt;mv&lt;span class="w"&gt; &lt;/span&gt;FujiXerox.ppd&lt;span class="w"&gt; &lt;/span&gt;FujiXerox.ppd.bak
$&lt;span class="w"&gt; &lt;/span&gt;driverless&lt;span class="w"&gt; &lt;/span&gt;ipp://192.168.1.2/ipp/print&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;FujiXerox.ppd
$&lt;span class="w"&gt; &lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;start&lt;span class="w"&gt; &lt;/span&gt;cups
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After swapping out the generic PPD for the one created by &lt;tt class="docutils literal"&gt;driverless&lt;/tt&gt;, the 100mm square is now 100mm. We did it!&lt;/p&gt;
&lt;p&gt;In closing, there was a fantastic bit the late John Clarke did in his show &lt;em&gt;The Games&lt;/em&gt;, where a bumbling contractor has to justify to the Sydney Olympic Committee that it's fine the 100 metres track he built is only 94 metres long. I'd include a clip, except the ABC decided to copyright strike it. Oh well! Now to give up computers for good this time.&lt;/p&gt;
</content><category term="Devlog"/><category term="show your working"/><category term="printing"/><category term="linux"/><category term="cups"/></entry><entry><title>Intro to ROM Hacking: Ghost House</title><link href="/writing/2020/06/09/lrat_ghosthouse/" rel="alternate"/><published>2020-06-09T00:00:00+08:00</published><updated>2020-06-09T00:00:00+08:00</updated><author><name>Scott</name></author><id>tag:None,2020-06-09:/writing/2020/06/09/lrat_ghosthouse/</id><summary type="html">&lt;div class="responsive-embed widescreen"&gt;
    &lt;iframe src="https://www.youtube-nocookie.com/embed/Nu0Fj4FxdDU" width="560" height="315" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;&lt;p&gt;A long time between updates! I've been distracted by working on ScummVM's Macromedia Director engine, but it's mostly bugfixing and I couldn't find a good focal point. So on a whim I made this!
The previous video took maybe a week, this was a good test of writing/filming/editing in two days.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;In this video Scott takes apart &amp;quot;Ghost House&amp;quot; by Sega, and figures out how to remove the obnoxious screen flashing effects by reverse engineering the Z80 code. This contains a full step-by-step explanation of how to use the MAME debugger for hacking ROMs, even if you don't …&lt;/em&gt;&lt;/p&gt;</summary><content type="html">&lt;div class="responsive-embed widescreen"&gt;
    &lt;iframe src="https://www.youtube-nocookie.com/embed/Nu0Fj4FxdDU" width="560" height="315" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;&lt;p&gt;A long time between updates! I've been distracted by working on ScummVM's Macromedia Director engine, but it's mostly bugfixing and I couldn't find a good focal point. So on a whim I made this!
The previous video took maybe a week, this was a good test of writing/filming/editing in two days.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;In this video Scott takes apart &amp;quot;Ghost House&amp;quot; by Sega, and figures out how to remove the obnoxious screen flashing effects by reverse engineering the Z80 code. This contains a full step-by-step explanation of how to use the MAME debugger for hacking ROMs, even if you don't know assembly language!&lt;/em&gt;&lt;/p&gt;
</content><category term="Video"/><category term="video"/><category term="reversing"/><category term="sega"/><category term="master system"/><category term="games"/><category term='"educational" "content"'/></entry><entry><title>Let's Reverse: Adventures of Lomax graphics</title><link href="/writing/2019/09/25/lrat_lomax/" rel="alternate"/><published>2019-09-25T00:00:00+08:00</published><updated>2019-09-25T00:00:00+08:00</updated><author><name>Scott</name></author><id>tag:None,2019-09-25:/writing/2019/09/25/lrat_lomax/</id><content type="html">&lt;div class="responsive-embed widescreen"&gt;
    &lt;iframe src="https://www.youtube-nocookie.com/embed/e0x7KU4wLGw" width="560" height="315" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;&lt;p&gt;I guess I'm a YouTuber now. Wonder how long I can fight this newfound urge to tase a dead rat.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;In this video Scott takes apart &amp;quot;Adventures of Lomax&amp;quot; by Psygnosis and reverse engineers the graphics data. This didn't require taking apart the code, and contains a step-by-step explanation of the black-box analysis process. A good introduction for anyone getting started in reversing games!&lt;/em&gt;&lt;/p&gt;
</content><category term="Video"/><category term="video"/><category term="reversing"/><category term="graphics"/><category term="games"/><category term='"educational" "content"'/></entry><entry><title>DDD 2018: Reverse Engineering is Good and also For Everyone</title><link href="/writing/2019/04/20/reigaafe/" rel="alternate"/><published>2019-04-20T00:00:00+08:00</published><updated>2019-04-20T00:00:00+08:00</updated><author><name>Scott</name></author><id>tag:None,2019-04-20:/writing/2019/04/20/reigaafe/</id><content type="html">&lt;div class="responsive-embed widescreen"&gt;
    &lt;iframe src="https://www.youtube-nocookie.com/embed/3GAsQoe5Mvs" width="560" height="315" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;img src="/writing/2019/04/20/reigaafe/reigaafe.jpg" style="display: none;" alt="The author preaching about reverse engineering to a captive audience" title="Fight me"/&gt;&lt;p&gt;Last year I gave my first talk at &lt;a class="reference external" href="https://dddperth.com"&gt;DDD Perth 2018&lt;/a&gt;! It was about reverse engineering, how I think everyone interested should be encouraged to try it, and some useful tips for people starting out. &lt;a class="reference external" href="/talks/reigaafe/"&gt;Here is the slide deck.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Apologies that the audio is a bit muffled; the microphone had a recording issue, so instead enjoy some lo-fi camcorder goodness. Thanks again to DDD for having me, and for going the extra mile to accomodate first-time presenters. Extra special thanks to &lt;a class="reference external" href="https://twitter.com/battlepanda"&gt;David&lt;/a&gt; for salvaging these black box recordings (on a long weekend, no less!).&lt;/p&gt;
</content><category term="Talk"/><category term="talk"/><category term="ddd"/><category term="reversing"/><category term="please don't make me watch this"/></entry><entry><title>Using your car as a giant joystick for $20</title><link href="/writing/2017/12/18/canbus_car_game_controller/" rel="alternate"/><published>2017-12-18T00:00:00+08:00</published><updated>2017-12-18T00:00:00+08:00</updated><author><name>Scott</name></author><id>tag:None,2017-12-18:/writing/2017/12/18/canbus_car_game_controller/</id><summary type="html">&lt;div class="responsive-embed widescreen"&gt;
    &lt;iframe src="https://www.youtube-nocookie.com/embed/MXvI5MorsO4" width="560" height="315" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;img src="/writing/2017/12/18/canbus_car_game_controller/car_games.jpg" style="display: none;" alt="A car rigged to play videogames with a projector pointed at a wall" title="An earlier version of the plan involved using the the horn as a fire button, but I like this building and didn't feel the need to get evicted just yet"/&gt;&lt;p&gt;&lt;strong&gt;DISCLAIMER:&lt;/strong&gt; &lt;em&gt;Cars can be dangerous. The electrical and mechanical systems of a car are not designed for this use case, and you run the risk of damaging them. This project worked fine for this particular vehicle, but could damage critical systems such as steering and braking in a different model. Do not attempt this with a vehicle that isn't yours, or a vehicle that would leave you with no contingency if it were to break. The author will not be held accountable for any damage caused from following these instructions.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This project is about how to rig the controls of nearly any recent car (in my case a 2007 edition Mazda 3) to act as a giant game controller. In a nutshell; input from the car will be scraped as CANbus messages from a cheap OBD-II adapter, then converted to standard joystick and keyboard events, which will be used to drive a video game projected onto a screen in front of the windshield. No physical modifications to the car are required; the only one I made was to pull the fuses for the headlights so as not to blind the projector screen.&lt;/p&gt;
&lt;p&gt;The full source code used for the above demo is available at &lt;a class="reference external" href="https://github.com/moralrecordings/elm327_joystick"&gt;GitHub&lt;/a&gt;. With luck, anyone with entry-level Python experience should be able to adapt this for their car. I think this is a nice practical introduction to car hacking and reverse engineering, without the need to spend a lot on exotic debugging hardware.&lt;/p&gt;
</summary><content type="html">&lt;div class="responsive-embed widescreen"&gt;
    &lt;iframe src="https://www.youtube-nocookie.com/embed/MXvI5MorsO4" width="560" height="315" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;img src="/writing/2017/12/18/canbus_car_game_controller/car_games.jpg" style="display: none;" alt="A car rigged to play videogames with a projector pointed at a wall" title="An earlier version of the plan involved using the the horn as a fire button, but I like this building and didn't feel the need to get evicted just yet"/&gt;&lt;p&gt;&lt;strong&gt;DISCLAIMER:&lt;/strong&gt; &lt;em&gt;Cars can be dangerous. The electrical and mechanical systems of a car are not designed for this use case, and you run the risk of damaging them. This project worked fine for this particular vehicle, but could damage critical systems such as steering and braking in a different model. Do not attempt this with a vehicle that isn't yours, or a vehicle that would leave you with no contingency if it were to break. The author will not be held accountable for any damage caused from following these instructions.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This project is about how to rig the controls of nearly any recent car (in my case a 2007 edition Mazda 3) to act as a giant game controller. In a nutshell; input from the car will be scraped as CANbus messages from a cheap OBD-II adapter, then converted to standard joystick and keyboard events, which will be used to drive a video game projected onto a screen in front of the windshield. No physical modifications to the car are required; the only one I made was to pull the fuses for the headlights so as not to blind the projector screen.&lt;/p&gt;
&lt;p&gt;The full source code used for the above demo is available at &lt;a class="reference external" href="https://github.com/moralrecordings/elm327_joystick"&gt;GitHub&lt;/a&gt;. With luck, anyone with entry-level Python experience should be able to adapt this for their car. I think this is a nice practical introduction to car hacking and reverse engineering, without the need to spend a lot on exotic debugging hardware.&lt;/p&gt;

&lt;div class="section" id="requirements"&gt;
&lt;h2&gt;Requirements&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;A car with a CANbus based internal network and an &lt;a class="reference external" href="https://en.wikipedia.org/wiki/On-board_diagnostics#OBD-II"&gt;OBD-II port&lt;/a&gt;. Most cars manufactured 2005 and later have this port in either the driver or passenger side footwell under the dashboard. Once you find the port, you should do a search for your car's model, see what internal control networks it has, and how they map to pins on the port.&lt;/li&gt;
&lt;li&gt;A bootleg ELM327 OBD-II adapter. These things are everywhere and dirt cheap, you can pick up one on eBay for about 20 dollarydoos. Make sure you get one with a USB cable and &lt;em&gt;not&lt;/em&gt; a WiFi/BT one; honking great security risk aside, the chip used in the wireless variant is a &lt;a class="reference external" href="https://timyouard.wordpress.com/2015/09/02/disection-of-a-counterfeit-elm327-obdii-adapter-from-china/"&gt;different architecture with worse firmware&lt;/a&gt; than a stock bootleg ELM327, and troubleshooting &lt;em&gt;will&lt;/em&gt; make you go mad/start smashing things up.&lt;/li&gt;
&lt;li&gt;A computer running Linux, a projector, and some HDMI/3.5mm audio cables.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="your-friend-the-canbus"&gt;
&lt;h2&gt;Your friend the CANbus&lt;/h2&gt;
&lt;p&gt;I think it's important that we touch briefly on what CAN is.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/CAN_bus"&gt;Controller Area Network (CAN)&lt;/a&gt; is a standard for a twisted wire pair &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Bus_network"&gt;bus&lt;/a&gt; designed for the very noisy conditions inside a vehicle.&lt;/li&gt;
&lt;li&gt;There's a few different variants in terms of speed and message size, but communication over the bus is done with messages made from a message ID (either 11 or 29 bits) and a payload (usually 2-12 bytes). In general the message ID denotes the type of message.&lt;/li&gt;
&lt;li&gt;Unlike &lt;a class="reference external" href="https://en.wikipedia.org/wiki/OBD-II_PIDs"&gt;OBD-II PID requests&lt;/a&gt;, CANbus messages are non-standard; every vehicle model has its own proprietary message formats, usually with multiple sensor readings packed into each message.&lt;/li&gt;
&lt;li&gt;Being a multi-master bus, there is no source or destination for messages! Electronic Control Units in the vehicle are constantly listening for/sending out messages thousands of times a second. A pretty ideal medium for sniffing and injecting traffic!&lt;/li&gt;
&lt;/ul&gt;
&lt;img src="/writing/2017/12/18/canbus_car_game_controller/obd2.svg" alt="Relevant pins on the Mazda 3&amp;#x27;s OBD-II socket: 3 - HS CAN high, 4 and 5 - ground, 6 - MS CAN high, 11 - HS CAN low, 14 - MS CAN low, 16 - +12V" class="" title=""/&gt; &lt;p&gt;As mentioned above, every communications bus in your vehicle should be accessible through the OBD-II port. If your vehicle's CANbus is exposed on a different set of pins than the standard pair, be prepared to crack the adapter open and resolder some wires, or to buy a premodified adapter. My Mazda 3 has two CANbus networks; a boring medium-speed bus on the standard OBD-II pins, and a high-speed bus where the cool stuff happens on two of the vendor pins. The adapter I bought has a throw switch to toggle which bus to use; these ones are sold as &amp;quot;modified for Ford&amp;quot;.&lt;/p&gt;
&lt;p&gt;The ELM327 supports autodetection of several common message formats and CAN baud rates, plus has two slots for custom settings.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Protocol&lt;span class="w"&gt; &lt;/span&gt;mapping&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;taken&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;ELM327&lt;span class="w"&gt; &lt;/span&gt;datasheet&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;0&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;Autodetect&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;everything&lt;span class="w"&gt; &lt;/span&gt;except&lt;span class="w"&gt; &lt;/span&gt;User1&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;User2&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;1&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;SAE&lt;span class="w"&gt; &lt;/span&gt;J1850&lt;span class="w"&gt; &lt;/span&gt;PWM&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;41&lt;/span&gt;.6&lt;span class="w"&gt; &lt;/span&gt;kbaud&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;2&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;SAE&lt;span class="w"&gt; &lt;/span&gt;J1850&lt;span class="w"&gt; &lt;/span&gt;VPW&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.4&lt;span class="w"&gt; &lt;/span&gt;kbaud&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;3&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;ISO&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;9141&lt;/span&gt;-2&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;baud&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;4&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;ISO&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14230&lt;/span&gt;-4&lt;span class="w"&gt; &lt;/span&gt;KWP&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;baud&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;5&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;ISO&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14230&lt;/span&gt;-4&lt;span class="w"&gt; &lt;/span&gt;KWP&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;fast&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;6&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;ISO&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15765&lt;/span&gt;-4&lt;span class="w"&gt; &lt;/span&gt;CAN&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bit&lt;span class="w"&gt; &lt;/span&gt;ID,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;kbaud&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;7&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;ISO&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15765&lt;/span&gt;-4&lt;span class="w"&gt; &lt;/span&gt;CAN&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;29&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bit&lt;span class="w"&gt; &lt;/span&gt;ID,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;kbaud&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;8&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;ISO&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15765&lt;/span&gt;-4&lt;span class="w"&gt; &lt;/span&gt;CAN&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bit&lt;span class="w"&gt; &lt;/span&gt;ID,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;250&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;kbaud&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;9&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;ISO&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15765&lt;/span&gt;-4&lt;span class="w"&gt; &lt;/span&gt;CAN&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;29&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bit&lt;span class="w"&gt; &lt;/span&gt;ID,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;250&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;kbaud&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;A&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;SAE&lt;span class="w"&gt; &lt;/span&gt;J1939&lt;span class="w"&gt; &lt;/span&gt;CAN&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;29&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bit&lt;span class="w"&gt; &lt;/span&gt;ID,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;250&lt;/span&gt;*&lt;span class="w"&gt; &lt;/span&gt;kbaud&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;B&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;User1&lt;span class="w"&gt; &lt;/span&gt;CAN&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;*&lt;span class="w"&gt; &lt;/span&gt;bit&lt;span class="w"&gt; &lt;/span&gt;ID,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;125&lt;/span&gt;*&lt;span class="w"&gt; &lt;/span&gt;kbaud&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;C&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;User2&lt;span class="w"&gt; &lt;/span&gt;CAN&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;*&lt;span class="w"&gt; &lt;/span&gt;bit&lt;span class="w"&gt; &lt;/span&gt;ID,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;*&lt;span class="w"&gt; &lt;/span&gt;kbaud&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And yes, there are some vehicle models which have an OBD-II port but don't use (ISO 15765-4) CAN. If you have one of these, I would suggest searching to check if anyone has had success using the ELM327 with your car model, and from there seeing if you can dump bus messages through the &amp;quot;Monitor All&amp;quot; feature (described below).&lt;/p&gt;
&lt;p&gt;Speaking of searching, check to see if anyone on the internet has mapped out the CAN message format for your vehicle. We will be reversing the message format ourselves for kicks, but it's always nice to have reference material and build off the research of others. I was lucky that someone had mapped most of the CANbus format for the Mazda 3, but in the end I didn't really use the information other than to confirm my own findings.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="so-how-s-this-going-to-work-then"&gt;
&lt;h2&gt;So how's this going to work then?&lt;/h2&gt;
&lt;p&gt;We are going to make a software bridge in Python that connects between sensors on the car's CANbus, and a virtual joystick made with Linux uinput. uinput is amazing; writing a driver for is literally just calling a function every time you want to press/unpress a button or change the readong on an axis. Better still, in Python you can use &lt;a class="reference external" href="https://github.com/pyserial/pyserial"&gt;pyserial&lt;/a&gt; and &lt;a class="reference external" href="https://github.com/tuomasjjrasanen/python-uinput"&gt;python-uinput&lt;/a&gt; to skip nearly all of the messy boilerplate.&lt;/p&gt;
&lt;p&gt;If you're not running Linux that's ok, a &lt;a class="reference external" href="http://www.linuxliveusb.com/en/home"&gt;live USB of Linux with persistent storage&lt;/a&gt; will work just as well for running games and emulators. I imagine a lot of readers right now are internally screaming &amp;quot;yes yes but what about the only OS that ever truly matters which is Microsoft Windows?!?!?&amp;quot;. Things there are... considerably harder, as the OS does not come with a easy-to-script userspace input driver. You would need to install a third party driver like &lt;a class="reference external" href="http://vjoystick.sourceforge.net"&gt;vJoy&lt;/a&gt;, write a Python shim for the C API it offers or maybe &lt;a class="reference external" href="https://github.com/ahkscript/AHK-vJoy-Library"&gt;chain it through another input manager like AutoHotkey&lt;/a&gt;, then replace all of the calls to uinput with that. I should mention that nearly all of the joystick configurations I made generated keyboard presses for some buttons, which is a seperate Windows API to joystick events but you could use a cross-platform wrapper like &lt;a class="reference external" href="https://github.com/SavinaRoja/PyUserInput"&gt;PyUserInput&lt;/a&gt;. And the other software option would be to write a Windows kernel driver! So that's nice.&lt;/p&gt;
&lt;p&gt;(I'm starting to think that the massive popularity of microcontroller boards such as the &lt;a class="reference external" href="https://www.pjrc.com/teensy/"&gt;Teensy&lt;/a&gt;, pretending to be standard USB devices so as to piggyback generic driver support, might be related to Windows' userspace driver options being so bad?)&lt;/p&gt;
&lt;!-- Car's CANbus --&gt;
&lt;!-- MCP25511 CAN --&gt;
&lt;!-- "ELM327" (PIC18F2480) --&gt;
&lt;!-- FT232RL serial to USB converter --&gt;
&lt;!-- Python script --&gt;
&lt;!-- uinput --&gt;
&lt;!-- Joystick --&gt;
&lt;/div&gt;
&lt;div class="section" id="limitations-of-cheap-hardware"&gt;
&lt;h2&gt;Limitations of cheap hardware&lt;/h2&gt;
&lt;p&gt;Real talk: the ELM327 is not a proper CAN interface, it's a cheapo OBD-II diagnostic unit with CAN sniffing bolted on as an afterthought. ELM327 based devices are not very good at capturing all of the packets off the CANbus. The read buffer on the chip is a paltry 256 &lt;strong&gt;bytes&lt;/strong&gt;, so in the default configuration the &amp;quot;Monitor All&amp;quot; command (which prints incoming CAN messages until the buffer overflows) will vomit blood and give up after a few seconds.&lt;/p&gt;
&lt;p&gt;By contrast, a real CAN interface will allow duplex communication (aka. near-simultaneous read and write) with the CANbus; you can &lt;em&gt;sort of&lt;/em&gt; write messages to the CANbus with the ELM327, but it's not duplex and you have to switch modes first and the process is really painful. In Linux there is a whole subsystem devoted to CAN support (SocketCAN), which makes CANbus interfaces show up just like network interfaces (i.e. can0 instead of eth0), and all Linux CAN software (e.g. the can-utils collection) builds on top of this generic layer. A proper CAN interface will have native kernel support for SocketCAN, or provide a serial interface compatible with the barebones SLCAN/LAWICEL protocol.&lt;/p&gt;
&lt;p&gt;Anyway. So those downsides make the ELM327 not the greatest adapter ever for CAN reverse engineering, BUT! Doesn't mean we can't do it.&lt;/p&gt;
&lt;!-- (For this project we are fortunate that we don't have to do much greenfields reverse engineering. We already have most of the CAN mappings for the Mazda 3, so all we need to do is check that the signals we want are there, then filter for those specific CAN message IDs.) --&gt;
&lt;!-- A more fancy goal would be to rig the dials on the dashboard to be in sync with the gameplay using MAME's Lua scripting. This definitely requires a more capable CAN interface, as when you write your own messages you are fighting against the car! According to the book, the trick is to wait until the car's ECU fires off a message with e.g. dial values, then -immediately- send out your own message with the numbers you want to override. --&gt;
&lt;/div&gt;
&lt;div class="section" id="connecting-to-the-elm327"&gt;
&lt;h2&gt;Connecting to the ELM327&lt;/h2&gt;
&lt;p&gt;I purchased a bootleg ELM327 adapter with a beautifully soldered &amp;quot;Ford&amp;quot; mod switch on eBay for $30. Straight out of the gate, I could not get my adapter to respond in either Linux or Windows on any of the standard serial baud rates, even with the (cracked, commercial) software included on the complimentary CD-R! But the little TX light &lt;em&gt;did&lt;/em&gt; flash momentarily after turning it on, which suggested that the adapter was sending out the startup message, and I could make the TX/RX lights flash by sending garbage over the serial connection. Evidently the chip was fine and I was trying to communicate over serial at the wrong baud rate (aka. speed in bits/second), but none of the standard RS232 rates seemed to work!&lt;/p&gt;
&lt;img src="/writing/2017/12/18/canbus_car_game_controller/oscilloscope.jpg" alt="Oscilloscope wired to ELM327 adapter" class="image-process-1200" title="Only another 21 broken things and the scope will have paid for itself"/&gt; &lt;p&gt;After a few hours I gave up poking it, stripped the adapter down, shoved some tiny wires down the vias on the main chip's TX and RX lines, and ran them through an oscilloscope. Lo and behold it was communicating at exactly 500000 bit/s! (AKA a completely non-standard speed for RS232 that I never would have guessed in a million years).&lt;/p&gt;
&lt;p&gt;Non-standard speeds aren't a bad thing; the datasheet suggested that stock ELM327s run at 38400 bit/s or 9600 bit/s, which are unbearably slow for data capture given our high-speed CANbus is rated for 500kbit/s. But that's still some expert trolling by whoever rigged this, as it rendered the adapter incompatible with pretty much EVERY OBD-II/ELM327 program ever. Maybe they picked 500000 because the HS CANbus was rated for &amp;quot;exactly&amp;quot; that much... except the ELM327 dumps CAN messages in human-readable hexadecimal, which cuts the effective data rate by more than half. Oops.&lt;/p&gt;
&lt;p&gt;Once you've figured out the baud rate, you can connect to the device using whatever serial terminal you want. Make sure your user has permission to access the /dev/ttyUSBx interfaces!&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Device: Run &amp;quot;dmesg&amp;quot; just after you plug in the USB to see what it comes up as (most likely /dev/ttyUSB0)&lt;/li&gt;
&lt;li&gt;Baud rate: As discussed (possibly 38400, 9600, or 500000)&lt;/li&gt;
&lt;li&gt;Data bits: 8&lt;/li&gt;
&lt;li&gt;Parity bits: None&lt;/li&gt;
&lt;li&gt;Stop bit: 1&lt;/li&gt;
&lt;li&gt;Hardware flow control: No&lt;/li&gt;
&lt;li&gt;Software flow control: No&lt;/li&gt;
&lt;li&gt;Extra: Add line feeds&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One thing I learnt when doing this project; there are hundreds of open source serial terminals and nearly all of them are bad, or have the same dumb problems. To save time I recommend grabbing &lt;a class="reference external" href="https://github.com/vsergeev/ssterm"&gt;ssterm&lt;/a&gt; (a.k.a. the only good serial terminal) and using it like so.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;scott@hoagie:~$&lt;span class="w"&gt; &lt;/span&gt;ssterm&lt;span class="w"&gt; &lt;/span&gt;/dev/ttyUSB0&lt;span class="w"&gt; &lt;/span&gt;-b&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;500000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--rx-nl&lt;span class="w"&gt; &lt;/span&gt;cr&lt;span class="w"&gt; &lt;/span&gt;--tx-nl&lt;span class="w"&gt; &lt;/span&gt;cr

?

&amp;gt;AT&lt;span class="w"&gt; &lt;/span&gt;I
ELM327&lt;span class="w"&gt; &lt;/span&gt;v1.4
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Hitting the enter key once should give you a &amp;gt; prompt. Typing &amp;quot;AT I&amp;quot; and hitting enter should give you the version message. At any time, Ctrl-] stops the terminal. &amp;quot;AT I&amp;quot; is an example of an AT command supported by the ELM327, which you can find in the &lt;a class="reference external" href="https://www.elmelectronics.com/ic/elm327/"&gt;official chip datasheet/AT Commands list&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="changing-the-comms-speed"&gt;
&lt;h2&gt;Changing the comms speed&lt;/h2&gt;
&lt;p&gt;The very first thing we're going to do is tempt fate and force the ELM327 to run at the fastest speed it can. I know from the FT232RL datasheet that the USB serial interface is capable of up to 2Mbit/second. According to the ELM327 datasheet, baud rate is stored as a divisor for 4Mbit/second in Programmable Parameter 0C. I confirmed this by looking at the PP summary:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&amp;gt;AT&lt;span class="w"&gt; &lt;/span&gt;PPS
&lt;span class="m"&gt;00&lt;/span&gt;:FF&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;01&lt;/span&gt;:FF&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;02&lt;/span&gt;:FF&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;03&lt;/span&gt;:32&lt;span class="w"&gt; &lt;/span&gt;F
&lt;span class="m"&gt;04&lt;/span&gt;:01&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;05&lt;/span&gt;:FF&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:F1&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;07&lt;/span&gt;:09&lt;span class="w"&gt; &lt;/span&gt;F
&lt;span class="m"&gt;08&lt;/span&gt;:FF&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;09&lt;/span&gt;:00&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;0A:0A&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;0B:FF&lt;span class="w"&gt; &lt;/span&gt;F
0C:08&lt;span class="w"&gt; &lt;/span&gt;N&lt;span class="w"&gt;  &lt;/span&gt;0D:0D&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;0E:9A&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;0F:FF&lt;span class="w"&gt; &lt;/span&gt;F
&lt;span class="m"&gt;10&lt;/span&gt;:0D&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;:00&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;:FF&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;:32&lt;span class="w"&gt; &lt;/span&gt;F
&lt;span class="m"&gt;14&lt;/span&gt;:FF&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;:0A&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;16&lt;/span&gt;:FF&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;:92&lt;span class="w"&gt; &lt;/span&gt;F
&lt;span class="m"&gt;18&lt;/span&gt;:00&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;:28&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;1A:FF&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;1B:FF&lt;span class="w"&gt; &lt;/span&gt;F
1C:FF&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;1D:FF&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;1E:FF&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;1F:FF&lt;span class="w"&gt; &lt;/span&gt;F
&lt;span class="m"&gt;20&lt;/span&gt;:FF&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;21&lt;/span&gt;:FF&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;22&lt;/span&gt;:FF&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;23&lt;/span&gt;:FF&lt;span class="w"&gt; &lt;/span&gt;F
&lt;span class="m"&gt;24&lt;/span&gt;:00&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;25&lt;/span&gt;:00&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;26&lt;/span&gt;:00&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;:FF&lt;span class="w"&gt; &lt;/span&gt;F
&lt;span class="m"&gt;28&lt;/span&gt;:FF&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;29&lt;/span&gt;:FF&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;2A:38&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;2B:02&lt;span class="w"&gt; &lt;/span&gt;F
2C:E0&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;2D:04&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;2E:80&lt;span class="w"&gt; &lt;/span&gt;F&lt;span class="w"&gt;  &lt;/span&gt;2F:0A&lt;span class="w"&gt; &lt;/span&gt;F
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Seems legit. PP 0C is on (N means oN!) and set to 0x08, meaning 4Mbit/sec / 8 = 500kbit/sec.&lt;/p&gt;
&lt;p&gt;By default, PP 0C is set to off, which sets the baud rate to the default of 38400. If your ELM327 is running at 38400, you can boost it to 500000 by running the following commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&amp;gt;AT&lt;span class="w"&gt; &lt;/span&gt;PP&lt;span class="w"&gt; &lt;/span&gt;0C&lt;span class="w"&gt; &lt;/span&gt;ON
OK

&amp;gt;AT&lt;span class="w"&gt; &lt;/span&gt;PP&lt;span class="w"&gt; &lt;/span&gt;0C&lt;span class="w"&gt; &lt;/span&gt;SV&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;08&lt;/span&gt;
OK

&amp;gt;AT&lt;span class="w"&gt; &lt;/span&gt;Z
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Disconnect your serial terminal and you should be able to reconnect at 500000. To revert it back to the default (and make all your ELM327/OBD scanning apps work again):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&amp;gt;AT&lt;span class="w"&gt; &lt;/span&gt;PP&lt;span class="w"&gt; &lt;/span&gt;0C&lt;span class="w"&gt; &lt;/span&gt;OFF
OK

&amp;gt;AT&lt;span class="w"&gt; &lt;/span&gt;Z
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;On my bootleg chip 500000 baud is the fastest you can go; setting the baud rate divisor any lower than 08 will return '?'. That kinda sucks, but whatever.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="teaching-python-elm327ese"&gt;
&lt;h2&gt;Teaching Python ELM327ese&lt;/h2&gt;
&lt;p&gt;I wrote a little Python harness for bootstrapping and communicating over serial with the ELM327. By default, the ELM327 will echo the user's input back to them, and seperate new lines with a carriage return ('\r'). Prompts for input are made with a &amp;gt;. Because it's all terminal-styled plaintext, we have no idea how long to expect a serial message to be; we just know it'll be terminated with a '&amp;gt;' (when ready to receive input again) or a '\r' (for a stream of CAN messages).&lt;/p&gt;
&lt;p&gt;As we are both cheap and lazy, we will use pyserial and synchronous IO. If you call read( n ), pyserial will block until there's enough data in the receive buffer to fulfil your request. So the easiest way around that is to repeatedly ask for 1 byte (oh man I die a little on the inside when I think how inefficient this is), then stop when you hit the terminator character.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;ELM327&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/dev/ttyUSB0&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;baud_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;500000&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;0&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;elm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serial&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;baud_rate&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;protocol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;protocol&lt;/span&gt;


    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;elm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reset_input_buffer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;elm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reset_output_buffer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;elm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;elm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt; &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytearray&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;elm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;
            &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;


    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;recv_line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt; &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytearray&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;elm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;EOFError&lt;/span&gt;
            &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now that we have serial comms, we'll have to write a small set of bootstrap commands to get the adapter into a receive-ready state.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt; &lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# send garbage&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;this will reset the prompt&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt; &lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# reset interface&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_prompt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;AT Z&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# return to defaults&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;AT D&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# turn echo off&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;AT E0&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# check that information string works&lt;/span&gt;
    &lt;span class="c1"&gt;# (my bootleg responds with &amp;#39;ELM327 v1.4&amp;#39;)&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;AT I&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;ack&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;ack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ELM&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# set the CANbus protocol used by the car&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;AT SP &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;protocol&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ascii&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# initialize the CANbus interface&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;0100&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# get protocol&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;AT DPN&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# next 3 are about disabling the &amp;quot;helpful&amp;quot; autoformatting of CAN messages&lt;/span&gt;
    &lt;span class="c1"&gt;# enable messages longer than 7 bytes (standards are for chumps!)&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;AT AL&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# turn on headers (why would you not want those)&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;AT H1&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# disable formatting (boo)&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;AT CAF0&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# disable whitespace in CAN messages&lt;/span&gt;
    &lt;span class="c1"&gt;# ELM327 has only 256 bytes, so this nets you ~33% extra buffer space!&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;AT S0&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# set huuuuge timeout before giving up&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;AT STFF&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;start_can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt; &lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_prompt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;AT MA&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And yes we need the recv() after each command; the ELM327 only buffers input when the prompt is displayed and Python will execute faster than the chip has time to react, so we need to block Python until we get another '&amp;gt;'. All of the above was figured out by trial and error; playing with the ELM327 through a serial terminal helped a lot, plus reading all the AT commands in the ELM327 datasheet and the source code for some ELM327-targeted software (e.g. pyOBD). The ELM327 communicates everything in human-readable text, so I just kept poking at it in the terminal until it produced CAN output in a nice compact format.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="sniffing-the-canbus"&gt;
&lt;h2&gt;Sniffing the CANbus&lt;/h2&gt;
&lt;p&gt;I mentioned earlier the ELM327 isn't the best for reverse engineering. If you don't mind writing your own sniffer, it's actually alright for basic things like this!&lt;/p&gt;
&lt;p&gt;The Python harness will set everything up into &amp;quot;Monitor All&amp;quot; mode. Once we ask the ELM327 for messages, it will begin transmitting them over the serial connection encoded as plaintext hexadecimal; first three characters are the message ID (the CANbus in the Mazda 3 uses 11-bit message IDs; 000 to 7FF), the rest is message body. We will need to quickly decode this back into bytes (groan) so we can scrape the sensor information.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;CAN_RE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;([0-9A-F]&lt;/span&gt;&lt;span class="si"&gt;{3}&lt;/span&gt;&lt;span class="s1"&gt;)&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;W*([0-9A-F&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;W]+)&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;recv_can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt; &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;msg_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recv_line&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;msg_m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CAN_RE&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;msg_raw&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;msg_m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;msg_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;msg_m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;msg_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;msg_m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;ascii&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg_b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There is a CAN filtering feature in the ELM327 which sounded ideal for cutting down traffic, but I struggled a bit figuring out how it worked. There's two components: a filter (CF) and a mask (CM). Like an idiot I thought it would work like any other mask, as in the test would pass any message ID that ANDs with it and remains the same. It's kind of the opposite: you set the filter to any message ID that passes the test, and set the mask to be 1s for the bits in the filter (1 or 0) that &lt;em&gt;must&lt;/em&gt; match with the incoming message ID. So you could implement an AND-style filter by setting CF to your mask and CM to (0x7ff ^ mask).&lt;/p&gt;
&lt;p&gt;As it happens, cranking the baud rate and disabling whitespace gave juuuuust enough buffer space for the ELM327 to deal with every CAN message produced by my car. Which is good, because the message IDs I needed were so sprawled I couldn't use filtering.&lt;/p&gt;
&lt;p&gt;With that working I rigged up a simple tool that read every CAN message and printed only the changes for each message ID. This is available in the source code repository as elm_scan.py.&lt;/p&gt;
&lt;p&gt;There was a little bit of noise; two CAN message types had a clock signal of some kind, and there was some in-place oscillation from (I think) the level sensor in the fuel tank, so I just ignored those specific message IDs. I then performed the highly-advanced reverse engineering technique of wiggling a thing in the car while staring at the screen. If a message showed up, it meant the wiggling had made it in some way to the CANbus, and the changed bits were what I needed to check for in the joystick driver.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="designing-a-joystick"&gt;
&lt;h2&gt;Designing a joystick&lt;/h2&gt;
&lt;p&gt;We're going to be using the car with the ignition set to ON, the engine OFF (duh), transmission in park and handbrake on, so there are physical limits on what controls we can use. In the Mazda 3 power steering isn't available with the engine switched off, but you can still tug on the fixed steering wheel and get a decent force reading left and right. I read some research online which suggested you &lt;em&gt;could&lt;/em&gt; get the brake position as a force axis measurement, but I only saw it as an on-off switch with my sniffer. (maybe the ECU only reports force when the engine is running and the pedal isn't locked?) Also to my chagrin, most of the buttons on the steering wheel etc. are wired up directly to an ECU, so those signals never even reach the CANbus! So sad.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;UPDATE:&lt;/strong&gt; someone pointed out correctly that a lot of cars will enable power steering and turn the wheels even if the engine is off. If this is your vehicle, I would strongly advise placing something slippy like a folded tarp underneath each front tyre, else you might end up with some nice bald spots where the rubber meets the bitumen.&lt;/p&gt;
&lt;p&gt;Regardless I found some good controls after a bit of sniffing, all on the HS CANbus. I was worried I'd need to rig a second adapter in tandem to collect traffic from the car's second MS CANbus, but there weren't any usable inputs I could find (other than the indicators and the volume control) so I didn't bother.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;STEERING&lt;span class="w"&gt; &lt;/span&gt;ANGLE&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;range&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;locked&lt;span class="o"&gt;)&lt;/span&gt;:
4da:&lt;span class="w"&gt; &lt;/span&gt;8000c00000000000&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;neutral
4da:&lt;span class="w"&gt; &lt;/span&gt;80d2c00000000000&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;hard&lt;span class="w"&gt; &lt;/span&gt;right
4da:&lt;span class="w"&gt; &lt;/span&gt;7f2ec00000000000&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;hard&lt;span class="w"&gt; &lt;/span&gt;left

ACCELERATOR&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;range&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="m"&gt;201&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;000040000000c800&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;floored
&lt;span class="m"&gt;201&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0000400000000000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;depressed

BRAKE&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;on/off&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="m"&gt;205&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0000400000000000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;on
&lt;span class="m"&gt;205&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0000000000000000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;off

CRUISE&lt;span class="w"&gt; &lt;/span&gt;CONTROL&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;on/off&lt;span class="o"&gt;)&lt;/span&gt;:
4ec:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8000000000000000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;on
4ec:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0000000000000000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;off

DRIVER&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;S&lt;span class="w"&gt; &lt;/span&gt;SIDE&lt;span class="w"&gt; &lt;/span&gt;DOOR&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;open/shut&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="m"&gt;433&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;800450010004f000&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;open
&lt;span class="m"&gt;433&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;000450010004f000&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;shut

HIGH&lt;span class="w"&gt; &lt;/span&gt;BEAMS&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;on/off&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="m"&gt;433&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;000450410004f000&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;on
&lt;span class="m"&gt;433&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;000450010004f000&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;off
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Each CAN message has a bunch of sensors bitpacked into it, so we need some fast routines to extract only the data we care about. I used the struct unpacker from my library Mr. Crowbar (&lt;a class="reference external" href="https://github.com/moralrecordings/mrcrowbar"&gt;plug, plug!&lt;/a&gt;) to do the dirty work:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Steering&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;mrc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Block&lt;/span&gt; &lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;RANGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x00D2&lt;/span&gt;

    &lt;span class="n"&gt;axis_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mrc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UInt16_BE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;axis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt; &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;axis_raw&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mh"&gt;0x8000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RANGE&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Accelerator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;mrc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Block&lt;/span&gt; &lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;RANGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0xC8&lt;/span&gt;

    &lt;span class="n"&gt;axis_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mrc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UInt8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mh"&gt;0x06&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;axis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt; &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;axis_raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RANGE&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Brake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;mrc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Block&lt;/span&gt; &lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mrc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mh"&gt;0x02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;0b01000000&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Cruise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;mrc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Block&lt;/span&gt; &lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mrc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;0b10000000&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Controls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;mrc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Block&lt;/span&gt; &lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;driver_door&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mrc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;0b10000000&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;high_beams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mrc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mh"&gt;0x03&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;0b01000000&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Of course you could use Python's built in struct module to do the same thing, or hack something together with mask and shift operations. But look how clean and pure the message parser is now!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;msg_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mh"&gt;0x4da&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;steering&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Steering&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;msg_b&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;msg_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mh"&gt;0x201&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accelerator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Accelerator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;msg_b&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;msg_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mh"&gt;0x205&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;brake&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Brake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;msg_b&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;msg_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mh"&gt;0x4ec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cruise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Cruise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;msg_b&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;msg_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mh"&gt;0x433&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Controls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;msg_b&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;high_beams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;high_beams&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;driver_door&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;driver_door&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Cruise control is a latched on/off switch, and the driver's side door is a bit unwieldy to use as a pressable button, so I had to wrap those to emit a single button press when the state changed. Bit annoying you can't register them as a long press, but whatever.&lt;/p&gt;
&lt;p&gt;You can see the results in mazda3_joystick.py. It's pretty basic; there's a base class called Mazda3 storing all the state, with a method update( msg_id, msg_b ) that tries to parse each incoming CAN message, and if there's a match the state gets changed and an update of the uinput controls is triggered.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="obligatory-live-action-demo"&gt;
&lt;h2&gt;Obligatory live action demo&lt;/h2&gt;
&lt;p&gt;Showing is better than telling, so &lt;a class="reference external" href="https://www.youtube.com/watch?v=MXvI5MorsO4"&gt;I taped myself testing a pile of games&lt;/a&gt; on my car joystick with a projector. Apologies for the quality; I am not into streaming and don't have a convenient arsenal of broadcast cameras and studio mics laying around, so you get to savour the lo-fi goodness of a single unlit 720p camcorder gaffer-taped to a car headrest.&lt;/p&gt;
&lt;p&gt;In order, the games I tested were:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=MXvI5MorsO4&amp;amp;t=82s"&gt;OutRun&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=MXvI5MorsO4&amp;amp;t=357s"&gt;Trackmania 2 Canyon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=MXvI5MorsO4&amp;amp;t=658s"&gt;Doom&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=MXvI5MorsO4&amp;amp;t=1023s"&gt;Descent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=MXvI5MorsO4&amp;amp;t=1023s"&gt;Space Harrier&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=MXvI5MorsO4&amp;amp;t=1273s"&gt;Mirror's Edge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=MXvI5MorsO4&amp;amp;t=1657s"&gt;Sonic and Knuckles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=MXvI5MorsO4&amp;amp;t=1951s"&gt;Lemmings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=MXvI5MorsO4&amp;amp;t=2147s"&gt;Prince of Persia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=MXvI5MorsO4&amp;amp;t=2424s"&gt;Grim Fandango&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I had to make almost as many joystick configurations as there were games. A common thing I had to tweak was the range of the accelerator pedal; even if you define a uinput axis with a range of (0, 255), nearly all games expecting an analogue stick will renormalize that to something like (-255, 255). The solution was to size the uinput axis as (-255, 255) but create an enormous dead zone from (-255, 0). I was glad the event processing loop was Python code as it meant I could do stupid things like overload a control to act as two buttons, giving me the misplaced confidence to think playing Descent on camera was a good idea.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusions"&gt;
&lt;h2&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;Doom is objectively the best game to play on a car.&lt;/p&gt;
&lt;p&gt;I was kind of taken aback by how few people had attempted something like this already, given how accessible it turned out to be. As of the time of this writeup I found a few similar projects:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://hackaday.com/2010/04/23/driving-the-car-without-going-anywhere/"&gt;This team from a Finnish university&lt;/a&gt; rigged a VW Scirocco to play Rally Trophy. But they never wrote it up and their website is long gone :(&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://www.roadandtrack.com/car-culture/entertainment/videos/a33002/hackers-turn-a-chevy-volt-into-a-mario-kart-64-controller/"&gt;A father and son team&lt;/a&gt; hacked a Chevrolet Volt &lt;a class="reference external" href="https://www.avidhacker.com/2016/02/29/catchMeIfYouCan/"&gt;to play Mario Kart 64&lt;/a&gt; (&lt;a class="reference external" href="https://github.com/DanH42/CatchMeIfYouCAN"&gt;code&lt;/a&gt;). Their approach was similar to mine; main difference is they used a Raspberry PI + native CANbus module to capture the vehicle messages, then shunted the raw data via UDP to a Windows laptop which converted them to keyboard presses.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=FJ6LvHBWGio"&gt;wirklichkeitssteuerungsgeraet&lt;/a&gt; (&lt;a class="reference external" href="https://github.com/m0ppers/wirklichkeitssteuerungsgeraet"&gt;code&lt;/a&gt;), a wildcard demo at Revision 2017 by Akronyme Analogiker which involved connecting up a &lt;em&gt;live&lt;/em&gt; motorcycle to drive an Amiga joystick?! This was passed to UAE through an Amiga-to-USB adapter in order to play The Cycles: International Grand Prix Racing while coasting around a car park.&lt;/li&gt;
&lt;li&gt;After publishing, Stan &amp;quot;P1kachu&amp;quot; Lejay let me know about his project from earlier this year to &lt;a class="reference external" href="https://www.youtube.com/watch?v=bB7m7J3ioQw"&gt;rig a Fiat 500c to play Dirt Showdown&lt;/a&gt; and &lt;a class="reference external" href="https://www.youtube.com/watch?v=-q2togYPXas"&gt;VDrift&lt;/a&gt; (&lt;a class="reference external" href="https://github.com/P1kachu/talking-with-cars"&gt;code&lt;/a&gt;, &lt;a class="reference external" href="https://lse.epita.fr/lse-summer-week-2017/slides/lse-summer-week-2017-13-talking-with-cars/index.html"&gt;slides&lt;/a&gt;). His approach uses a RasPi native CAN module + uinput. Go check out his talk at 34C3!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want to lay down 20 clams for an ELM327 and rig your own car to play games, nice! Hopefully there should be enough information in this writeup and the source code repositories to help you along. I am always available through email or Twitter for advice.&lt;/p&gt;
&lt;p&gt;If you're interested in more general car hacking resources I can thoroughly recommend the following:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Craig Smith's &lt;a class="reference external" href="https://www.nostarch.com/carhacking"&gt;Car Hacker's Handbook&lt;/a&gt; published by the fantastic No Starch Press.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://illmatics.com/carhacking.html"&gt;The reverse engineering writeups&lt;/a&gt; done by Charlie Miller and Chris Valasek (a.k.a. the duo that &lt;a class="reference external" href="https://www.wired.com/2015/07/hackers-remotely-kill-jeep-highway/"&gt;remotely hacked the controls of a Jeep Cherokee while a WIRED journalist was driving it&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content><category term="Devlog"/><category term="elm327"/><category term="canbus"/><category term="debugging"/><category term="car hacking"/><category term="electronics"/><category term="hardware"/><category term="doom"/><category term="wasting your limited time here on earth trying to play mirror's edge with a car"/></entry><entry><title>Show Your Working: Fixing a pile of bugs in MAME</title><link href="/writing/2017/12/03/mame_fixes/" rel="alternate"/><published>2017-12-03T00:00:00+08:00</published><updated>2017-12-03T00:00:00+08:00</updated><author><name>Scott</name></author><id>tag:None,2017-12-03:/writing/2017/12/03/mame_fixes/</id><summary type="html">&lt;img src="/writing/2017/12/03/mame_fixes/daisukiss.gif" alt="The parfait eating contest in Daisu-Kiss" class="letterbox" title="Dating culture was very different in 1996, in that most relationship grievances were settled by a series of parfait eating contests"/&gt; &lt;p&gt;&lt;em&gt;Show Your Working is a brand new segment where I write up things I have attempted to fix in open source software. Sometimes it's interesting to debug a problem yourself from first principles, even if the codebase is huge and you don't know anything going in. I will try and explain my thought process as I venture out into the weeds armed only with a butter knife.&lt;/em&gt;&lt;/p&gt;
&lt;div class="section" id="the-software"&gt;
&lt;h2&gt;The software&lt;/h2&gt;
&lt;p&gt;If you haven't heard of it, &lt;a class="reference external" href="http://mamedev.org"&gt;MAME&lt;/a&gt; is an emulation project with the aim of supporting basically every arcade machine ever made. To do this, thousands of circuit boards and custom chips have been probed, analysed, decapped with acid, and ultimately written out as code. By adding board ROMs (whole dumps of the memory chips containing the copyrighted program data), MAME can emulate a system by connecting together drivers for all of the chipsets as per the layout of the original board. Recently, the sister project &lt;a class="reference external" href="http://web.archive.org/web/20171109135823/https://www.mess.org/"&gt;MESS&lt;/a&gt; has been merged back into the MAME main codebase, adding ~2200 consoles and home computer systems to the lineup. It is an incredible feat of volunteer-driven engineering.&lt;/p&gt;
&lt;p&gt;Unlike other emulators, MAME places accuracy and preservation above all else. All CPU code is interpreted (with some IL speedups via the &lt;a class="reference external" href="https://wiki.mamedev.org/index.php/UML_Architecture"&gt;UML architecture&lt;/a&gt;) and runs time-shared in a single thread. Instead of tightly coupling the hardware code with the framework for performance, MAME has an infinitely-rewirable generic module architecture, to encourage reuse of chip drivers across platforms. Imagine MAME as a big box of all the different types of chips, plus some templates that explain what chips to take from the box and how to wire them together to make a system.&lt;/p&gt;
&lt;p&gt;Also I was not exaggerating when I said every arcade machine ever made. The MAME source tree has slightly north of 6900 .cpp files, and a princely 2.9 million lines of code (4 million if you count headers!). This is a Big Codebase.&lt;/p&gt;
</summary><content type="html">&lt;img src="/writing/2017/12/03/mame_fixes/daisukiss.gif" alt="The parfait eating contest in Daisu-Kiss" class="letterbox" title="Dating culture was very different in 1996, in that most relationship grievances were settled by a series of parfait eating contests"/&gt; &lt;p&gt;&lt;em&gt;Show Your Working is a brand new segment where I write up things I have attempted to fix in open source software. Sometimes it's interesting to debug a problem yourself from first principles, even if the codebase is huge and you don't know anything going in. I will try and explain my thought process as I venture out into the weeds armed only with a butter knife.&lt;/em&gt;&lt;/p&gt;
&lt;div class="section" id="the-software"&gt;
&lt;h2&gt;The software&lt;/h2&gt;
&lt;p&gt;If you haven't heard of it, &lt;a class="reference external" href="http://mamedev.org"&gt;MAME&lt;/a&gt; is an emulation project with the aim of supporting basically every arcade machine ever made. To do this, thousands of circuit boards and custom chips have been probed, analysed, decapped with acid, and ultimately written out as code. By adding board ROMs (whole dumps of the memory chips containing the copyrighted program data), MAME can emulate a system by connecting together drivers for all of the chipsets as per the layout of the original board. Recently, the sister project &lt;a class="reference external" href="http://web.archive.org/web/20171109135823/https://www.mess.org/"&gt;MESS&lt;/a&gt; has been merged back into the MAME main codebase, adding ~2200 consoles and home computer systems to the lineup. It is an incredible feat of volunteer-driven engineering.&lt;/p&gt;
&lt;p&gt;Unlike other emulators, MAME places accuracy and preservation above all else. All CPU code is interpreted (with some IL speedups via the &lt;a class="reference external" href="https://wiki.mamedev.org/index.php/UML_Architecture"&gt;UML architecture&lt;/a&gt;) and runs time-shared in a single thread. Instead of tightly coupling the hardware code with the framework for performance, MAME has an infinitely-rewirable generic module architecture, to encourage reuse of chip drivers across platforms. Imagine MAME as a big box of all the different types of chips, plus some templates that explain what chips to take from the box and how to wire them together to make a system.&lt;/p&gt;
&lt;p&gt;Also I was not exaggerating when I said every arcade machine ever made. The MAME source tree has slightly north of 6900 .cpp files, and a princely 2.9 million lines of code (4 million if you count headers!). This is a Big Codebase.&lt;/p&gt;

&lt;/div&gt;
&lt;div class="section" id="the-problem"&gt;
&lt;h2&gt;The problem&lt;/h2&gt;
&lt;p&gt;The reason I started looking at MAME was because of this tweet:&lt;/p&gt;
&lt;blockquote&gt;
&amp;lt;&amp;#64;bfod&amp;gt; &lt;a class="reference external" href="https://twitter.com/bfod/status/802395453027336192"&gt;I am beyond sad that MAME doesn’t support save states for Daisu Kiss, so I can’t put it in Multibowl&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.foddy.net/2016/08/multibowl/"&gt;Multibowl&lt;/a&gt; is a pretty amazing project; Bennett Foddy and AP Thomson have turned hundreds of MAME ROMs into this weird tournament of two-player minigames, thanks to judicious abuse of memory scraping and savestates. Similar to Joe Dante's &lt;a class="reference external" href="https://en.wikipedia.org/wiki/The_Movie_Orgy"&gt;The Movie Orgy&lt;/a&gt;, Multibowl is only available as a touring art installation due to the staggering amount of copyright infringement involved. Nevertheless I have a soft spot for illegal art, and I was looking for an opportunity to get my feet wet with emulators, and this seemed like it! I mean the hard part is already done, the game is playable, all that's left is to make sure all the bits of state are being saved... right?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-preparation"&gt;
&lt;h2&gt;The preparation&lt;/h2&gt;
&lt;p&gt;The only software we need installed to debug this is the Git VCS and the standard Linux command line tools, plus whatever build dependencies MAME has. Start by grabbing the latest MAME source code&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/mamedev/mame
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;mame
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The MAME build system is sort of nutso, however there is a nice way to build just the machine you want to debug. (And you &lt;em&gt;will&lt;/em&gt; want to do this, as a full symbols build of all the arcade machines is a 2.2gb executable.)&lt;/p&gt;
&lt;p&gt;First, find the .cpp file for the driver of the machine named on the MAME splash screen when you try and emulate it (in my case it was src/mame/drivers/konamigx.cpp). Then, run the following command from the project root:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;REGENIE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;SUBTARGET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;arcade&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;OPTIMIZE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;SYMBOLS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;SYMLEVEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;NOWERROR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PYTHON_EXECUTABLE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin/python2&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;TOOLS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ARCHOPTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-flifetime-dse&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;VERBOSE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;SOURCES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;src/mame/drivers/konamigx.cpp&lt;span class="w"&gt; &lt;/span&gt;-j3
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;(For further recompiles, remove the REGENIE=1)&lt;/p&gt;
&lt;p&gt;This will build a copy of MAME with symbols, but &lt;em&gt;without&lt;/em&gt; the features of the debug build level, like asserts! I did it this way because one of the asserts in the audio CPU was killing the emulator after loading a save state, and I wasn't quite feeling brave enough to debug the whole stack to find out why. (Alternatively you can add DEBUG=1 and comment out all the things)&lt;/p&gt;
&lt;p&gt;The other modification I recommend for save state hacking:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gh"&gt;diff --git a/src/emu/save.cpp b/src/emu/save.cpp&lt;/span&gt;
&lt;span class="gh"&gt;index c85f0d1..5e33895 100644&lt;/span&gt;
&lt;span class="gd"&gt;--- a/src/emu/save.cpp&lt;/span&gt;
&lt;span class="gi"&gt;+++ b/src/emu/save.cpp&lt;/span&gt;
&lt;span class="gu"&gt;@@ -291,6 +291,8 @@ save_error save_manager::write_file(emu_file &amp;amp;file)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;       if (m_illegal_regs &amp;gt; 0)
&lt;span class="w"&gt; &lt;/span&gt;               return STATERR_ILLEGAL_REGISTRATIONS;

&lt;span class="gi"&gt;+    dump_registry();&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;       // generate the header
&lt;span class="w"&gt; &lt;/span&gt;       u8 header[HEADER_SIZE];
&lt;span class="w"&gt; &lt;/span&gt;       memcpy(&amp;amp;header[0], STATE_MAGIC_NUM, 8);
&lt;span class="gu"&gt;@@ -353,7 +355,7 @@ u32 save_manager::signature() const&lt;/span&gt;
void save_manager::dump_registry() const
{
&lt;span class="w"&gt; &lt;/span&gt;       for (auto &amp;amp;entry : m_entry_list)
&lt;span class="gd"&gt;-               LOG((&amp;quot;%s: %d x %d\n&amp;quot;, entry-&amp;gt;m_name.c_str(), entry-&amp;gt;m_typesize, entry-&amp;gt;m_typecount));&lt;/span&gt;
&lt;span class="gi"&gt;+               osd_printf_verbose(&amp;quot;%s: %d x %d\n&amp;quot;, entry-&amp;gt;m_name.c_str(), entry-&amp;gt;m_typesize, entry-&amp;gt;m_typecount);&lt;/span&gt;
}
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This will modify the save state command to print out a big list of all the saved properties. Apparently you can get the logging mode to trigger the list, but I gave up trying. Make sure not to commit this change :P&lt;/p&gt;
&lt;p&gt;For testing I ran my single-machine build inside GDB, with MAME's built-in debugger switched on. The MAME debugger is great for inspecting the code and memory usage of the emulated CPUs, but if you want to see what's happening inside those chips you need to break out into C++ land with GDB.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;gdb&lt;span class="w"&gt; &lt;/span&gt;--args&lt;span class="w"&gt; &lt;/span&gt;~/Development/mame/arcade64&lt;span class="w"&gt; &lt;/span&gt;-debug&lt;span class="w"&gt; &lt;/span&gt;-window&lt;span class="w"&gt; &lt;/span&gt;-verbose&lt;span class="w"&gt; &lt;/span&gt;daiskiss.zip
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="the-diagnosis"&gt;
&lt;h2&gt;The diagnosis&lt;/h2&gt;
&lt;p&gt;Start by doing the obvious thing: open Daisu Kiss up in MAME and check out the machine info:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Daisu-Kiss (ver JAA)
1996 Konami
Driver: konamigx.cpp

CPU:
M68EC020 24.000000MHz
M68000 8.000000MHz
TMS57002 12.000000MHz

Sound:
TMS57002 12.000000MHz
2xSpeaker
2xK054539 ADPC 18.432000MHz

Video:
288 x 224 (H) 59.185606 Hz
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So it's a game using the &lt;a class="reference external" href="http://system16.com/hardware.php?id=574"&gt;Konami GX&lt;/a&gt; model of arcade boards, with a couple of trusty old &lt;a class="reference external" href="http://segaretro.org/Motorola_68000"&gt;Motorola 68000&lt;/a&gt; processors doing the computing spadework, some Texas Instruments DSP thingo, and... what looks like an avalanche of custom Konami chips doing the graphics and sound! Lovely.&lt;/p&gt;
&lt;p&gt;As expected, loading a saved state from a cold boot didn't work; the game would either hang with the music looping in the background, or hang with the music stuck on a slur. However save states usually DID work if you loaded them &lt;em&gt;after&lt;/em&gt; the machine has finished booting, which indicates there's some bit of state which gets set on boot that isn't accounted for.&lt;/p&gt;
&lt;div class="section" id="wrapping-your-head-around-save-states"&gt;
&lt;h3&gt;Wrapping your head around save states&lt;/h3&gt;
&lt;p&gt;My initial assumption about how save states would work: MAME dumps a copy of the memory, CPU registers, and anything else which holds machine state to a file. Loading does the reverse: updates the memory and sets all the CPU registers. The documentation mostly confirms this (ok fine so I missed timing information, bank-switching and perhipherals from my list), and reassures that most of the stock hardware has state support coded in already! So that's something. There was a rather tantalising page about save states in the MAME wiki, but it's maybe two codebase refactoring sprees behind and none of the names matched up anymore.&lt;/p&gt;
&lt;p&gt;Anyway, the way it works is there's a small window at the end of the driver init cycle where a component can flag the parts of itself which contain state. MAME provides two helper methods for this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;save_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_ItemType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;valname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;save_pointer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_ItemType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;valname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;!-- ** --&gt;
&lt;p&gt;save_item is pretty malleable, and works for scalar values, 1D and 2D static arrays, and 1D and 2D std::vectors. For malloc'd memory you can use save_pointer, but you need to provide a count of the number of elements. Most of the time you'll see them used in the form save_item(NAME(state_object)), where NAME(state_object) is a macro that expands to 'state_object, &amp;quot;state_object&amp;quot;', to save on repetition.&lt;/p&gt;
&lt;p&gt;(I am sad that save_item can't be used on structs. I mean, it totally &lt;em&gt;can&lt;/em&gt;, but every compiler aligns items inside structs differently, meaning that save states from different compilers would break in terrible ways).&lt;/p&gt;
&lt;p&gt;And that's it. Once an item has been flagged, the contents will be written to and loaded from save states. The simplicity of this system is that because each component tracks its own state, reuse between different arcade boards is quite high. For instance the Motorola 68000 has a fairly well-defined set of internal state, so every game which uses the M68000 driver will have that chip's state saved automatically.&lt;/p&gt;
&lt;p&gt;Bonus fact: there's no actual seperation of concerns in the save state file. There's a game ID, a CRC signature for the set of fields, then an unlabelled stream of raw data. If the CRC in the file matches the one generated by MAME from the field list, it will load said data into the fields sequentially.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="in-which-we-consider-the-majestic-konami-gx-arcade-board-in-all-its-proprietary-splendour"&gt;
&lt;h3&gt;In which we consider the majestic Konami GX arcade board in all its proprietary splendour&lt;/h3&gt;
&lt;p&gt;First place to look would be in /mame/src/mame/drivers/konamigx.cpp, which defines the hardware layout for all Konami GX games. Search around for &amp;quot;daiskiss&amp;quot;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;GAME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1996&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;daiskiss&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;konamigx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;konamigx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gokuparo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;konamigx_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;konamigx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ROT0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Konami&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Daisu-Kiss (ver JAA)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MACHINE_IMPERFECT_GRAPHICS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&amp;quot;gokuparo&amp;quot; is the name of another Konami GX game, so presumably this piggy-backs off the hardware config. The actual definition of how the hardware in the konamigx driver is wired together is in a big macro block MACHINE_CONFIG_START:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MACHINE_CONFIG_START&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;konamigx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;konamigx_state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* basic machine hardware */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_CPU_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;maincpu&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;M68EC020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MASTER_CLOCK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_CPU_PROGRAM_MAP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gx_type2_map&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_CPU_VBLANK_INT_DRIVER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;screen&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;konamigx_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;konamigx_type2_vblank_irq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_CPU_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;soundcpu&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;M68000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SUB_CLOCK&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_CPU_PROGRAM_MAP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gxsndmap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_CPU_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;dasp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TMS57002&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MASTER_CLOCK&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_CPU_DATA_MAP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gxtmsmap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_DEVICE_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k053252&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;K053252&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MASTER_CLOCK&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_K053252_OFFSETS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_K053252_INT1_ACK_CB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WRITELINE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;konamigx_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vblank_irq_ack_w&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_K053252_INT2_ACK_CB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WRITELINE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;konamigx_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hblank_irq_ack_w&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_VIDEO_SET_SCREEN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;screen&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_QUANTUM_TIME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attotime&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;from_hz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_MACHINE_START_OVERRIDE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;konamigx_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;konamigx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_MACHINE_RESET_OVERRIDE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;konamigx_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;konamigx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_EEPROM_SERIAL_93C46_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;eeprom&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* video hardware */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_SCREEN_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;screen&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RASTER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_SCREEN_VIDEO_ATTRIBUTES&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VIDEO_UPDATE_AFTER_VBLANK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_SCREEN_RAW_PARAMS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;384&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;383&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;224&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;223&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* These parameters are actual value written to the CCU.&lt;/span&gt;
&lt;span class="cm"&gt;    tbyahhoo attract mode desync is caused by another matter. */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;//MCFG_SCREEN_VBLANK_TIME(ATTOSECONDS_IN_USEC(600))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// TODO: WTF, without these most games crashes? Some legacy call in video code???&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_SCREEN_SIZE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_SCREEN_VISIBLE_AREA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;288-1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;224-1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_SCREEN_UPDATE_DRIVER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;konamigx_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;screen_update_konamigx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_PALETTE_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;palette&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8192&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_PALETTE_FORMAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;XRGB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_PALETTE_ENABLE_SHADOWS&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_PALETTE_ENABLE_HILIGHTS&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_DEVICE_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k056832&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;K056832&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_K056832_CB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;konamigx_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;type2_tile_callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_K056832_CONFIG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;gfx1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;K056832_BPP_5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;none&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_K056832_PALETTE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;palette&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_K055555_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k055555&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_DEVICE_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k054338&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;K054338&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_K054338_MIXER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k055555&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_K054338_SET_SCREEN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;screen&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_K054338_ALPHAINV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_DEVICE_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k055673&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;K055673&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_K055673_CB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;konamigx_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;type2_sprite_callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_K055673_CONFIG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;gfx2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;K055673_LAYOUT_GX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-23&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_K055673_SET_SCREEN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;screen&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_K055673_PALETTE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;palette&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_VIDEO_START_OVERRIDE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;konamigx_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;konamigx_5bpp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* sound hardware */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_SPEAKER_STANDARD_STEREO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;lspeaker&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;rspeaker&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_DEVICE_MODIFY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;dasp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_SOUND_ROUTE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;lspeaker&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_SOUND_ROUTE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;rspeaker&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_K056800_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k056800&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;XTAL_18_432MHz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_K056800_INT_HANDLER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;INPUTLINE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;soundcpu&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;M68K_IRQ_1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_DEVICE_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k054539_1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;K054539&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;XTAL_18_432MHz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_K054539_REGION_OVERRRIDE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;shared&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_K054539_TIMER_HANDLER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WRITELINE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;konamigx_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k054539_irq_gen&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_SOUND_ROUTE_EX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;dasp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_SOUND_ROUTE_EX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;dasp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_SOUND_ROUTE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;lspeaker&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_SOUND_ROUTE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;rspeaker&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_DEVICE_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k054539_2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;K054539&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;XTAL_18_432MHz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_K054539_REGION_OVERRRIDE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;shared&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_SOUND_ROUTE_EX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;dasp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_SOUND_ROUTE_EX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;dasp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_SOUND_ROUTE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;lspeaker&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_SOUND_ROUTE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;rspeaker&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;MACHINE_CONFIG_END&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MACHINE_CONFIG_DERIVED&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gokuparo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;konamigx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;MCFG_DEVICE_MODIFY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k055673&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;MCFG_K055673_CONFIG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;gfx2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;K055673_LAYOUT_GX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-46&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-23&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;MACHINE_CONFIG_END&lt;/span&gt;
&lt;span class="o"&gt;**&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That was long, but we have our full list of chips that the board uses, which we can cross-reference against the MAME driver source to determine what they do.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;M68EC020 (Motorola 68020 - main CPU)&lt;/li&gt;
&lt;li&gt;M68000 (Motorola 68000 - sound CPU)&lt;/li&gt;
&lt;li&gt;TMS57002 (Texas Instruments- sound DASP)&lt;/li&gt;
&lt;li&gt;K053252 (Konami - display timing/interrupt)&lt;/li&gt;
&lt;li&gt;K056832 (Konami - tilemap)&lt;/li&gt;
&lt;li&gt;K055555 (Konami - video mixer/priority encoder)&lt;/li&gt;
&lt;li&gt;K054338 (Konami - video mixer/alpha)&lt;/li&gt;
&lt;li&gt;K055673 (Konami - sprite generator)&lt;/li&gt;
&lt;li&gt;K056800 (Konami - sound controller)&lt;/li&gt;
&lt;li&gt;K054539_1, K054539_2 (Konami - PCM sound)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Yep. That's a lot. Uhhh.&lt;/p&gt;
&lt;!-- Originally I thought it would be graphics related, however the palette/tilemaps/charmaps all seem okay in the MAME viewer. --&gt;
&lt;/div&gt;
&lt;div class="section" id="finding-where-the-lockup-is"&gt;
&lt;h3&gt;Finding where the lockup is&lt;/h3&gt;
&lt;p&gt;Okay, we can reproduce the issue &lt;em&gt;and&lt;/em&gt; we have access to a fancy debugger. My plan is to open two debug copies of MAME: one pre-boot, one post-boot, load the state in each, then step through them both line by line until execution diverges. Then that line shall magically disclose what the problem is. No, it will! Shush.&lt;/p&gt;
&lt;p&gt;Well. Well well well. The main CPU activity on the hung session is basically 4 lines of assembly.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# The code which is responsible for the hang loop (A1: 0x00c08400):&lt;/span&gt;

&lt;span class="err"&gt;28707&lt;/span&gt;&lt;span class="nf"&gt;C&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="no"&gt;tst.w&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;A1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c1"&gt;# check that the uint16_be at address 0x00c08400 == 0x0000&lt;/span&gt;
&lt;span class="err"&gt;28707&lt;/span&gt;&lt;span class="nf"&gt;E&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="no"&gt;beq&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="no"&gt;$287184&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# if so, jump to 287184!&lt;/span&gt;
&lt;span class="na"&gt;...&lt;/span&gt;
&lt;span class="err"&gt;287184&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nf"&gt;tst.w&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;$646&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="no"&gt;A1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# check that the uint16_be at address (0x00c08400 + 0x646) == 0x0000&lt;/span&gt;
&lt;span class="err"&gt;287188&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nf"&gt;beq&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="no"&gt;$28707c&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# if so, jump to 28707C!&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, at the time of execution, register A1 is set to 0x00c08400. What part of the memory map is this?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ADDRESS_MAP_START&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gx_base_memmap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AS_PROGRAM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;konamigx_state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x01ffff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_ROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// BIOS ROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x200000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x3fffff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_ROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// main program ROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x400000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x7fffff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_ROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// data ROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xc00000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xc1ffff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_RAM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_SHARE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;workram&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xd00000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xd01fff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_DEVREAD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k056832&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k056832_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k_5bpp_rom_long_r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xd20000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xd20fff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_DEVREADWRITE16&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k055673&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k055673_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k053247_word_r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k053247_word_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xffffffff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xd21000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xd21fff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_RAM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// second bank of sprite RAM, accessed thru ESC&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xd22000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xd23fff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_RAM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// extra bank checked at least by sexyparo, pending further investigation.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xd40000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xd4003f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_DEVWRITE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k056832&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k056832_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;long_w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xd44000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xd4400f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_WRITE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;konamigx_tilebank_w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xd48000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xd48007&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_DEVWRITE16&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k055673&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k055673_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k053246_word_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xffffffff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xd4a000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xd4a00f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_DEVREAD16&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k055673&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k055673_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k055673_rom_word_r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xffffffff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xd4a010&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xd4a01f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_DEVWRITE16&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k055673&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k055673_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k055673_reg_word_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xffffffff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xd4c000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xd4c01f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_DEVREADWRITE8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k053252&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k053252_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xff00ff00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xd4e000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xd4e01f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_WRITENOP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// left-over for &amp;quot;secondary&amp;quot; CCU, apparently (used by type 3/4 for slave screen?)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xd50000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xd500ff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_DEVWRITE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k055555&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k055555_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;K055555_long_w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xd52000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xd5201f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_DEVREADWRITE8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k056800&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k056800_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;host_r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;host_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xff00ff00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xd56000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xd56003&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_WRITE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eeprom_w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xd58000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xd58003&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_WRITE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;control_w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xd5a000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xd5a003&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_READ_PORT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;SYSTEM_DSW&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xd5c000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xd5c003&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_READ_PORT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;INPUTS&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xd5e000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xd5e003&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_READ_PORT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;SERVICE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xd80000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xd8001f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_DEVWRITE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k054338&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k054338_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;long_w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xda0000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xda1fff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_DEVREADWRITE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k056832&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k056832_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ram_long_r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ram_long_w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xda2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xda3fff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_DEVREADWRITE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k056832&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k056832_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ram_long_r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ram_long_w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ADDRESS_MAP_END&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Why, it's smack in the middle of the work RAM! Let's dump it from the save state and do a diff with one dumped from a clean run.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;dump&lt;span class="w"&gt; &lt;/span&gt;/tmp/work.dmp,0xc00000,0x20000
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Eh, the RAM contents match. Seems legit, MAME will add any part of the address range flagged as RAM to the save state.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;taken&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;my&lt;span class="w"&gt; &lt;/span&gt;dump_registry&lt;span class="w"&gt; &lt;/span&gt;printout&lt;span class="o"&gt;)&lt;/span&gt;
...
memory/:maincpu/0/00c00000-00c1ffff:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;x&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;32768&lt;/span&gt;
...
memory/:maincpu/0/00d20000-00d2ffff:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;x&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;16384&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Still, we can confirm by putting breakpoints immediately after those two jumps that no amount of waiting will break the main CPU out of this hang. Contrast that with the full run, where the breakout happens instantaneously!&lt;/p&gt;
&lt;p&gt;After a more thorough examination, it doesn't look like that section of work RAM has a direct memory access (DMA) arrangement, or is touched by anything other than the CPU. Which means the CPU has to be the one to change it, despite being stuck in an infinite loop. Perhaps there's an interrupt that causes it to jump to another chunk of code...&lt;/p&gt;
&lt;p&gt;Oh look! Using the debugger's &amp;quot;Run to the next Interrupt on this CPU&amp;quot; command on the working copy, we can see there are two interrupts that get called every frame in the working copy: IRQ 1 and IRQ 4. And neither of those are being called in the busted save state copy! Dun dun duuuuuuuuunnnn.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt; * IRQs:&lt;/span&gt;
&lt;span class="cm"&gt; * 1: VBL (at 60 Hz)&lt;/span&gt;
&lt;span class="cm"&gt; * 2: HBL&lt;/span&gt;
&lt;span class="cm"&gt; * 3: Sprite DMA complete&lt;/span&gt;
&lt;span class="cm"&gt; * 4: from protection device, indicates command completion for &amp;quot;ESC&amp;quot; chip&lt;/span&gt;
&lt;span class="cm"&gt; */&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;!-- ** --&gt;
&lt;p&gt;As far as I can tell it's the vertical blanking interrupt (IRQ 1) that breaks the main CPU out of the loop and gives it more things to do, so let's inspect that. In real life, you'd interrupt the M68020C by pulling the voltage up on one of the chip legs or something. In MAME this happens in the driver C++ code with the handy command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;m_maincpu&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;set_input_line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HOLD_LINE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There are two spots in the code (?!) which call IRQ 1, and only one of them is called every frame for the hung session:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;INTERRUPT_GEN_MEMBER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;konamigx_state&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;konamigx_type2_vblank_irq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// lift idle suspension&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m_resume_trigger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;m_suspension_active&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;m_suspension_active&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m_resume_trigger&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// IRQ 1 is the main 60hz vblank interrupt&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m_gx_syncen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;m_gx_syncen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mh"&gt;0x20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;m_gx_wrport1_1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x81&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x81&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m_gx_syncen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;m_gx_syncen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// TODO: enabling ASSERT_LINE breaks opengolf, annoying.&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;set_input_line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HOLD_LINE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;dmastart_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There it is! If either of those two if statements fail, it doesn't call IRQ 1! In the hung session, m_gx_syncen is always 0, and &lt;em&gt;that's not saved in the state&lt;/em&gt;. Oh hoh hoh hoh hoh.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;MACHINE_START_MEMBER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;konamigx_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;konamigx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;save_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NAME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m_gx_wrport1_0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;save_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NAME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m_gx_wrport1_1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;save_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NAME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m_gx_wrport2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;save_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NAME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m_gx_rdport1_3&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;save_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NAME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m_gx_syncen&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;save_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NAME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m_suspension_active&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;save_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NAME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m_prev_pixel_clock&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I added all the registers that were nearby m_gx_syncen in MACHINE_RESET_MEMBER, because why the hell not. AND IT WORKS! Man what an adventure that was, ah well let's hammer out a pull request and wrap this up nicely.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="hang-on-what-about-the-dodgy-sound-you-mentioned-earlier-don-t-be-cheap"&gt;
&lt;h3&gt;Hang on, what about the dodgy sound you mentioned earlier? Don't be cheap&lt;/h3&gt;
&lt;p&gt;You got me, it &lt;em&gt;mostly&lt;/em&gt; works. The main CPU execution saunters through without a hitch, but I could still break the audio by loading a save state on a cold boot. There must be some more state variables we haven't captured yet.&lt;/p&gt;
&lt;p&gt;Good thing we have a canary to help us out; the crashy assert in the debug build mentioned earlier.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Thread&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;arcade64d&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;received&lt;span class="w"&gt; &lt;/span&gt;signal&lt;span class="w"&gt; &lt;/span&gt;SIGTRAP,&lt;span class="w"&gt; &lt;/span&gt;Trace/breakpoint&lt;span class="w"&gt; &lt;/span&gt;trap.
0x00007ffff3e3e367&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;kill&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;/usr/lib/libc.so.6
&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bt
&lt;span class="c1"&gt;#0  0x00007ffff3e3e367 in kill () at /usr/lib/libc.so.6&lt;/span&gt;
&lt;span class="c1"&gt;#1  0x0000555555fc7475 in osd_break_into_debugger(char const*) (message=message@entry=0x55555a530448 &amp;quot;assert: ../../../../../src/devices/cpu/tms57002/tms57002.cpp:790: samples == 1&amp;quot;) at ../../../../../src/osd/modules/lib/osdlib_unix.cpp:97&lt;/span&gt;
&lt;span class="c1"&gt;#2  0x0000555555acb405 in emu_fatalerror::emu_fatalerror(char const*, ...) (this=0x55555a530440, format=&amp;lt;optimized out&amp;gt;) at ../../../../../src/emu/emucore.cpp:29&lt;/span&gt;
&lt;span class="c1"&gt;#3  0x00005555559ba7c2 in tms57002_device::sound_stream_update(sound_stream&amp;amp;, int**, int**, int) (this=&amp;lt;optimized out&amp;gt;, stream=..., inputs=&amp;lt;optimized out&amp;gt;, outputs=&amp;lt;optimized out&amp;gt;, samples=&amp;lt;optimized out&amp;gt;) at ../../../../../src/devices/cpu/tms57002/tms57002.cpp:790&lt;/span&gt;
&lt;span class="c1"&gt;#4  0x0000555555c006c4 in delegate_base&amp;lt;void, sound_stream&amp;amp;, int**, int**, int&amp;gt;::operator()(sound_stream&amp;amp;, int**, int**, int) const (args#3=721, args#2=&amp;lt;optimized out&amp;gt;, args#1=&amp;lt;optimized out&amp;gt;, args#0=..., this=0x555559687cb0) at ../../../../../src/lib/util/delegate.h:544&lt;/span&gt;
&lt;span class="c1"&gt;#5  0x0000555555c006c4 in sound_stream::generate_samples(int) (this=this@entry=0x555559687c00, samples=721) at ../../../../../src/emu/sound.cpp:640&lt;/span&gt;
&lt;span class="c1"&gt;#6  0x0000555555c01759 in sound_stream::update() (this=0x555559687c00) at ../../../../../src/emu/sound.cpp:289&lt;/span&gt;
&lt;span class="c1"&gt;#7  0x0000555555c01759 in sound_stream::sync_update(void*, int) (this=0x555559687c00) at ../../../../../src/emu/sound.cpp:299&lt;/span&gt;
&lt;span class="c1"&gt;#8  0x0000555555be64ae in delegate_base&amp;lt;void, void*, int&amp;gt;::operator()(void*, int) const (args#1=&amp;lt;optimized out&amp;gt;, args#0=&amp;lt;optimized out&amp;gt;, this=&amp;lt;optimized out&amp;gt;) at ../../../../../src/lib/util/delegate.h:544&lt;/span&gt;
&lt;span class="c1"&gt;#9  0x0000555555be64ae in device_scheduler::execute_timers() (this=&amp;lt;optimized out&amp;gt;) at ../../../../../src/emu/schedule.cpp:908&lt;/span&gt;
&lt;span class="c1"&gt;#10 0x0000555555be64ae in device_scheduler::timeslice() (this=this@entry=0x7fffffffce68) at ../../../../../src/emu/schedule.cpp:519&lt;/span&gt;
&lt;span class="c1"&gt;#11 0x0000555555b99398 in running_machine::run(bool) (this=this@entry=0x7fffffff6820, quiet=quiet@entry=false) at ../../../../../src/emu/machine.cpp:348&lt;/span&gt;
&lt;span class="c1"&gt;#12 0x0000555555743942 in mame_machine_manager::execute() (this=this@entry=0x555556afb0e0) at ../../../../../src/frontend/mame/mame.cpp:223&lt;/span&gt;
&lt;span class="c1"&gt;#13 0x00005555557cb8c4 in cli_frontend::start_execution(mame_machine_manager*, int, char**, std::__cxx11::basic_string&amp;lt;char, std::char_traits&amp;lt;char&amp;gt;, std::allocator&amp;lt;char&amp;gt; &amp;gt;&amp;amp;) (this=this@entry=0x7fffffffda50, manager=manager@entry=0x555556afb0e0, argc=argc@entry=5, argv=argv@entry=0x7fffffffe128, option_errors=&amp;quot;&amp;quot;) at ../../../../../src/frontend/mame/clifront.cpp:287&lt;/span&gt;
&lt;span class="c1"&gt;#14 0x00005555557cbdc9 in cli_frontend::execute(int, char**) (this=this@entry=0x7fffffffda50, argc=argc@entry=5, argv=argv@entry=0x7fffffffe128) at ../../../../../src/frontend/mame/clifront.cpp:314&lt;/span&gt;
&lt;span class="c1"&gt;#15 0x00005555557416b1 in emulator_info::start_frontend(emu_options&amp;amp;, osd_interface&amp;amp;, int, char**) (options=..., osd=..., argc=5, argv=0x7fffffffe128) at ../../../../../src/frontend/mame/mame.cpp:319&lt;/span&gt;
&lt;span class="c1"&gt;#16 0x000055555561271c in main(int, char**) (argc=5, argv=0x7fffffffe128) at ../../../../../src/osd/sdl/sdlmain.cpp:214&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Actually I think this is a red herring. The issue is that upon loading a save state, generate_samples is getting called with a large sample count (~720) which gets passed on to sound_stream_update, which has an assert(samples == 1) at the beginning. (Under normal operation the function gets called like a billion times a second with a sample count of 1.) This doesn't have any reflection on whether or not sound is broken, as it always happens when you load a state.&lt;/p&gt;
&lt;p&gt;So, back to zero clues! I revisted src/mame/drivers/konamigx.cpp to try and get more of an idea of how the audio system is wired together.&lt;/p&gt;
&lt;p&gt;These two K054539 chips interest me. K054539 is listed as a &amp;quot;PCM Sound Chip&amp;quot;, and a quick peek at the registers makes it clear that it's designed for wavetable music playback.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cm"&gt;/* Registers:&lt;/span&gt;
&lt;span class="cm"&gt;00..ff: 20 bytes/channel, 8 channels&lt;/span&gt;
&lt;span class="cm"&gt;  00..02: pitch (lsb, mid, msb)&lt;/span&gt;
&lt;span class="cm"&gt;      03: volume (0=max, 0x40=-36dB)&lt;/span&gt;
&lt;span class="cm"&gt;      04: reverb volume (idem)&lt;/span&gt;
&lt;span class="cm"&gt;  05: pan (1-f right, 10 middle, 11-1f left)&lt;/span&gt;
&lt;span class="cm"&gt;  06..07: reverb delay (0=max, current computation non-trusted)&lt;/span&gt;
&lt;span class="cm"&gt;  08..0a: loop (lsb, mid, msb)&lt;/span&gt;
&lt;span class="cm"&gt;  0c..0e: start (lsb, mid, msb) (and current position ?)&lt;/span&gt;

&lt;span class="cm"&gt;100.1ff: effects?&lt;/span&gt;
&lt;span class="cm"&gt;  13f: pan of the analog input (1-1f)&lt;/span&gt;

&lt;span class="cm"&gt;200..20f: 2 bytes/channel, 8 channels&lt;/span&gt;
&lt;span class="cm"&gt;  00: type (b2-3), reverse (b5)&lt;/span&gt;
&lt;span class="cm"&gt;  01: loop (b0)&lt;/span&gt;

&lt;span class="cm"&gt;214: Key on (b0-7 = channel 0-7)&lt;/span&gt;
&lt;span class="cm"&gt;215: Key off          &amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="cm"&gt;225: ?&lt;/span&gt;
&lt;span class="cm"&gt;227: Timer frequency&lt;/span&gt;
&lt;span class="cm"&gt;228: ?&lt;/span&gt;
&lt;span class="cm"&gt;229: ?&lt;/span&gt;
&lt;span class="cm"&gt;22a: ?&lt;/span&gt;
&lt;span class="cm"&gt;22b: ?&lt;/span&gt;
&lt;span class="cm"&gt;22c: Channel active? (b0-7 = channel 0-7)&lt;/span&gt;
&lt;span class="cm"&gt;22d: Data read/write port&lt;/span&gt;
&lt;span class="cm"&gt;22e: ROM/RAM select (00..7f == ROM banks, 80 = Reverb RAM)&lt;/span&gt;
&lt;span class="cm"&gt;22f: Global control:&lt;/span&gt;
&lt;span class="cm"&gt;     .......x - Enable PCM&lt;/span&gt;
&lt;span class="cm"&gt;     ......x. - Timer related?&lt;/span&gt;
&lt;span class="cm"&gt;     ...x.... - Enable ROM/RAM readback from 0x22d&lt;/span&gt;
&lt;span class="cm"&gt;     ..x..... - Timer output enable?&lt;/span&gt;
&lt;span class="cm"&gt;     x....... - Disable register RAM updates&lt;/span&gt;

&lt;span class="cm"&gt; The chip has an optional 0x8000 byte reverb buffer.&lt;/span&gt;
&lt;span class="cm"&gt; The reverb delay is actually an offset in this buffer.&lt;/span&gt;
&lt;span class="cm"&gt; */&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;!-- ** --&gt;
&lt;p&gt;At first I assumed the two chips were for some form of stereo playback, but the MCFG_SOUND_ROUTE stuff showed that each chip was outputting stereo and sending to both speakers. The K054539 has 8 sound channels with adjustable panning and effects, including a luxury 8kb buffer just for reverb! That means 16 audio channels all up. For kicks I tried turning down the gain on each chip to hear the changes; both chips are handling a mixture of PCM sample and music playback.&lt;/p&gt;
&lt;p&gt;Sound programming is handled by the M68000 audio CPU, which I'm guessing keeps the two K054539s fed with frame-by-frame playback instructions, and meaning the main CPU only has to worry about firing off the odd cue every now and then. Communication between the two is managed with &lt;em&gt;another&lt;/em&gt; beloved K chip, the K056800! Mercifully there's not too much to this one, it's basically a set of registers that the main CPU and sound CPU can read/write to.&lt;/p&gt;
&lt;p&gt;Because clues are so thin on the ground, we need to make our own luck. The K054539 doesn't sound like it's playing back the music properly, so let's gets some eyes on those registers and check if that's indeed the case.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gh"&gt;diff --git a/src/devices/sound/k054539.cpp b/src/devices/sound/k054539.cpp&lt;/span&gt;
&lt;span class="gh"&gt;index c275494..15e4b10 100644&lt;/span&gt;
&lt;span class="gd"&gt;--- a/src/devices/sound/k054539.cpp&lt;/span&gt;
&lt;span class="gi"&gt;+++ b/src/devices/sound/k054539.cpp&lt;/span&gt;
&lt;span class="gu"&gt;@@ -94,12 +94,14 @@ bool k054539_device::regupdate()&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;void k054539_device::keyon(int channel)
&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="gi"&gt;+    osd_printf_verbose(&amp;quot;k054539:%p Ch%d ON\n&amp;quot;, this, channel);&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;        if(regupdate())
&lt;span class="w"&gt; &lt;/span&gt;                regs[0x22c] |= 1 &amp;lt;&amp;lt; channel;
&lt;span class="w"&gt; &lt;/span&gt;}

&lt;span class="w"&gt; &lt;/span&gt;void k054539_device::keyoff(int channel)
&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="gi"&gt;+    osd_printf_verbose(&amp;quot;k054539:%p Ch%d OFF\n&amp;quot;, this, channel);&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;        if(regupdate())
&lt;span class="w"&gt; &lt;/span&gt;                regs[0x22c] &amp;amp;= ~(1 &amp;lt;&amp;lt; channel);
&lt;span class="w"&gt; &lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The above code prints a message every time one of the sound chips starts or ends playing a note. Here's a text dump from a working copy:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Samples:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;871&lt;/span&gt;
k054539:0x55f4aa874710&lt;span class="w"&gt; &lt;/span&gt;Ch0&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x55f4aa874710&lt;span class="w"&gt; &lt;/span&gt;Ch0&lt;span class="w"&gt; &lt;/span&gt;ON
k054539:0x55f4aa874710&lt;span class="w"&gt; &lt;/span&gt;Ch1&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x55f4aa874710&lt;span class="w"&gt; &lt;/span&gt;Ch1&lt;span class="w"&gt; &lt;/span&gt;ON
k054539:0x55f4aa874710&lt;span class="w"&gt; &lt;/span&gt;Ch2&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x55f4aa874710&lt;span class="w"&gt; &lt;/span&gt;Ch2&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x55f4aa874710&lt;span class="w"&gt; &lt;/span&gt;Ch2&lt;span class="w"&gt; &lt;/span&gt;ON
k054539:0x55f4aa874710&lt;span class="w"&gt; &lt;/span&gt;Ch3&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x55f4aa874710&lt;span class="w"&gt; &lt;/span&gt;Ch3&lt;span class="w"&gt; &lt;/span&gt;ON
k054539:0x55f4aa874710&lt;span class="w"&gt; &lt;/span&gt;Ch4&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x55f4aa874710&lt;span class="w"&gt; &lt;/span&gt;Ch4&lt;span class="w"&gt; &lt;/span&gt;ON
k054539:0x55f4aa874710&lt;span class="w"&gt; &lt;/span&gt;Ch5&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x55f4aa874710&lt;span class="w"&gt; &lt;/span&gt;Ch5&lt;span class="w"&gt; &lt;/span&gt;ON
k054539:0x55f4aa874710&lt;span class="w"&gt; &lt;/span&gt;Ch0&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x55f4aa874710&lt;span class="w"&gt; &lt;/span&gt;Ch0&lt;span class="w"&gt; &lt;/span&gt;ON
k054539:0x55f4aa874710&lt;span class="w"&gt; &lt;/span&gt;Ch1&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x55f4aa874710&lt;span class="w"&gt; &lt;/span&gt;Ch1&lt;span class="w"&gt; &lt;/span&gt;ON
k054539:0x55f4aa874710&lt;span class="w"&gt; &lt;/span&gt;Ch0&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x55f4aa874710&lt;span class="w"&gt; &lt;/span&gt;Ch0&lt;span class="w"&gt; &lt;/span&gt;ON
k054539:0x55f4aa874710&lt;span class="w"&gt; &lt;/span&gt;Ch1&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x55f4aa874710&lt;span class="w"&gt; &lt;/span&gt;Ch1&lt;span class="w"&gt; &lt;/span&gt;ON
k054539:0x55f4aa874710&lt;span class="w"&gt; &lt;/span&gt;Ch2&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x55f4aa874710&lt;span class="w"&gt; &lt;/span&gt;Ch2&lt;span class="w"&gt; &lt;/span&gt;ON
...
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And here's a dump from a busted save state copy:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Samples:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;871&lt;/span&gt;
k054539:0x5600870a8680&lt;span class="w"&gt; &lt;/span&gt;Ch3&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x5600870a8680&lt;span class="w"&gt; &lt;/span&gt;Ch4&lt;span class="w"&gt; &lt;/span&gt;OFF
&lt;span class="o"&gt;[&lt;/span&gt;slight&lt;span class="w"&gt; &lt;/span&gt;pause&lt;span class="o"&gt;]&lt;/span&gt;
k054539:0x5600870a8680&lt;span class="w"&gt; &lt;/span&gt;Ch0&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x5600870a8680&lt;span class="w"&gt; &lt;/span&gt;Ch1&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x5600870a8680&lt;span class="w"&gt; &lt;/span&gt;Ch2&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x5600870a8680&lt;span class="w"&gt; &lt;/span&gt;Ch3&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x5600870a8680&lt;span class="w"&gt; &lt;/span&gt;Ch4&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x5600870a8680&lt;span class="w"&gt; &lt;/span&gt;Ch5&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x5600870a8680&lt;span class="w"&gt; &lt;/span&gt;Ch6&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x5600870a8680&lt;span class="w"&gt; &lt;/span&gt;Ch7&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x5600870a9a40&lt;span class="w"&gt; &lt;/span&gt;Ch0&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x5600870a9a40&lt;span class="w"&gt; &lt;/span&gt;Ch1&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x5600870a9a40&lt;span class="w"&gt; &lt;/span&gt;Ch2&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x5600870a9a40&lt;span class="w"&gt; &lt;/span&gt;Ch3&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x5600870a9a40&lt;span class="w"&gt; &lt;/span&gt;Ch4&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x5600870a9a40&lt;span class="w"&gt; &lt;/span&gt;Ch5&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x5600870a9a40&lt;span class="w"&gt; &lt;/span&gt;Ch6&lt;span class="w"&gt; &lt;/span&gt;OFF
k054539:0x5600870a9a40&lt;span class="w"&gt; &lt;/span&gt;Ch7&lt;span class="w"&gt; &lt;/span&gt;OFF
&lt;span class="o"&gt;[&lt;/span&gt;nothing&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To my eyes that looks like a CPU somewhere tripping over and resetting, so the K054539 is mostly absolved of blame. Reloading the save state after the reset provides working sound. Checking the IRQs, on the busted save state the sound CPU infiniloops and doesn't receive any signals on IRQ 2 until juuust before the reset dance, which is very unusual. Normal behaviour is lots and lots and lots of IRQ 2 all of the time.&lt;/p&gt;
&lt;p&gt;Oh man. Interest is fading fast, but we're so close! SO CLOSE! Let's find out what is responsible for smashing IRQ 2.&lt;/p&gt;
&lt;p&gt;Wait, what?! There's a whole other chunk of address map that I missed?!?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;WRITE16_MEMBER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;konamigx_state&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;tms57002_control_word_w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ACCESSING_BITS_0_7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="n"&gt;m_soundcpu&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;set_input_line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;M68K_IRQ_2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CLEAR_LINE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;m_dasp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;pload_w&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;m_dasp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cload_w&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;m_dasp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;set_input_line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;INPUT_LINE_RESET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CLEAR_LINE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ASSERT_LINE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;m_sound_ctrl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* 68000 memory handling */&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ADDRESS_MAP_START&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gxsndmap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AS_PROGRAM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;konamigx_state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x03ffff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_ROM&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x100000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x10ffff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_RAM&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x200000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x2004ff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_DEVREADWRITE8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k054539_1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k054539_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xff00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x200000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x2004ff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_DEVREADWRITE8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k054539_2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k054539_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x00ff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x300000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x300001&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_READWRITE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tms57002_data_word_r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tms57002_data_word_w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x400000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x40001f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_DEVREADWRITE8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k056800&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k056800_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sound_r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sound_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x00ff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x500000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x500001&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_READWRITE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tms57002_status_word_r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tms57002_control_word_w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x580000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x580001&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_WRITENOP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// &amp;#39;NRES&amp;#39; - D2: K056602 /RESET&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ADDRESS_MAP_END&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ADDRESS_MAP_START&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gxtmsmap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AS_DATA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;konamigx_state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;AM_RANGE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x00000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x3ffff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AM_RAM&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ADDRESS_MAP_END&lt;/span&gt;


&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;WRITE_LINE_MEMBER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;konamigx_state&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;k054539_irq_gen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m_sound_ctrl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="c1"&gt;// Trigger an interrupt on the rising edge&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;m_sound_intck&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="n"&gt;m_soundcpu&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;set_input_line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;M68K_IRQ_2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ASSERT_LINE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;m_sound_intck&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So there's two spots inside the address mapper that change the state of IRQ 2 on the sound CPU, which rely on (surprise!) some untracked state. Now keep cool, but I think I've got this. Calm blue ocean, calm blue ocean, calm blue ocean...&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gh"&gt;diff --git a/src/mame/drivers/konamigx.cpp b/src/mame/drivers/konamigx.cpp&lt;/span&gt;
&lt;span class="gh"&gt;index e200d1c..e58a552 100644&lt;/span&gt;
&lt;span class="gd"&gt;--- a/src/mame/drivers/konamigx.cpp&lt;/span&gt;
&lt;span class="gi"&gt;+++ b/src/mame/drivers/konamigx.cpp&lt;/span&gt;
&lt;span class="gu"&gt;@@ -3707,6 +3707,9 @@ ROM_END&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;MACHINE_START_MEMBER(konamigx_state,konamigx)
&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="gi"&gt;+       save_item(NAME(m_sound_ctrl));&lt;/span&gt;
&lt;span class="gi"&gt;+       save_item(NAME(m_sound_intck));&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;       save_item(NAME(m_gx_wrport1_0));
&lt;span class="w"&gt; &lt;/span&gt;       save_item(NAME(m_gx_wrport1_1));
&lt;span class="w"&gt; &lt;/span&gt;       save_item(NAME(m_gx_wrport2));
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In true monkey's paw fashion, the music plays back now but &lt;em&gt;never stops&lt;/em&gt;, even as the game continues on. Ah well, like before it's only happening from a cold boot, so it's the same problem.&lt;/p&gt;
&lt;p&gt;Backtracking a little: how does the main CPU tell the sound CPU to cue up a different thing? If you guessed &amp;quot;some ridiculous one-off chip starting with a K&amp;quot;, you're right! It's the K056800.&lt;/p&gt;
&lt;p&gt;Oh look. There's one variable that wasn't saved out of the four that makes up the chip.&lt;/p&gt;
&lt;p&gt;Surely it couldn't be that.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;devices&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sound&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;k056800&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cpp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;devices&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sound&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;k056800&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cpp&lt;/span&gt;
&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;19e572&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="n"&gt;fe42ed0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100644&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;devices&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sound&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;k056800&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cpp&lt;/span&gt;
&lt;span class="o"&gt;+++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;devices&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sound&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;k056800&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cpp&lt;/span&gt;
&lt;span class="err"&gt;@@&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-36&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@@&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k056800_device&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;device_start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;m_int_handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resolve_safe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;save_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NAME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m_int_pending&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;save_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NAME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m_int_enabled&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;save_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NAME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m_host_to_snd_regs&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;save_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NAME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m_snd_to_host_regs&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;YEAH BOIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="the-solution"&gt;
&lt;h2&gt;The solution&lt;/h2&gt;
&lt;p&gt;Keep adding save_item() calls to chunks of untracked state until the machine can resurrect itself.&lt;/p&gt;
&lt;p&gt;(&lt;a class="reference external" href="https://github.com/mamedev/mame/pull/1857"&gt;MAME pull request #1857&lt;/a&gt;)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="bonus-content"&gt;
&lt;h2&gt;Bonus content!&lt;/h2&gt;
&lt;p&gt;After getting in contact with Bennett Foddy, I foolishly asked if there were any more MAME games he wanted to include in Multibowl but couldn't for technical reasons. And, uh, there were some! So in my hubris I did some &lt;em&gt;more&lt;/em&gt; debugging.&lt;/p&gt;
&lt;div class="section" id="pc-vga-cs4031-fix-save-state-support"&gt;
&lt;h3&gt;pc_vga/cs4031: fix save state support&lt;/h3&gt;
&lt;p&gt;(&lt;a class="reference external" href="https://github.com/mamedev/mame/pull/2127"&gt;MAME pull request #2127&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;With the VGA driver I could see what was wrong immediately (registers not being saved), but I had to comb through the huuuuuge commit history for the file to figure out what was going on. Quite often the problems are easy to read, but the line of reasoning for the current design choices are much harder to guess.&lt;/p&gt;
&lt;p&gt;In short, the registers for the VGA card were being stored twice; once as a flat array called vga.crtc.data, and again as a whole bunch of unpacked discrete variables (e.g. vga.crtc.horz_total, vga.crtc.horz_disp_end, ...). All of the business logic was checking the discrete variables, but vga.crtc.data was the only thing being save_pointer()'d. Some repo dredging revealed the mystery: vga.crtc.data was originally the &lt;em&gt;only&lt;/em&gt; thing, but 5 years ago there was a refactor that added the discrete variables and ported all the business logic to use them. vga.crtc.data hung around I guess for ease of testing (when you save_pointer() a thing, you can watch it in the debugger) and still gets updated by MAME, but it remains functionally useless.&lt;/p&gt;
&lt;p&gt;So far so good. But what I couldn't figure out was why all the Protected Mode DOS games I ran would crash when loading the state from a cold boot? And at points which seemed to have nothing at all to do with the code?&lt;/p&gt;
&lt;p&gt;Unfortunately, I couldn't find a nice way of trapping the MAME debugger when the crash happened, so I had to do the old standby of manually stepping through a bunch of times and taking lots of notes about e.g. which iteration of an inner loop to step into rather than over. Here are my scrawly notes from debugging the game Boppin':&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Find that the function called at 0x002a326f causes the crash and severs control to the debugger!&lt;/li&gt;
&lt;li&gt;Step into the function and keep hitting &amp;quot;step over&amp;quot; until the crash instruction is reached&lt;/li&gt;
&lt;li&gt;It was on the 4th call to 0x002ac752, but why?!?&lt;/li&gt;
&lt;li&gt;0x002ac9ca, step into function, loop ahead until DL = 0x16&lt;/li&gt;
&lt;li&gt;0x002b2a2a, step 6 times&lt;/li&gt;
&lt;li&gt;it's this iteration of the inner loop which is triggering it&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;0x002B2A18&lt;span class="w"&gt;      &lt;/span&gt;mov&lt;span class="w"&gt; &lt;/span&gt;ecx,&lt;span class="o"&gt;[&lt;/span&gt;ebp+20h&lt;span class="o"&gt;]&lt;/span&gt;
0x002B2A1B&lt;span class="w"&gt;      &lt;/span&gt;lodsb&lt;span class="w"&gt;               &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;load&lt;span class="w"&gt; &lt;/span&gt;byte&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;address&lt;span class="w"&gt; &lt;/span&gt;DS:ESI&lt;span class="w"&gt; &lt;/span&gt;into&lt;span class="w"&gt; &lt;/span&gt;AL,&lt;span class="w"&gt; &lt;/span&gt;increment&lt;span class="w"&gt; &lt;/span&gt;ESI
0x002B2A1C&lt;span class="w"&gt;      &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;al,&lt;span class="w"&gt; &lt;/span&gt;al
0x002B2A1E&lt;span class="w"&gt;      &lt;/span&gt;je&lt;span class="w"&gt; &lt;/span&gt;2B2A30h&lt;span class="w"&gt;          &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;jump&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;AL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
0x002B2A20&lt;span class="w"&gt;      &lt;/span&gt;stosb&lt;span class="w"&gt;               &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;store&lt;span class="w"&gt; &lt;/span&gt;AL&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;address&lt;span class="w"&gt; &lt;/span&gt;ES:EDI,&lt;span class="w"&gt; &lt;/span&gt;increment&lt;span class="w"&gt; &lt;/span&gt;EDI
0x002B2A21&lt;span class="w"&gt;      &lt;/span&gt;loop&lt;span class="w"&gt; &lt;/span&gt;2B2A1Bh&lt;span class="w"&gt;        &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;decrement&lt;span class="w"&gt; &lt;/span&gt;ECX,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;

...

0x002B2A30&lt;span class="w"&gt;      &lt;/span&gt;inc&lt;span class="w"&gt; &lt;/span&gt;edi
0x002B2A31&lt;span class="w"&gt;      &lt;/span&gt;loop&lt;span class="w"&gt; &lt;/span&gt;2B2A1Bh
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The value of ESI is loaded in as garbage! Or rather, the 32-bit int from 0x0030248c is loaded in. And it's wrong. Very wrong.&lt;/p&gt;
&lt;p&gt;Before my eyes in the debugger, on that stosb command for loop iteration CL=0D, the &lt;em&gt;whole stack memory segment&lt;/em&gt; gets overwritten by garbage, from 0x300000 to 0x310000. Oh lord it looks like a reeaaaaaallll MAAAAAAAME buuuuuggggg. I'm sooooooooo saaaaaaaaddddddd.&lt;/p&gt;
&lt;p&gt;Showing off my high pain tolerance, I ran MESS through Valgrind to see if the garbage came from uninitialized memory. It was very slow, but thankfully by now I knew what to type and could afford to wait 10 seconds after each action. No luck, the garbage was perfectly reproducible each time and didn't trigger any warnings.&lt;/p&gt;
&lt;p&gt;INTERESTINGLY, when I tried to hot reload the save state, that region of memory remained as garbage! This suggests this region wasn't part of the saved CPU RAM map, which is definitely problematic. Most of the PC address range above 0x100000 is just free extended memory.&lt;/p&gt;
&lt;!-- Scrubbing around, 0x2f4c60 contained text from the game --&gt;
&lt;!-- Wait. Whut. This isn't right. 0x300580 contains the path to the EXE. --&gt;
&lt;p&gt;Now we're walking the knife edge of sanity that is not trusting your tools, or rather not trusting the MAME debugger. The only way to bring back certainty is to start proving individual facts. First up, is what we're seeing here &lt;em&gt;really&lt;/em&gt; the allocated memory used for the RAM getting trashed?&lt;/p&gt;
&lt;p&gt;We can do this in GDB with the following steps:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Put a breakpoint in the constructor of ram_device so you have eyes on all new RAM objects created&lt;/li&gt;
&lt;li&gt;Grab the last buffer that appears before the emulator initializes&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&amp;amp;((ram_device * const) 0x555557257380)-&amp;gt;m_pointer[0]
&lt;/pre&gt;&lt;/div&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;For the default PC configuration it should be 4M in size. This maps perfectly to the address space.&lt;/li&gt;
&lt;li&gt;Confirm that this is the right buffer by finding a string in the memory viewer and searching for it in GDB with find&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;find /b &amp;amp;((ram_device * const) 0x555557257380)-&amp;gt;m_pointer[0], +0x400000, &amp;#39;\\&amp;#39; ,&amp;#39;B&amp;#39;, &amp;#39;O&amp;#39;, &amp;#39;P&amp;#39;, &amp;#39;P&amp;#39;, &amp;#39;I&amp;#39;, &amp;#39;N&amp;#39;, &amp;#39;.&amp;#39;, &amp;#39;E&amp;#39;, &amp;#39;X&amp;#39;, &amp;#39;E&amp;#39;
&lt;/pre&gt;&lt;/div&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Add a memory write watch&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;p &amp;amp;((ram_device * const) 0x555557257380)-&amp;gt;m_pointer[0x300000]

// $39 = (unsigned char * ) 0x7fffacab2010

watch * 0x7fffacab2010
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;No change. Even after the debugger shows the address space getting sprayed, the RAM pointer is fine! Does this mean the bug is in MAME's memory mapper?&lt;/p&gt;
&lt;p&gt;We need to go up one more level. The memory mapper is run inside the i386_device, so grab the device pointer from a breakpoint (I did it in i386_common_init), and try running READ8&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;p ((i386_device * ) 0x5555572ae3f0)-&amp;gt;READ8(0x300000)
$47 = 0 &amp;#39;\000&amp;#39;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As expected. Now we can fast forward until after the memory has been sprayed.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;p ((i386_device * ) 0x5555572ae3f0)-&amp;gt;READ8(0x300000)
$53 = 65 &amp;#39;A&amp;#39;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;FINALLY. FINALLY WE CAN REPRODUCE THE PROBLEM IN GDB SPACE. AFTER MANY GORMLESS HOURS OF PISSING IN THE WIND. I AM CRYING.&lt;/p&gt;
&lt;p&gt;So, next question is: what in God's name is going on in READ8 to mess up like this?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;b i386_device::READ8
Breakpoint 7 at 0x555555d4412c: file ../../../../../src/devices/cpu/i386/i386priv.h, line 596.
p ((i386_device * ) 0x5555572ae3f0)-&amp;gt;READ8(0x300000)
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;From there we step through things a bit. The address is ANDed with m_a20_mask (uh?) which in the post-spray scenario reduces it from 0x300000 to 0x200000. Then it gets sent through a wall of templatese to read the single byte, which ends up in address_space::read_native. From HERE it does a lookup for the right read handler for the job, which itself has a pointer to some memory!&lt;/p&gt;
&lt;p&gt;The difference seems to be the offset passed to ramptr. After spray, the byteaddress is 0x200000 and offset returned is 0x100000. Before spray, the byteaddress is 0x300000 and offset returned is 0x200000. Why, that's the same amount that m_a20_mask thing removed. Maybe I should &lt;a class="reference external" href="https://www.win.tue.nl/~aeb/linux/kbd/A20.html"&gt;read up a bit on this A20 thing&lt;/a&gt; and we might get som&lt;/p&gt;
&lt;p&gt;OH&lt;/p&gt;
&lt;p&gt;OH NO&lt;/p&gt;
&lt;p&gt;OH SWEET MOTHER OF GOD NO&lt;/p&gt;
&lt;p&gt;aaaaaaaaaaaAA
AAAAAAAA̧̩̖A͔̱̖AAA̘̖̗̠͟A̶͕͙͍̪̣
AA̛̺̱̗͇̳̬̺̟͕͢À͕͍̀Ą͍̺̜̬͍͟A̻̠̦͞A͇̦̻͔͕̤̪̤͢͜A͓̭̫̺̪̜A̯̮A͉̠̕͡Ḁ͙̺͡A̡̛͉̞̪̕Ą͙A̮͚̞͇͇͕͢͠
Ą͚̯̭̰̹̮̫̬̥Ą̡͇̙̫̤̟̲̖͚̞̖͔͎̫͈̯̕͟A̢̮̪͈̳̝̖̘̤̟̜͔̲͕͓̱̘͢Ą͍̺̜̬͍͟A̻̠̦͞A̵̴͈̱͔̳̝̠̙̘̗̹͟͟A̴̛͚̠̰̻̻̣̟͔̭̯̪̮̬Ą̸̝̹͖̻́͞͠A͏̵҉̨̗̝̖̜̻̟̲̺̥̪̯A̸̛̝̼͍̹͚̦̤͉̠͙͉͖͜A̘͚̤̤̺̜̗͘͜͠͝ͅḀ̶̹̜͖̠̝̟̮͉̟̖̣̙̲̻̞̣͡A̡͙̞̫̭̳͙͈͖͢ͅ&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;p&gt;Okay I'm back now. This whole experience in learning Old Testament x86 has been quite troubling.&lt;/p&gt;
&lt;p&gt;x86 nerds probably know what went wrong. So say you're IBM, carving up a storm with your Intel 8088 range of business computers. After a while you decide to upgrade to Intel's fancier 80286 chip, which features a luxurious 24 line address bus (16MB) instead of 20 lines (1MB). But what happened on the old chip when you tried to access memory addresses greater than 1MB? Well the upper bits weren't used, so it wrapped around. And because segment math in x86 is so bad and adding things is fast, smart programmers everywhere started to use this &amp;quot;feature&amp;quot;.&lt;/p&gt;
&lt;p&gt;No problem, says Intel! The 80286 will boot in Real Mode to satisfy people who wrote bad apps between 1981 and 1984, and have the new Protected Mode which allows addressing all those new megabytes!&lt;/p&gt;
&lt;p&gt;The 80286 ships, and Intel completely forgot to add 8086-style addressing to Real Mode. 0x100000 still accesses Extended Memory. At this point the IBM engineers started to get clever/desperate; nearly all of the bad apps only need to wrap around once (from 0xfffff to 0x00000), so technically we only need to be able to disable a single bit of the address space (0x100000, bit 20). That's easy; chip exposes address line 20, we AND-gate it against a switch, bish bash bosh. Better yet, force it to &lt;em&gt;off&lt;/em&gt; by default, because it's 1984 and our future selves definitely won't mind having to do this dumb workaround again and again for eternity! Now how to control this switch from userspace... saaaayyyy, is that a spare line I see on the 8042?&lt;/p&gt;
&lt;p&gt;What's that? You &lt;em&gt;don't&lt;/em&gt; think the absence of a bit in the IBM AT keyboard controller should make the memory map repeat itself every alternate 1MB? Pat yourself on the back, that one took Intel &lt;strong&gt;29 years&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The one silver lining to all this is that countless people have written low-level x86 and survived the freaky design cruft before; once your monster has a name, it's easy to track.&lt;/p&gt;
&lt;p&gt;One mystery left; &lt;em&gt;where&lt;/em&gt; is the state for the A20 switch? The trail goes cold from the i386 side: m_a20_mask is set by an interrupt fired by lord knows what code, and isn't read back anywhere. Better check out the ct486.cpp driver, as that defines the routing between components.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;MCFG_DEVICE_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;keybc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AT_KEYBOARD_CONTROLLER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;XTAL_12MHz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;MCFG_AT_KEYBOARD_CONTROLLER_SYSTEM_RESET_CB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEVWRITELINE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;cs4031&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cs4031_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kbrst_w&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;MCFG_AT_KEYBOARD_CONTROLLER_GATE_A20_CB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEVWRITELINE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;cs4031&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cs4031_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gatea20_w&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;MCFG_AT_KEYBOARD_CONTROLLER_INPUT_BUFFER_FULL_CB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEVWRITELINE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;cs4031&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cs4031_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;irq01_w&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;MCFG_AT_KEYBOARD_CONTROLLER_KEYBOARD_CLOCK_CB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEVWRITELINE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;pc_kbdc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pc_kbdc_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clock_write_from_mb&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;MCFG_AT_KEYBOARD_CONTROLLER_KEYBOARD_DATA_CB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEVWRITELINE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;pc_kbdc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pc_kbdc_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data_write_from_mb&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;MCFG_DEVICE_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;pc_kbdc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PC_KBDC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;MCFG_PC_KBDC_OUT_CLOCK_CB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEVWRITELINE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;keybc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;at_keyboard_controller_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;keyboard_clock_w&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;MCFG_PC_KBDC_OUT_DATA_CB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEVWRITELINE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;keybc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;at_keyboard_controller_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;keyboard_data_w&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;MCFG_PC_KBDC_SLOT_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;pc_kbdc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;kbd&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pc_at_keyboards&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;STR_KBD_MICROSOFT_NATURAL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Oh yeah, the chipset! I forgot that the PC architecture uses those.&lt;/p&gt;
&lt;p&gt;OH! OH! cs4031.cpp has 3 registers for A20 state (to deal with the three different ways of triggering it that existed in 1993), and only two of them had the state saved.&lt;/p&gt;
&lt;p&gt;So there we go. A single missing register in the IBM keyboard controller was causing the PC's whole memory map to fail explosively. What day is it again?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="i386-pc-vga-save-state-fixes"&gt;
&lt;h3&gt;i386/pc_vga: save state fixes&lt;/h3&gt;
&lt;p&gt;(&lt;a class="reference external" href="https://github.com/mamedev/mame/pull/2178"&gt;MAME pull request #2178&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;More PC fun! Liero would run for about 4 frames after cold-loading a state, then the screen would go black. By watching the the vga.memory buffer in the debugger as the frames went by, it was clear the animation continued on well after the screen disappeared, suggesting that maybe the VGA palette was getting trashed. And lo, it was: I forgot to add vga.dac to the save state. The MAME palette device acts as a cache which gets the real palette in vga.dac pulled into it every so often. I guess I didn't run into it with the test game I first tried because it only forced an update after the palette had physically changed? Weird.&lt;/p&gt;
&lt;p&gt;Jump 'n Bump, on the other hand, would crash after a single frame somewhere in the VGA redraw wait loop. More precisely, all CPU instructions to pull info from the VGA port about redraw (0x3da) were getting cut off at the knees by a macro CRTC_PORT_ADDR which queried vga.miscellaneous_output (?!?)? I would've thought that getting as far as the READ8_MEMBER called &amp;quot;port_03d0_r&amp;quot; would be enough proof, but maybe this fixes some other crappy edge case.&lt;/p&gt;
&lt;!-- 10003d17 - end of loop before graphics idle --&gt;
&lt;!-- There was a great bit which would crash the debugger, but hang the regular emulator (while the CPU continued merrilly along in the background). --&gt;
&lt;p&gt;Pressing further would make DOS4GW throw a page fault; the mini stack trace it printed showed the instruction pointer (EIP) was being set to some huge address like 0x3DF80000. That looks really suspicious; we know for a fact there's no memory mapped there, and the low word being all blank suggests that it was actually a 16-bit address being loaded. Jump 'n Bump is a proper 32-bit game, perhaps the issue is some &amp;quot;am I 16 or 32 bit&amp;quot; flag inside the CPU isn't being saved? I'm chuffed, this is officially the first time I've used the mini stack trace given by a DOS program to fix something.&lt;/p&gt;
&lt;p&gt;Of course, vanilla DOS doesn't care about page faults! By loading a 16-bit savestate of the DOS command prompt from 32-bit mode, I could fill the screen with garbage as soon as e.g. a RET instruction hit and it tried to read the wrong size address off the stack. And it would chug along fine; one time it even ran AUTOEXEC.BAT again without asking.&lt;/p&gt;
&lt;p&gt;Okay now I've probed some more, it appears to be crashing in the system timer IRQ 0 interrupt. Is it because that code is 16-bit and the stack segment is 32-bit? A working session changes SS, SSBASE, SSFLAGS et al. to be 16-bit, but the save state run doesn't do that. Let's throw a gdb breakpoint into the code which changes the segments and see how often it gets called right up until the interrupt handover.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;BROKEN:
#0  0x0000555555d06841 in i386_device::i386_load_protected_mode_segment(i386_device::I386_SREG*, unsigned long*) (this=0x55555dfffda0, seg=0x7fffffff6690, desc=0x0) at ../../../../../src/devices/cpu/i386/i386.cpp:180
#1  0x0000555555d08ec9 in i386_device::i386_trap(int, int, int) (this=0x55555dfffda0, irq=136, irq_gate=1, trap_level=0) at ../../../../../src/devices/cpu/i386/i386.cpp:845

#0  0x0000555555d06841 in i386_device::i386_load_protected_mode_segment(i386_device::I386_SREG*, unsigned long*) (this=0x55555dfffda0, seg=0x55555e0024ac, desc=0x0) at ../../../../../src/devices/cpu/i386/i386.cpp:180
#1  0x0000555555d06b42 in i386_device::i386_load_segment_descriptor(int) (this=0x55555dfffda0, segment=1) at ../../../../../src/devices/cpu/i386/i386.cpp:244
#2  0x0000555555d09f8b in i386_device::i386_trap(int, int, int) (this=0x55555dfffda0, irq=136, irq_gate=1, trap_level=0) at ../../../../../src/devices/cpu/i386/i386.cpp:1089

WORKING:
#0  0x0000555555d06841 in i386_device::i386_load_protected_mode_segment(i386_device::I386_SREG*, unsigned long*) (this=0x55555dfffda0, seg=0x7fffffff6690, desc=0x0) at ../../../../../src/devices/cpu/i386/i386.cpp:180
#1  0x0000555555d08ec9 in i386_device::i386_trap(int, int, int) (this=0x55555dfffda0, irq=136, irq_gate=1, trap_level=0) at ../../../../../src/devices/cpu/i386/i386.cpp:845

#0  0x0000555555d06841 in i386_device::i386_load_protected_mode_segment(i386_device::I386_SREG*, unsigned long*) (this=0x55555dfffda0, seg=0x7fffffff66b0, desc=0x0) at ../../../../../src/devices/cpu/i386/i386.cpp:180
#1  0x0000555555d0929d in i386_device::i386_trap(int, int, int) (this=0x55555dfffda0, irq=136, irq_gate=1, trap_level=0) at ../../../../../src/devices/cpu/i386/i386.cpp:895

#0  0x0000555555d06841 in i386_device::i386_load_protected_mode_segment(i386_device::I386_SREG*, unsigned long*) (this=0x55555dfffda0, seg=0x55555e0024c0, desc=0x0) at ../../../../../src/devices/cpu/i386/i386.cpp:180
#1  0x0000555555d0991f in i386_device::i386_trap(int, int, int) (this=0x55555dfffda0, irq=136, irq_gate=1, trap_level=0) at ../../../../../src/devices/cpu/i386/i386.cpp:971

#0  0x0000555555d06841 in i386_device::i386_load_protected_mode_segment(i386_device::I386_SREG*, unsigned long*) (this=0x55555dfffda0, seg=0x55555e0024ac, desc=0x0) at ../../../../../src/devices/cpu/i386/i386.cpp:180
#1  0x0000555555d06b42 in i386_device::i386_load_segment_descriptor(int) (this=0x55555dfffda0, segment=1) at ../../../../../src/devices/cpu/i386/i386.cpp:244
#2  0x0000555555d09f8b in i386_device::i386_trap(int, int, int) (this=0x55555dfffda0, irq=136, irq_gate=1, trap_level=0) at ../../../../../src/devices/cpu/i386/i386.cpp:1089
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This looks promising, whole chunks of i386_trap aren't being reached!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;CPL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;m_CPL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// current privilege level&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;DPL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x03&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// descriptor privilege level&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x0004&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DPL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CPL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There it is. m_CPL isn't saved. Ughhhhh. So far I've tried to be austere about which registers I track, but that is proving to be a worse idea than just tracking everything. Shortly after finding that, a whole slew of new bugs pop up from not having certain two-letter CPU flags in the state, so I add all of them (after a cursory check that they were all persistent and not clobbered each cycle or whatnot). Surprise! No more crashes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="taito-f3-fix-missing-graphics-on-state-load"&gt;
&lt;h3&gt;taito_f3: fix missing graphics on state load&lt;/h3&gt;
&lt;p&gt;(&lt;a class="reference external" href="https://github.com/mamedev/mame/pull/1903"&gt;MAME pull request #1903&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;This was pretty straightforward! CPU and sound were both fine, only problem was large chunks of graphics were missing. The one big catch was this editable set of tiles (called the &amp;quot;pixel layer&amp;quot;) which wasn't being updated in MAME's gfx_element when the state loaded, even when the backing RAM for it &lt;em&gt;was&lt;/em&gt;! I didn't really want to mess with the behaviour of gfx_element and friends, so after a lot of hemming and hawing I added a device_post_load hook on taito_t3_state to mark all the tiles in the gfx_element as &amp;quot;dirty&amp;quot;, which hints to MAME to reload all the tiles from the source (i.e. the backing RAM) on the next access. I'm still not sure why this doesn't happen by default. And I probably never will! Because it's done.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="sblaster-ymf262-save-state-fixes"&gt;
&lt;h3&gt;sblaster/ymf262: save state fixes&lt;/h3&gt;
&lt;p&gt;(&lt;a class="reference external" href="https://github.com/mamedev/mame/pull/2193"&gt;MAME pull request #2193&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Another boring one. The DSP and YMF262 chips on a Sound Blaster card have like a billion registers that needed to be tracked for sound playback to survive a save state. Actually while testing this I got really irritated by another bug; every time MAME dropped a frame my speakers would make this horrendous popping noise, but this behaviour would immediately stop once the soundcard had played some music. I recorded my computer's audio output with Audacity; on the signed 16-bit scale audio output was being held at -1 by default, but during the pops it would step back to 0. The fix was to explicitly set the twin DACs to 0 (or rather 0x8000, as the DACs are unsigned 16-bit) in sb_device::device_reset().&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="Devlog"/><category term="debugging"/><category term="show your working"/><category term="mame"/><category term="konami"/><category term="x86"/><category term="ibm"/><category term="gluttony"/><category term="bless those who preserve our creepy arcade past"/></entry><entry><title>Show Your Working: Patching a bug in a 25 year old Sierra game</title><link href="/writing/2017/09/23/sierra_bug/" rel="alternate"/><published>2017-09-23T00:00:00+08:00</published><updated>2017-09-23T00:00:00+08:00</updated><author><name>Scott</name></author><id>tag:None,2017-09-23:/writing/2017/09/23/sierra_bug/</id><summary type="html">&lt;img src="/writing/2017/09/23/sierra_bug/flamingo.gif" alt="That bloody flamingo puzzle from the Sierra game &amp;quot;Island of Dr. Brain&amp;quot;" class="letterbox" title="Is this what the kids these days mean by vapourwave?"/&gt; &lt;p&gt;&lt;em&gt;Show Your Working is a brand new segment where I write up things I have attempted to fix in open source software. Sometimes it's interesting to debug a problem yourself from first principles, even if the codebase is huge and you don't know anything going in. I will try and explain my thought process as I venture out into the weeds armed only with a butter knife.&lt;/em&gt;&lt;/p&gt;
&lt;div class="section" id="the-software"&gt;
&lt;h2&gt;The software&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.scummvm.org"&gt;ScummVM&lt;/a&gt; is a heroic effort to make a cross-platform engine for hundreds of classic adventure games, making them natively playable on modern systems. Since 2010, ScummVM contains an interpreter originally from the FreeSCI project, and by now does a pretty good job playing games based on the &lt;a class="reference external" href="http://wiki.scummvm.org/index.php/SCI"&gt;Sierra Creative Interpreter&lt;/a&gt;. If you haven't heard of ScummVM, I can't recommend it enough.&lt;/p&gt;
&lt;p&gt;It's gotten to the point where ScummVM provides a much better experience than emulating the games in DOS/Windows. Still, given that the engine is reverse engineered without the original code, there's all manner of edge cases and quirks that have to be emulated in order for the games to work correctly, as we'll find out!&lt;/p&gt;
</summary><content type="html">&lt;img src="/writing/2017/09/23/sierra_bug/flamingo.gif" alt="That bloody flamingo puzzle from the Sierra game &amp;quot;Island of Dr. Brain&amp;quot;" class="letterbox" title="Is this what the kids these days mean by vapourwave?"/&gt; &lt;p&gt;&lt;em&gt;Show Your Working is a brand new segment where I write up things I have attempted to fix in open source software. Sometimes it's interesting to debug a problem yourself from first principles, even if the codebase is huge and you don't know anything going in. I will try and explain my thought process as I venture out into the weeds armed only with a butter knife.&lt;/em&gt;&lt;/p&gt;
&lt;div class="section" id="the-software"&gt;
&lt;h2&gt;The software&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="http://www.scummvm.org"&gt;ScummVM&lt;/a&gt; is a heroic effort to make a cross-platform engine for hundreds of classic adventure games, making them natively playable on modern systems. Since 2010, ScummVM contains an interpreter originally from the FreeSCI project, and by now does a pretty good job playing games based on the &lt;a class="reference external" href="http://wiki.scummvm.org/index.php/SCI"&gt;Sierra Creative Interpreter&lt;/a&gt;. If you haven't heard of ScummVM, I can't recommend it enough.&lt;/p&gt;
&lt;p&gt;It's gotten to the point where ScummVM provides a much better experience than emulating the games in DOS/Windows. Still, given that the engine is reverse engineered without the original code, there's all manner of edge cases and quirks that have to be emulated in order for the games to work correctly, as we'll find out!&lt;/p&gt;

&lt;/div&gt;
&lt;div class="section" id="the-problem"&gt;
&lt;h2&gt;The problem&lt;/h2&gt;
&lt;p&gt;The old Sierra mantra was &amp;quot;save early, save often&amp;quot;; and not just because a game could be rendered unwinnable by a moon-logic puzzle requiring a 1px * 1px item from the first screen you didn't pick up. Or a bad arcade sequence tied to the CPU clock, playing at insane speeds on anything faster than a 386. Or the frequent risk of clicking the wrong spot and watching your character &lt;a class="reference external" href="https://youtu.be/C7fYdX_89hM?t=3m8s"&gt;have all their skin eaten away&lt;/a&gt;. No, there are bugs and race conditions in the scripting code that makes up these games! Quite a lot of these bugs are overridden/ignored by the build of SCI the game was shipped with, but other times you aren't that lucky and the whole game tanks, wiping out whatever progress you've made.&lt;/p&gt;
&lt;p&gt;In case you're not familiar, the original Sierra crash screen looked like this:&lt;/p&gt;
&lt;img src="/writing/2017/09/23/sierra_bug/sierra_sci_crash.png" alt="Crash screen for Sierra game &amp;quot;The Dagger of Amon-Ra&amp;quot;" class="" title="This is extremely on-brand with the rest of Sierra&amp;#x27;s in-game death screens: i.e. blaming the player for trying things."/&gt; &lt;p&gt;Oh man do not get me started on this message. Protip for designers: if your software crashes, don't be condescending (&amp;quot;&lt;em&gt;of course&lt;/em&gt; doing that thing would break the game, what are you taking stupid pills???&amp;quot;) and don't lie to save face (plenty of crashes can be triggered by doing the correct thing and you damn well know it Sierra). 7 year old me felt worse than he should have because of that.&lt;/p&gt;
&lt;p&gt;Okay, some specifics. I felt compelled to replay Island of Dr. Brain, an edutainment game for ages 12 and up, to rack up a large score. (Don't judge.) I got as far as that accursed puzzle with the colour changing flamingos, when ScummVM threw open the debug console.&lt;/p&gt;
&lt;img src="/writing/2017/09/23/sierra_bug/island_crash.png" alt="ScummVM crash screen + debug console for the Island of Dr. Brain" class="" title="See, this is much better! Give me false hope so I can watched my doomed progress hang tantalisingly out of reach, if only I know what magic runes to type."/&gt; &lt;/div&gt;
&lt;div class="section" id="the-preparation"&gt;
&lt;h2&gt;The preparation&lt;/h2&gt;
&lt;p&gt;The only software we need installed to debug this is the Git VCS and the standard Linux command line tools, plus whatever build dependencies ScummVM has. Start by grabbing the latest ScummVM source code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/scummvm/scummvm
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;scummvm
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="the-diagnosis"&gt;
&lt;h2&gt;The diagnosis&lt;/h2&gt;
&lt;p&gt;The ScummVM source tree is very modular, with each engine compartmentalised from the rest of the cross-platform code. All the stuff we're going to want is in the SCI engine folder /engines/sci.&lt;/p&gt;
&lt;p&gt;Let's start with the autodetection. ScummVM does some very basic game auto-detection by MD5ing the first 5000 bytes of files in the game directory. The detection tables are in /engines/sci/detection_tables.h&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;// The Island of Dr. Brain - English DOS (from Quietust)&lt;/span&gt;
&lt;span class="c1"&gt;// Executable scanning reports &amp;quot;1.001.053&amp;quot;, VERSION file reports &amp;quot;1.1 2.3.93&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;islandbrain&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;resource.map&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;3c07da06bdd1689f9d07af78fb94d0ec&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3101&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;resource.000&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;ecc686e0034fb4d41de077ac7167b3cf&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1947866&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AD_LISTEND&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Common&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;EN_ANY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Common&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;kPlatformDOS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GUIO4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GUIO_NOSPEECH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GAMEOPTION_PREFER_DIGITAL_SFX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GAMEOPTION_ORIGINAL_SAVELOAD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GAMEOPTION_FB01_MIDI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is the entry that matches my copy. The 5000 byte hashes for resource.map and resource.000 are correct, and date in the version file is correct. Now, let's see if there's any specific modifications made for the game:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;scott@hoagie:~/Development/scummvm/engines/sci$&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-rni&lt;span class="w"&gt; &lt;/span&gt;islandbrain
engine/workarounds.cpp:305:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;GID_ISLANDBRAIN,&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;,&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;937&lt;/span&gt;,&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;IconBar&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dispatchEvent&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt;                   &lt;/span&gt;NULL,&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;58&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;WORKAROUND_FAKE,&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;when&lt;span class="w"&gt; &lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;ENTER&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;startup&lt;span class="w"&gt; &lt;/span&gt;menu&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;bug&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;#5241&lt;/span&gt;
engine/workarounds.cpp:306:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;GID_ISLANDBRAIN,&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;140&lt;/span&gt;,&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;140&lt;/span&gt;,&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;piece&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;init&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt;                            &lt;/span&gt;NULL,&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;WORKAROUND_FAKE,&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;first&lt;span class="w"&gt; &lt;/span&gt;puzzle&lt;span class="w"&gt; &lt;/span&gt;right&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;start,&lt;span class="w"&gt; &lt;/span&gt;some&lt;span class="w"&gt; &lt;/span&gt;initialization&lt;span class="w"&gt; &lt;/span&gt;variable.&lt;span class="w"&gt; &lt;/span&gt;bnt&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;it,&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;it&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;non-0
engine/workarounds.cpp:307:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;GID_ISLANDBRAIN,&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;,&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;268&lt;/span&gt;,&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;anElement&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;select&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt;                          &lt;/span&gt;NULL,&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;WORKAROUND_FAKE,&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;elements&lt;span class="w"&gt; &lt;/span&gt;puzzle,&lt;span class="w"&gt; &lt;/span&gt;gets&lt;span class="w"&gt; &lt;/span&gt;used&lt;span class="w"&gt; &lt;/span&gt;before&lt;span class="w"&gt; &lt;/span&gt;super&lt;span class="w"&gt; &lt;/span&gt;TextIcon
engine/workarounds.cpp:550:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;GID_ISLANDBRAIN,&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;300&lt;/span&gt;,&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;300&lt;/span&gt;,&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;geneDude&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;show&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt;                       &lt;/span&gt;NULL,&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;WORKAROUND_IGNORE,&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;when&lt;span class="w"&gt; &lt;/span&gt;looking&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;gene&lt;span class="w"&gt; &lt;/span&gt;explanation&lt;span class="w"&gt; &lt;/span&gt;chart&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;parameter&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;an&lt;span class="w"&gt; &lt;/span&gt;object
engine/workarounds.cpp:648:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;GID_ISLANDBRAIN,&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;300&lt;/span&gt;,&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;300&lt;/span&gt;,&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dudeViewer&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;show&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt;                       &lt;/span&gt;NULL,&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;WORKAROUND_STILLCALL,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;when&lt;span class="w"&gt; &lt;/span&gt;looking&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;gene&lt;span class="w"&gt; &lt;/span&gt;explanation&lt;span class="w"&gt; &lt;/span&gt;chart,&lt;span class="w"&gt; &lt;/span&gt;gets&lt;span class="w"&gt; &lt;/span&gt;called&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;extra&lt;span class="w"&gt; &lt;/span&gt;parameter
engine/workarounds.cpp:670:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;GID_ISLANDBRAIN,&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;290&lt;/span&gt;,&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;291&lt;/span&gt;,&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;upElevator&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;changeState&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;sig_kGraphSaveBox_ibrain_1,&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;WORKAROUND_STILLCALL,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;when&lt;span class="w"&gt; &lt;/span&gt;testing&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;elevator&lt;span class="w"&gt; &lt;/span&gt;puzzle,&lt;span class="w"&gt; &lt;/span&gt;gets&lt;span class="w"&gt; &lt;/span&gt;called&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;argument&lt;span class="w"&gt; &lt;/span&gt;less&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;stack&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;bug&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;#4943&lt;/span&gt;
engine/workarounds.cpp:671:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;GID_ISLANDBRAIN,&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;290&lt;/span&gt;,&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;291&lt;/span&gt;,&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;downElevator&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;changeState&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;sig_kGraphSaveBox_ibrain_1,&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;WORKAROUND_STILLCALL,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;see&lt;span class="w"&gt; &lt;/span&gt;above
engine/workarounds.cpp:672:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;GID_ISLANDBRAIN,&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;290&lt;/span&gt;,&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;291&lt;/span&gt;,&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;correctElevator&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;changeState&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;sig_kGraphSaveBox_ibrain_1,&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;WORKAROUND_STILLCALL,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;see&lt;span class="w"&gt; &lt;/span&gt;above&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;when&lt;span class="w"&gt; &lt;/span&gt;testing&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;correct&lt;span class="w"&gt; &lt;/span&gt;solution&lt;span class="o"&gt;)&lt;/span&gt;
engine/workarounds.cpp:741:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;GID_ISLANDBRAIN,&lt;span class="w"&gt;   &lt;/span&gt;-1,&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;999&lt;/span&gt;,&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;List&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;eachElementDo&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt;                &lt;/span&gt;NULL,&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;WORKAROUND_FAKE,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;when&lt;span class="w"&gt; &lt;/span&gt;going&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;game&lt;span class="w"&gt; &lt;/span&gt;options,&lt;span class="w"&gt; &lt;/span&gt;choosing&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Info&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;selecting&lt;span class="w"&gt; &lt;/span&gt;anything&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;list,&lt;span class="w"&gt; &lt;/span&gt;gets&lt;span class="w"&gt; &lt;/span&gt;called&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;an&lt;span class="w"&gt; &lt;/span&gt;invalid&lt;span class="w"&gt; &lt;/span&gt;parameter&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;error&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;bug&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;#4989&lt;/span&gt;
engine/workarounds.cpp:869:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;GID_ISLANDBRAIN,&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;300&lt;/span&gt;,&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;310&lt;/span&gt;,&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;childBreed&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;changeState&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;sig_kStrAt_ibrain_1,&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;WORKAROUND_FAKE,&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;when&lt;span class="w"&gt; &lt;/span&gt;clicking&lt;span class="w"&gt; &lt;/span&gt;Breed&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;second-generation&lt;span class="w"&gt; &lt;/span&gt;cyborg&lt;span class="w"&gt; &lt;/span&gt;hybrid&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Standard&lt;span class="w"&gt; &lt;/span&gt;difficulty&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;two&lt;span class="w"&gt; &lt;/span&gt;parameters&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;swapped&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;bug&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;#5088&lt;/span&gt;
...
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Yup. There's a few workarounds on file for this. Here's the C struct format for each workaround, taken from /sci/engine/workarounds.h:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SciWorkaroundType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;WORKAROUND_NONE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// only used by terminator or when no workaround was found&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;WORKAROUND_IGNORE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// ignore kernel call&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;WORKAROUND_STILLCALL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// still do kernel call&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;WORKAROUND_FAKE&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c1"&gt;// fake kernel call / replace temp value / fake opcode&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;SciWorkaroundSolution&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;SciWorkaroundType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;uint16&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="cm"&gt;/**&lt;/span&gt;
&lt;span class="cm"&gt;* A structure describing a &amp;#39;workaround&amp;#39; for a SCI script bug.&lt;/span&gt;
&lt;span class="cm"&gt;*&lt;/span&gt;
&lt;span class="cm"&gt;* Arrays of SciWorkaroundEntry instances are terminated by&lt;/span&gt;
&lt;span class="cm"&gt;* a fake entry in which &amp;quot;objectName&amp;quot; is NULL.&lt;/span&gt;
&lt;span class="cm"&gt;*/&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;SciWorkaroundEntry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;SciGameId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gameId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;roomNr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scriptNr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;int16&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;inheritanceLevel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;objectName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;methodName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uint16&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;localCallSignature&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;SciWorkaroundSolution&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Seems pretty straightforward. Our game is GID_ISLANDBRAIN, and from that stack trace we know the room number is 180 and the script number is 185. Inheritance level I assume is 0, and again from the stack trace objectName is &amp;quot;flamingo&amp;quot; and methodName is &amp;quot;showHelp&amp;quot;. index I assume for now is 0. localCallSignature is more interesting... in SCI code there are subroutines that can get called without names, and we need a way of identifying them without just saying &amp;quot;the code starts from this offset&amp;quot;, as that isn't robust for multiple versions of the same game. Our stack trace was blaming localCall 5d6 for doing an invalid command, so we'll possibly need to identify it. newValue will be the workaround action we'll ask ScummVM to shim in, which we'll find out once we troubleshoot.&lt;/p&gt;
&lt;p&gt;As this is an emulator, we are going to be using the internal debugger a &lt;em&gt;lot&lt;/em&gt;. Internal debuggers are pretty daunting, as tradition dictates they have barely any documentation, plus you're grappling with some weirdo niche instruction set maybe 10 people know properly. That's ok! Most internal debuggers list all the allowed commands with &amp;quot;help&amp;quot;, and as long as you have a very basic understanding of how assembled programs work (i.e. registers, pushing and popping things on the stack, jumps, conditionals), you can do pretty much anything you want in a debugger as soon as you find out how to do 3 things:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;Look at the current state.&lt;/strong&gt; For ScummVM's SCI engine, this is &amp;quot;registers&amp;quot; to see all the registers, &amp;quot;stack 10&amp;quot; to see the first 10 things pushed on the stack, &amp;quot;vm_vars [local|global|temp|param] id&amp;quot; to view a variable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Run/step in/step over code.&lt;/strong&gt; For ScummVM's SCI engine, this is &amp;quot;go&amp;quot; to continue execution, &amp;quot;s&amp;quot; to step in and &amp;quot;p&amp;quot; to step over.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Set code breakpoints.&lt;/strong&gt; For ScummVM's SCI engine, the two I used were &amp;quot;bp_method object::methodName&amp;quot; to break at the start of a named method, and &amp;quot;bp_address [segment]:[offset]&amp;quot; to stop at a section of code.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# in ScummVM debug console&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;disasm&lt;span class="w"&gt; &lt;/span&gt;flamingos&lt;span class="w"&gt; &lt;/span&gt;showHelp&lt;span class="w"&gt; &lt;/span&gt;bc
...
&lt;span class="m"&gt;0036&lt;/span&gt;:01b5:&lt;span class="w"&gt; &lt;/span&gt;b5&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;01&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;sati&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;01&lt;/span&gt;
&lt;span class="m"&gt;0036&lt;/span&gt;:01b7:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;76&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;push0
&lt;span class="m"&gt;0036&lt;/span&gt;:01b8:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;85&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;lat&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;
&lt;span class="m"&gt;0036&lt;/span&gt;:01ba:&lt;span class="w"&gt; &lt;/span&gt;b3&lt;span class="w"&gt; &lt;/span&gt;1c&lt;span class="w"&gt;          &lt;/span&gt;sali&lt;span class="w"&gt;      &lt;/span&gt;1c&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;28&lt;/span&gt;
&lt;span class="m"&gt;0036&lt;/span&gt;:01bc:&lt;span class="w"&gt; &lt;/span&gt;c5&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;+at&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;
&lt;span class="m"&gt;0036&lt;/span&gt;:01be:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;33&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ea&lt;span class="w"&gt;          &lt;/span&gt;jmp&lt;span class="w"&gt;       &lt;/span&gt;ea&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;01aa&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="m"&gt;0036&lt;/span&gt;:01c0:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;76&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;push0
&lt;span class="m"&gt;0036&lt;/span&gt;:01c1:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;40&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;04&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;call&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0411&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;05d5&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;
&lt;span class="m"&gt;0036&lt;/span&gt;:01c5:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;78&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;push1
&lt;span class="m"&gt;0036&lt;/span&gt;:01c6:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;39&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;pushi&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;name
&lt;span class="m"&gt;0036&lt;/span&gt;:01c8:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;40&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;48&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;03&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;02&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;call&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0348&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0513&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;02&lt;/span&gt;
&lt;span class="m"&gt;0036&lt;/span&gt;:01cc:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;35&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;ldi&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;
&lt;span class="m"&gt;0036&lt;/span&gt;:01ce:&lt;span class="w"&gt; &lt;/span&gt;a5&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;sat&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;
&lt;span class="m"&gt;0036&lt;/span&gt;:01d0:&lt;span class="w"&gt; &lt;/span&gt;8d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;lst&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;
&lt;span class="m"&gt;0036&lt;/span&gt;:01d2:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;39&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;04&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;pushi&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;04&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cel
&lt;span class="m"&gt;0036&lt;/span&gt;:01d4:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;81&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;72&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;lag&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;72&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;114&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;r&amp;#39;&lt;/span&gt;
&lt;span class="m"&gt;0036&lt;/span&gt;:01d6:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;02&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;add
&lt;span class="m"&gt;0036&lt;/span&gt;:01d7:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;22&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;lt?
&lt;span class="m"&gt;0036&lt;/span&gt;:01d8:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;31&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;1c&lt;span class="w"&gt;          &lt;/span&gt;bnt&lt;span class="w"&gt;       &lt;/span&gt;1c&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;01f6&lt;span class="o"&gt;]&lt;/span&gt;
...
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In the stack trace, the second last line said the program counter for the bad call was 0036:01c5. To make sense of this, I recommend having a look in /engines/sci/engine/vm.cpp, which contains the opcode list.&lt;/p&gt;
&lt;p&gt;Annoyingly I couldn't figure out how to make ScummVM disassemble the local method, so I had to rely on a third-party tool &lt;a class="reference external" href="http://scicompanion.com/"&gt;SCICompanion&lt;/a&gt; to do the dirty work and disassemble all of script 185.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;05d6:3f&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;02&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;link&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;var&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
05d8:35&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;ldi&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
05da:a5&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;sat&lt;span class="w"&gt; &lt;/span&gt;temp0

&lt;span class="w"&gt;        &lt;/span&gt;code_05dc
05dc:8d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;lst&lt;span class="w"&gt; &lt;/span&gt;temp0
05de:35&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;09&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;ldi&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;
05e0:22&lt;span class="w"&gt;                 &lt;/span&gt;lt?
05e1:31&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;22&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;bnt&lt;span class="w"&gt; &lt;/span&gt;code_0605
05e3:39&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;03&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;pushi&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;loop
05e5:78&lt;span class="w"&gt;               &lt;/span&gt;push1
05e6:85&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;lat&lt;span class="w"&gt; &lt;/span&gt;temp0
05e8:9b&lt;span class="w"&gt; &lt;/span&gt;1c&lt;span class="w"&gt;             &lt;/span&gt;lsli&lt;span class="w"&gt; &lt;/span&gt;local28
05ea:39&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;03&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;pushi&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;loop
05ec:76&lt;span class="w"&gt;               &lt;/span&gt;push0
05ed:93&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;lali&lt;span class="w"&gt; &lt;/span&gt;local0
05ef:4a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;04&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;send&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;

05f1:36&lt;span class="w"&gt;                &lt;/span&gt;push
05f2:35&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;01&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;ldi&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
05f4:12&lt;span class="w"&gt;                 &lt;/span&gt;and
05f5:02&lt;span class="w"&gt;                 &lt;/span&gt;add
05f6:36&lt;span class="w"&gt;                &lt;/span&gt;push

05f7:38&lt;span class="w"&gt; &lt;/span&gt;00d9&lt;span class="w"&gt;          &lt;/span&gt;pushi&lt;span class="w"&gt; &lt;/span&gt;d9&lt;span class="w"&gt; &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$d9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;show
05fa:76&lt;span class="w"&gt;               &lt;/span&gt;push0
05fb:85&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;lat&lt;span class="w"&gt; &lt;/span&gt;temp0
05fd:93&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;lali&lt;span class="w"&gt; &lt;/span&gt;local0
05ff:4a&lt;span class="w"&gt; &lt;/span&gt;0a&lt;span class="w"&gt;             &lt;/span&gt;send&lt;span class="w"&gt; &lt;/span&gt;a

&lt;span class="m"&gt;0601&lt;/span&gt;:c5&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;+at&lt;span class="w"&gt; &lt;/span&gt;temp0
&lt;span class="m"&gt;0603&lt;/span&gt;:33&lt;span class="w"&gt; &lt;/span&gt;d7&lt;span class="w"&gt;              &lt;/span&gt;jmp&lt;span class="w"&gt; &lt;/span&gt;code_05dc

&lt;span class="w"&gt;        &lt;/span&gt;code_0605
&lt;span class="m"&gt;0605&lt;/span&gt;:48&lt;span class="w"&gt;                 &lt;/span&gt;ret
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;05ef is where the last line of the stack trace claims the code stops executing. From my very tenuous understanding of the error, send is freaking out because the argument passed in through the accumulator register (0000:0000) isn't an object. And the lali instruction sets the accumulator to the value of local variable local0.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# in ScummVM console&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;acc_object
Information&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;currently&lt;span class="w"&gt; &lt;/span&gt;active&lt;span class="w"&gt; &lt;/span&gt;object&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;class&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;address&lt;span class="w"&gt; &lt;/span&gt;indexed&lt;span class="w"&gt; &lt;/span&gt;by&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;accumulator:
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0000&lt;/span&gt;:0000&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;Not&lt;span class="w"&gt; &lt;/span&gt;an&lt;span class="w"&gt; &lt;/span&gt;object.
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;vm_vars&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;var&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0027&lt;/span&gt;:0eb9&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;object&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bird&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Uhhhhh. What. If local0 is not a null, why did that break?!&lt;/p&gt;
&lt;p&gt;Okay. Reloading the game up to before the crash, I opened the console with CTRL + LSHIFT + D, and added a breakpoint in the broken method. I then triggered the puzzle and set a breakpoint for the code just before the crash, using the segment from the program counter.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# in ScummVM console&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;bp_method&lt;span class="w"&gt; &lt;/span&gt;flamingos::showHelp
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;#0: Execute flamingos::showHelp&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;

&lt;span class="c1"&gt;# open the puzzle&lt;/span&gt;
Break&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;flamingos::showHelp&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0036&lt;/span&gt;:075c&lt;span class="o"&gt;])&lt;/span&gt;
flamingos::showHelp&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0000&lt;/span&gt;:0000&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0036&lt;/span&gt;:019b
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;registers
Current&lt;span class="w"&gt; &lt;/span&gt;register&lt;span class="w"&gt; &lt;/span&gt;values:
&lt;span class="nv"&gt;acc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0000&lt;/span&gt;:0001&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;prev&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0000&lt;/span&gt;:0001&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;rest&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="nv"&gt;pc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0036&lt;/span&gt;:019b&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0036&lt;/span&gt;:075c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;fp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ST:0036&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ST:0036
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;bp_address&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0036&lt;/span&gt;:05ec
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;go

&lt;span class="c1"&gt;# click through&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;acc_object
Information&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;currently&lt;span class="w"&gt; &lt;/span&gt;active&lt;span class="w"&gt; &lt;/span&gt;object&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;class&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;address&lt;span class="w"&gt; &lt;/span&gt;indexed&lt;span class="w"&gt; &lt;/span&gt;by&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;accumulator:
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0000&lt;/span&gt;:0000&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;Not&lt;span class="w"&gt; &lt;/span&gt;an&lt;span class="w"&gt; &lt;/span&gt;object.
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;vm_vars&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;var&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0027&lt;/span&gt;:0eb8&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;object&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bird&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# skip to 05ef&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;p
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;p
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;acc_object
Information&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;currently&lt;span class="w"&gt; &lt;/span&gt;active&lt;span class="w"&gt; &lt;/span&gt;object&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;class&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;address&lt;span class="w"&gt; &lt;/span&gt;indexed&lt;span class="w"&gt; &lt;/span&gt;by&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;accumulator:
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0027&lt;/span&gt;:0eb8&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bird&lt;span class="w"&gt; &lt;/span&gt;:&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;34&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vars,&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;methods
...
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So the first time around we're entering the send instruction exactly as we should, with the bird object in the accumulator. This subroutine loops a few times, and every test run it crashed at a different iteration of the loop. Which I 100% do not get, as there is nothing in the subroutine that would set acc to the wrong thing! Could... could it be that the bug was in ScummVM all along?&lt;/p&gt;
&lt;p&gt;Looks like we have to go a level deeper. If we run ScummVM inside gdb and stop it just before we execute the lali instruction, we can add a breakpoint to the code responsible for lali.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;b&lt;span class="w"&gt; &lt;/span&gt;engines/sci/engine/vm.cpp:1284
Breakpoint&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;0x5555567b9750:&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;engines/sci/engine/vm.cpp,&lt;span class="w"&gt; &lt;/span&gt;line&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1284&lt;/span&gt;.
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;op_lag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 0x40 (64)&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;op_lal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 0x41 (65)&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;op_lat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 0x42 (66)&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;op_lap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 0x43 (67)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Load global, local, temp or param variable into the accumulator&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;op_lagi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 0x48 (72)&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;op_lali&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 0x49 (73)&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;op_lati&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 0x4a (74)&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;op_lapi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 0x4b (75)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Same as the 4 ones above, except that the accumulator is used as&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// an additional index&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;var_type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;opcode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Gets the variable type: g, l, t or p&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;var_number&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;opparams&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opcode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;op_lagi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;r_acc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requireSint16&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;r_acc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;read_var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;var_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;var_number&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Probing the variables with gdb, I realised I was mistaken. The &amp;quot;lali [arg]&amp;quot; instruction doesn't load local variable [arg] into acc, it loads local variable [arg+acc] into acc. And at that part of the code acc (stored on the stack) increases by 1 on each loop. Like an idiot I forgot the puzzle has 9 birds in it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# in ScummVM console&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;vm_vars&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;var&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0027&lt;/span&gt;:0e09&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;object&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bird&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;vm_vars&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;var&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0027&lt;/span&gt;:0e07&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;object&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bird&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;vm_vars&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;var&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0027&lt;/span&gt;:0e04&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;object&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bird&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;vm_vars&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;
&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;var&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0027&lt;/span&gt;:0e03&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;object&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bird&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;vm_vars&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;
&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;var&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0027&lt;/span&gt;:0e0a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;object&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bird&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;vm_vars&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;
&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;var&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0027&lt;/span&gt;:0e09&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;object&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bird&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;vm_vars&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;
&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;var&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0000&lt;/span&gt;:0000
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;vm_vars&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;
&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;var&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0027&lt;/span&gt;:0e06&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;object&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bird&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;vm_vars&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;
&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;var&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0027&lt;/span&gt;:0e0b&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;object&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bird&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Well &lt;em&gt;there's&lt;/em&gt; your problem. But where did that 7th bird go? Better yet, on other runs there are &lt;strong&gt;multiple&lt;/strong&gt; birds missing from the list at very different spots. In a twist the showHelp command seems to be innocent, and it's really a setup process somewhere else that's botching up! Thankfully we only have one short disassembly to look through for the culprit (script 185), and from vm.cpp there's only a few opcodes which can set local variables.&lt;/p&gt;
&lt;p&gt;Sure enough, in the flamingos::init method, there's an instruction &amp;quot;sali local0&amp;quot;. I throw a breakpoint on it, then quizzed the VM for information before and after.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# in ScummVM console&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;bp_method&lt;span class="w"&gt; &lt;/span&gt;flamingos::init
...
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;bp_address&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0036&lt;/span&gt;:00ac
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;go
...
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;registers
Current&lt;span class="w"&gt; &lt;/span&gt;register&lt;span class="w"&gt; &lt;/span&gt;values:
&lt;span class="nv"&gt;acc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0000&lt;/span&gt;:0006&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;prev&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0000&lt;/span&gt;:0009&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;rest&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="nv"&gt;pc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0036&lt;/span&gt;:00ac&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0036&lt;/span&gt;:075c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;fp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ST:002b&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ST:0042
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;stack&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;
ST:003d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0000&lt;/span&gt;:0001
ST:003e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0000&lt;/span&gt;:0006
ST:003f&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0000&lt;/span&gt;:0075
ST:0040&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0000&lt;/span&gt;:0000
ST:0041&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0027&lt;/span&gt;:0ec0
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;p
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;registers
Current&lt;span class="w"&gt; &lt;/span&gt;register&lt;span class="w"&gt; &lt;/span&gt;values:
&lt;span class="nv"&gt;acc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0027&lt;/span&gt;:0ec0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;prev&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0000&lt;/span&gt;:0009&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;rest&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="nv"&gt;pc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0036&lt;/span&gt;:00ae&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0036&lt;/span&gt;:075c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;fp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ST:002b&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ST:0041
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;stack&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;
ST:003c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0000&lt;/span&gt;:003d
ST:003d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0000&lt;/span&gt;:0001
ST:003e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0000&lt;/span&gt;:0006
ST:003f&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0000&lt;/span&gt;:0075
ST:0040&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0000&lt;/span&gt;:0000
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As expected (after reading vm.cpp), &amp;quot;sali local0&amp;quot; pops the bird object from the top of the stack, and stores it at variable local[0+acc]. I did this for each iteration of the loop, and found something strange. I was expecting acc to randomly cycle through the numbers from 0 to 8 with each bird getting a slot, but sometimes the same number was coming up multiple times, which would overwrite a variable and leave a gap. Which leads to my next question; how is acc chosen?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;00a1:8b&lt;span class="w"&gt; &lt;/span&gt;1b&lt;span class="w"&gt;              &lt;/span&gt;lsl&lt;span class="w"&gt; &lt;/span&gt;local27
00a3:8d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;lst&lt;span class="w"&gt; &lt;/span&gt;temp0
00a5:43&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;62&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;04&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;callk&lt;span class="w"&gt; &lt;/span&gt;StrAt&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;acc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;StrAt&lt;span class="o"&gt;(&lt;/span&gt;local&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;temp&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt;

00a8:36&lt;span class="w"&gt;                &lt;/span&gt;push&lt;span class="w"&gt;             &lt;/span&gt;//
00a9:35&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;61&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;ldi&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;61&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;//
00ab:04&lt;span class="w"&gt;                 &lt;/span&gt;sub&lt;span class="w"&gt;             &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;acc&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;0x61
00ac:b3&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;sali&lt;span class="w"&gt; &lt;/span&gt;local0&lt;span class="w"&gt;      &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;+acc&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;acc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pop&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;local&lt;span class="o"&gt;[&lt;/span&gt;index&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;acc
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I really didn't get this at first. What the hell is StrAt? Why the arbitrary 0x61 offset?&lt;/p&gt;
&lt;p&gt;It's not that bad. &amp;quot;callk&amp;quot; is the opcode for a so-called kernel call, which is basically predefined C code in the SCI VM. It took me way too long to realise, but acc = StrAt(string, index) is the equivalent of acc = string[index], which seems ridiculous but okay. And what was the string, I hear you ask?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Thread&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scummvm&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;hit&lt;span class="w"&gt; &lt;/span&gt;Breakpoint&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;Sci::kStrAt&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;s&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x555558dff180,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;argc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;argv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x555558e7292c&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;engines/sci/engine/kstring.cpp:88
&lt;span class="m"&gt;88&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;argv&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;SIGNAL_REG&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;l
&lt;span class="m"&gt;83&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;argv&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="m"&gt;84&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="m"&gt;85&lt;/span&gt;
&lt;span class="m"&gt;86&lt;/span&gt;
&lt;span class="m"&gt;87&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;reg_t&lt;span class="w"&gt; &lt;/span&gt;kStrAt&lt;span class="o"&gt;(&lt;/span&gt;EngineState&lt;span class="w"&gt; &lt;/span&gt;*s,&lt;span class="w"&gt; &lt;/span&gt;int&lt;span class="w"&gt; &lt;/span&gt;argc,&lt;span class="w"&gt; &lt;/span&gt;reg_t&lt;span class="w"&gt; &lt;/span&gt;*argv&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="m"&gt;88&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;argv&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;SIGNAL_REG&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="m"&gt;89&lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;warning&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Attempt to perform kStrAt() on a signal reg&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="m"&gt;90&lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;NULL_REG&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="m"&gt;91&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="m"&gt;92&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;p&lt;span class="w"&gt; &lt;/span&gt;argv
&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Sci::reg_t&lt;span class="w"&gt; &lt;/span&gt;*&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;0x555558e7292c
&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;p&lt;span class="w"&gt; &lt;/span&gt;argv&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="nv"&gt;_segment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;54&lt;/span&gt;,
&lt;span class="nv"&gt;_offset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2100&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;p&lt;span class="w"&gt; &lt;/span&gt;argv&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="nv"&gt;$3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="nv"&gt;_segment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,
&lt;span class="nv"&gt;_offset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;p&lt;span class="w"&gt; &lt;/span&gt;argc
&lt;span class="nv"&gt;$4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;n
&lt;span class="m"&gt;93&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;SegmentRef&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;dest_r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;s-&amp;gt;_segMan-&amp;gt;dereference&lt;span class="o"&gt;(&lt;/span&gt;argv&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;n
&lt;span class="m"&gt;94&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;!dest_r.isValid&lt;span class="o"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;p&lt;span class="w"&gt; &lt;/span&gt;dest_r
&lt;span class="nv"&gt;$5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="nv"&gt;isRaw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;true,
&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;raw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;0x555558fa70a4&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cdhfagbbi&amp;quot;&lt;/span&gt;,
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;reg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;0x555558fa70a4
&lt;span class="o"&gt;}&lt;/span&gt;,
&lt;span class="nv"&gt;maxSize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;54&lt;/span&gt;,
&lt;span class="nv"&gt;skipByte&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Oh. Ohhhhhh. So there's ANOTHER method that's generating a 9 character string with the letters from a to i to indicate the bird position in the variables list, and storing the pointer to it in local[27]. 0x61 is the ASCII character 'a', so subtracting that from each letter gives an index from 0 to 8. Does that seriously mean the SCI VM doesn't have any array types other than byte strings? If so it's pretty wild they made all these math based puzzles.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# in ScummVM console&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;bp_method&lt;span class="w"&gt; &lt;/span&gt;flamingos::init
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;#0: Execute flamingos::init&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;

&lt;span class="c1"&gt;# open the puzzle&lt;/span&gt;
Break&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;flamingos::init&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0036&lt;/span&gt;:075c&lt;span class="o"&gt;])&lt;/span&gt;
flamingos::init&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0036&lt;/span&gt;:002c
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;vm_vars&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;
&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;var&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0036&lt;/span&gt;:0834&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;reference&lt;span class="o"&gt;)&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;view_reference&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0036&lt;/span&gt;:0834
&lt;span class="m"&gt;0036&lt;/span&gt;:0834&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;0x10:&lt;span class="w"&gt; &lt;/span&gt;raw&lt;span class="w"&gt; &lt;/span&gt;data
&lt;span class="m"&gt;000000&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;61&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;62&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;63&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;65&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;66&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;67&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;68&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;69&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;52&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;65&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;73&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;65&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;74&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;74&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;abcdefghi.Resett&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="m"&gt;000010&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;69&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;6e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;67&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;66&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;6c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;61&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;6d&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;69&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;6e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;67&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;6f&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;73&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;66&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;6c&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;ing.flamingos.fl&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="m"&gt;000020&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;61&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;6d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;57&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;69&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;6e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;62&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;69&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;72&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;04&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;3a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;amWin.bird....:.&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="m"&gt;000030&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;72&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;01&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;aa&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;01&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;..r...&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;flamingos::init() starts with local[27] pointing to the pre-baked string 'abcdefghi', then does some operations to shuffle the contents around. This process fails and overwrites parts of the string 10-20% of the time. In fact, the 6th instruction in flamingos::init() throws to a subroutine, and checking before/after we know that subroutine is responsible for doing the shuffle.&lt;/p&gt;
&lt;p&gt;Because I don't know the instruction set that well, usually I go through and translate each instruction to the C/Python equivalent, in this case using the interpreter in vm.cpp as a guide, consolidating lines as I go. Here's the copy with my annotations:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;procedure&lt;span class="w"&gt; &lt;/span&gt;proc_04c3&lt;span class="w"&gt;                      &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;param&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;local&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
04c3:3f&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;03&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;link&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;link&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
04c5:78&lt;span class="w"&gt;               &lt;/span&gt;push1&lt;span class="w"&gt;               &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# 1 arg&lt;/span&gt;
04c6:8f&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;01&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;lsp&lt;span class="w"&gt; &lt;/span&gt;param1&lt;span class="w"&gt;        &lt;/span&gt;//
04c8:43&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;46&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;02&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;callk&lt;span class="w"&gt; &lt;/span&gt;StrLen&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;acc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;StrLen&lt;span class="o"&gt;(&lt;/span&gt;param&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt;

04cb:a5&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;sat&lt;span class="w"&gt; &lt;/span&gt;temp0&lt;span class="w"&gt;         &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;temp&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;acc

&lt;span class="w"&gt;        &lt;/span&gt;code_04cd
04cd:85&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;lat&lt;span class="w"&gt; &lt;/span&gt;temp0&lt;span class="w"&gt;         &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;acc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;temp&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
04cf:31&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;42&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;bnt&lt;span class="w"&gt; &lt;/span&gt;code_0513&lt;span class="w"&gt;     &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;acc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;goto&lt;span class="w"&gt; &lt;/span&gt;code_0513
04d1:7a&lt;span class="w"&gt;               &lt;/span&gt;push2&lt;span class="w"&gt;               &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# 2 args&lt;/span&gt;
04d2:78&lt;span class="w"&gt;               &lt;/span&gt;push1
04d3:36&lt;span class="w"&gt;                &lt;/span&gt;push
04d4:43&lt;span class="w"&gt; &lt;/span&gt;3c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;04&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;callk&lt;span class="w"&gt; &lt;/span&gt;Random&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;acc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Random&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;acc&lt;span class="o"&gt;)&lt;/span&gt;

04d7:36&lt;span class="w"&gt;                &lt;/span&gt;push&lt;span class="w"&gt;               &lt;/span&gt;//
04d8:35&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;01&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;ldi&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;//
04da:04&lt;span class="w"&gt;                 &lt;/span&gt;sub&lt;span class="w"&gt;               &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;acc&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
04db:a5&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;02&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;sat&lt;span class="w"&gt; &lt;/span&gt;temp2&lt;span class="w"&gt;         &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;temp&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;acc
04dd:7a&lt;span class="w"&gt;               &lt;/span&gt;push2&lt;span class="w"&gt;               &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# 2 args&lt;/span&gt;
04de:8f&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;01&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;lsp&lt;span class="w"&gt; &lt;/span&gt;param1&lt;span class="w"&gt;        &lt;/span&gt;//
04e0:36&lt;span class="w"&gt;                &lt;/span&gt;push&lt;span class="w"&gt;               &lt;/span&gt;//
04e1:43&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;62&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;04&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;callk&lt;span class="w"&gt; &lt;/span&gt;StrAt&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;acc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;StrAt&lt;span class="o"&gt;(&lt;/span&gt;param&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;acc&lt;span class="o"&gt;)&lt;/span&gt;

04e4:a5&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;01&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;sat&lt;span class="w"&gt; &lt;/span&gt;temp1&lt;span class="w"&gt;         &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;temp&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;acc
04e6:7a&lt;span class="w"&gt;               &lt;/span&gt;push2&lt;span class="w"&gt;               &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# 2 args&lt;/span&gt;
04e7:8f&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;01&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;lsp&lt;span class="w"&gt; &lt;/span&gt;param1&lt;span class="w"&gt;        &lt;/span&gt;//
04e9:85&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;02&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;lat&lt;span class="w"&gt; &lt;/span&gt;temp2&lt;span class="w"&gt;         &lt;/span&gt;//
04eb:02&lt;span class="w"&gt;                 &lt;/span&gt;add&lt;span class="w"&gt;               &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;acc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;param&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;temp&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
04ec:36&lt;span class="w"&gt;                &lt;/span&gt;push&lt;span class="w"&gt;               &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;dest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;acc
04ed:8f&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;01&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;lsp&lt;span class="w"&gt; &lt;/span&gt;param1&lt;span class="w"&gt;        &lt;/span&gt;//
04ef:85&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;02&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;lat&lt;span class="w"&gt; &lt;/span&gt;temp2&lt;span class="w"&gt;         &lt;/span&gt;//
04f1:02&lt;span class="w"&gt;                 &lt;/span&gt;add&lt;span class="w"&gt;               &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;acc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;param&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;temp&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
04f2:36&lt;span class="w"&gt;                &lt;/span&gt;push&lt;span class="w"&gt;               &lt;/span&gt;//
04f3:35&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;01&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;ldi&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;//
04f5:02&lt;span class="w"&gt;                 &lt;/span&gt;add&lt;span class="w"&gt;               &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;acc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
04f6:36&lt;span class="w"&gt;                &lt;/span&gt;push&lt;span class="w"&gt;               &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;acc
04f7:43&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;47&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;04&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;callk&lt;span class="w"&gt; &lt;/span&gt;StrCpy&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;strcpy&lt;span class="o"&gt;(&lt;/span&gt;dest,&lt;span class="w"&gt; &lt;/span&gt;src&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;acc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src

04fa:39&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;03&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;pushi&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# 3 args&lt;/span&gt;
04fc:8f&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;01&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;lsp&lt;span class="w"&gt; &lt;/span&gt;param1&lt;span class="w"&gt;        &lt;/span&gt;//
04fe:39&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;08&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;pushi&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;//
&lt;span class="m"&gt;0500&lt;/span&gt;:8d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;01&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;lst&lt;span class="w"&gt; &lt;/span&gt;temp1&lt;span class="w"&gt;         &lt;/span&gt;//
&lt;span class="m"&gt;0502&lt;/span&gt;:43&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;62&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;callk&lt;span class="w"&gt; &lt;/span&gt;StrAt&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;acc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;StrAt&lt;span class="o"&gt;(&lt;/span&gt;param&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;temp&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt;

&lt;span class="m"&gt;0505&lt;/span&gt;:39&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;03&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;pushi&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# 3 args&lt;/span&gt;
&lt;span class="m"&gt;0507&lt;/span&gt;:8f&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;01&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;lsp&lt;span class="w"&gt; &lt;/span&gt;param1&lt;span class="w"&gt;        &lt;/span&gt;//
&lt;span class="m"&gt;0509&lt;/span&gt;:39&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;09&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;pushi&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;//
050b:76&lt;span class="w"&gt;               &lt;/span&gt;push0&lt;span class="w"&gt;               &lt;/span&gt;//
050c:43&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;62&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;callk&lt;span class="w"&gt; &lt;/span&gt;StrAt&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;acc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;StrAt&lt;span class="o"&gt;(&lt;/span&gt;param&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

050f:e5&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;-at&lt;span class="w"&gt; &lt;/span&gt;temp0&lt;span class="w"&gt;         &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;temp0&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="m"&gt;0511&lt;/span&gt;:33&lt;span class="w"&gt; &lt;/span&gt;ba&lt;span class="w"&gt;              &lt;/span&gt;jmp&lt;span class="w"&gt; &lt;/span&gt;code_04cd&lt;span class="w"&gt;     &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;temp0&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;goto&lt;span class="w"&gt; &lt;/span&gt;code_04cd

&lt;span class="w"&gt;        &lt;/span&gt;code_0513
&lt;span class="m"&gt;0513&lt;/span&gt;:48&lt;span class="w"&gt;                 &lt;/span&gt;ret&lt;span class="w"&gt;               &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Easy. For 9 iterations, the shuffler picks a random point in the string, copies all the characters to the right of it one space left, then re-adds the missing character and a null to the end.&lt;/p&gt;
&lt;p&gt;Right I think I can guess the twist, it's going to be the Random function doing something stupid. If I was a gambling man, probably Random(1, 1) will do something idiotic like return 0 and balls up the shuffling math.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# in ScummVM console&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;bp_address&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0036&lt;/span&gt;:04d4
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;bp_address&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0036&lt;/span&gt;:04f7
...
Break&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0036&lt;/span&gt;:4d4
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;p
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;registers
Current&lt;span class="w"&gt; &lt;/span&gt;register&lt;span class="w"&gt; &lt;/span&gt;values:
&lt;span class="nv"&gt;acc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0000&lt;/span&gt;:0001&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;prev&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0000&lt;/span&gt;:0001&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;rest&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="nv"&gt;pc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0036&lt;/span&gt;:04d7&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0036&lt;/span&gt;:075c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;fp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ST:0030&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ST:0033
...
&lt;span class="m"&gt;0036&lt;/span&gt;:04f7:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;43&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;47&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;04&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;callk&lt;span class="w"&gt;     &lt;/span&gt;StrCpy&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;47&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;04&lt;/span&gt;
Kernel&lt;span class="w"&gt; &lt;/span&gt;params:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0036&lt;/span&gt;:0834,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0036&lt;/span&gt;:0835&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="m"&gt;000000&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;61&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;62&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;65&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;66&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;67&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;68&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;69&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;63&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;52&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;65&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;73&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;65&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;74&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;74&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;abdefghic.Resett&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="m"&gt;000010&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;69&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;6e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;67&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;66&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;6c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;61&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;6d&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;69&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;6e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;67&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;6f&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;73&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;66&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;6c&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;ing.flamingos.fl&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="m"&gt;000020&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;61&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;6d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;57&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;69&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;6e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;62&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;69&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;72&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;04&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;3a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;amWin.bird....:.&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="m"&gt;000030&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;72&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;01&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;aa&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;01&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;..r...&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;
Step&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;#144526&lt;/span&gt;
&lt;span class="m"&gt;0036&lt;/span&gt;:04fa:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;39&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;03&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;pushi&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;03&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;loop
&lt;span class="m"&gt;000000&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;62&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;65&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;66&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;67&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;69&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;63&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;63&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;52&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;65&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;73&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;65&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;74&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;74&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;bdefgicc..Resett&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="m"&gt;000010&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;69&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;6e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;67&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;66&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;6c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;61&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;6d&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;69&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;6e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;67&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;6f&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;73&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;66&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;6c&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;ing.flamingos.fl&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="m"&gt;000020&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;61&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;6d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;57&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;69&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;6e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;62&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;69&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;72&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;04&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;3a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;amWin.bird....:.&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="m"&gt;000030&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;72&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;01&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;aa&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;01&lt;/span&gt;&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;..r...&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I lose. Random is working fine. It was StrCpy; the one in ScummVM's SCI engine doesn't react well when the source and destination strings overlap!&lt;/p&gt;
&lt;p&gt;And by &amp;quot;the one in ScummVM's SCI engine&amp;quot;, I mean glibc. The C spec says the behaviour in the case of overlapping is undefined, and some individuals have taken that to mean that it's 100% A-OK to change that behaviour and (why not) break every closed-source program that relies on it. In fact, &lt;a class="reference external" href="https://sourceware.org/bugzilla/show_bug.cgi?id=12518"&gt;this legendary bug report&lt;/a&gt; has those two titans of community outreach and civil discourse, Linus Torvalds and Ulrich Drepper, arguing whether or not it was fine to break Adobe Flash for an alleged infintessimal speed boost you got on x86 by copying things in reverse.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-solution"&gt;
&lt;h2&gt;The solution&lt;/h2&gt;
&lt;p&gt;As the bug was in one of the SCI kernel functions, we didn't need to add anything to the workaround table after all. The fix was to replace the SCI kernel's underlying calls to strcpy/strncpy with strlen + memmove, which allow an overlapping source and destination buffer. Voila, awful flamingo puzzle works.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gh"&gt;diff --git a/engines/sci/engine/seg_manager.cpp b/engines/sci/engine/seg_manager.cpp&lt;/span&gt;
&lt;span class="gh"&gt;index aa70d5f838..3b65e78d52 100644&lt;/span&gt;
&lt;span class="gd"&gt;--- a/engines/sci/engine/seg_manager.cpp&lt;/span&gt;
&lt;span class="gi"&gt;+++ b/engines/sci/engine/seg_manager.cpp&lt;/span&gt;
&lt;span class="gu"&gt;@@ -624,11 +624,18 @@ void SegManager::strncpy(reg_t dest, const char* src, size_t n) {&lt;/span&gt;


&lt;span class="w"&gt; &lt;/span&gt;       if (dest_r.isRaw) {
&lt;span class="gi"&gt;+               size_t len = ::strlen(src);&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;               // raw -&amp;gt; raw
&lt;span class="gd"&gt;-               if (n == 0xFFFFFFFFU)&lt;/span&gt;
&lt;span class="gd"&gt;-                       ::strcpy((char *)dest_r.raw, src);&lt;/span&gt;
&lt;span class="gd"&gt;-               else&lt;/span&gt;
&lt;span class="gd"&gt;-                       ::strncpy((char *)dest_r.raw, src, n);&lt;/span&gt;
&lt;span class="gi"&gt;+               if (n == 0xFFFFFFFFU) {&lt;/span&gt;
&lt;span class="gi"&gt;+                       ::memmove((char *)dest_r.raw, src, len+1);&lt;/span&gt;
&lt;span class="gi"&gt;+               } else {&lt;/span&gt;
&lt;span class="gi"&gt;+                       if (n &amp;lt; len) {&lt;/span&gt;
&lt;span class="gi"&gt;+                               ::memmove((char *)dest_r.raw, src, n);&lt;/span&gt;
&lt;span class="gi"&gt;+                       } else {&lt;/span&gt;
&lt;span class="gi"&gt;+                               ::memmove((char *)dest_r.raw, src, len);&lt;/span&gt;
&lt;span class="gi"&gt;+                               ::memset((char *)dest_r.raw+len, 0, n-len);&lt;/span&gt;
&lt;span class="gi"&gt;+                       }&lt;/span&gt;
&lt;span class="gi"&gt;+               }&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;       } else {
&lt;span class="w"&gt; &lt;/span&gt;               // raw -&amp;gt; non-raw
&lt;span class="w"&gt; &lt;/span&gt;               for (uint i = 0; i &amp;lt; n; i++) {
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;(&lt;a class="reference external" href="https://github.com/scummvm/scummvm/pull/1030"&gt;ScummVM pull request #1030&lt;/a&gt;)&lt;/p&gt;
&lt;/div&gt;
</content><category term="Show Your Working"/><category term="debugging"/><category term="show your working"/><category term="scummvm"/><category term="sierra"/><category term="sci"/><category term="unfair deaths"/><category term="the 90s had a lot of drugs okay"/></entry><entry><title>Show Your Working: Turning off PulseAudio "flat volumes"</title><link href="/writing/2017/07/23/pulseaudio/" rel="alternate"/><published>2017-07-23T00:00:00+08:00</published><updated>2017-07-23T00:00:00+08:00</updated><author><name>Scott</name></author><id>tag:None,2017-07-23:/writing/2017/07/23/pulseaudio/</id><summary type="html">&lt;img src="/writing/2017/07/23/pulseaudio/flatvolumes.gif" alt="PulseAudio volume control application with awful &amp;quot;flat volumes&amp;quot; behavior" class="" title="I&amp;#x27;m not an idiot, PulseAudio, I know that turning the master volume down makes everything quieter! You don&amp;#x27;t need to ruin the per-application volume sliders to tell me this."/&gt; &lt;p&gt;&lt;em&gt;Show Your Working is a brand new segment where I write up things I have attempted to fix in open source software. Sometimes it's interesting to debug a problem yourself from first principles, even if the codebase is huge and you don't know anything going in. I will try and explain my thought process as I venture out into the weeds armed only with a butter knife.&lt;/em&gt;&lt;/p&gt;
&lt;div class="section" id="the-software"&gt;
&lt;h2&gt;The software&lt;/h2&gt;
&lt;!-- Audio devices in Linux are exposed with ALSA drivers, which for a soundcard without hardware-mixing will usually limit the ability to output sound to one process at a time. (nowdays it's a little fuzzier as there's a software mixing plugin called dmix that gets autoenabled most places?) --&gt;
&lt;!-- Anyway, in response to this a number of laughably bad desktop sound daemons sprung up. (my favourite was KDE's aRTs, reknowned for playing system sounds a good 10 seconds after triggering) PulseAudio was announced as the definitive replacement for all of them; it offered application-level mixing, compatibility with all the major Linux sound APIs, proper device management (e.g. headphone/HDMI autoswitching), and even space-age stuff like network transparency. --&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.freedesktop.org/wiki/Software/PulseAudio/"&gt;PulseAudio&lt;/a&gt; is the software audio mixer for most desktop Linux distributions. Let's not mince words; PulseAudio has an awful reputation, mostly earned from the botched rollout by Ubuntu and other distributions back in 2008. Long-time Linux users remember these as the dark years where games were unplayable, and everything was punctuated by a loud crackling from the constant buffer underruns. The most egregious problems were caused by the aforementioned shonky buffering, plus a reliance on timing-related ALSA features which had never been used/tested properly for many sound drivers. It didn't help that barely any applications had direct support for the PulseAudio API; there were compatibility shims for ALSA/OSS applications, but they were a crapshoot at best.&lt;/p&gt;
&lt;p&gt;But that was a decade ago! We've moved from version 0.9 to version 11.1, a metric ass-ton of effort has gone into making the mixer first rate, most Linux sound libraries are fully Pulse compatible, etc. etc. Surely everything is perfect now, right?&lt;/p&gt;
</summary><content type="html">&lt;img src="/writing/2017/07/23/pulseaudio/flatvolumes.gif" alt="PulseAudio volume control application with awful &amp;quot;flat volumes&amp;quot; behavior" class="" title="I&amp;#x27;m not an idiot, PulseAudio, I know that turning the master volume down makes everything quieter! You don&amp;#x27;t need to ruin the per-application volume sliders to tell me this."/&gt; &lt;p&gt;&lt;em&gt;Show Your Working is a brand new segment where I write up things I have attempted to fix in open source software. Sometimes it's interesting to debug a problem yourself from first principles, even if the codebase is huge and you don't know anything going in. I will try and explain my thought process as I venture out into the weeds armed only with a butter knife.&lt;/em&gt;&lt;/p&gt;
&lt;div class="section" id="the-software"&gt;
&lt;h2&gt;The software&lt;/h2&gt;
&lt;!-- Audio devices in Linux are exposed with ALSA drivers, which for a soundcard without hardware-mixing will usually limit the ability to output sound to one process at a time. (nowdays it's a little fuzzier as there's a software mixing plugin called dmix that gets autoenabled most places?) --&gt;
&lt;!-- Anyway, in response to this a number of laughably bad desktop sound daemons sprung up. (my favourite was KDE's aRTs, reknowned for playing system sounds a good 10 seconds after triggering) PulseAudio was announced as the definitive replacement for all of them; it offered application-level mixing, compatibility with all the major Linux sound APIs, proper device management (e.g. headphone/HDMI autoswitching), and even space-age stuff like network transparency. --&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.freedesktop.org/wiki/Software/PulseAudio/"&gt;PulseAudio&lt;/a&gt; is the software audio mixer for most desktop Linux distributions. Let's not mince words; PulseAudio has an awful reputation, mostly earned from the botched rollout by Ubuntu and other distributions back in 2008. Long-time Linux users remember these as the dark years where games were unplayable, and everything was punctuated by a loud crackling from the constant buffer underruns. The most egregious problems were caused by the aforementioned shonky buffering, plus a reliance on timing-related ALSA features which had never been used/tested properly for many sound drivers. It didn't help that barely any applications had direct support for the PulseAudio API; there were compatibility shims for ALSA/OSS applications, but they were a crapshoot at best.&lt;/p&gt;
&lt;p&gt;But that was a decade ago! We've moved from version 0.9 to version 11.1, a metric ass-ton of effort has gone into making the mixer first rate, most Linux sound libraries are fully Pulse compatible, etc. etc. Surely everything is perfect now, right?&lt;/p&gt;

&lt;/div&gt;
&lt;div class="section" id="the-problem"&gt;
&lt;h2&gt;The problem&lt;/h2&gt;
&lt;p&gt;Ahahahaha not quite. Five years ago PulseAudio added a feature called &amp;quot;flat volumes&amp;quot;; wherein by default, every per-application volume control is welded in some byzantine way to the master volume control. Completely ignoring that, you know, everyone assumed that per-application volume controls actually meant PER-APPLICATION, and quite a lot of apps had a knack for setting the volume to 100% on startup (or just at random intervals)! Cue newly-deafened users coming out in droves to complain, and package maintainers grumping about one more non-upstream setting they'll have to override.&lt;/p&gt;
&lt;p&gt;Anyway. Disabling flat volumes reverts things back to per-application volumes scaled (post-fader) by an independent master volume, and all is right in the land once again. As of... recently? Disabling flat-volumes in /etc/pulse/daemon.conf stopped working on my system.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-preparation"&gt;
&lt;h2&gt;The preparation&lt;/h2&gt;
&lt;p&gt;The only software we need installed for tracking this down is the Git VCS and the standard Linux command line tools, plus whatever build dependencies PulseAudio has. Start by grabbing the PulseAudio source code of the release we know is bad.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://anongit.freedesktop.org/git/pulseaudio/pulseaudio
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pulseaudio
&lt;span class="c1"&gt;# commit taken from https://git.archlinux.org/svntogit/packages.git/tree/trunk/PKGBUILD?h=packages/pulseaudio&amp;amp;id=f9dd2850e5eff2d2a7fde2af6e932d785a521912&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;checkout&lt;span class="w"&gt; &lt;/span&gt;84952e6a092b6a0c5b153bd7a4f6e490810681c8
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="the-diagnosis"&gt;
&lt;h2&gt;The diagnosis&lt;/h2&gt;
&lt;p&gt;Let's track down what went wrong. We know one thing for sure: the name of the config switch which is meant to fix this (&amp;quot;flat-volumes&amp;quot;). Let's find where it's used.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-rn&lt;span class="w"&gt; &lt;/span&gt;flat-volumes&lt;span class="w"&gt; &lt;/span&gt;*
man/pulse-daemon.conf.5.xml.in:220:&lt;span class="w"&gt;      &lt;/span&gt;&amp;lt;p&amp;gt;&amp;lt;opt&amp;gt;flat-volumes&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;/opt&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Enable&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;flat&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;volumes,&lt;span class="w"&gt; &lt;/span&gt;i.e.&lt;span class="w"&gt; &lt;/span&gt;where
src/daemon/daemon-conf.c:531:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;flat-volumes&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt;               &lt;/span&gt;pa_config_parse_bool,&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;c-&amp;gt;flat_volumes,&lt;span class="w"&gt; &lt;/span&gt;NULL&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,
src/daemon/daemon-conf.c:740:&lt;span class="w"&gt;    &lt;/span&gt;pa_strbuf_printf&lt;span class="o"&gt;(&lt;/span&gt;s,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;flat-volumes = %s\n&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;pa_yes_no&lt;span class="o"&gt;(&lt;/span&gt;c-&amp;gt;flat_volumes&lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
src/daemon/daemon.conf.in:61:&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;flat-volumes&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;yes
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If I had to pick the single most important tool for troubleshooting something you've never seen before, it would be grep. It does two things extremely well: search a bunch of files or piped input for a text/regex match, and adjust the amount of context you get back for the findings. grepping is the de-facto method to scrub through a ton of source code quickly to establish causality; the chain of events linking the thing we change (i.e. the config file) to the expected action (i.e. the volume adjusting as it damn well should).&lt;/p&gt;
&lt;p&gt;Now we follow the trail of breadcrumbs and zero in on the code path which does the useful work. We can see above that the &amp;quot;flat-volumes&amp;quot; config parser sets a variable called flat_volumes, so we search for that.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-rn&lt;span class="w"&gt; &lt;/span&gt;flat_volumes&lt;span class="w"&gt; &lt;/span&gt;*
src/daemon/daemon-conf.c:71:&lt;span class="w"&gt;    &lt;/span&gt;.flat_volumes&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;true,
src/daemon/daemon-conf.c:531:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;flat-volumes&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt;               &lt;/span&gt;pa_config_parse_bool,&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;c-&amp;gt;flat_volumes,&lt;span class="w"&gt; &lt;/span&gt;NULL&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,
src/daemon/daemon-conf.c:740:&lt;span class="w"&gt;    &lt;/span&gt;pa_strbuf_printf&lt;span class="o"&gt;(&lt;/span&gt;s,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;flat-volumes = %s\n&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;pa_yes_no&lt;span class="o"&gt;(&lt;/span&gt;c-&amp;gt;flat_volumes&lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
src/daemon/daemon-conf.h:76:&lt;span class="w"&gt;        &lt;/span&gt;flat_volumes,
src/daemon/main.c:1044:&lt;span class="w"&gt;    &lt;/span&gt;c-&amp;gt;flat_volumes&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;conf-&amp;gt;flat_volumes&lt;span class="p"&gt;;&lt;/span&gt;
src/pulsecore/core.c:138:&lt;span class="w"&gt;    &lt;/span&gt;c-&amp;gt;flat_volumes&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;true&lt;span class="p"&gt;;&lt;/span&gt;
src/pulsecore/core.h:197:&lt;span class="w"&gt;    &lt;/span&gt;bool&lt;span class="w"&gt; &lt;/span&gt;flat_volumes:1&lt;span class="p"&gt;;&lt;/span&gt;
src/pulsecore/sink.c:541:&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;s-&amp;gt;core-&amp;gt;flat_volumes&lt;span class="p"&gt;;&lt;/span&gt;
src/pulsecore/source.c:492:&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;s-&amp;gt;core-&amp;gt;flat_volumes&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Okay, the only plased where flat_volumes is used to drive a decision is in src/pulseaudio/sink.c and src/pulseaudio/source.c. We'll focus in on sink.c as &amp;quot;sink&amp;quot; is PulseAudioese for &amp;quot;sound output&amp;quot;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-rn&lt;span class="w"&gt; &lt;/span&gt;-B8&lt;span class="w"&gt; &lt;/span&gt;-A16&lt;span class="w"&gt; &lt;/span&gt;flat_volumes&lt;span class="w"&gt; &lt;/span&gt;src/pulsecore/sink.c
&lt;span class="m"&gt;533&lt;/span&gt;-&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="m"&gt;534&lt;/span&gt;-
&lt;span class="m"&gt;535&lt;/span&gt;-static&lt;span class="w"&gt; &lt;/span&gt;void&lt;span class="w"&gt; &lt;/span&gt;enable_flat_volume&lt;span class="o"&gt;(&lt;/span&gt;pa_sink&lt;span class="w"&gt; &lt;/span&gt;*s,&lt;span class="w"&gt; &lt;/span&gt;bool&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="m"&gt;536&lt;/span&gt;-&lt;span class="w"&gt;    &lt;/span&gt;pa_sink_flags_t&lt;span class="w"&gt; &lt;/span&gt;flags&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="m"&gt;537&lt;/span&gt;-
&lt;span class="m"&gt;538&lt;/span&gt;-&lt;span class="w"&gt;    &lt;/span&gt;pa_assert&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="m"&gt;539&lt;/span&gt;-
&lt;span class="m"&gt;540&lt;/span&gt;-&lt;span class="w"&gt;    &lt;/span&gt;/*&lt;span class="w"&gt; &lt;/span&gt;Always&lt;span class="w"&gt; &lt;/span&gt;follow&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;overall&lt;span class="w"&gt; &lt;/span&gt;user&lt;span class="w"&gt; &lt;/span&gt;preference&lt;span class="w"&gt; &lt;/span&gt;here&lt;span class="w"&gt; &lt;/span&gt;*/
&lt;span class="m"&gt;541&lt;/span&gt;:&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;s-&amp;gt;core-&amp;gt;flat_volumes&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="m"&gt;542&lt;/span&gt;-
&lt;span class="m"&gt;543&lt;/span&gt;-&lt;span class="w"&gt;    &lt;/span&gt;/*&lt;span class="w"&gt; &lt;/span&gt;Save&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;current&lt;span class="w"&gt; &lt;/span&gt;flags&lt;span class="w"&gt; &lt;/span&gt;so&lt;span class="w"&gt; &lt;/span&gt;we&lt;span class="w"&gt; &lt;/span&gt;can&lt;span class="w"&gt; &lt;/span&gt;tell&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;they&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;ve&lt;span class="w"&gt; &lt;/span&gt;changed&lt;span class="w"&gt; &lt;/span&gt;*/
&lt;span class="m"&gt;544&lt;/span&gt;-&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;flags&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;s-&amp;gt;flags&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="m"&gt;545&lt;/span&gt;-
&lt;span class="m"&gt;546&lt;/span&gt;-&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="m"&gt;547&lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;s-&amp;gt;flags&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PA_SINK_FLAT_VOLUME&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="m"&gt;548&lt;/span&gt;-&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="m"&gt;549&lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;s-&amp;gt;flags&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~PA_SINK_FLAT_VOLUME&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="m"&gt;550&lt;/span&gt;-
&lt;span class="m"&gt;551&lt;/span&gt;-&lt;span class="w"&gt;    &lt;/span&gt;/*&lt;span class="w"&gt; &lt;/span&gt;If&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;flags&lt;span class="w"&gt; &lt;/span&gt;have&lt;span class="w"&gt; &lt;/span&gt;changed&lt;span class="w"&gt; &lt;/span&gt;after&lt;span class="w"&gt; &lt;/span&gt;init,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;any&lt;span class="w"&gt; &lt;/span&gt;clients&lt;span class="w"&gt; &lt;/span&gt;know&lt;span class="w"&gt; &lt;/span&gt;via&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;change&lt;span class="w"&gt; &lt;/span&gt;event&lt;span class="w"&gt; &lt;/span&gt;*/
&lt;span class="m"&gt;552&lt;/span&gt;-&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;s-&amp;gt;state&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PA_SINK_INIT&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;flags&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;s-&amp;gt;flags&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="m"&gt;553&lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;pa_subscription_post&lt;span class="o"&gt;(&lt;/span&gt;s-&amp;gt;core,&lt;span class="w"&gt; &lt;/span&gt;PA_SUBSCRIPTION_EVENT_SINK&lt;span class="p"&gt;|&lt;/span&gt;PA_SUBSCRIPTION_EVENT_CHANGE,&lt;span class="w"&gt; &lt;/span&gt;s-&amp;gt;index&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="m"&gt;554&lt;/span&gt;-&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="m"&gt;555&lt;/span&gt;-
&lt;span class="m"&gt;556&lt;/span&gt;-void&lt;span class="w"&gt; &lt;/span&gt;pa_sink_enable_decibel_volume&lt;span class="o"&gt;(&lt;/span&gt;pa_sink&lt;span class="w"&gt; &lt;/span&gt;*s,&lt;span class="w"&gt; &lt;/span&gt;bool&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="m"&gt;557&lt;/span&gt;-&lt;span class="w"&gt;    &lt;/span&gt;pa_sink_flags_t&lt;span class="w"&gt; &lt;/span&gt;flags&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We can see that in terms of honouring the setting, nothing has changed. Enabling flat volume is always overridden by what the user has set, and the actual state is stored in a variable flags with the bitflag name PA_SINK_FLAT_VOLUME. Guess what we're searching for next???&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-rn&lt;span class="w"&gt; &lt;/span&gt;PA_SINK_FLAT_VOLUME&lt;span class="w"&gt; &lt;/span&gt;*
src/modules/dbus/iface-device.c:461:&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;has_flat_volume&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;d-&amp;gt;type&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PA_DEVICE_TYPE_SINK&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;?&lt;span class="w"&gt; &lt;/span&gt;!!&lt;span class="o"&gt;(&lt;/span&gt;d-&amp;gt;sink-&amp;gt;flags&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PA_SINK_FLAT_VOLUME&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;FALSE&lt;span class="p"&gt;;&lt;/span&gt;
src/modules/dbus/iface-device.c:841:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;has_flat_volume&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!!&lt;span class="o"&gt;(&lt;/span&gt;d-&amp;gt;sink-&amp;gt;flags&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PA_SINK_FLAT_VOLUME&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
src/modules/module-virtual-sink.c:564:&lt;span class="w"&gt;        &lt;/span&gt;u-&amp;gt;sink-&amp;gt;flags&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PA_SINK_FLAT_VOLUME&lt;span class="p"&gt;;&lt;/span&gt;
src/modules/module-virtual-surround-sink.c:702:&lt;span class="w"&gt;        &lt;/span&gt;u-&amp;gt;sink-&amp;gt;flags&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PA_SINK_FLAT_VOLUME&lt;span class="p"&gt;;&lt;/span&gt;
src/pulse/def.h:801:&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;PA_SINK_FLAT_VOLUME&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;0x0040U,
src/pulse/def.h:838:#define&lt;span class="w"&gt; &lt;/span&gt;PA_SINK_FLAT_VOLUME&lt;span class="w"&gt; &lt;/span&gt;PA_SINK_FLAT_VOLUME
src/pulsecore/cli-text.c:286:&lt;span class="w"&gt;            &lt;/span&gt;sink-&amp;gt;flags&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PA_SINK_FLAT_VOLUME&lt;span class="w"&gt; &lt;/span&gt;?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;FLAT_VOLUME &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;,
src/pulsecore/sink.c:547:&lt;span class="w"&gt;        &lt;/span&gt;s-&amp;gt;flags&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PA_SINK_FLAT_VOLUME&lt;span class="p"&gt;;&lt;/span&gt;
src/pulsecore/sink.c:549:&lt;span class="w"&gt;        &lt;/span&gt;s-&amp;gt;flags&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~PA_SINK_FLAT_VOLUME&lt;span class="p"&gt;;&lt;/span&gt;
src/pulsecore/sink.c:1554:&lt;span class="w"&gt; &lt;/span&gt;*&lt;span class="w"&gt; &lt;/span&gt;When&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;sink&lt;span class="w"&gt; &lt;/span&gt;uses&lt;span class="w"&gt; &lt;/span&gt;volume&lt;span class="w"&gt; &lt;/span&gt;sharing,&lt;span class="w"&gt; &lt;/span&gt;it&lt;span class="w"&gt; &lt;/span&gt;never&lt;span class="w"&gt; &lt;/span&gt;has&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;PA_SINK_FLAT_VOLUME&lt;span class="w"&gt; &lt;/span&gt;flag
src/pulsecore/sink.c:1563:&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;s-&amp;gt;flags&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PA_SINK_FLAT_VOLUME&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
src/pulsecore/sink.h:441:/*&lt;span class="w"&gt; &lt;/span&gt;Use&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;instead&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;checking&lt;span class="w"&gt; &lt;/span&gt;s-&amp;gt;flags&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;PA_SINK_FLAT_VOLUME&lt;span class="w"&gt; &lt;/span&gt;directly.&lt;span class="w"&gt; &lt;/span&gt;*/
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now THIS is a great example of why you want to grep as much source code as possible. We could have continued our investigation just in sink.c, but searching everywhere revealed something super useful! Namely src/pulsecore/cli-text.c; there is a command line thing somewhere which prints if PA_SINK_FLAT_VOLUME is set or not. This just potentially saved us having to make a debug build to find this out.&lt;/p&gt;
&lt;p&gt;A little bit of poking around revealed that the string is printed when you invoke pacmd list-sinks (&lt;em&gt;not&lt;/em&gt; pactl list sinks! that of course produces output that's near identical, but missing the one thing we want.), so we did that.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ pacmd list-sinks | grep -B8 -A8 FLAT
6 sink(s) available.
    index: 0
    name: &amp;lt;alsa_output.pci-0000_2a_00.3.iec958-stereo&amp;gt;
    driver: &amp;lt;module-alsa-card.c&amp;gt;
    flags: HARDWARE HW_MUTE_CTRL DECIBEL_VOLUME LATENCY FLAT_VOLUME DYNAMIC_LATENCY
    state: IDLE
    suspend cause:
    priority: 9058
    volume: front-left: 0 /   0% / -inf dB,   front-right: 0 /   0% / -inf dB
            balance 0.00
    base volume: 65536 / 100% / 0.00 dB
    volume steps: 65537
    muted: no
--
    ports:
        iec958-stereo-output: Digital Output (S/PDIF) (priority 0, latency offset 0 usec, available: unknown)
            properties:

    active port: &amp;lt;iec958-stereo-output&amp;gt;
* index: 1
    name: &amp;lt;jack_out&amp;gt;
    driver: &amp;lt;module-jack-sink.c&amp;gt;
    flags: DECIBEL_VOLUME LATENCY FLAT_VOLUME
    state: IDLE
    suspend cause:
    priority: 0
    volume: front-left: 22925 /  35% / -27.37 dB,   front-right: 22925 /  35% / -27.37 dB
            balance 0.00
    base volume: 65536 / 100% / 0.00 dB
    volume steps: 65537
    muted: no
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Okay well there's the problem; the sink I'm using (JACK to my Scarlett box) has FLAT_VOLUME enabled. But why. Whyyyyyyyyy. I mean I told it not to!&lt;/p&gt;
&lt;p&gt;No getting around it, we need to have a look at the program while it's running. Let's make a test build.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# copying the build steps from the PKGCONFIG&lt;/span&gt;
&lt;span class="c1"&gt;# by default CFLAGS should include -g, which adds debug symbols&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;./bootstrap.sh
$&lt;span class="w"&gt; &lt;/span&gt;./configure&lt;span class="w"&gt; &lt;/span&gt;--prefix&lt;span class="o"&gt;=&lt;/span&gt;/usr&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;--sysconfdir&lt;span class="o"&gt;=&lt;/span&gt;/etc&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;--libexecdir&lt;span class="o"&gt;=&lt;/span&gt;/usr/lib&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;--localstatedir&lt;span class="o"&gt;=&lt;/span&gt;/var&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;--with-udev-rules-dir&lt;span class="o"&gt;=&lt;/span&gt;/usr/lib/udev/rules.d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;--with-database&lt;span class="o"&gt;=&lt;/span&gt;tdb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;--disable-tcpwrap&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;--disable-bluez4&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;--disable-samplerate&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;--disable-rpath&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;--disable-default-build-tests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nv"&gt;DATADIRNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;share
$&lt;span class="w"&gt; &lt;/span&gt;make
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There's now a freshly built pulseaudio executable in the src directory. Except it's not, it's some libtool shell spew that passes enough lies to the built copy (in .libs) to make it run. Which is a problem, because you can't run gdb against shell spew.&lt;/p&gt;
&lt;p&gt;Because they're not all heartless bastards, the libtool developers made a wrapper for the wrapper which allows pass-through execution. Before we start the session, make sure PulseAudio isn't set to autospawn, kill the current daemom, and then fire up the debugger.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ pulseaudio -k
$ libtool --mode=execute gdb --args pulseaudio
GNU gdb (GDB) 8.0
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later &amp;lt;http://gnu.org/licenses/gpl.html&amp;gt;
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type &amp;quot;show copying&amp;quot;
and &amp;quot;show warranty&amp;quot; for details.
...
(gdb)
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Smashing. All of the functions we want to test are in shared libraries, which we can only see after execution starts, so lets breakpoint immediately after starting.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(gdb) b main
Breakpoint 1 at 0x52e0: file daemon/main.c, line 369.
(gdb) r
Starting program: /home/scott/Development/pulseaudio/src/.libs/lt-pulseaudio
[Thread debugging using libthread_db enabled]
Using host libthread_db library &amp;quot;/usr/lib/libthread_db.so.1&amp;quot;.

Breakpoint 1, main (argc=1, argv=0x7fffffffdf68) at daemon/main.c:369
369 int main(int argc, char *argv[]) {
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We'll start by adding a breakpoint in enable_flat_volume() and seeing what's going down there.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(gdb) b pulsecore/sink.c:536
Breakpoint 2 at 0x7ffff7b75690: pulsecore/sink.c:536. (2 locations)
(gdb) c
Continuing.
W: [lt-pulseaudio] main.c: /proc/self/exe does not point to /usr/bin/pulseaudio, cannot self execute. Are you playing games?
N: [lt-pulseaudio] daemon-conf.c: Detected that we are run from the build tree, fixing search path.
E: [lt-pulseaudio] module-alsa-card.c: Failed to find a working profile.
E: [lt-pulseaudio] module.c: Failed to load module &amp;quot;module-alsa-card&amp;quot; (argument: &amp;quot;device_id=&amp;quot;3&amp;quot; name=&amp;quot;usb-Clavia_DMI_AB_Nord_Electro_5-01&amp;quot; card_name=&amp;quot;alsa_card.usb-Clavia_DMI_AB_Nord_Electro_5-01&amp;quot; namereg_fail=false tsched=yes fixed_latency_range=no ignore_dB=no deferred_volume=yes use_ucm=yes card_properties=&amp;quot;module-udev-detect.discovered=1&amp;quot;&amp;quot;): initialization failed.

Breakpoint 2, enable_flat_volume (s=0x555555877570, enable=true)
    at pulsecore/sink.c:538
538     pa_assert(s);
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's do a backtrace to see where the method got called from.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(gdb) bt
#0  enable_flat_volume (s=0x5555558734f0, enable=true) at pulsecore/sink.c:541
#1  0x00007ffff7b77110 in pa_sink_enable_decibel_volume (s=0x5555558734f0,
    enable=&amp;lt;optimized out&amp;gt;) at pulsecore/sink.c:566
...
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The default behaviour seems to be to enable flat volumes if the sink identifies itself as having decibel-based volume control? I... you know what I won't even pretend to understand why. So what happened to the config file? Isn't it meant to override this?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(gdb) p s-&amp;gt;core-&amp;gt;flat_volumes
$3 = true
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I knew it! You betrayed me, config parser!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(gdb) b daemon/daemon-conf.c:607
Breakpoint 6 at 0x55555555f3a9: file daemon/daemon-conf.c, line 610.
(gdb) c
Continuing.

Breakpoint 6, pa_daemon_conf_load (c=0x55555578d990, filename=0x0)
    at daemon/daemon-conf.c:607
...
612         pa_open_config_file(DEFAULT_CONFIG_FILE, DEFAULT_CONFIG_FILE_USER, ENV_CONFIG_FILE, &amp;amp;c-&amp;gt;config_file);
...
(gdb) b pa_open_config_file
Breakpoint 7 at 0x7ffff6d5d580: file pulsecore/core-util.c, line 1993.
(gdb) c
Continuing.

Breakpoint 7, pa_open_config_file (
    global=global@entry=0x5555555638e1 &amp;quot;/etc/pulse/daemon.conf&amp;quot;,
    local=local@entry=0x5555555638eb &amp;quot;/daemon.conf&amp;quot;,
    env=env@entry=0x5555555638d4 &amp;quot;PULSE_CONFIG&amp;quot;,
    result=result@entry=0x55555578d9e8) at pulsecore/core-util.c:1993
1993        FILE *pa_open_config_file(const char *global, const char *local, const char *env, char **result) {
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Okay mea culpa time. I stupidly thought it would load /etc/pulse/daemon.conf first, which has the line 'flat-volumes = no' in it, then any changes made in the local config file (~/.pulse/daemon.conf) would override those base settings. We can see the truth by reading the code for pa_open_config_file; what ACTUALLY happens is that PulseAudio &lt;strong&gt;only&lt;/strong&gt; reads the first config file it can find (first it tries the path in environment variable PULSE_CONFIG, then ~/.pulse/daemon.conf, then /etc/pulse/daemon.conf).&lt;/p&gt;
&lt;p&gt;So because I had a near-empty ~/.pulse/daemon.conf which &lt;em&gt;didn't&lt;/em&gt; define flat-volumes, the flat-volumes setting was forced on. I don't even know where the file came from, it has a last-modified date of two days ago. Urghhhh.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-solution"&gt;
&lt;h2&gt;The solution&lt;/h2&gt;
&lt;p&gt;Remove ~/.pulse/daemon.conf. Restart PulseAudio.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Show Your Working"/><category term="debugging"/><category term="show your working"/><category term="pulseaudio"/><category term="flat volumes"/><category term="broken audio"/><category term="lennart knows best"/></entry><entry><title>Balls to learning how to animate, let's film some parkour!</title><link href="/writing/2016/08/27/animation/" rel="alternate"/><published>2016-08-27T00:00:00+08:00</published><updated>2016-08-27T00:00:00+08:00</updated><author><name>Scott</name></author><id>tag:None,2016-08-27:/writing/2016/08/27/animation/</id><summary type="html">&lt;img src="/writing/2016/08/27/animation/therealismithurts.gif" alt="Overlay of live footage with rotoscoping" class="" title="AAAAAAH MY FACE WHAT DID YOU DO TO MY FACE"/&gt; &lt;p&gt;Hallå! Welcome to my first game devlog.&lt;/p&gt;
&lt;!-- In case you're not aware, there *was* a gold-rush for indie games over the past seven or so years, which has well and truly run its course. A few early movers struck it rich, an awful lot of latecomers gave up their careers to find nothing but strip-mined sand, and a handful of pick-and-shovel manufacturers can remodel their offices in solid gold brick. --&gt;
&lt;!-- In a nutshell, in these post-boom times there's now a massive problem with oversupply and visibility. New titles are guaranteed to be drowned out upon release, so the name of the game now is to build an audience during development, and keep them hooked with exciting tales of how the sausage is made. Then hopefully they'll tell someone else and you'll have two sales! --&gt;
&lt;p&gt;I figured I'd start out talking about graphics, as they are a large source of anxiety for first-time developers. And not without reason! Everyone is a judgy bastard when it comes to how your low-budget game is presented. Oh sure, it's become easier for a solo developer to mask the familiar, rancid stench of &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Programmer_art"&gt;programmer art&lt;/a&gt; with the equivalent of some &lt;a class="reference external" href="https://www.assetstore.unity3d.com/en/"&gt;pine-scented&lt;/a&gt; &lt;a class="reference external" href="https://www.unrealengine.com/marketplace"&gt;air freshener&lt;/a&gt;, but in practice this is a huge creative tradeoff. It really pays off to have a unique and consistent visual style in your work, even if you're not a proper artist.&lt;/p&gt;
&lt;p&gt;Which I'm not. Lord no. What little visual talent I have is spread thinly between drafting/technical drawing (a survival skill picked up working as an engineer) and sandwich-grade graphic design. Oh and I can maybe do a scratchy pencil test of a background at 1/8th the speed of a regular artist. Drawing actual characters and animating them? That's just not going to happen.&lt;/p&gt;
</summary><content type="html">&lt;img src="/writing/2016/08/27/animation/therealismithurts.gif" alt="Overlay of live footage with rotoscoping" class="" title="AAAAAAH MY FACE WHAT DID YOU DO TO MY FACE"/&gt; &lt;p&gt;Hallå! Welcome to my first game devlog.&lt;/p&gt;
&lt;!-- In case you're not aware, there *was* a gold-rush for indie games over the past seven or so years, which has well and truly run its course. A few early movers struck it rich, an awful lot of latecomers gave up their careers to find nothing but strip-mined sand, and a handful of pick-and-shovel manufacturers can remodel their offices in solid gold brick. --&gt;
&lt;!-- In a nutshell, in these post-boom times there's now a massive problem with oversupply and visibility. New titles are guaranteed to be drowned out upon release, so the name of the game now is to build an audience during development, and keep them hooked with exciting tales of how the sausage is made. Then hopefully they'll tell someone else and you'll have two sales! --&gt;
&lt;p&gt;I figured I'd start out talking about graphics, as they are a large source of anxiety for first-time developers. And not without reason! Everyone is a judgy bastard when it comes to how your low-budget game is presented. Oh sure, it's become easier for a solo developer to mask the familiar, rancid stench of &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Programmer_art"&gt;programmer art&lt;/a&gt; with the equivalent of some &lt;a class="reference external" href="https://www.assetstore.unity3d.com/en/"&gt;pine-scented&lt;/a&gt; &lt;a class="reference external" href="https://www.unrealengine.com/marketplace"&gt;air freshener&lt;/a&gt;, but in practice this is a huge creative tradeoff. It really pays off to have a unique and consistent visual style in your work, even if you're not a proper artist.&lt;/p&gt;
&lt;p&gt;Which I'm not. Lord no. What little visual talent I have is spread thinly between drafting/technical drawing (a survival skill picked up working as an engineer) and sandwich-grade graphic design. Oh and I can maybe do a scratchy pencil test of a background at 1/8th the speed of a regular artist. Drawing actual characters and animating them? That's just not going to happen.&lt;/p&gt;

&lt;p&gt;But I wanted to do both of those things! So I figured the best way to fly closer to the sun would be to create an art and asset pipeline that played to my strengths, making the process for churning out Fine Art™ as close as possible to drafting. My weapon of choice for Fine Art™ is the vector editor &lt;a class="reference external" href="http://www.inkscape.org"&gt;Inkscape&lt;/a&gt;; very easy to pick up, and with a little practice can produce some polished results. I especially like the CAD-like pixel-perfect control you can use to make things like schematics or icons. Saved my bacon loads of times.&lt;/p&gt;
&lt;p&gt;The only downside is that Inkscape, while wonderful, is not an animation editor. And even if it was, there was still the matter of sprouting talent from somewhere to draw realistic human movement. And I wasn't satisfied with the options of buying pre-fabbed animation packs from an asset store (artistic license strongly urges me to include a diverse range of player movements) or even commissioning someone competent to do it all (artistic license strongly urges me to be a cheap bastard). If only there was a way for dunce programmers like myself to cheat their way across this insurmountable art gap...&lt;/p&gt;
&lt;div class="section" id="rotoscoping"&gt;
&lt;h2&gt;Rotoscoping&lt;/h2&gt;
&lt;img src="/writing/2016/08/27/animation/pop-game.gif" alt="Running animation from Prince of Persia" class="" title="This is from the original Apple II version of Prince programmed by Mechner, which I tried for the first time when capturing this animation. Gott im Himmel the movement feels SO NICE! I grew up with the DOS port, so I assumed it was meant to feel like you were glued to the floor when running or jumping."/&gt; &lt;p&gt;Let's wind the clock back to &lt;a class="reference external" href="http://www.hardcoregaming101.net/princeofpersia/princeofpersia.htm"&gt;Prince of Persia&lt;/a&gt; (yes &lt;em&gt;that&lt;/em&gt; one, from 1989); critically acclaimed, and one of the best-known examples in computer game history of programmer art surpassing regular art. Along with its predecessor Karateka, POP was one of the first home video games to use rotoscoped animation. And just look at it! Even on the Apple II with only 4 colours it's mind-blowingly detailed and smooth!&lt;/p&gt;
&lt;img src="/writing/2016/08/27/animation/pop-rotoscope.jpg" alt="Photos used for Prince of Persia rotoscoping" class="" title="Here&amp;#x27;s the source frames used to rotoscope the above animation. Don&amp;#x27;t let the ghostly pallor fool you! Jordan Mechner&amp;#x27;s brother is in fact quite healthy; he was altered with state-of-the-art Liquid Paper and Sharpie technology to fit the palette restrictions of the Apple II."/&gt; &lt;p&gt;But how was it done? The ingenious process is detailed in Jordan Mechner's &lt;a class="reference external" href="http://www.jordanmechner.com/backstage/journals/"&gt;&amp;quot;Making of Prince of Persia&amp;quot; diary&lt;/a&gt;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Mechner would &lt;a class="reference external" href="https://www.youtube.com/watch?v=WAjRNU3DbSY"&gt;film his brother David performing a movement&lt;/a&gt; (e.g. climbing onto a wall) freehand with a VHS camcorder&lt;/li&gt;
&lt;li&gt;The cassette was taken to a professional edit suite with a space age VHS deck that supported freeze-frame, and for every frame of animation at 15fps, Mechner would take a film photograph of the screen.&lt;/li&gt;
&lt;li&gt;After the roll of film was developed Mechner would hand-draw a high contrast monochrome silhouette on each photo; blacking out the background with permanent marker and using white paint to add in shape detail rendered blurry by the video.&lt;/li&gt;
&lt;li&gt;Finally, the finished sheet of silhouettes was mounted in front of the camcorder. The analogue video out was fed into an Apple II fitted with a &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Genlock"&gt;GenLock&lt;/a&gt; device; no fancy editing software here, all it did was replace the black background of the Apple II screen with the live video, whereupon Mechner painted over the image &lt;em&gt;again&lt;/em&gt; but digitally!&lt;/li&gt;
&lt;/ul&gt;
&lt;img src="/writing/2016/08/27/animation/aw-poster.jpg" alt="Cover art of Another World" class="letterbox image-process-1200" title="This magnificant oil painting by Chahi is the cover for Another World. Because it didn&amp;#x27;t have enough going for it already. Would you believe that Interplay didn&amp;#x27;t think it would sell to kids, and made their own nonsensical boxart which is a masterclass in terrible 90s design - http://tinyurl.com/guuxbgu"/&gt; &lt;p&gt;Another similar use of rotoscoping was by Eric Chahi in his masterpiece &lt;a class="reference external" href="http://www.hardcoregaming101.net/anotherworld/anotherworld.htm"&gt;Another World&lt;/a&gt;. The setup was similar to Mechner's in that Chahi would film himself with a tripod-mounted VHS camcorder. The main difference is he owned a VHS deck with freeze-frame capability; he had this hooked up to his Amiga GenLock and would draw polygons over each frame of the frozen recording from the VHS cassette. In &lt;a class="reference external" href="http://www.anotherworld.fr/anotherworld_uk/page_genese.htm"&gt;his notes&lt;/a&gt; Chahi describes this process as &amp;quot;a time attack race&amp;quot;; a paused VHS deck stops showing the picture after a minute or so to avoid ruining the playback heads. This setup was mostly used to animate closeups and cutscenes; by this point Chahi was already an accomplished game artist, and from what I gather drew a lot of the player movement animations freehand.&lt;/p&gt;
&lt;img src="/writing/2016/08/27/animation/aw-rotoscope.gif" alt="Comparison of Another World raw/rotoscoped footage" class="" title="I couldn&amp;#x27;t get the two animations to match up good. DON&amp;#x27;T JUDGE"/&gt; &lt;p&gt;Instead of pixel-based bitmap graphics, most of the art was made with solid-filled polygons. At the time it was an innovative way to fit cinematic visuals into 2 floppy disks worth of space. Indeed, publisher Delphine Software found massive success using the same style in their next game &lt;a class="reference external" href="http://www.hardcoregaming101.net/flashback/flashback.htm"&gt;Flashback&lt;/a&gt;, shortly before dooming the company with Shaquille O'Neal fighting simulator &lt;a class="reference external" href="http://www.hardcoregaming101.net/shaqfu/shaqfu.htm"&gt;Shaq-Fu&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the Another World engine, animation is stored as a fixed-FPS series of static keyframes. That is to say, every frame is a dumb list of pre-baked polygons. This can be rendered extremely quickly; no preprocessing or transformation was required, which is really good news on pride-of-the-90s computer hardware with no floating point processor. But this format has the same limitation as sprites: each animation has one native speed (~15fps), which looks jarring if the refresh rate of the device is higher (e.g. mobile devices run at 60fps).  Also this can be a gruelling process for the animator, as they have to spend much of their time redrawing frame after frame of largely the same collection of body parts.&lt;/p&gt;
&lt;!-- .. image:: {attach}aw-chase.gif
:alt: Another World chase sequence
:title: This game is every bit as beautiful as it is frustrating to play. --&gt;
&lt;p&gt;Regardless, I really liked this visual style of simple filled polygons. Most projects nowdays use flat-shaded 3D models to achieve this look (e.g. &lt;a class="reference external" href="http://www.holyfingers.co.uk/main/blog/tag/toryanse/"&gt;Toryansé&lt;/a&gt;, &lt;a class="reference external" href="http://kentuckyroutezero.com"&gt;Kentucky Route Zero&lt;/a&gt;). In fact, if you have any measurable skill in the fields of modelling and rigging, this approach is &lt;em&gt;considerably&lt;/em&gt; cheaper than creating 2D animation frames. (I'm unburdened by either, so pure expensive 2D it is!)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="skeletal-animation"&gt;
&lt;h2&gt;Skeletal animation&lt;/h2&gt;
&lt;p&gt;3D games have moved away from static animation techniques and instead rely on skeletal animation: that is, the skin of the character is created in 3D from vertices joined up as triangles, then rigged around a series of bones and joints which define the ranges of movement for each appendage. Bones and joints are just a convenient analogy to something we are already familiar with; the musculoskeletal system of an animal. In reality, they are a stack of mathematical operations used by the renderer to warp the vertices of the skin in real time.&lt;/p&gt;
&lt;p&gt;What does this mean? Well, with Chahi's approach, for each animation keyframe you'd be setting the position of every vertex in the model. It's cheap to compute, but if you have lots of geometry you will waste a ton of time streaming the data in for every single frame. Also as mentioned before, a fixed FPS animation will look stuttery if your engine is drawing the rest of the world at a faster framerate.&lt;/p&gt;
&lt;p&gt;Skeletal animation solves both these problems; you upload a single copy of the character model into memory, and your renderer uses a tiny list of bones to transform said model on the fly. Better still, you can make the animation buttery smooth by &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Tweening"&gt;interpolating between frames&lt;/a&gt;. The traditional cheapo way doing this was linear interpolation (storing each bone frame as two points in (x, y) space); nowdays 2D animators prefer polar interpolation (storing each bone frame as a rotation angle and a length). Here's a demo of how both styles look from the same low-FPS source material:&lt;/p&gt;
&lt;img src="/writing/2016/08/27/animation/tween_compare.gif" alt="Comparison of linear and polar interpolation" class="" title="I cut this animation down to 5fps and quarter-speed playback to make the differences more visible. Also it took +150 lines of code and two roundtrips to simulate the worse-looking linear option in my polar-based graphics engine. Well worth it for this snazzy animated GIF!"/&gt; &lt;p&gt;At first it doesn't look like much of a difference, but play close attention to the length of the character's legs around the knee joint as they swing over the bar. The linear tween looks weird and telescopy, because the lengths of the bones aren't preserved during the diagonal point-to-point movement. And as rotations/scaling are both fairly cheap and easy to do on today's graphics hardware, going for polar is a no-brainer.&lt;/p&gt;
&lt;!-- (The above footage is taken from my WIP C++ game engine with a homemade 2D vector scenegraph. I'll save all that technical junk for another devlog.) --&gt;
&lt;/div&gt;
&lt;div class="section" id="writing-your-own-tools"&gt;
&lt;h2&gt;Writing your own tools&lt;/h2&gt;
&lt;p&gt;If you haven't guessed from all that foreshadowing, my plan is to use rotoscoping, 2D vector graphics and skeletal animation. To my advantage I have the luxury of writing my engine for today's graphics hardware, and the technology to capture and edit HD video is super cheap.&lt;/p&gt;
&lt;p&gt;There was one missing piece: tooling. When I was starting out in 2012, what I really needed was an animation tool that could do the following:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Construct a 2D skeleton with named bones&lt;/li&gt;
&lt;li&gt;Use a feed of video frames as a background&lt;/li&gt;
&lt;li&gt;Allow binding vector assets to each bone and editing them frame-by-frame&lt;/li&gt;
&lt;li&gt;Provide some simple controls for adding metadata (e.g. set the relative scale, define a fixed point in space like a ledge, define the distance covered by a walk cycle)&lt;/li&gt;
&lt;li&gt;Output all of the above in some format I can use in my asset pipeline&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- - Allow a 2D vector asset to be bound to the bone, with automatic rotating and scaling transforms --&gt;
&lt;!-- - Allow the 2D vector asset to be editable while bound, so that the changes are stored in the asset's native orientation and scaling --&gt;
&lt;p&gt;You'll be shocked to learn that I couldn't find a tool that did these things! The closest was Flash, which was a fairly ugly choice to contemplate, or a 3D animation tool like Maya or Blender, which (after a few hours of YouTube tutorials encouraging me to unlearn all of my Inkscape know-how) I had zero interest in learning how to use.&lt;/p&gt;
&lt;p&gt;If I were starting out again I would probably consider &lt;a class="reference external" href="http://esotericsoftware.com"&gt;Spine&lt;/a&gt; for skeletal animation; they seem to have fixed nearly all of the grievences people had with Flash, and it supports a lot of game engines out of the box. No idea if it supports rotoscoping, but the workflow looks pretty solid. Also worth checking out is the rotoscoping paint tool &lt;a class="reference external" href="https://dunin.itch.io/ptop"&gt;Paint of Persia&lt;/a&gt;, if pixel art is your forte.&lt;/p&gt;
&lt;p&gt;In the end I wrote my own animation tool, &lt;strong&gt;jaiwhite&lt;/strong&gt;. It's spartan and has the above features and nothing else. It's not the best tool, but by writing it I learned a fair amount about animation formats and making the data useable in a game engine.&lt;/p&gt;
&lt;!-- Animation looks different. Spine seems to place a greater emphasis on only changing a single attribute at a time --&gt;
&lt;!-- The tool works in cartesian coordinates to describe the bone positions in each frame as they're nice and easy to push around in an editor. My asset pipeline compiles these raw animation files into relative polar coordinates, as needed by my homebaked graphics engine. --&gt;
&lt;!-- In retrospect I should have written it in Python, not C++, but I really wanted to learn Qt for some reason? --&gt;
&lt;p&gt;The first thing I ever animated with it was a clip from the film &lt;em&gt;Caddyshack&lt;/em&gt;: the iconic &amp;quot;Start a Party With a Radio in Your Golf Bag&amp;quot; scene. I rotoscoped it at the full 24fps of the source material, here's a playback capture at 48fps:&lt;/p&gt;
&lt;img src="/writing/2016/08/27/animation/norespect.gif" alt="Rodney Dangerfield starting a party the only way he knows how" class="" title="ALL NIGHT, ALLLL NIGHT, AWWWW EVERY NIGHT"/&gt; &lt;p&gt;That went quite well, if a little jittery, so I moved onto phase 2: filming parkour.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="le-parkour"&gt;
&lt;h2&gt;Le Parkour&lt;/h2&gt;
&lt;p&gt;I have been training as a &lt;a class="reference external" href="http://parkourpedia.com/about/what-is-parkour"&gt;traceur&lt;/a&gt; for 4 years. At some stage I intend to write a full piece about how parkour has changed my life for the better, and how much I love the philosophy behind it, and how the wider parkour community is one of the most wonderfully diverse, amazing, inclusive groups of humans you'll ever come across. For now I'll leave you with this: if you've ever thought about trying parkour, go for it. Find a local parkour association and see if they do classes. You absolutely do not have to be a tank to start out, but if you persist you will become a tank. And for the love of God &lt;em&gt;ignore everything on YouTube.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Where was I? Oh yeah. Filming. I used the following:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;1x computer (Toshiba US build-a-PC)&lt;/li&gt;
&lt;li&gt;1x Canon Vixia HF M500 NTSC camcorder + cheap SD card (Amazon US)&lt;/li&gt;
&lt;li&gt;1x Fancier FT-6222A tripod (mystery HK seller on eBay)&lt;/li&gt;
&lt;li&gt;1x Lufkin Autolock 8m tape measure (no idea, maybe Bunnings?)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The camera I have is not the best. It films in 720p30 and has lots of motion blur, but it's more than acceptable for low-rent mocap. If you have sunk all your money nursing an expensive photography habit, a DSLR with a rapid-fire capture feature makes nice crisp frames to animate with. I wouldn't recommend using a GoPro/other fixed-focus action camera, as the &lt;a class="reference external" href="https://www.youtube.com/watch?v=rbvxlvSIIww"&gt;fish eye lens&lt;/a&gt; would distort the footage too much.&lt;/p&gt;
&lt;p&gt;After a few practice runs I found a good rhythm for recording. I'd go to a place with an unobstructed view of an obstacle, set the camera up to frame the obstacle square-on with plenty of side margins to capture the movement's entrance and exit, mark out 1 metre on the ground with the tape measure, and then go ahead and perform the same action multiple times. After each take I would run past the camera and double-tap the record button to switch to a new file, then immediately go back for another shot.&lt;/p&gt;
&lt;p&gt;This worked out pretty well. When you drill a movement in parkour there's a sort of bell curve for smoothness and control; the first attempts are a little rusty as you size up the obstacle, but after a few tries muscle memory will outgrow hesitation and you can focus entirely on form. Until you fatigue, at which point form is the first thing to get thrown under the bus.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="editing"&gt;
&lt;h2&gt;Editing&lt;/h2&gt;
&lt;p&gt;Great! We have an SD card full of parkour footage. What comes next?&lt;/p&gt;
&lt;img src="/writing/2016/08/27/animation/somanyvideos.png" alt="File manager window full of parkour videos" class="" title="Ol&amp;#x27; Craney McBailsalot here needs work on sticking his box jumps."/&gt; &lt;p&gt;If your answer was &amp;quot;some light filing&amp;quot;, have a biscuit. Nothing fancy, just renaming all of the takes from today to have the name of the movement. Now out of the footage collected, pick a movement and go find the least silly-looking take. Eventually you'll realise that there aren't any (because you're an adult) and just guess which one'll animate the cleanest. I use &lt;a class="reference external" href="http://ffmpeg.org/download.html"&gt;ffmpeg&lt;/a&gt; to convert the video to a series of still images to use as keyframes.&lt;/p&gt;
&lt;p&gt;For this runthrough I've picked a &lt;a class="reference external" href="http://parkourpedia.com/technique/passement-dash-vault"&gt;dash vault&lt;/a&gt;. On a practical level these are pretty useless; a &lt;a class="reference external" href="http://parkourpedia.com/technique/passement-speed-vault"&gt;speed vault&lt;/a&gt; or &lt;a class="reference external" href="http://parkourpedia.com/technique/saut-de-chat-cat-passkongpanthermonkeygorillafairy-floss"&gt;cat pass&lt;/a&gt; will do just as well for clearing an obstacle, with a much lower risk of catching both your feet on the edge and faceplanting. But they do wonders for your self-esteem and I'm about as shallow as they come! On a more technical level this is a good animation to run as a test because all of the limb movements happen &lt;em&gt;exactly&lt;/em&gt; (if you squint) along the viewing plane, meaning a single copy of the limbs drawn in at a side-facing angle should be enough for the whole animation. Other more complicated movements (e.g. &lt;a class="reference external" href="http://parkourpedia.com/technique/passement-lazy-vault-theif-vault"&gt;lazy vaults&lt;/a&gt;, &lt;a class="reference external" href="http://parkourpedia.com/technique/rolling"&gt;rolls&lt;/a&gt;) will have limbs overtly pointing towards/away from the screen, which might be better handled with multiple angles.&lt;/p&gt;
&lt;img src="/writing/2016/08/27/animation/jw-skel.png" alt="JaiWhite editing session mid animation" class="" title="Not shown due to motion blur; the weird puffer fish expression I sometimes adopt mid-vault."/&gt; &lt;p&gt;Let's run through the rotoscoping process. I start out by marking some useful points on the ground: the length of the 1 metre tape so that each animation has the same scale, and the start and finish point of the animation to create a walk cycle. There are other markers for e.g. ledges, to indicate a climbing animation around a fixed point, but we don't need them. Next, the bones are drawn in. My model has 19 bones; 4 for each limb, 2 for the back, one for the head. The bones are stored as a flat tree structure, with each keyframe storing relative polar coordinates plus depth for all the bones. I set the base of the neck as the &amp;quot;anchor&amp;quot; point from which all of the bones are defined, and ground level between the feet as the (x, y) origin of the model to match the physics engine.&lt;/p&gt;
&lt;p&gt;There's a common naming scheme for the bones (e.g. head, forearm-l) so that multiple animations can use the same skeleton tree. We can make a reverse copy of a directional animation for free by replacing &amp;quot;-l&amp;quot; with &amp;quot;-r&amp;quot; and inverting all the x coordinates. If the animation needs to span two depth planes (e.g. that lazy vault animation I showed earlier), there's a hack where you can add a non-functional &amp;quot;plane&amp;quot; bone to represent the barrier.&lt;/p&gt;
&lt;p&gt;After that, you pose the skeleton to match each frame of footage. Unlike Spine, &lt;strong&gt;jaiwhite&lt;/strong&gt; has zero safeguards. You drag the nodes around until the pose looks okay. Plus there's no undo! I'm guessing this is very unhelpful for consistency if you're animating out of thin air? But we aren't, so... eat that, nerds! For my 30fps source video, I found that sampling at 10fps kept a lot of detail while smoothing out the jitter.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="rigging"&gt;
&lt;h2&gt;Rigging&lt;/h2&gt;
&lt;p&gt;And now the hardest part by far: drawing a vector representation of the body in Inkscape, and mapping it to the skeleton. By &amp;quot;drawing&amp;quot; I of course mean &amp;quot;noob tracing&amp;quot;. All of the above was based on stuff I did over the previous couple of years. This? This is brand new. I have put it off for ages.&lt;/p&gt;
&lt;p&gt;It's important to expand on &lt;em&gt;why&lt;/em&gt; have I put it off for ages. I kept thinking about where I wanted to be (quickly-prototypable easy 2D rigging for my skeletons that I can churn out) in terms of what I already had (an animation format without the ability to store hints for the vector graphics format, an animation tool with semi-working support for drawing vector graphics, some dinky Python scripts), and kept getting hung up on how it &lt;strong&gt;was&lt;/strong&gt; technically possible to move forward without changing anything! Just very, very unreasonably hard. And hey, it's not like there's a time limit to any of this...&lt;/p&gt;
&lt;p&gt;That loud screeching sound was the whole project grinding to a dead stop. To get things back on track, I wrote down all of the roadblocks and a workaround strategy for each. Here's that list in full:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;dl class="first docutils"&gt;
&lt;dt&gt;No animation file format support for binding graphics to bones&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last"&gt;
&lt;li&gt;Add another int32 index to each bone movement frame, denoting which graphics frame to use for each limb. Even if we start out with only one perspective (i.e. set the index to 0 for all frames), there's enough wiggle room to scale up later.&lt;/li&gt;
&lt;li&gt;Once a standard set of perspectives was picked, all a skin would have to do is make frames for each orientation.&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
&lt;li&gt;&lt;dl class="first docutils"&gt;
&lt;dt&gt;jaiwhite support for editing graphics on the skeleton is fiddly and weak&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last"&gt;
&lt;li&gt;Shelve jaiwhite graphics editing for now; we don't want it to be easy to add a lot of extra graphics frames, otherwise making new character skins will be a nightmare&lt;/li&gt;
&lt;li&gt;Instead, pick a number of reference poses (e.g. front facing, profile, 45deg) and create graphics for these in Inkscape, because it's a good tool and you're good at that&lt;/li&gt;
&lt;li&gt;To whit, figure out a way of storing the reference poses bone information + graphics information in a SVG file, make a template pose in Inkscape, then bodge up another Python script to export this to the engine graphics format&lt;/li&gt;
&lt;li&gt;Don't worry about getting multiple poses to tesselate with the same mesh, I'm sure Future Me won't mind cleaning up that particular mess&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
&lt;li&gt;&lt;dl class="first docutils"&gt;
&lt;dt&gt;No game engine support for binding graphics to bones&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last"&gt;
&lt;li&gt;Add engine stubs to load a skin, bind a graphics file containing all limbs (i.e. a complete skin) to an animation object, and render the correct graphics frame based on the bone frame&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
&lt;li&gt;&lt;dl class="first docutils"&gt;
&lt;dt&gt;No vector graphics for player character yet&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last"&gt;
&lt;li&gt;Well trace something, you munch!&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Extending the animation file format was by far the easiest part; all my asset formats were done in &lt;a class="reference external" href="https://github.com/google/protobuf"&gt;Protocol Buffers&lt;/a&gt;, where you can write the schema once and get lovely type-checked C++/Python/whatever bindings for free. Adding an extra int32 for the graphics frame took one additional line to the .proto file.&lt;/p&gt;
&lt;p&gt;The rest I bodged together with scripts. I wrote one to dump a single frame from my animation format into a SVG template, with one layer per bone and the bones drawn in as arrowed lines, and another to convert it back to my graphics format. Once that was set up I traced over each limb with low-res polygons. I stress that this is pure &lt;em&gt;drafting&lt;/em&gt;; choosing a handful of points along the curves and connecting the dots. If I did not have the photo as a base I would be reeeeally struggling. If you were feeling lazy you could make a single arm and leg then copy them across, but as all my bone lengths were non-fixed I opted to draw them in twice.&lt;/p&gt;
&lt;img src="/writing/2016/08/27/animation/littlewoodenboy.png" alt="Editing the character skin in Inkscape" class="" title="&amp;quot;What&amp;#x27;s this? It&amp;#x27;s... it&amp;#x27;s a little boy&amp;#x27;s face! Oh don&amp;#x27;t worry, little wooden boy! I&amp;#x27;ll free you from this block of wood!&amp;quot;"/&gt; &lt;p&gt;There we have it, a perfect likeness! Now we just go through the motions of binding those shapes to the bones we did earlier, load it all into the engine, and we should have a finished product that's ready to ship!&lt;/p&gt;
&lt;img src="/writing/2016/08/27/animation/shipit.gif" alt="Wrongly proportioned animation plays back at weird speed" class="" title="People new to gamedev might see this as some kind of hilarious fail picture? No. This is a goddamn TRIUMPH. With low level graphics you can go hours, days, sometimes even *weeks* of debugging without seeing anything at all. If the character is on screen and moving with more than 80% of the limbs in the right spot, crack open the sparkling wine because you&amp;#x27;ve NAILED IT"/&gt; &lt;p&gt;Well, maybe if you &lt;a class="reference external" href="http://www.eurogamer.net/articles/2015-10-02-tony-hawks-pro-skater-5-review"&gt;work for Activision!&lt;/a&gt; Hi and welcome to game development.&lt;/p&gt;
&lt;p&gt;The thickness was a bit of a surprise. I did a thorough recheck of the source material to make sure I hadn't merely eaten too many pies that day, but it turned out I forgot to add the metre-autoscaling feature to the skin compiler. As in, the parallel axis of each bone was saved okay, but the perpendicular axis was saved in whatever weirdo non-metric moon units the SVG was drawn in, not the engine's 1 metre:100px.&lt;/p&gt;
&lt;p&gt;After fixing up the scaling, colour palette, playback speed, missing arm, deformed limbs, joint seam ordering and edge lighting, it looked more like this!&lt;/p&gt;
&lt;img src="/writing/2016/08/27/animation/dash.gif" alt="Finished dash vault animation" class="" title="Original plan was to have the camera follow the movement for this GIF, but the traffic barrier moved at the wrong speed because it&amp;#x27;s closer to the camera and I was too sleep deprived to make it parallax nicely. So sad."/&gt; &lt;/div&gt;
&lt;div class="section" id="conclusions"&gt;
&lt;h2&gt;Conclusions&lt;/h2&gt;
&lt;!-- There's still a bit of polish left in this animation before it reaches a Chahi-like level of quality (e.g. buffing out the joint seams), but really I just wanted to show that it was possible. --&gt;
&lt;p&gt;I think this is an okay result, coming from a programmer with coarse drawing ability and zero prior experience of animating things. Rotoscoping is a powerful technique that hasn't aged a bit, and is incredibly cheap to produce compared to proper motion capture. I am at a loss as to why it has dropped in popularity, maybe it's the aforementioned lack of good tools?&lt;/p&gt;
&lt;p&gt;Okay, so perhaps this &lt;em&gt;exact&lt;/em&gt; method for producing animation is hard to recommend. You do waste a lot of time dicking about with tooling and engine support; time that could be spent rotoscoping statically! In essence, for a tool-and-engine driven approach you need to be sure that that [estimated time spent dicking about] &amp;lt; [estimated time agonizing over animating everything by hand].&lt;/p&gt;
&lt;p&gt;Also you absolutely don't need to learn parkour for the filming part! &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt; Rotoscoping is useful for capturing any physical object or realistic human movement you care to name.&lt;/p&gt;
&lt;p&gt;Anyway, the actual conclusion I wanted to bring up was that you should always try and take advantage of whatever tools and skillsets you have to hand. After all, part of the fun in amateur gamedev is to try a bit of everything yourself, even the stuff you suck at.&lt;/p&gt;
&lt;!-- I knew I was better at programming than at producing art assets, and that (in my case) it would be sorta efficent to make an Inkscape-based asset pipeline with a bunch of scripts to get what I wanted. There is a strong element of sunk cost fallacy to this thinking though; I wrote my own graphics renderer (to learn OpenGL and to experiment with mobile/WebGL), something 90% of today's gamedevs don't need to bother with. There's so many engine middleware options available for building a cross-platform game! In the long run no-one cares about what you write your game with, so pick whatever technology you work the fastest with. --&gt;
&lt;!-- If you have a monolithic, impossible-looking problem, lop enough little bits off to turn it into a wagonful of manageable problems. --&gt;
&lt;!-- [qwop photo] --&gt;
&lt;!-- It's interesting. Comparing back to Spine, I see the basic workflow encourages rotation only; that is, to keep the bones at constant length between frames. On the surface this sounds like a good idea, but this is why so many skeletal 2D games exhibit the weird paper-doll effect; all the parts of the character are effectively locked side-on to the camera. Perhaps good enough for running and jumping, but you can't do movements which deviate from this perfectly side-on view, such as turning the character to face the other way. --&gt;
&lt;p&gt;&lt;a class="reference external" href="https://forums.tigsource.com/index.php?topic=57570.msg1276185#msg1276185"&gt;(TIGSource thread for this post)&lt;/a&gt;&lt;/p&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Seriously you should though it's super fun and useful&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;!-- Okay, I *begrudgingly* accept that it's not practical to spend 4 years drilling vaults and precisions, in order to save a few cents hiring someone to draw amazing parkour moves from their imagination. --&gt;
&lt;!-- Right, so you have an SD card full of parkour footage. Step 1 is to scrub through each file and label it. --&gt;
&lt;!-- .. raw:: html --&gt;
&lt;!-- &lt;img src="/writing/2016/08/27/animation/somanyvideos.png" title="Wha"/&gt; --&gt;
&lt;!-- Step 2: pick a movement out of the footage collected, then find the least silly-looking take. Eventually you'll realise that there aren't any (because you're an adult) and just guess which one'll animate the cleanest. --&gt;
&lt;!-- Step 3: copy the video into a new folder, then dump the frames of the video to image files. `ffmpeg &lt;http://ffmpeg.org/download.html&gt;`_ is your friend; here I dumped the frames I cared about (from 0m8s to 0m11s) as PNGs. --&gt;
&lt;!-- .. code-block:: bash --&gt;
&lt;!-- $ ffmpeg -i lazy11.MP4 -f image2 -ss 00:08 -t 00:03 lazy-%03d.png --&gt;
&lt;!-- Use an image viewer (ideally one which lets you flick through files with the arrow keys) to find good start and finish frames. I decided the start stance for all of my animation stock would be with the character's legs and feet directly underneath (i.e. not outstretched), to make blending easier. --&gt;
&lt;!-- Step 4: go ahead and throw out all the frames you don't need. This would be all the ones outside of the start-finish range, plus thinning out the frames that are left so that interpolation looks more natural. After a bit of experimenting I found that for most movements I could get away with rotoscoping at 10fps; this sounds really low, but assuming you don't have any small fiddly movements it works a treat, thanks to all that scaling and rotating. --&gt;
&lt;!-- As an example, here's one of the bones that I use in my renderer. --&gt;
&lt;!-- .. code-block:: python --&gt;
&lt;!-- [[m_xx, m_xy],
[m_yx, m_yy],
[x_off, y_off],
[z_off, a]] --&gt;
&lt;!-- 8 floats. The first four are a `2D transformation matrix &lt;https://en.wikipedia.org/wiki/Transformation_matrix#Examples_in_2D_computer_graphics&gt;`_; this defines the rotation and scaling. (**x_off**, **y_off**) is the position offset; basically, how far away from (0, 0) in model space to draw the object after the transformation matrix has been applied. **z_off** is the depth, so the renderer knows what front-to-back order the 2D surfaces should appear in. **a** is the alpha component of the animation; this is a value between 0 and 1 which describes how far we are between the current keyframe and the next keyframe. --&gt;
&lt;/div&gt;
</content><category term="Devlog"/><category term="gamedev"/><category term="parkour"/><category term="animation"/><category term="rotoscoping"/><category term="drawing"/><category term="inkscape"/><category term="gifs"/><category term="screenshot saturday"/><category term="infinite chessboard of doom"/></entry><entry><title>Dead KORG microKEY, Part 2: Noobface McHamfists Desolders His First SMD</title><link href="/writing/2016/06/17/korg_smd_repair/" rel="alternate"/><published>2016-06-17T00:00:00+08:00</published><updated>2016-06-17T00:00:00+08:00</updated><author><name>Scott</name></author><id>tag:None,2016-06-17:/writing/2016/06/17/korg_smd_repair/</id><summary type="html">&lt;img src="/writing/2016/06/17/korg_smd_repair/header.jpg" alt="J-Link and microKEY plugged into breadboard" class="letterbox image-process-1200" title="Ulp"/&gt; &lt;p&gt;Oh God. Oh God. I've been putting this one off, but the stuff has arrived. I can't back down now!&lt;/p&gt;
&lt;p&gt;A brief résumé of what happened in &lt;a class="reference external" href="/writing/2015/11/08/jlink_korg_microkey/"&gt;Part 1&lt;/a&gt;: this great little KORG microKEY MIDI controller stopped powering up for reasons unknown. By wiring it up to a J-Link debug probe, we found that the CPU was still alive and kicking, yet somehow the computer was receiving garbled USB messages. As this keyboard has a built-in USB hub, the stab-in-the-dark diagnosis was that the chip responsible (a Genesys GL850G) had gone bad and needed replacing. Unfortunately, the chip is a 28-pin 10mm*8mm surface mounted design, and our bumbling hero has never before soldered an SMD component, much less reworked a board!&lt;/p&gt;
&lt;p&gt;To properly capture the fear of someone doing surface-mount rework for the first time, everything in this piece was written minutes after happening.&lt;/p&gt;
</summary><content type="html">&lt;img src="/writing/2016/06/17/korg_smd_repair/header.jpg" alt="J-Link and microKEY plugged into breadboard" class="letterbox image-process-1200" title="Ulp"/&gt; &lt;p&gt;Oh God. Oh God. I've been putting this one off, but the stuff has arrived. I can't back down now!&lt;/p&gt;
&lt;p&gt;A brief résumé of what happened in &lt;a class="reference external" href="/writing/2015/11/08/jlink_korg_microkey/"&gt;Part 1&lt;/a&gt;: this great little KORG microKEY MIDI controller stopped powering up for reasons unknown. By wiring it up to a J-Link debug probe, we found that the CPU was still alive and kicking, yet somehow the computer was receiving garbled USB messages. As this keyboard has a built-in USB hub, the stab-in-the-dark diagnosis was that the chip responsible (a Genesys GL850G) had gone bad and needed replacing. Unfortunately, the chip is a 28-pin 10mm*8mm surface mounted design, and our bumbling hero has never before soldered an SMD component, much less reworked a board!&lt;/p&gt;
&lt;p&gt;To properly capture the fear of someone doing surface-mount rework for the first time, everything in this piece was written minutes after happening.&lt;/p&gt;

&lt;img src="/writing/2016/06/17/korg_smd_repair/tools.jpg" alt="Pile of electronics tools laid out" class="image-process-1200" title="For this photo I forgot to include the multimeter, but we&amp;#x27;ve managed to George Lucas one in. I&amp;#x27;ve since lost those tweezers :("/&gt; &lt;p&gt;Tools used:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Brymen BM867s multimeter; possibly overkill for the job? Should only need the voltmeter and continuity tester.&lt;/li&gt;
&lt;li&gt;Tube of 5 replacement GL850Gs from eBay, snipped off the end of someone's pick-and-place reel.&lt;/li&gt;
&lt;li&gt;Pack of ChipQuik SMD Removal Alloy. This stuff has a super low melting point (85°C), meaning you can heat it up once with your iron and it will blob around like soft-serve icecream. Just the thing for pulling miniature chips off a board crowded with things that can be ruined.&lt;/li&gt;
&lt;li&gt;Solder, 0.5mm and 40% lead. Mmmmm, that's good breathing!&lt;/li&gt;
&lt;li&gt;Wick. I decided to go all high-falutin' and buy a new reel which has flux in the braid.&lt;/li&gt;
&lt;li&gt;Stainless steel chip pincers. I hope these things don't get welded to my hand.&lt;/li&gt;
&lt;li&gt;Flux pen. The miracle ingredient that makes solder do impossible things, like stick uniquely to pads that are less than 0.4mm apart. Uh.&lt;/li&gt;
&lt;li&gt;Electronics-grade isopropyl alcohol. I've used up about a third of this bottle from zapping parkour wounds.&lt;/li&gt;
&lt;li&gt;Flux cleaner. I just read the warning label on the side and I am terrified the exposure will make me vomit up a kidney or grow a second anus.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr class="docutils" /&gt;
&lt;img src="/writing/2016/06/17/korg_smd_repair/chipquik.jpg" alt="Tweezers, a replacement chip and a blob of ChipQuik" class="letterbox image-process-1200" title="How did I lose them. They were seriously great at what they did. All my other weirdo tools survived the house move."/&gt; &lt;p&gt;Let's begin by snapping the ChipQuik reel in a futile attempt to unwind it. Surprisingly the solid form is really brittle, but all we care about is that it remains super molten after you've heated it. Hell, I had enough time to take 4 crappy photos of the blob with my lousy camera and it was still pliable with tweezers. Welp, nothing for it but to &lt;em&gt;caaaarefully&lt;/em&gt; daub it all over the chip legs!&lt;/p&gt;
&lt;img src="/writing/2016/06/17/korg_smd_repair/chipquik_ruined.jpg" alt="Board with chip legs covered in ChipQuik" class="letterbox image-process-1200" title="I&amp;#x27;M SO SORRY"/&gt; &lt;p&gt;oh god oh god oh god oh god what am i doing&lt;/p&gt;
&lt;p&gt;My hands are shaking like crazy; I'm cooking the ChipQuik with my wrong hand and tugging at the chip with my tweezers in the other, but it's not moving! Still, gotta be patient, after all the solder melts at 80 and my iron is 340...&lt;/p&gt;
&lt;img src="/writing/2016/06/17/korg_smd_repair/chipquik_success.jpg" alt="Tweezers grasping removed chip" class="letterbox image-process-1200" title="IN YOUR FACE, SOLDERING TALENT"/&gt; &lt;p&gt;YES! I DID IT! THE CHIPQUIK FREAKING WORKS! And by some miracle, the blobbing is confined to the chip pads and not the rest of the PCB!&lt;/p&gt;
&lt;img src="/writing/2016/06/17/korg_smd_repair/chipquik_janitor.jpg" alt="Removing ChipQuik with a cottonbud" class="letterbox image-process-1200" title="If I intended to do this regularly I would definitely try to recycle the ChipQuik; what little you see there is worth an eye-watering $4!"/&gt; &lt;p&gt;Oh. It would appear that ChipQuik's amazing surface tension and blobbing ability means that it's damn near impossible to absorb with solder braid. The best you can do is smoosh it down. Reading the instructions, what you're supposed to do is flux up a cotton bud and wipe it off while applying heat. After using my pen to create a little puddle of flux on the workbench, I get to work.&lt;/p&gt;
&lt;p&gt;The cotton swab is still smooshing, but at least you can scoop it in one go. It dawns on me that it's probably a good idea to swipe away from all the exciting SMD parts. Also you need to be frigging ambidextrous in this game; with the red hot piece of metal in one hand, and the fiddly cotton bud in the other...&lt;/p&gt;
&lt;img src="/writing/2016/06/17/korg_smd_repair/chipquik_sparkly.jpg" alt="Board with cleaned pads where chip used to be" class="letterbox image-process-1200" title=""/&gt; &lt;p&gt;Right, an hour has passed. I have switched to pumping flux from the pen straight onto the cotton buds, and employing a lot more rubbing force to scrape the last of the ChipQuik off. This, of course, makes a lot of angel-hair bridges. Now that I've finished polishing the board with isopropyl I can still see two left. Booooo.&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;p&gt;Next day! Right, let's put the new chip on there. Start off by blobbing some solder on one of the legs to make a tack point.&lt;/p&gt;
&lt;img src="/writing/2016/06/17/korg_smd_repair/tack_fail.jpg" alt="Board with misaligned new chip" class="letterbox image-process-1200" title="It only occured to me months later that there was a bright yellow Miami Bakehouse loyalty card hogging all these shots. My problem is I&amp;#x27;m not smart enough to plan the product placement in advance and lose all negotiating power."/&gt; &lt;p&gt;DAMN YOU TO HELL, SURFACE MOUNT TECHNOLOGY&lt;/p&gt;
&lt;p&gt;Okay. It's on the right spot. There's zero room for error as the pads are slightly fatter than the chip. How the shit are you supposed to tack down one corner of the chip without touching it!?&lt;/p&gt;
&lt;img src="/writing/2016/06/17/korg_smd_repair/tack.jpg" alt="Board with new chip tacked down in one corner" class="letterbox image-process-1200" title="That was a tasty breakfast though. Tasty enough to immortalise the experience by mentioning it here."/&gt; &lt;p&gt;The answer to this question is apparently &amp;quot;try a whole pile of times with the chip cupped with tweezers, instead of pre-placed&amp;quot;. Brillo, time to flux up the pin opposite and get this shit tacked!&lt;/p&gt;
&lt;img src="/writing/2016/06/17/korg_smd_repair/bridge.jpg" alt="Board with new chip tacked by a solder bridge" class="letterbox image-process-1200" title="WON THGIR SLLOR NOMANNIC DEKAB YLHSERF EMOS YUB"/&gt; &lt;p&gt;Sigh. There's a bridge already.&lt;/p&gt;
&lt;p&gt;Right; because the definition of madness is to keep trying what isn't working, I'm swapping the iron tip. All the source material on YouTube shows a large chisel tip; originally I used the (pack-in) tiny chisel tip because I doubted I'd be any better at microsurgery with a shovel. This apparently was a mistake; the larger tip has a much bigger thermal mass and is supposedly easier to rake over the chip legs.&lt;/p&gt;
&lt;p&gt;Sigh. There's 5 bridges already. (I didn't take a photo, but imagine a scene similar to the mess made with the ChipQuik).&lt;/p&gt;
&lt;p&gt;I give up. Every attempt to use the sucker or the desolder braid mushes the solder deeper under the chip legs, making the bridges even harder to remove. Okay. Time out. We'll return to this soon.&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;p&gt;Since I wrote that last bit, 6 months have passed. Many things have changed: I'm in a brand new apartment, the sun now goes down at 5pm, I can do almost proper climb-ups if the wall is grippy and there's a strong tail wind, and I've gotten some practice in by soldering up a &lt;a class="reference external" href="https://www.youtube.com/watch?v=_S8xRiOqXtc"&gt;fuzz factory pedal&lt;/a&gt; with the whacking great chisel tip.&lt;/p&gt;
&lt;!-- I've also learned the secrets of solder braid. You'll often hear that the secret to melting old, hardened solder joints is to add a bit more solder; the thinking behind this is that the flux in the fresh solder will clean away all the oxidised shit on the surface and let the existing stuff melt. Of course, if the joint is already huge, adding more solder brings the risk of spilling over to the next contact. --&gt;
&lt;!-- The chisel tip makes this very easy. Tin the tip with a generous quantity of solder, then scrape it off in brass wool; I find that the brass wool leaves the tip "damp" with solder, as opposed to cleaning with a sponge which more thouroughly removes the drops. Next, place a clean length of wick against the blob in a way which maximises surface area and minimises heat sinks (e.g. for a bridge, lay it out parallel to the chip legs so just the bridge is touching). Crank your iron to 320deg. Apply business end lengthways to braid, wait for the solder to start flowing up the braid. The teeny tiny amount of solder on your iron is enough to wet the braid and jumpstart the real melting process underneath, which will get sucked up too. --&gt;
&lt;img src="/writing/2016/06/17/korg_smd_repair/braid.jpg" alt="Removing bridges with solder braid" class="letterbox image-process-1200" title="Why it works when the iron and braid are upright, but not towards the board like every video on YouTube? Leprechauns. There were an insufficient number of flux leprechauns in this piece of braid. There is your answer."/&gt; &lt;p&gt;I can hardly believe it. It took about 50 goes but I found a way of using solder braid on the SMD chip legs.&lt;/p&gt;
&lt;p&gt;Drop some flux on the bridge area with your flux pen. (This encourages all the solder in the nooks and crannies to flow out). Crank your iron to 320-350degC, clean your tip well on a sponge. If the solder is all oxidised and tough, preheat the bridge area with the chisel vertical (maximum surface area over a minimum no. of pins) until the solder is molten. As soon as it starts becoming liquid, apply the end of the braid vertically (as shown) and wait for 5-10 seconds. Raising two middle fingers to logic, the solder will rise up through the braid like a sticky silver phoenix. Once the braid is soaked, cut off the end and continue sucking with a fresh piece.&lt;/p&gt;
&lt;p&gt;Afterwards I went and checked every adjacent pair of chip legs with my multimeter's continuity tester, then repeated cleaning and wicking until all the shorts had gone. It took 3 passes to clean it all up; for one really stubborn pair I had to melt a tiny bit more solder on before the braid would wick all of it.&lt;/p&gt;
&lt;p&gt;That was definitely near the worst case scenario, and I've proven I can recover from it. Time to solder down the other side!&lt;/p&gt;
&lt;img src="/writing/2016/06/17/korg_smd_repair/soldered.jpg" alt="Far side of the new chip after soldering" class="letterbox image-process-1200" title="If a YouTuber does something that doesn&amp;#x27;t seem right, like cook up a full centimetre of solder on their iron before raking it, maybe think it through a little first before blindly repeating the exercise."/&gt; &lt;p&gt;Done, that went a &lt;strong&gt;lot&lt;/strong&gt; better. See the thing they don't tell you in SMD soldering guides is when raking with your leviathan chisel tip, you should put a &lt;em&gt;miserly&lt;/em&gt; amount of solder on the flat part. An amount that would leave a church mouse angry and resentful because of the false hope. An amount so small even Scrooge McDuck wouldn't think twice about giving it away unconditionally to shut you up. The literal smallest dab possible.&lt;/p&gt;
&lt;p&gt;This plus the flux will send it where it needs to go.&lt;/p&gt;
&lt;p&gt;There's one pair of pins which was bugging me; the pair from the 5V-to-3.3V regulator (5th and 6th from the right, with the meaty traces underneath). No matter how much I wicked it, the continuity tester insisted there was a 1 ohm connection from Vout to the +5V power rail. The datasheet added to the confusion by insisting all of the supply voltage points on the chip were 3.3V, yet I know for a fact the board ran them all on 5V. I'm guessing that's okay then.&lt;/p&gt;
&lt;p&gt;This is all fine and dandy, but two big questions remain unanswered.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;How sturdy is this board against trained apes like me reworking it?&lt;/li&gt;
&lt;li&gt;Did I wreck the replacement chip by heating the crap out of it with my iron?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Whatever there is a time for checks and a time for action and THIS IS A TIME FOR TEST RIG 2: THE SECRET OF THE OOZE&lt;/p&gt;
&lt;img src="/writing/2016/06/17/korg_smd_repair/testrigmk2.jpg" alt="J-Link and microKEY plugged into laptop via a breadboard" class="image-process-1200" title="Not pictured: hours of hemming and hawing about whether to plug it in or continuity-test just one more time."/&gt; &lt;p&gt;PLUGGED IN! No initial response from the J-Link (probably I attached the probes wrong), but no magic smoke from the board &lt;em&gt;or&lt;/em&gt; my PC! Wait does this mean... could... could it be?!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;scott@dangerfield:~/Tools/JLink_Linux_V502_x86_64$&lt;span class="w"&gt; &lt;/span&gt;dmesg&lt;span class="w"&gt; &lt;/span&gt;-T
...
&lt;span class="o"&gt;[&lt;/span&gt;Tue&lt;span class="w"&gt; &lt;/span&gt;Jun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;:02:10&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2016&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;full-speed&lt;span class="w"&gt; &lt;/span&gt;USB&lt;span class="w"&gt; &lt;/span&gt;device&lt;span class="w"&gt; &lt;/span&gt;number&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;xhci_hcd
&lt;span class="o"&gt;[&lt;/span&gt;Tue&lt;span class="w"&gt; &lt;/span&gt;Jun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;:19:33&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2016&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-3:&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;high-speed&lt;span class="w"&gt; &lt;/span&gt;USB&lt;span class="w"&gt; &lt;/span&gt;device&lt;span class="w"&gt; &lt;/span&gt;number&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;xhci_hcd
&lt;span class="o"&gt;[&lt;/span&gt;Tue&lt;span class="w"&gt; &lt;/span&gt;Jun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;:19:34&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2016&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;hub&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-3:1.0:&lt;span class="w"&gt; &lt;/span&gt;USB&lt;span class="w"&gt; &lt;/span&gt;hub&lt;span class="w"&gt; &lt;/span&gt;found
&lt;span class="o"&gt;[&lt;/span&gt;Tue&lt;span class="w"&gt; &lt;/span&gt;Jun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;:19:34&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2016&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;hub&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-3:1.0:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ports&lt;span class="w"&gt; &lt;/span&gt;detected
&lt;span class="o"&gt;[&lt;/span&gt;Tue&lt;span class="w"&gt; &lt;/span&gt;Jun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;:19:34&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2016&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-3.4:&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;full-speed&lt;span class="w"&gt; &lt;/span&gt;USB&lt;span class="w"&gt; &lt;/span&gt;device&lt;span class="w"&gt; &lt;/span&gt;number&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;xhci_hcd
&lt;span class="o"&gt;[&lt;/span&gt;Tue&lt;span class="w"&gt; &lt;/span&gt;Jun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;:19:34&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2016&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usbcore:&lt;span class="w"&gt; &lt;/span&gt;registered&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;interface&lt;span class="w"&gt; &lt;/span&gt;driver&lt;span class="w"&gt; &lt;/span&gt;snd-usb-audio
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I&lt;/p&gt;
&lt;p&gt;I DID IT&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;scott@dangerfield:~/Tools/JLink_Linux_V502_x86_64$&lt;span class="w"&gt; &lt;/span&gt;lsusb
...
Bus&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;003&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Device&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;004&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;ID&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0944&lt;/span&gt;:0111&lt;span class="w"&gt; &lt;/span&gt;KORG,&lt;span class="w"&gt; &lt;/span&gt;Inc.
Bus&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;003&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Device&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;003&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;ID&lt;span class="w"&gt; &lt;/span&gt;05e3:0608&lt;span class="w"&gt; &lt;/span&gt;Genesys&lt;span class="w"&gt; &lt;/span&gt;Logic,&lt;span class="w"&gt; &lt;/span&gt;Inc.&lt;span class="w"&gt; &lt;/span&gt;Hub
Bus&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;003&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Device&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;002&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;ID&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1366&lt;/span&gt;:0101&lt;span class="w"&gt; &lt;/span&gt;SEGGER&lt;span class="w"&gt; &lt;/span&gt;J-Link&lt;span class="w"&gt; &lt;/span&gt;PLUS
Bus&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;003&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Device&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;001&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;ID&lt;span class="w"&gt; &lt;/span&gt;1d6b:0002&lt;span class="w"&gt; &lt;/span&gt;Linux&lt;span class="w"&gt; &lt;/span&gt;Foundation&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.0&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;hub
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;I FIXED THE KEYBOARD&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;scott@dangerfield:~/Tools/JLink_Linux_V502_x86_64$&lt;span class="w"&gt; &lt;/span&gt;./JLinkExe
SEGGER&lt;span class="w"&gt; &lt;/span&gt;J-Link&lt;span class="w"&gt; &lt;/span&gt;Commander&lt;span class="w"&gt; &lt;/span&gt;V5.02&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;?&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;help&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
Compiled&lt;span class="w"&gt; &lt;/span&gt;Aug&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;28&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;:48:33
DLL&lt;span class="w"&gt; &lt;/span&gt;version&lt;span class="w"&gt; &lt;/span&gt;V5.02,&lt;span class="w"&gt; &lt;/span&gt;compiled&lt;span class="w"&gt; &lt;/span&gt;Aug&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;28&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;:48:30
Firmware:&lt;span class="w"&gt; &lt;/span&gt;J-Link&lt;span class="w"&gt; &lt;/span&gt;V9&lt;span class="w"&gt; &lt;/span&gt;compiled&lt;span class="w"&gt; &lt;/span&gt;Aug&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;28&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;:49:48
Hardware:&lt;span class="w"&gt; &lt;/span&gt;V9.30
S/N:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;redacted&lt;span class="o"&gt;]&lt;/span&gt;
OEM:&lt;span class="w"&gt; &lt;/span&gt;SEGGER-EDU
Feature&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;FlashBP,&lt;span class="w"&gt; &lt;/span&gt;GDB
Emulator&lt;span class="w"&gt; &lt;/span&gt;has&lt;span class="w"&gt; &lt;/span&gt;Trace&lt;span class="w"&gt; &lt;/span&gt;capability
&lt;span class="nv"&gt;VTarget&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;.022V
Info:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;TotalIRLen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;?,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;IRPrint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;0x..FFFFFFFFFFFFFFFFFFFFFFF1
Info:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;TotalIRLen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;?,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;IRPrint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;0x..FFFFFFFFFFFFFFFFFFFFFFF1
No&lt;span class="w"&gt; &lt;/span&gt;devices&lt;span class="w"&gt; &lt;/span&gt;found&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;JTAG&lt;span class="w"&gt; &lt;/span&gt;chain.&lt;span class="w"&gt; &lt;/span&gt;Trying&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;device&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;SWD.
Info:&lt;span class="w"&gt; &lt;/span&gt;Found&lt;span class="w"&gt; &lt;/span&gt;SWD-DP&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;ID&lt;span class="w"&gt; &lt;/span&gt;0x2BA01477
Info:&lt;span class="w"&gt; &lt;/span&gt;Found&lt;span class="w"&gt; &lt;/span&gt;Cortex-M3&lt;span class="w"&gt; &lt;/span&gt;r2p1,&lt;span class="w"&gt; &lt;/span&gt;Little&lt;span class="w"&gt; &lt;/span&gt;endian.
Info:&lt;span class="w"&gt; &lt;/span&gt;FPUnit:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;code&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;BP&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;slots&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;literal&lt;span class="w"&gt; &lt;/span&gt;slots
Info:&lt;span class="w"&gt; &lt;/span&gt;CoreSight&lt;span class="w"&gt; &lt;/span&gt;components:
Info:&lt;span class="w"&gt; &lt;/span&gt;ROMTbl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;@&lt;span class="w"&gt; &lt;/span&gt;E00FF000
Info:&lt;span class="w"&gt; &lt;/span&gt;ROMTbl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;FFF0F000,&lt;span class="w"&gt; &lt;/span&gt;CID:&lt;span class="w"&gt; &lt;/span&gt;B105E00D,&lt;span class="w"&gt; &lt;/span&gt;PID:&lt;span class="w"&gt; &lt;/span&gt;000BB000&lt;span class="w"&gt; &lt;/span&gt;SCS
Info:&lt;span class="w"&gt; &lt;/span&gt;ROMTbl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;FFF02000,&lt;span class="w"&gt; &lt;/span&gt;CID:&lt;span class="w"&gt; &lt;/span&gt;B105E00D,&lt;span class="w"&gt; &lt;/span&gt;PID:&lt;span class="w"&gt; &lt;/span&gt;003BB002&lt;span class="w"&gt; &lt;/span&gt;DWT
Info:&lt;span class="w"&gt; &lt;/span&gt;ROMTbl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;FFF03000,&lt;span class="w"&gt; &lt;/span&gt;CID:&lt;span class="w"&gt; &lt;/span&gt;B105E00D,&lt;span class="w"&gt; &lt;/span&gt;PID:&lt;span class="w"&gt; &lt;/span&gt;002BB003&lt;span class="w"&gt; &lt;/span&gt;FPB
Info:&lt;span class="w"&gt; &lt;/span&gt;ROMTbl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;FFF01000,&lt;span class="w"&gt; &lt;/span&gt;CID:&lt;span class="w"&gt; &lt;/span&gt;B105E00D,&lt;span class="w"&gt; &lt;/span&gt;PID:&lt;span class="w"&gt; &lt;/span&gt;003BB001&lt;span class="w"&gt; &lt;/span&gt;ITM
Info:&lt;span class="w"&gt; &lt;/span&gt;ROMTbl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;FFF41000,&lt;span class="w"&gt; &lt;/span&gt;CID:&lt;span class="w"&gt; &lt;/span&gt;B105900D,&lt;span class="w"&gt; &lt;/span&gt;PID:&lt;span class="w"&gt; &lt;/span&gt;003BB923&lt;span class="w"&gt; &lt;/span&gt;TPIU-Lite
Info:&lt;span class="w"&gt; &lt;/span&gt;ROMTbl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;FFF42000,&lt;span class="w"&gt; &lt;/span&gt;CID:&lt;span class="w"&gt; &lt;/span&gt;B105900D,&lt;span class="w"&gt; &lt;/span&gt;PID:&lt;span class="w"&gt; &lt;/span&gt;003BB924&lt;span class="w"&gt; &lt;/span&gt;ETM-M3
Cortex-M3&lt;span class="w"&gt; &lt;/span&gt;identified.
Target&lt;span class="w"&gt; &lt;/span&gt;interface&lt;span class="w"&gt; &lt;/span&gt;speed:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;kHz
J-Link&amp;gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;hr class="docutils" /&gt;
&lt;img src="/writing/2016/06/17/korg_smd_repair/victory.jpg" alt="Working microKEY" class="letterbox image-process-1200" title="Enjoying a celebratory ale with an old friend."/&gt; &lt;p&gt;The little bugger has risen from the dead, working as well as it ever has. It's great when a crazy guess turns out to be vindicated like that. Plus I definitely levelled up on my soldering from this experience.&lt;/p&gt;
&lt;p&gt;I suppose we should finish with some Lessons Learned:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Soldering is a game of mastering surface tension to your own ends. You've probably noticed that surface tension causes large bulbous drops of water to form on dry surfaces and edges, whereas adding water to already-damp surfaces produces tinier thinner droplets. The clown-sized chisel tip is far better at carrying small amounts of solder because of this same principle; because there's a large pre-wetted tip area, the solder won't accumulate in a ball but instead hug closer to the tip surface. As a bonus, you don't have to overcome the large natural surface tension of a ball to get it to flow onto another surface.&lt;/li&gt;
&lt;li&gt;For that reason, if your iron doesn't come supplied with a 2mm chisel tip, buy one on eBay and make it your standard.&lt;/li&gt;
&lt;li&gt;ChipQuik really takes the misery out of desoldering and saved having to buy an expensive board heating/hot air station, but it costs a fortune.&lt;/li&gt;
&lt;li&gt;The one piece of advice I took from YouTube that paid off was to use a smaller gauge of braid (1.5mm) and solder wire (0.5mm), both multicore flux.&lt;/li&gt;
&lt;li&gt;Oh and cover everything in flux too, it gives you more cheat points to beat surface tension.&lt;/li&gt;
&lt;li&gt;Solder suckers are useless. You get a gap of maybe 30 milliseconds between your iron leaving the joint and the solder hardening. Save your money.&lt;/li&gt;
&lt;li&gt;I probably could have bought 3 new microKEYs with the amount I ultimately dropped on tools and consumables. Save your money.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I might return later to explore the rest of this Cortex M3 hardware; all of the interesting code isn't blocked off by the broken USB comms anymore, the key input and wheel ADC routines are up for grabs. Right now I'm just happy to finally clear away this mess of tools that have invaded my workbench, and to have my keyboard back. I knew you would pull through, old chum!&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;p&gt;&lt;strong&gt;AFTERWORD:&lt;/strong&gt; The more clued-up people might've noticed that I missed a crucial step; resoldering the first set of legs properly after wicking away the bridges. I didn't realise this would be an issue until after a couple of days of running, when the keyboard would lose USB sync every hour or so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Jun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;:27:12&lt;span class="w"&gt; &lt;/span&gt;dangerfield&lt;span class="w"&gt; &lt;/span&gt;kernel:&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;-2-port4:&lt;span class="w"&gt; &lt;/span&gt;disabled&lt;span class="w"&gt; &lt;/span&gt;by&lt;span class="w"&gt; &lt;/span&gt;hub&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;EMI?&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;re-enabling...
Jun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;:27:12&lt;span class="w"&gt; &lt;/span&gt;dangerfield&lt;span class="w"&gt; &lt;/span&gt;kernel:&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;-2.4:&lt;span class="w"&gt; &lt;/span&gt;USB&lt;span class="w"&gt; &lt;/span&gt;disconnect,&lt;span class="w"&gt; &lt;/span&gt;device&lt;span class="w"&gt; &lt;/span&gt;number&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;16&lt;/span&gt;
Jun&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;:27:13&lt;span class="w"&gt; &lt;/span&gt;dangerfield&lt;span class="w"&gt; &lt;/span&gt;kernel:&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;-2.4:&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;full-speed&lt;span class="w"&gt; &lt;/span&gt;USB&lt;span class="w"&gt; &lt;/span&gt;device&lt;span class="w"&gt; &lt;/span&gt;number&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;ehci-pci
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Port 4 is the Cortex M3, so I guess DM4 and DP4 were hanging on by nothing but the whims of thermal expansion. I re-raked those pins and the dropouts have gone.&lt;/p&gt;
</content><category term="Edutainment"/><category term="segger"/><category term="j-link"/><category term="korg"/><category term="microkey"/><category term="debugging"/><category term="soldering"/><category term="electronics"/><category term="inexperience"/><category term="pure uncut fear"/><category term="christmas miracle"/></entry><entry><title>Audio Analysis, Part 1: Digital Audio</title><link href="/writing/2015/11/16/digital_audio/" rel="alternate"/><published>2015-11-16T00:00:00+08:00</published><updated>2015-11-16T00:00:00+08:00</updated><author><name>Scott</name></author><id>tag:None,2015-11-16:/writing/2015/11/16/digital_audio/</id><summary type="html">&lt;img src="/writing/2015/11/16/digital_audio/header.png" alt="Spectogram of some recorded music" class="image-process-1200" title="I ran out of ideas for what to show in the header to represent digital audio, so here&amp;#x27;s the spectogram of &amp;quot;Champions of Red Wine&amp;quot; by The New Pornographers. Go listen!"/&gt; &lt;p&gt;Today I'm going to try and explain how digital audio works. Most people have a vague idea about how sound works. &amp;quot;Sound is a wave!&amp;quot;, they might say. &amp;quot;Sound bounces off things!&amp;quot;, adds another. &amp;quot;Sound increases as you get closer to the stacks!&amp;quot;, relates one subject matter expert. &amp;quot;What?&amp;quot;, asks someone else? &amp;quot;I SAID SOUND INCREASES AS YOU GET CLOSER TO THE STACKS!&amp;quot; &amp;quot;Sorry I couldn't hear you over my crippling tinnitus! Dear Jesus if only I had known more about sound!&amp;quot;&lt;/p&gt;
&lt;p&gt;This will be a really simple primer about digital sound waves and how they contain musical notes. To follow along at home, you'll need the excellent free audio editor &lt;a class="reference external" href="http://www.audacityteam.org"&gt;Audacity&lt;/a&gt;. Download it if you don't have it already, then open it up to an empty window.&lt;/p&gt;
&lt;p&gt;Right, so what does a single note &amp;quot;look&amp;quot; like? Let's start with a sine wave. Sine waves have an easily recognisable and pure-sounding tone. In our Audacity window, we'll click Generate -&amp;gt; Tone.&lt;/p&gt;
&lt;img src="/writing/2015/11/16/digital_audio/tone_generator.png" alt="Audacity tone generator window" class="" title="Crap. I just realised that I&amp;#x27;ve signed up to make a bunch of popup jokes about tone generators."/&gt; &lt;p&gt;Oh look, they've helpfully picked a Sine wave as the default, with a frequency of 440 Hz and an amplitude of 0.8! (I changed the duration to only 5 seconds, as the default of 30 is a bit too long.) Let's hit OK.&lt;/p&gt;
</summary><content type="html">&lt;img src="/writing/2015/11/16/digital_audio/header.png" alt="Spectogram of some recorded music" class="image-process-1200" title="I ran out of ideas for what to show in the header to represent digital audio, so here&amp;#x27;s the spectogram of &amp;quot;Champions of Red Wine&amp;quot; by The New Pornographers. Go listen!"/&gt; &lt;p&gt;Today I'm going to try and explain how digital audio works. Most people have a vague idea about how sound works. &amp;quot;Sound is a wave!&amp;quot;, they might say. &amp;quot;Sound bounces off things!&amp;quot;, adds another. &amp;quot;Sound increases as you get closer to the stacks!&amp;quot;, relates one subject matter expert. &amp;quot;What?&amp;quot;, asks someone else? &amp;quot;I SAID SOUND INCREASES AS YOU GET CLOSER TO THE STACKS!&amp;quot; &amp;quot;Sorry I couldn't hear you over my crippling tinnitus! Dear Jesus if only I had known more about sound!&amp;quot;&lt;/p&gt;
&lt;p&gt;This will be a really simple primer about digital sound waves and how they contain musical notes. To follow along at home, you'll need the excellent free audio editor &lt;a class="reference external" href="http://www.audacityteam.org"&gt;Audacity&lt;/a&gt;. Download it if you don't have it already, then open it up to an empty window.&lt;/p&gt;
&lt;p&gt;Right, so what does a single note &amp;quot;look&amp;quot; like? Let's start with a sine wave. Sine waves have an easily recognisable and pure-sounding tone. In our Audacity window, we'll click Generate -&amp;gt; Tone.&lt;/p&gt;
&lt;img src="/writing/2015/11/16/digital_audio/tone_generator.png" alt="Audacity tone generator window" class="" title="Crap. I just realised that I&amp;#x27;ve signed up to make a bunch of popup jokes about tone generators."/&gt; &lt;p&gt;Oh look, they've helpfully picked a Sine wave as the default, with a frequency of 440 Hz and an amplitude of 0.8! (I changed the duration to only 5 seconds, as the default of 30 is a bit too long.) Let's hit OK.&lt;/p&gt;

&lt;img src="/writing/2015/11/16/digital_audio/tone_1.png" alt="Audacity window showing zoomed out sine wave" class="" title="I could swear I had something for this."/&gt; &lt;p&gt;That's a sine wave? Looks more like a giant blue slab to me. Let's zoom in a little bit.&lt;/p&gt;
&lt;img src="/writing/2015/11/16/digital_audio/tone_2.png" alt="Audacity window showing zoomed in visible sine wave" class="" title="Hey, look at that crazy wobbly line! So wobbly. That line. There."/&gt; &lt;p&gt;Flashbacks of early high-school maths should be running through your head. Let's figure out what's going on in this picture. The graph you see here is basically tracing the path that the speaker cone will take to reproduce the sound, with +1.0 meaning pushed all the way forward and -1.0 pulled all the way back. If we think of sound output (vertical axis) as &lt;strong&gt;y&lt;/strong&gt;, and time (horizontal axis) as &lt;strong&gt;t&lt;/strong&gt;, here's the formula:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;ph&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;A&lt;/strong&gt; is the amplitude of the wave; in our case, we picked 0.8, and sure enough we can see on the graph that the wave has peaks and troughs at +/- 0.8.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;w&lt;/strong&gt; is a little tricky, but it corresponds to the frequency we picked. We picked 440Hz as the frequency &lt;strong&gt;f&lt;/strong&gt;. Basically, we want sin() to repeat itself every 1/f seconds (also known as the period of the wave), and the function sin() takes radians as an input (so one full repetition of sin() happens after every 2*pi radians). Combining these, we get &lt;strong&gt;w&lt;/strong&gt; = 2*pi/(1/f) = 2*pi*f. This means that the wave should repeat itself every (1/440) = 0.002272 seconds, which matches up with what we see on the graph.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ph&lt;/strong&gt; is the phase of the wave; basically, at what point along the wave pattern do we want to start our signal. Right now it's 0.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you also suffer flashbacks to high-school music lessons, playing back the 440 Hz sine wave might also remind you of &amp;quot;concert A&amp;quot;, aka. the favourite tuning note for string musicians and orchestras. Musical notes from instruments (as we'll find out later) are a mix of a whole pile of different frequency waves, but usually there's one frequency that's loudest and that's the one we assign the note value to. 440 Hz (a.ka. the A wot comes after middle C on a keyboard) is referred to as A4 in scientific pitch notation, or 69 on the MIDI scale. There are 12 notes in an octave (great choice of name, guys), and interestingly an octave jump between notes is a multiple of 2 in the frequency domain. So that means that 57 (A3) is 220 Hz, while 81 (A5) is 880 Hz! Try these out in the tone generator if you like, don't they sound real similar?&lt;/p&gt;
&lt;p&gt;Anyway, you're probably wondering if there's a nice and easy formula for converting note values in the MIDI domain to tones in the frequency domain for your tone generator. To which my response would be &amp;quot;define easy&amp;quot;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;#define FREQ_TO_MIDI( freq ) (69.0f+12.0f*log( freq/440.0f )/log( 2.0f ))&lt;/span&gt;
&lt;span class="cp"&gt;#define MIDI_TO_FREQ( midi ) (exp( log( 2.0f )*(midi-69.0f)/12.0f )*440.0f)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Sometimes it's easier to use a lookup table instead. Like this one! (thanks &lt;a class="reference external" href="http://newt.phys.unsw.edu.au/jw/notes.html"&gt;J. Wolfe of UNSW&lt;/a&gt;)&lt;/p&gt;
&lt;img src="/writing/2015/11/16/digital_audio/notes.gif" alt="Diagram mapping musical notes to MIDI frequencies" class="" title="Ah, the majestic keyboard. As made famous by TV&amp;#x27;s Liberace."/&gt; &lt;p&gt;One final thing; digital audio is based on the principle of sampling; unlike an analogue audio signal, which is a continuous sound output from an electrical or mechanical process (e.g. piezo cartridge running through the grooves of a record, electric guitar plugged into a tube amp), the sound output of a digital audio signal operates at a fixed number of &amp;quot;samples&amp;quot; per second. You see that number 44100 Hz down in the bottom left corner? That's known as the sampling rate. Every second of audio can be thought of as a big two-column list with 44100 timecodes down the left hand side and 44100 speaker positions on the right. 44100 Hz is the standard sampling rate of audio CDs and most downloadable music.&lt;/p&gt;
&lt;p&gt;Wouldn't having a fixed number of samples per second limit the quality of sound you would get? Absolutely! If you've wondered why voices on a landline telephone sound so lousy, it's because Telstra can't maintain their copper for beans and the pit outside is probably full of water. It's also because the sound is processed with &lt;a class="reference external" href="https://en.wikipedia.org/wiki/G.711"&gt;G.711&lt;/a&gt;, an audio codec from 1972 that has an abysmal sampling rate of 8000 Hz.&lt;/p&gt;
&lt;p&gt;In digital audio, the highest frequency sound you can physically reproduce is half the sampling rate, known as the Nyquist frequency. Which makes sense, because the tightest you'd be able to pack sound into your digital signal is with a +1.0, followed by a -1.0, followed by another +1.0… and so on. Now 44100 Hz has a Nyquist frequency of 22050 Hz, which conveniently is juuuust above the range of human hearing. Telephones have a Nyquist frequency of 4000 Hz, which is not. It's extremely audible. Try loading some music into Audacity and crank the Project Rate down to 8000 Hz; notice how it's sounding all muddy and not crisp? That's what happens when you can't play frequencies higher than 4000 Hz. It stinks, right?&lt;/p&gt;
&lt;p&gt;Alright, enough minutiae. Time to get to the most mind-bending part about how sound works. So we're looking at our sine wave here, thinking about how it corresponds to the movement of the speaker cone, but also how nice and symmetrical it is. One question that springs to mind then, how is it possible for a good set of loudspeakers to reproduce… well, any damn sound you can think of? How do you combine multiple sounds? Wouldn't the movement of the cone look all jagged and weird?&lt;/p&gt;
&lt;p&gt;Let's generate another tone in our project; a complimentary one to A4. Let's go for C#4 (61), as that produces a nice-sounding A major third. Add a new tone of a C#4 sine wave at 277.18Hz.&lt;/p&gt;
&lt;img src="/writing/2015/11/16/digital_audio/tone_3.png" alt="Audacity window showing two sine waves" class="" title="*sigh*"/&gt; &lt;p&gt;When you play that back, there's going to be a sharp edge to it. This is because the two tones we picked have a very high amplitude, enough so that combining them produces a result outside the range of -1.0 to +1.0, which you can hear as clipping. Lower the volume of the two waves by setting the little -/+ slider on the left of both tracks down to -10 dB. Listen again, you'll have a much cleaner sound.&lt;/p&gt;
&lt;p&gt;But they're still both separate! To see the resulting signal that drives the speaker, we need to merge them. Press Ctrl-A to select both tracks, then pick Tracks -&amp;gt; Mix and Render to New Track&lt;/p&gt;
&lt;img src="/writing/2015/11/16/digital_audio/tone_4.png" alt="Audacity window showing the sum of two sine waves" class="" title="Wait is that the end OH GOD YES FINALLY TWIST ENDING HAHA THE SINE WAVES ADD UP YAY SEE YOU NEXT TIME"/&gt; &lt;p&gt;Will you look at that? The samples from each of the two waves are just added together. The resulting signal, being simple, has a few obvious features which match up with the two parent sine waves. Still, looking at it by itself it's difficult to say what frequencies are in this new combination, and it gets even harder to examine by eye as more tones are combined. And yet, if you listen closely it's obvious it contains the two tones we made earlier! How do we separate tones back out again? If our ears can do it, why can't maths?&lt;/p&gt;
&lt;p&gt;If you haven't guessed already, a large part of audio analysis is going backwards from a complete digital audio signal to its component parts. We'll go into that more in the next thrilling installment!&lt;/p&gt;
</content><category term="Edutainment"/><category term="audio analysis"/><category term="audio"/><category term="digital"/><category term="sine waves"/><category term="frequency"/><category term="maths"/><category term="coma"/><category term="hospital"/></entry><entry><title>J-Link Detective Squad: Dead KORG microKEY-37</title><link href="/writing/2015/11/08/jlink_korg_microkey/" rel="alternate"/><published>2015-11-08T00:00:00+08:00</published><updated>2015-11-08T00:00:00+08:00</updated><author><name>Scott</name></author><id>tag:None,2015-11-08:/writing/2015/11/08/jlink_korg_microkey/</id><summary type="html">&lt;img src="/writing/2015/11/08/jlink_korg_microkey/microkey.jpg" alt="Korg microKEY-37" class="letterbox image-process-1200" title="For the record, I am one of those idiots who can be swayed by the words &amp;quot;limited edition in black and red&amp;quot;. At worst I will deliberate for tens of seconds."/&gt; &lt;p&gt;I love the KORG microKEY 37. It's an excellent entry-level MIDI controller that fits unobtrusively on your desk, great for impromptu jamming when you should be focused on something boring like &amp;quot;tax&amp;quot; or &amp;quot;finding a new house before eviction day&amp;quot;. The keys feel pretty soft, as they use rubber domes instead of metal springs, but the velocity response is excellent. I highly recommend it, as it's one of those products which basically lives forever and delivers year after year of good service.&lt;/p&gt;
&lt;p&gt;I bring this up because back in about February, my KORG microKEY 37 stopped turning on. The unit would no longer connect via USB; each time it would give up at different points during the initial handshake, with dmesg spewing a number of unhappy messages. Here's one attempt at plugging the device in.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:56&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;full-speed&lt;span class="w"&gt; &lt;/span&gt;USB&lt;span class="w"&gt; &lt;/span&gt;device&lt;span class="w"&gt; &lt;/span&gt;number&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;xhci_hcd
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:56&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;device&lt;span class="w"&gt; &lt;/span&gt;descriptor&lt;span class="w"&gt; &lt;/span&gt;read/64,&lt;span class="w"&gt; &lt;/span&gt;error&lt;span class="w"&gt; &lt;/span&gt;-71
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:57&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;xhci_hcd&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0000&lt;/span&gt;:00:14.0:&lt;span class="w"&gt; &lt;/span&gt;Setup&lt;span class="w"&gt; &lt;/span&gt;ERROR:&lt;span class="w"&gt; &lt;/span&gt;setup&lt;span class="w"&gt; &lt;/span&gt;context&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;slot&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;16&lt;/span&gt;.
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:57&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;hub&lt;span class="w"&gt; &lt;/span&gt;failed&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;device,&lt;span class="w"&gt; &lt;/span&gt;error&lt;span class="w"&gt; &lt;/span&gt;-22
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:57&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;full-speed&lt;span class="w"&gt; &lt;/span&gt;USB&lt;span class="w"&gt; &lt;/span&gt;device&lt;span class="w"&gt; &lt;/span&gt;number&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;xhci_hcd
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:57&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;device&lt;span class="w"&gt; &lt;/span&gt;descriptor&lt;span class="w"&gt; &lt;/span&gt;read/64,&lt;span class="w"&gt; &lt;/span&gt;error&lt;span class="w"&gt; &lt;/span&gt;-71
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:57&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;xhci_hcd&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0000&lt;/span&gt;:00:14.0:&lt;span class="w"&gt; &lt;/span&gt;Setup&lt;span class="w"&gt; &lt;/span&gt;ERROR:&lt;span class="w"&gt; &lt;/span&gt;setup&lt;span class="w"&gt; &lt;/span&gt;context&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;slot&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;.
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:57&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;hub&lt;span class="w"&gt; &lt;/span&gt;failed&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;device,&lt;span class="w"&gt; &lt;/span&gt;error&lt;span class="w"&gt; &lt;/span&gt;-22
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:57&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;high-speed&lt;span class="w"&gt; &lt;/span&gt;USB&lt;span class="w"&gt; &lt;/span&gt;device&lt;span class="w"&gt; &lt;/span&gt;number&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;xhci_hcd
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:57&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;Device&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;responding&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;setup&lt;span class="w"&gt; &lt;/span&gt;address.
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:57&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;Device&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;responding&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;setup&lt;span class="w"&gt; &lt;/span&gt;address.
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:58&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;device&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;accepting&lt;span class="w"&gt; &lt;/span&gt;address&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;error&lt;span class="w"&gt; &lt;/span&gt;-71
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:58&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;full-speed&lt;span class="w"&gt; &lt;/span&gt;USB&lt;span class="w"&gt; &lt;/span&gt;device&lt;span class="w"&gt; &lt;/span&gt;number&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;xhci_hcd
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:58&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;Device&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;responding&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;setup&lt;span class="w"&gt; &lt;/span&gt;address.
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:58&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;Device&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;responding&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;setup&lt;span class="w"&gt; &lt;/span&gt;address.
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:58&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;device&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;accepting&lt;span class="w"&gt; &lt;/span&gt;address&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;error&lt;span class="w"&gt; &lt;/span&gt;-71
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:58&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;usb3-port1:&lt;span class="w"&gt; &lt;/span&gt;unable&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;enumerate&lt;span class="w"&gt; &lt;/span&gt;USB&lt;span class="w"&gt; &lt;/span&gt;device
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Oh snap! I'm torn on what to do... buying a replacement is doable, but this one was limited edition and a replacement wouldn't be the same cool colour scheme! Maybe there's a clue to what went wrong inside the unit?&lt;/p&gt;
</summary><content type="html">&lt;img src="/writing/2015/11/08/jlink_korg_microkey/microkey.jpg" alt="Korg microKEY-37" class="letterbox image-process-1200" title="For the record, I am one of those idiots who can be swayed by the words &amp;quot;limited edition in black and red&amp;quot;. At worst I will deliberate for tens of seconds."/&gt; &lt;p&gt;I love the KORG microKEY 37. It's an excellent entry-level MIDI controller that fits unobtrusively on your desk, great for impromptu jamming when you should be focused on something boring like &amp;quot;tax&amp;quot; or &amp;quot;finding a new house before eviction day&amp;quot;. The keys feel pretty soft, as they use rubber domes instead of metal springs, but the velocity response is excellent. I highly recommend it, as it's one of those products which basically lives forever and delivers year after year of good service.&lt;/p&gt;
&lt;p&gt;I bring this up because back in about February, my KORG microKEY 37 stopped turning on. The unit would no longer connect via USB; each time it would give up at different points during the initial handshake, with dmesg spewing a number of unhappy messages. Here's one attempt at plugging the device in.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:56&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;full-speed&lt;span class="w"&gt; &lt;/span&gt;USB&lt;span class="w"&gt; &lt;/span&gt;device&lt;span class="w"&gt; &lt;/span&gt;number&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;xhci_hcd
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:56&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;device&lt;span class="w"&gt; &lt;/span&gt;descriptor&lt;span class="w"&gt; &lt;/span&gt;read/64,&lt;span class="w"&gt; &lt;/span&gt;error&lt;span class="w"&gt; &lt;/span&gt;-71
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:57&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;xhci_hcd&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0000&lt;/span&gt;:00:14.0:&lt;span class="w"&gt; &lt;/span&gt;Setup&lt;span class="w"&gt; &lt;/span&gt;ERROR:&lt;span class="w"&gt; &lt;/span&gt;setup&lt;span class="w"&gt; &lt;/span&gt;context&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;slot&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;16&lt;/span&gt;.
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:57&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;hub&lt;span class="w"&gt; &lt;/span&gt;failed&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;device,&lt;span class="w"&gt; &lt;/span&gt;error&lt;span class="w"&gt; &lt;/span&gt;-22
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:57&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;full-speed&lt;span class="w"&gt; &lt;/span&gt;USB&lt;span class="w"&gt; &lt;/span&gt;device&lt;span class="w"&gt; &lt;/span&gt;number&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;xhci_hcd
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:57&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;device&lt;span class="w"&gt; &lt;/span&gt;descriptor&lt;span class="w"&gt; &lt;/span&gt;read/64,&lt;span class="w"&gt; &lt;/span&gt;error&lt;span class="w"&gt; &lt;/span&gt;-71
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:57&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;xhci_hcd&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0000&lt;/span&gt;:00:14.0:&lt;span class="w"&gt; &lt;/span&gt;Setup&lt;span class="w"&gt; &lt;/span&gt;ERROR:&lt;span class="w"&gt; &lt;/span&gt;setup&lt;span class="w"&gt; &lt;/span&gt;context&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;slot&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;.
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:57&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;hub&lt;span class="w"&gt; &lt;/span&gt;failed&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;device,&lt;span class="w"&gt; &lt;/span&gt;error&lt;span class="w"&gt; &lt;/span&gt;-22
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:57&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;high-speed&lt;span class="w"&gt; &lt;/span&gt;USB&lt;span class="w"&gt; &lt;/span&gt;device&lt;span class="w"&gt; &lt;/span&gt;number&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;xhci_hcd
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:57&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;Device&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;responding&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;setup&lt;span class="w"&gt; &lt;/span&gt;address.
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:57&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;Device&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;responding&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;setup&lt;span class="w"&gt; &lt;/span&gt;address.
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:58&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;device&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;accepting&lt;span class="w"&gt; &lt;/span&gt;address&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;error&lt;span class="w"&gt; &lt;/span&gt;-71
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:58&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;full-speed&lt;span class="w"&gt; &lt;/span&gt;USB&lt;span class="w"&gt; &lt;/span&gt;device&lt;span class="w"&gt; &lt;/span&gt;number&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;xhci_hcd
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:58&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;Device&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;responding&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;setup&lt;span class="w"&gt; &lt;/span&gt;address.
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:58&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;Device&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;responding&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;setup&lt;span class="w"&gt; &lt;/span&gt;address.
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:58&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;-1:&lt;span class="w"&gt; &lt;/span&gt;device&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;accepting&lt;span class="w"&gt; &lt;/span&gt;address&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;error&lt;span class="w"&gt; &lt;/span&gt;-71
&lt;span class="o"&gt;[&lt;/span&gt;Sun&lt;span class="w"&gt; &lt;/span&gt;Sep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;06&lt;/span&gt;:00:58&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;usb&lt;span class="w"&gt; &lt;/span&gt;usb3-port1:&lt;span class="w"&gt; &lt;/span&gt;unable&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;enumerate&lt;span class="w"&gt; &lt;/span&gt;USB&lt;span class="w"&gt; &lt;/span&gt;device
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Oh snap! I'm torn on what to do... buying a replacement is doable, but this one was limited edition and a replacement wouldn't be the same cool colour scheme! Maybe there's a clue to what went wrong inside the unit?&lt;/p&gt;

&lt;img src="/writing/2015/11/08/jlink_korg_microkey/klm3130b.jpg" alt="Main circuit board inside microKEY" class="image-process-1200" title="All that surface mount stuff can really make a guy feel out of his depth."/&gt; &lt;p&gt;Here's the view of the main KLM-3130-B board. As you can see, there's nothing obviously charred or melted. I tried following the golden rule of electronics repair (check for busted capacitors), but all of the capacitors in this are tiny ceramic surface-mount ones. Which hopefully aren't going to fail because they don't have liquid in them.&lt;/p&gt;
&lt;p&gt;The big square chip I identified as a &lt;a class="reference external" href="http://www.spansion.com/products/microcontrollers/32-bit-arm-core/fm3/Pages/MB9AF312LAPMC-G-JNE2.aspx"&gt;Fujitsu (now Cypress) MB9A310A series ARM Cortex M3 processor (MB9AF312L)&lt;/a&gt;, and the smaller rectangular chip is a GL850G USB hub controller (as this keyboard has a two port expansion USB hub). Checking the &lt;a class="reference external" href="http://www.keil.com/dd/docs/datashts/fujitsu/mb9a310a/mb9af311la-ds706-00012-2v0-e.pdf"&gt;datasheet for the CPU&lt;/a&gt;, and employing my patented multimeter stabbing technique on the teeny tiny chip legs, I'm sure the CPU is getting +5V at the Vcc (a.k.a. supply voltage) pins and ground at the GND pins. The two clock crystals I believe are 12MHz (X1) and 4MHz (X2). There. That's my electronics knowledge exhausted.&lt;/p&gt;
&lt;p&gt;But wait! What are these intriguing pads there on the board marked &amp;quot;For Debug&amp;quot;? SWO? SWDIO? You mean, there's a chance that this board could be fixed in the &lt;em&gt;software&lt;/em&gt; realm?&lt;/p&gt;
&lt;p&gt;Ah well, in for a penny... after checking in with a Subject Matter Expert I purchased a &lt;a class="reference external" href="https://www.segger.com/jlink-general-info.html"&gt;Segger J-Link debug probe&lt;/a&gt; for $89. I then went ahead and hacked open a sturdy quadruple-shielded USB type-B cable (TIP: if you need to strip a USB cable go for the ultra-cheapass thin ones; the only &amp;quot;shielding&amp;quot; they have is a few microns of aluminium foil. Also cutting it ensures it will never be used again), soldered some hookup wire to the pads, grafted some prototype wires on the ends, and plugged everything into a breadboard.&lt;/p&gt;
&lt;img src="/writing/2015/11/08/jlink_korg_microkey/jlink_wiring.jpg" alt="J-Link and microKEY plugged into breadboard" class="image-process-1200" title="Yes I was cheap and covered the solder joins on those prototype wires with electrical tape. DON&amp;#x27;T JUDGE"/&gt; &lt;p&gt;For Serial Wire Debug there are &lt;a class="reference external" href="https://www.segger.com/interface-description.html"&gt;four pins on the J-Link&lt;/a&gt; which correspond to our pads (SWCLK, SWDIO, SWO, RESET), plus VTref (spliced off our supply voltage, aka. the +5V pin of the keyboard's USB in) and GND (spliced off ground pin of USB in). D- and D+ of the USB cable are passed through without any splicing. To keep things slightly less failure prone I'm running the J-Link off my desktop's USB controller, and the keyboard's spliced cable will be attached to my laptop.&lt;/p&gt;
&lt;p&gt;So... it was at this point that I thought about reading the J-Link manual. According to the Getting Started section, I should switch on the J-Link, then the keyboard, and it should identify the hardware. Hah. As if it'd be that easy.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;scott@dangerfield:~/Tools/JLink_Linux_V502_x86_64$&lt;span class="w"&gt; &lt;/span&gt;./JLinkExe
SEGGER&lt;span class="w"&gt; &lt;/span&gt;J-Link&lt;span class="w"&gt; &lt;/span&gt;Commander&lt;span class="w"&gt; &lt;/span&gt;V5.02&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;?&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;help&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
Compiled&lt;span class="w"&gt; &lt;/span&gt;Aug&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;28&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;:48:33
DLL&lt;span class="w"&gt; &lt;/span&gt;version&lt;span class="w"&gt; &lt;/span&gt;V5.02,&lt;span class="w"&gt; &lt;/span&gt;compiled&lt;span class="w"&gt; &lt;/span&gt;Aug&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;28&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;:48:30
Firmware:&lt;span class="w"&gt; &lt;/span&gt;J-Link&lt;span class="w"&gt; &lt;/span&gt;V9&lt;span class="w"&gt; &lt;/span&gt;compiled&lt;span class="w"&gt; &lt;/span&gt;Aug&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;28&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2015&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;:49:48
Hardware:&lt;span class="w"&gt; &lt;/span&gt;V9.30
S/N:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;redacted&lt;span class="o"&gt;]&lt;/span&gt;
OEM:&lt;span class="w"&gt; &lt;/span&gt;SEGGER-EDU
Feature&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;FlashBP,&lt;span class="w"&gt; &lt;/span&gt;GDB
Emulator&lt;span class="w"&gt; &lt;/span&gt;has&lt;span class="w"&gt; &lt;/span&gt;Trace&lt;span class="w"&gt; &lt;/span&gt;capability
&lt;span class="nv"&gt;VTarget&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.950V
Info:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;TotalIRLen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;?,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;IRPrint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;0x..FFFFFFFFFFFFFFFFFFFFFFF1
Info:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;TotalIRLen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;?,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;IRPrint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;0x..FFFFFFFFFFFFFFFFFFFFFFF1
No&lt;span class="w"&gt; &lt;/span&gt;devices&lt;span class="w"&gt; &lt;/span&gt;found&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;JTAG&lt;span class="w"&gt; &lt;/span&gt;chain.&lt;span class="w"&gt; &lt;/span&gt;Trying&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;device&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;SWD.
Info:&lt;span class="w"&gt; &lt;/span&gt;Found&lt;span class="w"&gt; &lt;/span&gt;SWD-DP&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;ID&lt;span class="w"&gt; &lt;/span&gt;0x2BA01477
Info:&lt;span class="w"&gt; &lt;/span&gt;Found&lt;span class="w"&gt; &lt;/span&gt;Cortex-M3&lt;span class="w"&gt; &lt;/span&gt;r2p1,&lt;span class="w"&gt; &lt;/span&gt;Little&lt;span class="w"&gt; &lt;/span&gt;endian.
Info:&lt;span class="w"&gt; &lt;/span&gt;FPUnit:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;code&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;BP&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;slots&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;literal&lt;span class="w"&gt; &lt;/span&gt;slots
Info:&lt;span class="w"&gt; &lt;/span&gt;CoreSight&lt;span class="w"&gt; &lt;/span&gt;components:
Info:&lt;span class="w"&gt; &lt;/span&gt;ROMTbl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;@&lt;span class="w"&gt; &lt;/span&gt;E00FF000
Info:&lt;span class="w"&gt; &lt;/span&gt;ROMTbl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;FFF0F000,&lt;span class="w"&gt; &lt;/span&gt;CID:&lt;span class="w"&gt; &lt;/span&gt;B105E00D,&lt;span class="w"&gt; &lt;/span&gt;PID:&lt;span class="w"&gt; &lt;/span&gt;000BB000&lt;span class="w"&gt; &lt;/span&gt;SCS
Info:&lt;span class="w"&gt; &lt;/span&gt;ROMTbl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;FFF02000,&lt;span class="w"&gt; &lt;/span&gt;CID:&lt;span class="w"&gt; &lt;/span&gt;B105E00D,&lt;span class="w"&gt; &lt;/span&gt;PID:&lt;span class="w"&gt; &lt;/span&gt;003BB002&lt;span class="w"&gt; &lt;/span&gt;DWT
Info:&lt;span class="w"&gt; &lt;/span&gt;ROMTbl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;FFF03000,&lt;span class="w"&gt; &lt;/span&gt;CID:&lt;span class="w"&gt; &lt;/span&gt;B105E00D,&lt;span class="w"&gt; &lt;/span&gt;PID:&lt;span class="w"&gt; &lt;/span&gt;002BB003&lt;span class="w"&gt; &lt;/span&gt;FPB
Info:&lt;span class="w"&gt; &lt;/span&gt;ROMTbl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;FFF01000,&lt;span class="w"&gt; &lt;/span&gt;CID:&lt;span class="w"&gt; &lt;/span&gt;B105E00D,&lt;span class="w"&gt; &lt;/span&gt;PID:&lt;span class="w"&gt; &lt;/span&gt;003BB001&lt;span class="w"&gt; &lt;/span&gt;ITM
Info:&lt;span class="w"&gt; &lt;/span&gt;ROMTbl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;FFF41000,&lt;span class="w"&gt; &lt;/span&gt;CID:&lt;span class="w"&gt; &lt;/span&gt;B105900D,&lt;span class="w"&gt; &lt;/span&gt;PID:&lt;span class="w"&gt; &lt;/span&gt;003BB923&lt;span class="w"&gt; &lt;/span&gt;TPIU-Lite
Info:&lt;span class="w"&gt; &lt;/span&gt;ROMTbl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;FFF42000,&lt;span class="w"&gt; &lt;/span&gt;CID:&lt;span class="w"&gt; &lt;/span&gt;B105900D,&lt;span class="w"&gt; &lt;/span&gt;PID:&lt;span class="w"&gt; &lt;/span&gt;003BB924&lt;span class="w"&gt; &lt;/span&gt;ETM-M3
Cortex-M3&lt;span class="w"&gt; &lt;/span&gt;identified.
Target&lt;span class="w"&gt; &lt;/span&gt;interface&lt;span class="w"&gt; &lt;/span&gt;speed:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;kHz
J-Link&amp;gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;AWW YISS. The chip is alive! Let's read through the command list and see what things we can do. Oh look! CPU registers! They contain things that are important. Can we see those?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;J-Link&amp;gt;regs
CPU&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;halted&lt;span class="w"&gt; &lt;/span&gt;!

J-Link&amp;gt;halt
&lt;span class="nv"&gt;PC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;00009C56,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;CycleCnt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;E3B49CD7
&lt;span class="nv"&gt;R0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00000000&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;R1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00000001&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;R2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;1FFFE444,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;R3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;1FFFEF04
&lt;span class="nv"&gt;R4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;1FFFEF04,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;R5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;1FFFE306,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;R6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00000000&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;R7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00000000&lt;/span&gt;
&lt;span class="nv"&gt;R8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00000000&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;R9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00000000&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;R10&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00000000&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;R11&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00000000&lt;/span&gt;
&lt;span class="nv"&gt;R12&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;4266609C
SP&lt;span class="o"&gt;(&lt;/span&gt;R13&lt;span class="o"&gt;)=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;20001FD0,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;MSP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;20001FD0,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PSP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00000000&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;R14&lt;span class="o"&gt;(&lt;/span&gt;LR&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;00009E29
&lt;span class="nv"&gt;XPSR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;81000000&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;APSR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Nzcvq,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;EPSR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;01000000&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;IPSR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;NoException&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;CFBP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00000000&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;CONTROL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;FAULTMASK&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;BASEPRI&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PRIMASK&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Yep. Those are certainly ARM registers. Piece of piss, this electronics lark.&lt;/p&gt;
&lt;p&gt;Okay. What next? We don't have the source code of what's running on this M3, so we want to whip out a debugger. But first, what about the stuff stored on the flash memory? It'd be a good idea to take a copy of that to safeguard against us screwing up. More importantly, where IS the flash memory? The Cortex M3 is a 32-bit microcontroller, so there's 4GB of address space!&lt;/p&gt;
&lt;p&gt;Back to the CPU datasheet, there's a memory map for the chip on page 55; of course, only a small amount of the 4GB address space is mapped to anything useful. We can see that the chip's princely 128kb of flash (a.k.a. stored code) is mapped to the range 0x00000000-0x00020000, and the generous 16kb of SRAM can be found at 0x1FFFE000-0x20002000. Notice how the values stored in several of those registers above are within the SRAM range? I think we can say with confidence that these are pointers to stuff in memory!&lt;/p&gt;
&lt;p&gt;But what's the deal with R12? According to our memory map that address sits in somewhere called the &amp;quot;Bit band alias&amp;quot;. The &lt;a class="reference external" href="http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0179b/CHDJHIDF.html"&gt;ARM manual for the M3&lt;/a&gt; describes this feature pretty well; this is basically another way of reading and writing to the SRAM, except every &lt;em&gt;bit&lt;/em&gt; is given a full processor word. Meaning the 1MB SRAM range gets stretched out to 1MB*(8 bits in a byte)*(4 bytes in a word) = 32MB! This may sound like the definition of wastefulness, but it means you can do bit-level operations with one instruction. As in, normally if you wanted to set bit 3 in a byte of memory to 1 you'd have to do something like [load byte into register] + [OR register with 0x08] + [write register to byte] = 3 instructions; with a bit band alias you can just write 1 to the address that corresponds to the bit. Snazzy!&lt;/p&gt;
&lt;p&gt;Okay, enough fun registers talk, let's get ourselves a dump of the flash memory range from our datasheet.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;J-Link&amp;gt;savebin&lt;span class="w"&gt; &lt;/span&gt;microkey.bin,&lt;span class="w"&gt; &lt;/span&gt;0x00000000,&lt;span class="w"&gt; &lt;/span&gt;0x20000
Opening&lt;span class="w"&gt; &lt;/span&gt;binary&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;writing...&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;microkey.bin&lt;span class="o"&gt;]&lt;/span&gt;
Reading&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;131072&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bytes&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;addr&lt;span class="w"&gt; &lt;/span&gt;0x00000000&lt;span class="w"&gt; &lt;/span&gt;into&lt;span class="w"&gt; &lt;/span&gt;file...O.K.
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Poking around inside the file with a hex editor, there's a definite rhythmic quality to the bytes, interspersed with large blocks of nulls and the occasional plaintext referencing KORG. Code!&lt;/p&gt;
&lt;p&gt;The first few subroutines are probing around the peripheral map at 0x40000000, but what does any of it map to? The memory map in that datasheet was a start, but pretty thin on detail. I found a page &lt;a class="reference external" href="http://www.keil.com/dd/chip/5770.htm"&gt;containing a copy of the C header file for the chip&lt;/a&gt; (mb9a310l.h, for those playing at home), which has a more detailed listing of all of the peripheral memory addresses. (Lesson learned: more detail doesn't help) This crap shouldn't be so hard, all we want is a rough idea what addresses match output from the keys and wheels. Let's take advantage of some street-grade debugging!&lt;/p&gt;
&lt;p&gt;Right, step one: halt the CPU and get a dump of the full peripheral range named in the datasheet.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;J-Link&amp;gt;halt
&lt;span class="nv"&gt;PC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;000090FA,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;CycleCnt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;5314F092
&lt;span class="nv"&gt;R0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00000002&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;R1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00000008&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;R2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00000008&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;R3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;E000E100
&lt;span class="nv"&gt;R4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;000000FF,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;R5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;1FFFE306,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;R6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00000000&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;R7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00000000&lt;/span&gt;
&lt;span class="nv"&gt;R8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00000000&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;R9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00000000&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;R10&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00000000&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;R11&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00000000&lt;/span&gt;
&lt;span class="nv"&gt;R12&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;4266609C
SP&lt;span class="o"&gt;(&lt;/span&gt;R13&lt;span class="o"&gt;)=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;20001FC0,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;MSP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;20001FC0,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PSP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00000000&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;R14&lt;span class="o"&gt;(&lt;/span&gt;LR&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;00009E0F
&lt;span class="nv"&gt;XPSR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;21000000&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;APSR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nzCvq,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;EPSR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;01000000&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;IPSR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;NoException&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;CFBP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00000000&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;CONTROL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;FAULTMASK&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;BASEPRI&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PRIMASK&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;

J-Link&amp;gt;savebin&lt;span class="w"&gt; &lt;/span&gt;per_clean.bin&lt;span class="w"&gt; &lt;/span&gt;0x40000000&lt;span class="w"&gt; &lt;/span&gt;0x61000
Opening&lt;span class="w"&gt; &lt;/span&gt;binary&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;writing...&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;per_clean.bin&lt;span class="o"&gt;]&lt;/span&gt;
Reading&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;397312&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bytes&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;addr&lt;span class="w"&gt; &lt;/span&gt;0x40000000&lt;span class="w"&gt; &lt;/span&gt;into&lt;span class="w"&gt; &lt;/span&gt;file...Could&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;memory.
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Whuuuuuuuut. You can't talk to me like that, I'm the debugger! I pay your salary!&lt;/p&gt;
&lt;p&gt;Fine. Okay, clearly &amp;quot;reserved&amp;quot; is EE speak for &amp;quot;get off my land&amp;quot;, so let's try just the GPIO block. Lots of buttons sounds like General-Purpose Input/Output to me.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;J-Link&amp;gt;savebin&lt;span class="w"&gt; &lt;/span&gt;per_gpio.bin&lt;span class="w"&gt; &lt;/span&gt;0x40033000&lt;span class="w"&gt; &lt;/span&gt;0x1000
Opening&lt;span class="w"&gt; &lt;/span&gt;binary&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;writing...&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;per_gpio.bin&lt;span class="o"&gt;]&lt;/span&gt;
Reading&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4096&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bytes&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;addr&lt;span class="w"&gt; &lt;/span&gt;0x40033000&lt;span class="w"&gt; &lt;/span&gt;into&lt;span class="w"&gt; &lt;/span&gt;file...O.K.
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ hexdump per_gpio.bin
0000000 001f 0000 0300 0000 0000 0000 0000 0000
0000010 0000 0000 0000 0000 0000 0000 0000 0000
0000020 0003 0000 0000 0000 0000 0000 0000 0000
0000030 0000 0000 0000 0000 000c 0000 0000 0000
0000040 0000 0000 0000 0000 0000 0000 0000 0000
*
0000100 001f 0000 0000 0000 0000 0000 020f 0000
0000110 00c0 0000 0007 0000 0000 0000 0000 0000
0000120 0000 0000 0000 0000 0000 0000 0000 0000
*
0000200 9000 0000 00ff 0000 000a 0000 fc00 0000
0000210 7e00 0000 0000 0000 0006 0000 0000 0000
0000220 0000 0000 0000 0000 0000 0000 0000 0000
*
0000300 0407 0000 0000 0000 0000 0000 320f 0000
0000310 00c0 0000 0007 0000 0001 0000 0000 0000
0000320 0000 0000 0000 0000 0000 0000 0000 0000
*
0000400 0000 0000 0000 0000 0000 0000 3000 0000
0000410 0000 0000 0000 0000 0000 0000 0000 0000
*
0000500 ff00 ffff 0000 0000 0000 0000 0000 0000
0000510 0000 0000 0000 0000 0000 0000 0000 0000
*
0000580 0014 0000 0000 0000 0000 0000 0000 0000
0000590 0000 0000 0000 0000 0000 0000 0000 0000
*
0000600 0000 0003 0000 0000 0000 0000 0000 0000
0000610 0000 0000 0000 0000 0000 0000 0000 0000
*
0000800 0000 0000 000d 0000 0000 0000 0000 0000
0000810 0000 0000 0000 0000 0000 0000 0000 0000
*
0001000
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Cracking it open in a hex editor, it's mostly 0s but a few 1s! Pulling a second dump shows it to be identical to the first.&lt;/p&gt;
&lt;p&gt;Now! The next step is to pull &lt;em&gt;another&lt;/em&gt; dump of the GPIO range, but this time whilst holding down C3 on the keyboard. Then, compare it with our first clean dump.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ hexdump per_gpio.bin &amp;gt; per_gpio.hex
$ hexdump per_gpioC3.bin &amp;gt; per_gpioC3.hex
$ diff per_gpio.hex per_gpioC3.hex
16c16
&lt;span class="gd"&gt;&amp;lt; 0000310 00c0 0000 0007 0000 0001 0000 0000 0000&lt;/span&gt;
&lt;span class="gs"&gt;---&lt;/span&gt;
&lt;span class="gi"&gt;&amp;gt; 0000310 00c0 0000 0004 0000 0001 0000 0000 0000&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Well whoda thunk it. We can see that bits 0 and 1 of offset 0x315 are zeroed when we hold down C3. How about C#3?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ diff per_gpio.hex per_gpioCsh3.hex
15,16c15,16
&lt;span class="gd"&gt;&amp;lt; 0000300 0407 0000 0000 0000 0000 0000 320f 0000&lt;/span&gt;
&lt;span class="gd"&gt;&amp;lt; 0000310 00c0 0000 0007 0000 0001 0000 0000 0000&lt;/span&gt;
&lt;span class="gs"&gt;---&lt;/span&gt;
&lt;span class="gi"&gt;&amp;gt; 0000300 0407 0000 0000 0000 0000 0000 320e 0000&lt;/span&gt;
&lt;span class="gi"&gt;&amp;gt; 0000310 00c0 0000 0003 0000 0001 0000 0000 0000&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Interesting. Bit 2 of 0x315 and bit 0 of 0x30d got zeroed that time. Strangely, this only worked for keys C3 to D#3, and the two octave buttons. Where's the rest of the keys and the two wheels? Or the key velocity? Given that the wheels are just potentiometers wired to the main board, I have a good feeling that they'd use the M3's built in analog-to-digital converter, but at a guess I'd say the block of code responsible for activating the ADC sampling wasn't being hit, so no dice.&lt;/p&gt;
&lt;p&gt;Okay, so far this seems like a distraction. The Cortex M3 is alive and well; at a guess from stepping through the code with the J-Link I'd say it was stuck in a holding pattern waiting for the USB interface to come up. That leaves the GL850G USB hub-on-a-chip, which has the M3 wired up to one of the downstream inputs, along with the two female USB connectors. Everything we care about is getting 5V; the M3, the hub, and the two USB ports. What's not happening is a proper USB handshake!&lt;/p&gt;
&lt;img src="/writing/2015/11/08/jlink_korg_microkey/soldering_crap.jpg" alt="Test rig next to soldering equipment" class="image-process-1200" title="YOUR DEATH WILL NOT GO UN-AVENGED"/&gt; &lt;p&gt;And sadly, this is where the current line of investigation stops. The GL850G doesn't have a JTAG/SWD interface; the datasheet does mention a TEST mode and an I2C interface, but buggered if I know how those work (the datasheet certainly doesn't say), and I'm not keen on soldering more stuff to the microscopic chip legs. Dumping the USB traffic using the Linux usbmon driver doesn't reveal anything new; just like the dmesg output says, the device fails to identify itself at seemingly random points during the initial handshake. I guess the next level down from there would be to try a USB logic analyser like the &lt;a class="reference external" href="http://www.totalphase.com/products/beagle-usb12/"&gt;Beagle 12&lt;/a&gt;, or maybe one of those fancy digital oscilloscopes that support USB TX, but those cost more than 500 dollars! I think the new plan will be to buy a totally-not-bootleg replacement GL850G on eBay (hello China!), then have a stab at desoldering the current one without ruining everything. Plan C is buy a used microKEY 37 on eBay and steal the main board.&lt;/p&gt;
&lt;p&gt;Still, using the J-Link was a fun experience. For anyone looking to brush up their reverse engineering know-how, I highly recommend finding some hardware with one of these Cortex M3 SOCs inside and having a go debugging it with a J-Link. As a beginner to assembly language, I was quite impressed with how easy to read the Cortex M3 instructions were. Then again my last experience was reading 16-bit x86, which even the fans will tell you is a horrific architecture with no redeeming qualities, except maybe the power to induce vomiting. If the GL850G transplant works, we'll be sure to pick up where we left off and continue exploring the many nooks and crannies of the microKEY hardware.&lt;/p&gt;
</content><category term="Edutainment"/><category term="segger"/><category term="j-link"/><category term="korg"/><category term="microkey"/><category term="debugging"/><category term="detectives"/><category term="electronics"/><category term="inexperience"/></entry><entry><title>Introducing Mr. Crowbar! Untold minutes of reverse engineering fun for ages 26-29!</title><link href="/writing/2015/10/22/introducing_mr_crowbar/" rel="alternate"/><published>2015-10-22T00:00:00+08:00</published><updated>2015-10-22T00:00:00+08:00</updated><author><name>Scott</name></author><id>tag:None,2015-10-22:/writing/2015/10/22/introducing_mr_crowbar/</id><summary type="html">&lt;img src="/writing/2015/10/22/introducing_mr_crowbar/header.svg" alt="Mr. Crowbar logo" class="" title="Amazing how directionless a personal project can seem until you take the time to draw a crowbar with googly eyes. Only then, my friends, does everything fall into place."/&gt; &lt;p&gt;I believe that one of the best ways of allowing creativity to overcome limited technical skill is to build upon stuff that already exists. Take game modding; just try and count the number of successful games that started out as a modification of an existing one. Would these developers have gotten anywhere as far with their idea if they couldn't build on an existing engine and assets, not to mention community support? WOULD THEY?&lt;/p&gt;
&lt;p&gt;Indeed, game modding is an important springboard for new developers to cut their teeth in a familiar setting and form a good understanding of how games work in the real world. There is, however, one rather large barrier of entry for people to start playing with the innards of their favourite game: &lt;strong&gt;tools&lt;/strong&gt;. Occasionally the engine author will encourage custom content and give their developer tools out for free. But most of the time games are 100% &lt;strong&gt;not&lt;/strong&gt; designed with modding in mind, leaving it up to a tiny number of skilled reverse engineers to write their own tools and share them with the community.&lt;/p&gt;
</summary><content type="html">&lt;img src="/writing/2015/10/22/introducing_mr_crowbar/header.svg" alt="Mr. Crowbar logo" class="" title="Amazing how directionless a personal project can seem until you take the time to draw a crowbar with googly eyes. Only then, my friends, does everything fall into place."/&gt; &lt;p&gt;I believe that one of the best ways of allowing creativity to overcome limited technical skill is to build upon stuff that already exists. Take game modding; just try and count the number of successful games that started out as a modification of an existing one. Would these developers have gotten anywhere as far with their idea if they couldn't build on an existing engine and assets, not to mention community support? WOULD THEY?&lt;/p&gt;
&lt;p&gt;Indeed, game modding is an important springboard for new developers to cut their teeth in a familiar setting and form a good understanding of how games work in the real world. There is, however, one rather large barrier of entry for people to start playing with the innards of their favourite game: &lt;strong&gt;tools&lt;/strong&gt;. Occasionally the engine author will encourage custom content and give their developer tools out for free. But most of the time games are 100% &lt;strong&gt;not&lt;/strong&gt; designed with modding in mind, leaving it up to a tiny number of skilled reverse engineers to write their own tools and share them with the community.&lt;/p&gt;

&lt;p&gt;And these tools? Sometimes they'll let you edit stuff, sometimes only view. Sometimes they will be open source, sometimes not. Sometimes they'll be actively maintained, sometimes a rando will post an EXE on a forum then vanish off the face of the planet. Sometimes there won't even be a tool; just some old C code, or a meticulously-put-together yet scabby text file from the bulletin board age describing the file format. There's no standard!&lt;/p&gt;
&lt;p&gt;It occured to me that most of the tools that are out there share a ton of common ground, in that they are mostly just fancy GUIs that implement a CRUD (Create Read Update Delete) interface. Sometimes it's just R! Well, what if you didn't have a fancy GUI per se, but instead had a nice understandable CRUD model bolted onto a scripting language with the option of a fancy GUI later on?&lt;/p&gt;
&lt;p&gt;So after getting this dumb idea after unearthing some horribly-written tools I made in high school, I reached for my favourite language for messy hacking times (Python), put together a test model targeted at a game that I mostly knew the file formats for (the 1991 classic &amp;quot;Lemmings&amp;quot; by DMA Design), and named it &lt;a class="reference external" href="https://github.com/moralrecordings/mrcrowbar"&gt;Mr. Crowbar&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The object model steals a lot of ideas from Django and Schematic; each Block class is defined as a set of Field objects, but thanks to the mindbending power of metaclasses they can be get/set by the user at runtime with normal types like ints and strings, hiding all of that tedious validation and type-safety. In Mr. Crowbar, each Block class is strongly typed and defines the equivalent of a C struct, and gives you importing and exporting stuff as bytes for free. For non-trivial storage (e.g. compression algorithms) you can write manual import/export routines and wrap them in a Transform class, which can be used as a preprocessor for loading bytes into Blocks. Finally there's a Loader class, which does most of the spadework in loading the full spread of files from a game and linking up cross-references.&lt;/p&gt;
&lt;p&gt;Here's an example of a Block object:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Terrain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;mrc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Block&lt;/span&gt; &lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;_block_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;       &lt;span class="mi"&gt;4&lt;/span&gt;

    &lt;span class="n"&gt;x_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;             &lt;span class="n"&gt;mrc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UInt16_BE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bitmask&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\x0f\xff&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1600&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;draw_back&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;         &lt;span class="n"&gt;mrc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;0b10000000&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;draw_upsidedown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;   &lt;span class="n"&gt;mrc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;0b01000000&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;draw_erase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;        &lt;span class="n"&gt;mrc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;0b00100000&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;y_raw_coarse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;      &lt;span class="n"&gt;mrc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Int8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mh"&gt;0x02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;82&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;y_raw_fine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;        &lt;span class="n"&gt;mrc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mh"&gt;0x03&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mb"&gt;0b10000000&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;obj_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;            &lt;span class="n"&gt;mrc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UInt8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mh"&gt;0x03&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bitmask&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\x3f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;x&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt; &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x_raw&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;y&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt; &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y_raw_coarse&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y_raw_fine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This was taken from mrcrowbar.lib.games.lemmings. Every Level object has a list of Terrain objects; basically, info on where to put selected background tiles to build the level up with. Each of these Terrain references is 4 bytes, and there's a lot packed in there! The x position of the background piece is stored in the first two bytes (unsigned big-endian) as the least significant 12 bits. The remaining most significant bits are used as flags which affect how the tile is rendered. The y position is stored in two parts: a signed coarse component in byte 2, and a fine modifier as the most-significant-bit of byte 3. The 6 least significant bits of byte 3 make up the ID of the terrain object.&lt;/p&gt;
&lt;p&gt;What does this all mean? Well, if we open this class with 4 bytes as input, or it gets chainloaded by a larger struct (e.g. Level), all of the above field definitions will get replaced with a variable you can edit. Notice how we've exposed proper cartesian x and y as properties, maybe later we could add a setter which casts back to the original packed format. Then, after you're done changing stuff up in the class, you can run export_data() and get four bytes back. Or do it on the parent Level class and get a full level file back as bytes. What's not to love?&lt;/p&gt;
&lt;p&gt;Also, we strongly recommend you use a terminal with support for ANSI 24-bit colour, such as &lt;a class="reference external" href="https://en.wikipedia.org/wiki/GNOME_Terminal"&gt;GNOME Terminal&lt;/a&gt; for Linux or &lt;a class="reference external" href="https://en.wikipedia.org/wiki/ConEmu"&gt;ConEmu&lt;/a&gt; for Windows. Why? Well...&lt;/p&gt;
&lt;img src="/writing/2015/10/22/introducing_mr_crowbar/mrcrowbar_ansi.png" alt="Bitmap graphics from Lemmings printed into a terminal with UTF-8" class="" title="How about this little thing called AMAZING GRAPHICS?"/&gt; &lt;p&gt;That's right. Live bitmap previews in the terminal! At two pixels per letter! This is exactly what Guido van Rossum had in mind when he allowed people to override __str__ and balls to you if you think otherwise.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;BONUS ARCHAEOLOGICAL FIND:&lt;/strong&gt; I got sidetracked at one point and wrote a parser for those colour text screens you sometimes see at the end of shareware games, nagging you to buy the full thing. Take a look at the nag screen for the game &amp;quot;Boppin'&amp;quot; published by Apogee:&lt;/p&gt;
&lt;img src="/writing/2015/10/22/introducing_mr_crowbar/mrcrowbar_apogee.png" alt="Apogee ordering screen from the end of Boppin&amp;#x27;, with and without colouring" class="" title="With the exception of ol&amp;#x27; Wolf3D, the &amp;quot;cool VGA games&amp;quot; lineup from Apogee is a veritable Who&amp;#x27;s That of gaming history. Anyone remember Mystic Towers? Of course not; for reference that was the one where you played as the pantless 80-year-old man who poked things with a stick."/&gt; &lt;p&gt;The top is what you would normally see, painstakingly recreated with Unicode box-drawing characters to mimic the ol' DOS text mode. Underneath is exactly the same text, but without any of the colours. Notice how there's a bunch of hidden text in the blank areas? Looks like the Nag Screen Creative Director was caught short and had to steal a graphic from the company BBS. After all, no-one would ever suspect that it wasn't an original work... UNTIL NOW!&lt;/p&gt;
&lt;p&gt;Anyway, right now the project is just a babby; the only thing it can do is read some files from the 1991 classic &amp;quot;Lemmings&amp;quot; by DMA Design. Here's a list of features in the pipeline:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Export support! The whole idea of having a strongly-typed data model is so you can edit the content in Python, then reverse the import steps to get a binary again.&lt;/li&gt;
&lt;li&gt;Foreign key support! Games love nothing more than to have multiple files that reference one another.&lt;/li&gt;
&lt;li&gt;More standard models for common structures like lookup tables and streams!&lt;/li&gt;
&lt;li&gt;More games!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We're not even up to a proper release yet; I just needed to write something down before I exploded. First release will be when enough of Lemmings is satisfactorily editable; as of this moment we are an R application, with CUD to follow. Thanks for reading!&lt;/p&gt;
&lt;!-- Reverse engineering is clean harmless fun. It's fun to . Sometimes you get lucky and can see assets that never made it to the final game; like Sophia Hapgood's waiting room in Indiana Jones and the Fate of Atlantis. --&gt;
&lt;!-- Today you don't have to slog through pages of assembly readouts to look inside most old games, as the internet is full of talented sorts who've done the slog and documented their findings. If you want to know how the file formats work you can look for an open-source port of the game engine, check out a reverse engineering community like Shikadi, or even dig up some scabby BBS-era text files from Usenet/FTP. --&gt;
&lt;!-- But what about tools? There's a few generic ones about; notably Wombat Game Tools by szevvy (unmaintained and hard to find) and `Camoto + libgame* by Malvineous &lt;http://www.shikadi.net/camoto&gt;`_. Camoto looks promising and has a nice graphical interface, but the libraries are C++; a high barrier to entry for hacking and prototyping monkeyshines. Apart from that, there might be some indecipherable once-off developer aids about (I'm looking at you ScummVM tools), but I could not find a scriptable all-purpose utensil for poking old games. --&gt;
</content><category term="Devlog"/><category term="mr. crowbar"/><category term="python"/><category term="reversing"/><category term="dos"/><category term="games"/><category term="lemmings"/><category term="niche"/><category term="scratching"/><category term="itch"/><category term="scraping"/><category term="barrel"/></entry><entry><title>Guide: Backing up crap DVDs</title><link href="/writing/2015/10/10/backing_up_dvds/" rel="alternate"/><published>2015-10-10T00:00:00+08:00</published><updated>2015-10-10T00:00:00+08:00</updated><author><name>Scott</name></author><id>tag:None,2015-10-10:/writing/2015/10/10/backing_up_dvds/</id><summary type="html">&lt;img src="/writing/2015/10/10/backing_up_dvds/brasseye-case.jpg" alt="Brass Eye DVD case" class="letterbox image-process-1200" title="&amp;quot;Institutionalised cruelty is one thing, but the twisted brain-wrong of a one-off man-mental is quite another.&amp;quot;"/&gt; &lt;p&gt;Let's tangent for a moment and talk about DVDs. The DVD standard was designed for the low-powered embedded hardware of the late 1990s. The copy protection is primitive, and the DVD format is public knowledge by now, making it easy to backup or format shift your DVD collection as is your God-given first sale right (unless you live in the US or anywhere that just signed a trade agreement).&lt;/p&gt;
&lt;p&gt;For reasons of Christ Knows Why, some publishers (ignorant of how top-down video piracy generally works) think that the best way to prevent this is by using third-party copy protection schemes. This so-called protection is of the &amp;quot;no True Scotsman&amp;quot; variety; it adds a lot of intentional errors to the DVD that would make most software players (and the authors of the spec) rightfully vomit. But that's ok! Because the discs are designed for a True Hardware DVD Player, one that is infinitely more robust than your PC-based counterparts and will soar over the corrupted blocks like a majestic eagle.&lt;/p&gt;
&lt;p&gt;Just to repeat this again, these publishers &lt;strong&gt;sell a product with severe mastering defects on purpose&lt;/strong&gt; and just hope the error correction is really good and you don't notice.&lt;/p&gt;
&lt;p&gt;This article will walk you through the five categories of dirty tricks used to limit fair use. Normally you only have to worry about a couple of these; but I had the misfortune to buy a DVD (Chris Morris' excellent &lt;a class="reference external" href="http://www.amazon.co.uk/gp/product/B000PC1MQO/"&gt;Brass Eye&lt;/a&gt;) which had ALL DAMN 5. And for the first time, none of the DVD playback software I tried would play the episodes when selected on the menu.&lt;/p&gt;
</summary><content type="html">&lt;img src="/writing/2015/10/10/backing_up_dvds/brasseye-case.jpg" alt="Brass Eye DVD case" class="letterbox image-process-1200" title="&amp;quot;Institutionalised cruelty is one thing, but the twisted brain-wrong of a one-off man-mental is quite another.&amp;quot;"/&gt; &lt;p&gt;Let's tangent for a moment and talk about DVDs. The DVD standard was designed for the low-powered embedded hardware of the late 1990s. The copy protection is primitive, and the DVD format is public knowledge by now, making it easy to backup or format shift your DVD collection as is your God-given first sale right (unless you live in the US or anywhere that just signed a trade agreement).&lt;/p&gt;
&lt;p&gt;For reasons of Christ Knows Why, some publishers (ignorant of how top-down video piracy generally works) think that the best way to prevent this is by using third-party copy protection schemes. This so-called protection is of the &amp;quot;no True Scotsman&amp;quot; variety; it adds a lot of intentional errors to the DVD that would make most software players (and the authors of the spec) rightfully vomit. But that's ok! Because the discs are designed for a True Hardware DVD Player, one that is infinitely more robust than your PC-based counterparts and will soar over the corrupted blocks like a majestic eagle.&lt;/p&gt;
&lt;p&gt;Just to repeat this again, these publishers &lt;strong&gt;sell a product with severe mastering defects on purpose&lt;/strong&gt; and just hope the error correction is really good and you don't notice.&lt;/p&gt;
&lt;p&gt;This article will walk you through the five categories of dirty tricks used to limit fair use. Normally you only have to worry about a couple of these; but I had the misfortune to buy a DVD (Chris Morris' excellent &lt;a class="reference external" href="http://www.amazon.co.uk/gp/product/B000PC1MQO/"&gt;Brass Eye&lt;/a&gt;) which had ALL DAMN 5. And for the first time, none of the DVD playback software I tried would play the episodes when selected on the menu.&lt;/p&gt;

&lt;p&gt;This is the official pitch video for the copy-protection found on this DVD.&lt;/p&gt;
&lt;iframe width="420" height="315" src="http://www.youtube-nocookie.com/embed/ELOr_dLgddE" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;p&gt;I had been officially swindled out of £5.75, so for the record I did make the exact face shown here at 1:07. I mean, at least when a music CD is mastered with defects in a similar way (e.g. Copy Control), the Red Book standard forbids those idiots from stamping the CDDA logomark onto their flat plastic turd. But I guess the DVD Consortium has a much simpler set of rules to qualify for their logo, such as &amp;quot;anyone who pays us $5000&amp;quot;.&lt;/p&gt;
&lt;img src="/writing/2015/10/10/backing_up_dvds/brasseye-disc.jpg" alt="Brass Eye DVD, focusing on &amp;quot;DVD Video&amp;quot; mark" class="letterbox image-process-1200" title="It&amp;#x27;s the lying that hurts the most."/&gt; &lt;div class="section" id="the-categories"&gt;
&lt;h2&gt;The categories&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;CSS.&lt;/strong&gt; The region coding protection built into the DVD standard. Each VOB file in VIDEO_TS has a 40-bit descrambling key, which is stored on a hidden area of the disc only accessible by querying the drive firmware. If you're lucky enough to have a RPC1 (region-free) drive, then libdvdcss will have no problem yanking the keys. Nearly all drives are RPC2 (a.k.a. you're allowed 5 region changes and you better like the last one because IT'S PERMANENT BUDDY), and will only let you access the disc keys if the drive region is one of the disc regions. Some DVD drives are manufactured by assholes; not only will they withold the keys, but they'll also throw a ton of IO errors if the region is incorrect!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Analogue copyprotection.&lt;/strong&gt; Another pack-in option, this is a flag which causes the analogue video ports on a hardware DVD player to send crazy nonsense during the vertical blanking interval, which will either confuse the shit out of the image gain circuitry in an old VHS deck, or tell a new VHS deck/DVR to stop recording and instead display a patronising message about stealing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Corrupted sectors.&lt;/strong&gt; A popular technique for some protection schemes is to simply master the DVD with intentional cockups in the data sectors on the disc. The thinking behind this is that True Hardware DVD Players are meant to deal with misanthropes who leave their discs face-down on sandpaper and powdered glass, and should therefore glide smoothly (hah!) over any unreadable bits. If you try and copy the VOB files straight from the disc with a file manager, it'll die in the arse once it reaches the first 2048-byte sector that it can't read.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;File table abuse.&lt;/strong&gt; Like most filesystems, UDF can be dumbed down to the idea of a file table (i.e. the tree structure of directories and files), followed by a large blob containing all the binary data. Each entry in the table has a reference to exactly where, inside the huge blob, a file's data starts and finishes. If you're thinking like a copy-protection engineer, you will immediately see the opportunity to abuse this and make hundreds of decoy files with references to overlapping data, creating a mess that appears several times larger the 9GB limit of a dual-layer DVD.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Title index mauling.&lt;/strong&gt; Because they just haven't done enough already, the final place to inflict pain on the user is in the title and chapter structure of the DVD. Hey, the standard lets us have up to 99 titles which can reference any chunk of the DVD video stream, why &lt;em&gt;shouldn't&lt;/em&gt; we fill them all up with corrupted copies that terminate 15 minutes in! And those chapter breaks; that sounds like an amazing opportunity to randomly skip to different episodes!&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="how-to-get-around-it"&gt;
&lt;h2&gt;How to get around it&lt;/h2&gt;
&lt;p&gt;I'm going to go through the steps (in order) I went through to surgically remove the damage. You will need:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;A faulty piece of shit video DVD&lt;/li&gt;
&lt;li&gt;A computer with a good DVD drive running Linux (a bootable Ubuntu USB will work in a pinch)&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://help.ubuntu.com/community/RestrictedFormats/PlayingDVDs"&gt;libdvdcss2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.gnu.org/software/ddrescue/"&gt;ddrescue&lt;/a&gt; (apt: gddrescue)&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://www.videolan.org/"&gt;VLC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://dvdbackup.sourceforge.net/"&gt;dvdbackup&lt;/a&gt;/&lt;a class="reference external" href="http://vobcopy.org/projects/c/c.shtml"&gt;vobcopy&lt;/a&gt;/&lt;a class="reference external" href="http://handbrake.fr/"&gt;Handbrake&lt;/a&gt; (depending on what you want to do with the DVD image)&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://www.winehq.org/"&gt;Wine&lt;/a&gt; + &lt;a class="reference external" href="http://www.videohelp.com/tools/VobBlanker"&gt;VobBlanker&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want instructions on how do this kind of thing using only Windows, you're insane. And you'll have to scour through someplace like Doom9.org because I don't know!&lt;/p&gt;
&lt;div class="section" id="css"&gt;
&lt;h3&gt;CSS&lt;/h3&gt;
&lt;p&gt;Try playing your disc in VLC.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;If the picture looks fine, your drive is cooperating with libdvdcss.&lt;/li&gt;
&lt;li&gt;If the picture looks green and corrupted and stuttery, then your drive can access the video data stored on the disc but can't get the CSS keys to descramble it; see the end of this section about how to copy the CSS title keys from a working computer.&lt;/li&gt;
&lt;li&gt;If VLC is throwing errors or a blank screen, and dmesg is full of whining like &amp;quot;Read of scrambled sector without authentication&amp;quot;, the firmware on your DVD drive has noticed the disc is the wrong region and is telling you at the IO level to piss off. At this point, you are stuck; you can't get around this without first switching the drive's region or flashing the firmware to be RPC1 (aka. region-free).&lt;/li&gt;
&lt;/ul&gt;
&lt;img src="/writing/2015/10/10/backing_up_dvds/brasseye-shant.png" alt="VLC region-lock error window" class="" title="Thanks for doing your bit to support the content industry!"/&gt; &lt;p&gt;Region-free flashing requires Windows; try looking up your drive's model using &lt;a class="reference external" href="http://discinfo.rpc1.org"&gt;DiscInfo&lt;/a&gt;, then check if either the &lt;a class="reference external" href="http://files.rpc1.org/"&gt;RPC1 firmware database&lt;/a&gt; or the &lt;a class="reference external" href="http://rpc1.org"&gt;RPC1 forums&lt;/a&gt; have an exact match. You can properly ruin your drive if you don't know what you're doing; if you're just starting out, try it on an older drive which you can live without if things go wrong.&lt;/p&gt;
&lt;p&gt;I can't remember if I'm just lucky, or if I flashed it once at 3am and can't remember, but my Pioneer BDR-206 is a region free drive 99% of the time. (so far it's failed exactly once; it couldn't read the CSS title keys for the brilliant &lt;a class="reference external" href="http://www.amazon.com/gp/product/B000CQQID0/"&gt;Mr. Show with Bob and David&lt;/a&gt;. I had to grab the keys from a machine with a Region 1 drive)&lt;/p&gt;
&lt;p&gt;Anyway, do whatever you have to do to get the disc playing in VLC under Linux. The next step is to open up the .dvdcss folder in your home directory; in here is a copy of the extracted CSS title keys of every disc you've played on the computer up until now. Mine were stored in a folder called BRASS_EYE-2010012910090800-0000000063. This is going to be important later on.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="analogue-copyprotection"&gt;
&lt;h3&gt;Analogue copyprotection&lt;/h3&gt;
&lt;p&gt;Not an issue, as all DVD and VOB backup tools disable this flag. Moving on!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="corrupted-sectors"&gt;
&lt;h3&gt;Corrupted Sectors&lt;/h3&gt;
&lt;p&gt;I'm going to invoke the famous &amp;quot;duck test&amp;quot; here; if it looks like a duck, walks like a duck, swims like a duck, has feathers like a duck and quacks like a duck, then it is probably best served crispy with hoisin sauce. We're going to treat our DVD just like we would any other damaged, inches-away-from-death piece of media, and use a recovery tool designed to scrub around all of the bad sectors, which (if the copy-protection peddler is to be believed) aren't part of the video stream. It's a command-line tool called ddrescue, and works roughly like the following:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Create an image file exactly the same size as the source medium&lt;/li&gt;
&lt;li&gt;Do a quick first pass, skipping over any read errors and rescuing as much good data as possible&lt;/li&gt;
&lt;li&gt;Do a series of bisection passes over the holes, attempting to subdivide the corrupted blocks into smaller and smaller chunks until some data can be read&lt;/li&gt;
&lt;li&gt;Repeat the above step over and over again&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;ddrescue&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;-b&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2048&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/dev/sr0&lt;span class="w"&gt; &lt;/span&gt;brasseye.iso&lt;span class="w"&gt; &lt;/span&gt;brasseye.log
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;2048 is the bytes-per-sector size on a standard DVD, so you can't subdivide smaller than this. /dev/sr0 is the block device for my DVD drive, and I want it to retry the bad parts 5 times before giving up.&lt;/p&gt;
&lt;img src="/writing/2015/10/10/backing_up_dvds/brasseye-ddrescue.png" alt="GNU ddrescue running in a terminal" class="" title="I&amp;#x27;ve lost count of the number of times ddrescue has saved my bacon, but it&amp;#x27;s in the same ballpark as the number of hard drives I&amp;#x27;ve accidentally smashed."/&gt; &lt;p&gt;Before long, I have a brand new ISO that's a near-perfect copy of the defective DVD. True, there's still 1.3mb worth of holes, but they're filled with zeros and won't throw any IO errors. Sweet, let's toss it at VLC!&lt;/p&gt;
&lt;img src="/writing/2015/10/10/backing_up_dvds/brasseye-fail.png" alt="VLC playing CSS-scrambled DVD with heavy artifacts" class="" title="Encryption so strong you can still make out the picture somehow!"/&gt; &lt;p&gt;But wait, what?! But I copied all of it! Well, most of it! It worked fine before!&lt;/p&gt;
&lt;p&gt;Yeah, some of you have already got this. Copying the disc image doesn't copy the CSS keys! Let's take another look inside ~/.dvdcss&lt;/p&gt;
&lt;img src="/writing/2015/10/10/backing_up_dvds/brasseye-dvdcss.png" alt="File listing of ~/.dvdcss in a terminal" class="" title="Good reminder, anyone who hasn&amp;#x27;t seen Danger 5 yet should stop reading this article and look it up. Go on. I&amp;#x27;ll wait."/&gt; &lt;p&gt;Notice how we've now got two sets of disc keys: the ones pulled when I originally played the DVD (timestamped 3:40pm), and a new incomplete set guessed by libdvdcss for that ISO I just tried (timestamped 11:41pm). Do the sensible thing and copy the real keys from the old folder into the new disc folder. Now let's try playing that ISO with VLC again.&lt;/p&gt;
&lt;img src="/writing/2015/10/10/backing_up_dvds/brasseye-success.png" alt="VLC playing DVD correctly" class="" title="Even though it&amp;#x27;s broken, I&amp;#x27;m glad the Brass Eye DVD has a standard menu. The Day Today DVD has a shitty carousel you have to wait through."/&gt; &lt;p&gt;Much better. This ISO will now work with any video player or tool that uses libdvdcss to handle descrambling.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="file-table-abuse"&gt;
&lt;h3&gt;File table abuse&lt;/h3&gt;
&lt;p&gt;If you are processing the DVD with a tool that supports loading ISOs through libdvdcss, then you don't have to worry about this.&lt;/p&gt;
&lt;p&gt;Me, I wanted to have a try at scouring that broken title index with VobBlanker, which needs a fully decrypted VIDEO_TS folder to work on. There's a nice little tool called dvdbackup; just run it in mirror mode to decrypt the whole DVD structure to a folder.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;dvdbackup&lt;span class="w"&gt; &lt;/span&gt;--mirror&lt;span class="w"&gt; &lt;/span&gt;--input&lt;span class="o"&gt;=&lt;/span&gt;brasseye.iso&lt;span class="w"&gt; &lt;/span&gt;--output&lt;span class="o"&gt;=&lt;/span&gt;brasseye-decss
&lt;/pre&gt;&lt;/div&gt;
&lt;img src="/writing/2015/10/10/backing_up_dvds/brasseye-toc.png" alt="Video thumbnail view of the corrupted VOB index on a DVD" class="" title="Looks legit!"/&gt; &lt;p&gt;Depending on how creative the mastering engineer was, you're going to need a lot of hard-drive space for this. As you can see from the preview icons, this DVD had a lot of straight-out duplicated junk, inflating the size of the mirror to an impossible 18.7 GB. But it's still accurate to the file structure of the original DVD image, and we didn't intend to save it back into DVD format, so not a big deal.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="title-index-mauling"&gt;
&lt;h3&gt;Title index mauling&lt;/h3&gt;
&lt;p&gt;Popular DVD tools such as dvdbackup, handbrake and vobcopy are usually fine for getting the raw video data out of the VOBs; they just need to know what title number to rip.&lt;/p&gt;
&lt;p&gt;The easy method of working around a huge list of broken decoy titles, is to navigate to an episode using the DVD menus, and then write down what title number is being played back (e.g. the &amp;quot;Playback -&amp;gt; Title&amp;quot; menu in VLC)&lt;/p&gt;
&lt;p&gt;Unfortunately for me, the Brass Eye disc would only play one episode from the menu. It also had about 90 decoy titles in the index, each with a random length between 27 and 33 minutes, and wired up to random (sometimes unplayable) chunks of video.&lt;/p&gt;
&lt;p&gt;I mentioned VobBlanker earlier. This is a Windows tool designed to pick and mix titles from the DVD index, and shows information about how they sit in the VOB filesystem structure.&lt;/p&gt;
&lt;p&gt;Here we can take advantage of a few things:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Regardless of how ruined the indexing is, the video data for a title has to be stored in the VOBs consecutively. Jumping around on the disc too much will leave noticeable seek-related gaps in the footage&lt;/li&gt;
&lt;li&gt;In this case, duplicates made by abusing the file table appeared in consecutive order&lt;/li&gt;
&lt;/ul&gt;
&lt;img src="/writing/2015/10/10/backing_up_dvds/brasseye-vobblank1.png" alt="VobBlanker browsing the corrupted title index" class="" title="This process was marginally less awful than trying all 100 titles in VLC."/&gt; &lt;p&gt;We can see looking here that the disc can be divided up into rough 1GB-sized chunks, which correspond to an episode's worth of footage. This image here shows one of the VOBs, with an index full of broken decoy titles as seen in the bottom pane. The 3rd column contains the title number, and the 7th column contains the &amp;quot;duration&amp;quot;, as seen in VLC's playback controls (good luck guessing which titles are real by the length!). By contrast, the 8th contains the actual size of the content the title marker points to, with most of them being 0 bytes long! Yes, all of these titles are bollocks.&lt;/p&gt;
&lt;img src="/writing/2015/10/10/backing_up_dvds/brasseye-vobblank2.png" alt="VobBlanker showing a correct title" class="" title="But not by much."/&gt; &lt;p&gt;This image shows the correct title (number 46), which as you can see has a sensible duration and the largest filesize associated with it. Oddly enough these always seemed to appear at the end of the chunk.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="archival"&gt;
&lt;h2&gt;Archival&lt;/h2&gt;
&lt;img src="/writing/2015/10/10/backing_up_dvds/brasseye-handbrake.png" alt="Handbrake ripping a DVD" class="" title="&amp;quot;And yet, an astonishing sense of community here now, a positive atmosphere, a sense of a job well done, a shared sigh of relief very much like the bizarre euphoria at the end of an hours vomiting.&amp;quot;"/&gt; &lt;p&gt;After going through and writing down all the correct title numbers, I had something I could give to Handbrake. And there you have it; after 4 hours of slog, finally I can watch the content I paid for! Yep, can't imagine why anyone would be driven to piracy.&lt;/p&gt;
&lt;/div&gt;
</content><category term="Edutainment"/><category term="dvd"/><category term="defective"/><category term="ripping"/><category term="format shifting"/><category term="despair"/><category term="the future"/><category term="lousy"/></entry></feed>