<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><generator uri="https://cgit.chrisoft.org/sbs.git" version="2.6">SSBS</generator><updated>2026-06-24T14:38:36.478Z</updated><link href="https://chrisoft.org/blog/atom.xml" rel="self" type="application/atom+xml" title="Atom"/><link href="https://chrisoft.org/blog/" rel="alternate" type="text/html"/><id>https://chrisoft.org/blog/atom.xml</id><title>Specluncam Ursae</title><subtitle>Chris Xiong's blog posts</subtitle><icon>https://chrisoft.org/cx.png</icon><logo>https://chrisoft.org/cx.png</logo><entry><title>Pet Peeves: Web design</title><link href="https://chrisoft.org/blog/post/2026-04-14.html" rel="alternate" type="text/html" title="Pet Peeves: Web design"/><published>2026-04-14T00:00:00.000Z</published><updated>2026-04-16T16:59:45.117Z</updated><id>https://chrisoft.org/blog/post/2026-04-14.html</id><author><name>Chris Xiong</name><uri>https://chrisoft.org/</uri></author><content type="html" xml:base="https://chrisoft.org/blog/post/2026-04-14.html"><![CDATA[
<p>This might become a series. Let’s see …</p>
<p>WARNING: this is an “old mfer yelling at clouds” kind of post. If you
don’t take this kind of posts well, please, be kind to yourself and
close this page immediately, and optionally, set fire to your
computer.</p>
<p>The premise of this post is simple. I’m just going to list a few
common things that irritates me the most on the modern web (and
promising you that you’ll never see any of them on this website) and
why<a id="n1" href="#note1" class="note">[1]</a>. Let’s get
started.</p>
<p>But before that … one <strong>caveat</strong>: The list applies only
to websites that once existed in a more traditional form, e.g.&nbsp;news
sites, forums, personal homepages, etc. Web applications are fully
exempt from this list.<a id="n2" href="#note2" class="note">[2]</a></p>
<h2 id="tocanch0" class="tvis">infinite scrolling</h2>
<p>The first time I noticed the existence of infinite scrolling, it was
on some social media website that I’m no longer active on. And I
instantly dreaded it.</p>
<p>Being the somewhat cynical person that I am, I’m quite convinced that
this mechanism is designed by social media companies to get people
addicted to their platform. There’s basically no other way to explain
why it even became a thing.</p>
<p>Set aside the psychological manipulation, many implementations of
infinite scrolling are plagued by performance issues caused by either
not unloading old content, or initiating the automatic request way too
late, or a combination of them. It also messes with scroll aide
extensions.</p>
<p>The bare minimum to make this infinite scrolling idea remotely
redeemable, in my opinion, is to trigger the loading only after an
explicit, obviously intentional user interaction that is not merely
scrolling close to the end of the page. Or better still, just implement
good old pagination, which, when implemented properly, would also allow
the viewer to skip to the page they want to see instantly, a feature
that most implementations of infinite scrolling lack.</p>
<table>
<tbody>
<tr class="odd">
<td style="text-align: left;">Offensiveness:</td>
<td style="text-align: left;">★★★★★</td>
</tr>
<tr class="even">
<td style="text-align: left;">Chance of encounter:</td>
<td style="text-align: left;">★★★★☆</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Occasions where excusable:</td>
<td style="text-align: left;">☆☆☆☆☆</td>
</tr>
</tbody>
</table>
<h2 id="tocanch1" class="tvis">wasted screen estate</h2>
<p style="width: 30vw; margin: auto;">
If the content on your website is artificially limited to take up only
less than half of the available length in either dimension, you should
probably reconsider your life choices.
</p>
<p>(Unless your content only use that much space, that is.)</p>
<table>
<tbody>
<tr class="odd">
<td style="text-align: left;">Offensiveness:</td>
<td style="text-align: left;">★★★★☆</td>
</tr>
<tr class="even">
<td style="text-align: left;">Chance of encounter:</td>
<td style="text-align: left;">★★★☆☆</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Occasions where excusable:</td>
<td style="text-align: left;">★☆☆☆☆</td>
</tr>
</tbody>
</table>
<h2 id="tocanch2" class="tvis">fake content blocks
as loading indicators</h2>
<p>This one annoys the hell out of me, because the only purpose it
appears to serve is to slow down the loading of the actual content,
which is already slow as f if you are browsing the modern web on an
older computer.</p>
<p>Yes the kind that shows empty cards that have an occasional animated
glare flashing through is the most annoying.</p>
<p>One occasion where this is potentially excusable is to stop elements
from being shifted around when the page fully loads.<a id="n3" href="#note3" class="note">[3]</a> However:</p>
<ul>
<li>many implementations are done so poorly that these placeholders fail
to do what they’re supposed to: elements still shift around as things
get loaded in.</li>
<li>if you don’t make your website so goddamn heavy, it wouldn’t take
such a long time to load would it? Saying this especially to those
websites that used to have a much lighter incarnation but are now simply
insufferably huge.</li>
<li>if you must use these as placeholders, they don’t have to be
animated in a way that takes <strong>more</strong> resources to render
than the actual content they are supposed to hold, do they?</li>
</ul>
<p>The Brits in fact have a pretty good word for this kind of design.
It’s called “wankery”.</p>
<table>
<colgroup>
<col style="width: 31%">
<col style="width: 68%">
</colgroup>
<tbody>
<tr class="odd">
<td style="text-align: left;">Offensiveness:</td>
<td style="text-align: left;">★★★★★</td>
</tr>
<tr class="even">
<td style="text-align: left;">Chance of encounter:</td>
<td style="text-align: left;">★★★☆☆ (Higher chance on websites owned by
the big corpo)</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Occasions where excusable:</td>
<td style="text-align: left;">★☆☆☆☆</td>
</tr>
</tbody>
</table>
<h2 id="tocanch3" class="tvis">overriding common browser
shortcuts</h2>
<p>No, I don’t want to see your poorly implemented custom search box
when I press ctrl+f.&nbsp;And I don’t want to be forced to press it twice to
use the native search either. So quit doing it.</p>
<p>Obviously if your custom search actually functions properly, or if
the overridden shortcut serves little purpose on your page, this is
probably excusable.</p>
<table>
<tbody>
<tr class="odd">
<td style="text-align: left;">Offensiveness:</td>
<td style="text-align: left;">★★★★☆</td>
</tr>
<tr class="even">
<td style="text-align: left;">Chance of encounter:</td>
<td style="text-align: left;">★★☆☆☆</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Occasions where excusable:</td>
<td style="text-align: left;">★★★☆☆</td>
</tr>
</tbody>
</table>
<p class="noindent">
<a id="n4" href="#note4" class="note">[4]</a>
</p>
<h2 id="tocanch4" class="tvis">autoplay on
non-media-centric pages</h2>
<p>Autoplay audio has been such an annoying problem that most browsers
these days are outright banning autoplay with sound. But autoplay of
silent videos is just equally as annoying. It might be silently (duh)
diverting the viewer’s attention without them even noticing it.</p>
<p>Unsurprisingly the biggest use of this is by big corpo websites to
show you ads. It’s almost like that they know exactly what they are
doing: being the only thing that moves on the screen, the reader’s
attention would be surely all drawn to it, giving them the ad impression
they crave.</p>
<table>
<tbody>
<tr class="odd">
<td style="text-align: left;">Offensiveness:</td>
<td style="text-align: left;">★★★★☆</td>
</tr>
<tr class="even">
<td style="text-align: left;">Chance of encounter:</td>
<td style="text-align: left;">★★★☆☆</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Occasions where excusable:</td>
<td style="text-align: left;">★☆☆☆☆</td>
</tr>
</tbody>
</table>
<h2 id="tocanch5" class="tvis">any attempted adblock
detection</h2>
<p>First of all, I’m sorry that you have to use one of the worst ways to
monetize your website. My condolences. Second, you know it will
absolutely be bypassed, right?</p>
<table>
<tbody>
<tr class="odd">
<td style="text-align: left;">Offensiveness:</td>
<td style="text-align: left;">★★★★★</td>
</tr>
<tr class="even">
<td style="text-align: left;">Chance of encounter:</td>
<td style="text-align: left;">★★☆☆☆ (but rising rapidly)</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Occasions where excusable:</td>
<td style="text-align: left;">☆☆☆☆☆</td>
</tr>
</tbody>
</table>
<h2 id="tocanch6" class="tvis">registration wall</h2>
<p>Well, I guess this is understandable (but in no way excusable) for
big corpo websites that have an unholy thirst for their visitor’s
personal info and selling it to data brokers later on, LinkedIn comes to
mind as the worst offender.</p>
<p>But what baffles me more is that this practice has also been
incredibly common in smaller forums. Some have even more ridiculous
requirements on account age or some kind of active engagement to perform
the most basic tasks. And no it’s not an anti-bot measure, because what
is being restricted is almost always access to some information that new
visitors might actually want, and the engagement requirement makes it
obvious that the owner is absolutely craving the engagement. What good
does it do to have two more registered users on your scrappy little
forum who will never return?</p>
<p>Fortunately this nonsense seems to be in decline especially on newer
websites, at least partially thanks to the Internet being dead or
dying.</p>
<table>
<tbody>
<tr class="odd">
<td style="text-align: left;">Offensiveness:</td>
<td style="text-align: left;">★★★☆☆</td>
</tr>
<tr class="even">
<td style="text-align: left;">Chance of encounter:</td>
<td style="text-align: left;">★★☆☆☆</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Occasions where excusable:</td>
<td style="text-align: left;">☆☆☆☆☆</td>
</tr>
</tbody>
</table>
<h2 id="tocanch7" class="tvis">dishonorable mentions</h2>
<p>These will not be given ratings, but are still disgusting in my
opinion.</p>
<h3 id="tocanch8" class="tvis">persistent,
substantial animated elements for no reason</h3>
<p>Well, I’m talking about your “cool mesh of vertices floating around
with highlight following mouse” kind of thing. I’ve seen that kind of
background on a surprisingly wide range of websites: from personal
websites to websites of startup companies. It comes in many different
flavors.</p>
<p>That stuff might be cool for the first 5 seconds, then it becomes
nothing but distraction and a waste of resources.</p>
<p>Yes, your cute Live2D-esque animated waifu also counts. At least let
the user to turn it off for goodness’ sake.</p>
<h3 id="tocanch9" class="tvis">using fixed-width
font for big wall of texts</h3>
<p class="reduced">
This would have been a self-own of epic proportions had the post been
published a day earlier. Because this entire website was using Computer
Modern Unicode Typewriter Text as its primary font …
</p>
<p>Well, the website isn’t your text editor, is it? So why would you do
that? Torture your readers’ eyes?</p>
<h2 id="tocanch10" class="tvis">disagree with the list? too
bad.</h2>
<p>This should go without saying, but this list is EXTREMELY SUBJECTIVE.
If you disagree with the list, feel free to do any of the following:</p>
<ul>
<li>Write a detailed rebuttal of the points you disagree with, and send
it as an e-mail to my <a href="https://chrisoft.org/#contact">compliant
inbox</a>, which I’ll surely read thoroughly, or better still, pipe it
right into <code>/dev/null</code>.</li>
<li>Make your own list.</li>
<li>Or set fire to your computer, again.</li>
</ul>
<p>Anyway, I’m getting outta here. 2026-04-15 13:25:35</p>
<br><hr><div class="TText" id="notediv" style="font-size:80%;"><span class="TText"><a id="note1" href="#n1">[1]</a>: obviously from my own perspective. Of course as a person
whose perceptions are functioning mostly within typical specifications,
I do not have much to say about accessibility.<br></span><span class="TText"><a id="note2" href="#n2">[2]</a>: Maybe the idea of “webapp” is bad on its
own merits? Yes you skip the download and install part, but now you get
something that often performs very poorly like trying to run Windows XP
on a Pentium MMX with 64MB RAM. What’s worse is that these days a big
chunk of the web is trying way too hard to become web apps despite there
being absolutely no good reason to, for example a certain piece of forum
software … cough cough.<br></span><span class="TText"><a id="note3" href="#n3">[3]</a>: This
section was added after a friend somehow convincing me that there
<em>could</em> be a legitimate use for these things, and I upped the
“occasions where excusable” rating from zero to one … See? I’m not
stubborn as a mule!<br></span><span class="TText"><a id="note4" href="#n4">[4]</a>: If at this point you started wondering, “is this post a hit
piece on Discourse?” because in its most commonly used configuration it
already hit at least 3 of the 4 points so far: then congratulations, you
are <em>almost</em> right! The idea of this post came to my mind because
I wanted to voice my grievances against Discourse. But it turned into
just me lamenting over the general enshittification of the Internet.
Unfortunately communities like KDE, Framework, Ubuntu, and GNOME have
fully embraced that piece of trash forum software. But folks at Gentoo,
Arch Linux and Debian are still resisting. So we will be okay after all,
right? <strong>RIGHT?</strong><br></span></div>]]></content></entry><entry><title>Another Random Mid-year Website Update and A Decade on the Internet</title><link href="https://chrisoft.org/blog/post/2026-04-09.html" rel="alternate" type="text/html" title="Another Random Mid-year Website Update and A Decade on the Internet"/><published>2026-04-09T00:00:00.000Z</published><updated>2026-04-16T16:58:11.381Z</updated><id>https://chrisoft.org/blog/post/2026-04-09.html</id><author><name>Chris Xiong</name><uri>https://chrisoft.org/</uri></author><content type="html" xml:base="https://chrisoft.org/blog/post/2026-04-09.html"><![CDATA[
<p>Almost exactly a decade ago, the domain you’re currently visiting was
registered … Feeling excited, I posted this on the Chinese social media
I was using at the time:</p>
<div class="block" style="display: block;">
<p>破费了53.14大洋搞到了个域名（服务器还是我的笔记本！。。）。。。于是qq空间即将关闭。。这次真的要迁移到这里以及twitter上了。。。</p>
<p>Translation: Grabbed a domain name after parting ways with 53.14
CNY<a id="n1" href="#note1" class="note">[1]</a> (the website is still
served from my laptop!) … I’m ditching this platform<a id="n2" href="#note2" class="note">[2]</a> … I’m finally migrating to my
own website and twitter for realz this time …</p>
<table class="gallery dynaheight">
<tbody><tr>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/2026-04-09/snapshot170.png"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/2026-04-09/ssbsthumb_1536x1536_snapshot170.png"></a>
</div>
</td>
</tr>
</tbody></table>
<div style="display:table;width:100%;table-layout:fixed;">
<span class="left">#QQ空间动态 (QZone Status)</span> <span class="right date"><span class="datetext">2016-01-03<span class="datet">T</span>20:22:00<span class="datetz">+08:00</span></span></span>
</div>
</div>
<p>Yes! This website has been on the Internet for more than a
decade!</p>
<h2 id="tocanch0" class="tvis">What’s new?</h2>
<h3 id="tocanch1" class="tvis">Gallery browsing</h3>
<p>You have already seen it above. But now the blog presents galleries
in a completely different way. To better demonstrate what this does,
here are some random flowers of this Spring (and a deer for some
reason):</p>
<table class="gallery">
<tbody><tr>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/2026-04-09/20260403_163912.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/2026-04-09/ssbsthumb_1536x1536_20260403_163912.jpg"></a>
</div>
</td>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/2026-04-09/20260327_164135.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/2026-04-09/ssbsthumb_1536x1536_20260327_164135.jpg"></a>
</div>
</td>
</tr>
<tr>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/2026-04-09/20260327_164124.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/2026-04-09/ssbsthumb_1536x1536_20260327_164124.jpg"></a>
</div>
</td>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/2026-04-09/20260327_163720.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/2026-04-09/ssbsthumb_1536x1536_20260327_163720.jpg"></a>
</div>
</td>
</tr>
<tr>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/2026-04-09/IMG_20260327_162610.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/2026-04-09/ssbsthumb_1536x1536_IMG_20260327_162610.jpg"></a>
</div>
</td>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/2026-04-09/20260324_120409.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/2026-04-09/ssbsthumb_1536x1536_20260324_120409.jpg"></a>
</div>
</td>
</tr>
<tr>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/2026-04-09/20260324_120303.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/2026-04-09/ssbsthumb_1536x1536_20260324_120303.jpg"></a>
</div>
</td>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/2026-04-09/20260223_024138.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/2026-04-09/ssbsthumb_1536x1536_20260223_024138.jpg"></a>
</div>
</td>
</tr>
</tbody></table>
<p>The experience is based on the design popularized by the Lightbox JS
library. This is however, my own implementation from scratch, so it
probably has some jank in there. But this is in fact a somewhat
frequently requested feature (for a low-traffic personal website), and
you all can have it now. One problem with the current implementation is
that it’s currently somewhat non-obvious that besides clicking on the
image to zoom, you can also do so using your mouse wheel.</p>
<p>If you have JavaScript disabled for this website, or using a browser
that doesn’t support it at all, worry not because care was taken to make
it have as little impact on users who have JavaScript disabled. Photo
galleries are still sent to your browser in their old form (as tables),
and then converted into the new gallery form with a client-side
script.</p>
<p>This also means if you prefer the old style for whatever reason, the
new gallery can be disabled trivially by using a link at the bottom of
the page, which sets a flag in your browser’s local storage. It is
<strong>not</strong> saved as a cookie and never reported back to the
server. If you do have JavaScript on in your browser though, this new
gallery feature will be enabled by default, because I do believe this
offers a superior experience than looking at photos in tables.</p>
<p>Notekins received the same feature.</p>
<h3 id="tocanch2" class="tvis">New about page</h3>
<p>The about page was completely redone. Gone was the bullet list. It
was replaced by a page featuring somewhat satirical uses of <a href="https://en.wikipedia.org/wiki/Wikipedia:Userboxes">“Userboxes”
found on Wikipedia</a>.</p>
<p>I’m actually slightly sad to see the old about page go. Apparently it
has inspired at least a few other people’s about pages, which I’m not
necessarily proud of: after all it is a <em>pretty</em> terrible way to
present an about page on a personal website (in my opinion).</p>
<p>Fun fact: when that about page was being set up, I was making it with
the expectation that it’s just going to be a placeholder and
anticipating that it would be replaced soon after. This “soon” lasted a
whopping 8 years almost. Whoopsies.</p>
<p>My Wikipedia user page itself (which I will <strong>not</strong> link
here) is also plastered with userboxes, some of which are even recreated
on my about page here. However, that was in fact not the direct
inspiration of the new about page. The inspiration actually came from
someone else’s personal website, which featured a late-90s / early-00s
design with a collection of badges (or “88x31s” ). I thought to myself:
“Huh, I kinda like that. But I’m absolutely hopeless when it comes to
making that kind of artwork. So instead of spamming badges on my
website, how about spamming userboxes? I LOVE userboxes!” And that was
how eventually the current version of my about page came into being.</p>
<p>The project listing was redesigned too, now simply using the
<code>&lt;details&gt;</code> element instead of the JavaScript solution
I had initially.</p>
<p>I found myself using flex box way too much on my website lately.
Probably doesn’t bode well for browsers from the 2000s … But hey it’s
still somewhat readable in Lynx!</p>
<p>I’m making a version of the about page that explains why each entry
is in there. This is, however, subject to my usual procrastination.</p>
<h3 id="tocanch3" class="tvis">Primary font changed</h3>
<p>The primary font used on this website has been switched to Computer
Modern Unicode Typewriter Text Variable Width, a proportional font, from
Computer Modern Unicode Typewriter Text, a monospace font.</p>
<p>It’s probably one of the bigger changes happened to this website that
will be instantly visible to all visitors. Even though it’s just the
proportional version of the same font, it gives the website a completely
different look and feel.</p>
<p>This change is made because I suddenly realized how weird large
paragraphs of texts look in monospace fonts (which is why no sane
website is doing it unless it’s going after a very specific kind of
aesthetics), and to prevent a self-own in my <a href="/blog/post/2026-04-14.html#tocanch9">new post</a>.</p>
<h3 id="tocanch4" class="tvis">AI Apocalypse</h3>
<div class="block TText" style="display:block;">
<p>This is what AI has done to us … or me in this case.</p>
<table class="gallery dynaheight">
<tbody><tr>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/notekins/media_thmb/53/26/5326edf264aa02578152e67d2ecc3a3bc24ee8c32cbe27f8d256edd9c4bf440d.webp"><img style="width: 50%;" src="//filestorage.chrisoft.org/notekins/media_orig/2025/1761191232-s.png" decoding="async"></a>
</div>
</td>
</tr>
</tbody></table>
<div style="display:table;width:100%;table-layout:fixed;">
<span class="left"></span> <span class="right date"><span class="datetext">2025-10-23<span class="datet">T</span>03:47:12<span class="datetz">Z</span></span><a class="anch" href="?post=1761191232">§</a></span>
</div>
</div>
<p>Starting from the final months of 2025, I noticed that the VPS
hosting this website will simply go completely unresponsive randomly.
The serial console would show a bunch of OOM log messages, but the cause
remained unknown to me until I captured the screenshot above. A few
hosts on AWS’s network were caught red-handed initiating thousands of
requests every minute to my cgit code browser, and the syntax
highlighting plugin caused a full performance breakdown.</p>
<p>As a result, all crawlers have been banned from <a href="https://cgit.chrisoft.org">cgit.chrisoft.org</a>. This includes
all search engine crawlers and crawlers used by “AI” companies. <a href="https://git.gammaspectra.live/git/go-away">Go Away</a> was
deployed on that subdomain to further prevent scraping (without using
its PoW payloads).</p>
<p>Pages on that subdomain are mostly useless as search results and I
don’t think those AI companies need to train their models on my stupid
code anyway.</p>
<p>Maybe it’s my bad to enable the syntax highlighting plugin in cgit.
But is it really that bad that I want to see my code highlighted in the
code browser?</p>
<p>Anyway, it seems the AI apocalypse might very well be already upon
us, at least for the webmasters. What have we actually gained from all
this?</p>
<p>Some fun statistics from the access logs: in the past month,
self-indicated bots initiated 14.4% of all requests to the main site
(chrisoft.org). Data served to them constitutes ~28% of total outgoing
data. The Internet is truly dead.</p>
<h3 id="tocanch5" class="tvis">The Stupid Online Player</h3>
<p>The music library received a major update of its own. You may have
seen the green <span style="color:#0f0"><em>D.S.</em></span> symbol
beside some of the tracks. These tracks have predefined loop points and
can be seamlessly looped in the online player, just like how music
tracks loops in Touhou games. The new seamless loop mode can be
activated by clicking through the repeat mode button.</p>
<p>This feature was implemented using the AudioWorklet API and thus
requires a reasonably new browser (i.e.&nbsp;from the past 5 years) to work.
A nice bonus of this addition is the support for “multi-mixes” (“Spirit
World”-like mixes for th07 remix tracks) are no longer limited to
chromium-based browsers with experimental features enabled. Now you can
switch between the mixes for these tracks seamlessly as long as seamless
loop mode is enabled.</p>
<p>Tracks are no longer offered in the ogg vorbis format. It was
replaced by a high bit rate ogg opus format.</p>
<p>Visualizations also received some tweaks. You can watch the
visualization in fullscreen without distractions from other UI elements
by simply pressing F11. The ink fountain visualization has also been
updated and now looks more dynamic.</p>
<h3 id="tocanch6" class="tvis">Navis</h3>
<p>This is not something most readers will care about, as this is
strictly a private service. But now I host an instance of <a href="https://cgit.chrisoft.org/navis.git/">Navis</a>, my personal
bookmark storage solution.</p>
<p>This is to remove Google Chrome cloud sync from the list of cloud
services that I rely upon. I have since removed the Google Cloud API key
from all my chromium builds and configurations. It’s another step
towards the faraway goal of digital sovereignty.</p>
<p>A more reasonable person probably would deploy one of the many mature
solutions that already exist. But me being the person I am, I just
couldn’t resist the urge to roll my own.</p>
<h2 id="tocanch7" class="tvis">Online Presence</h2>
<h3 id="tocanch8" class="tvis">Self-hosted e-mail</h3>
<p>Speaking of digital sovereignty, this is probably a much bigger step.
This has been on my mind since 2020. About a year ago, I finally took
action and set up my own self-hosted e-mail service. Shortly after, it
was promoted to become my primary personal email address. My GMail
address has officially become a sink for 2FA code spam and other types
of less important messages.</p>
<p>The process of setting up a self-hosted e-mail service these days is
pretty painful. There are pretty good guides to follow online, but it’s
extremely easy to run into outdated information that would just straight
up not work on modern setups. For these reasons I’ve put off this task
for years (and I just don’t want to settle with an auto-forwarder
solution). There’s all this DKIM, DMARC and SPF stuff, but ultimately I
was able to get it all working.</p>
<p>Some people claim it’s simply not possible or feasible to have
self-hosted e-mail in this day and age, claiming that your messages will
simply be rejected by popular e-mail services. This is simply not true,
at least to my experience. Messages from my server can be received by
GMail, Outlook (and enterprise hosted Microslop Exchange services), and
even Proton Mail just fine. It only becomes a problem when the recipient
server has a dubious DNS (mostly caused by a State-controlled network
infrastructure), which is a compromise I’m willing to make. I’ll not
link any guides here, I just want to confirm that no, self-hosted e-mail
is not dead.</p>
<p>My understanding is that setting up self-hosted e-mail is a
high-effort, high reward kind of deal. It does require quite a lot of
fiddling to get working, but once it’s done, it only requires minimal
maintenance to keep it healthy.<a id="n3" href="#note3" class="note">[3]</a> And, to put it bluntly, having an “<span class="citation" data-cites="pineapple.cat">@pineapple.cat</span>” email
address and seeing messages actually coming from a host managed by me is
<em>cool as hell</em>!</p>
<p>Right now the service is hosted on a VPS, which purists will claim is
not “true self-hosted”, and I fully concur – I’m just not in a position
to run a physical host that is up 24/7 right now. I might transition to
a locally hosted, but reverse proxied solution later, but that’s not
something that I feel like doing right now.</p>
<p>Check my about page for my current e-mail address.</p>
<h3 id="tocanch9" class="tvis">Ditching Discord</h3>
<p>Another long overdue item in my to-do list. Who could have foreseen
that my final straw is in fact Palantir and its surveillance
dystopia.</p>
<p>I took Discord off my preferred method of contact in 2024, actually
before the last mid-year website update was posted. But that never
resulted in any further action.</p>
<p>This is not the first time I’m fleeing from an instant messaging
platform. In fact this isn’t even the second or the third time. Over the
years, I’ve left the following platforms:</p>
<ul>
<li>Tencent QQ (OICQ)<a id="n4" href="#note4" class="note">[4]</a></li>
<li>MSN<a id="n5" href="#note5" class="note">[5]</a></li>
<li>Skype<a id="n6" href="#note6" class="note">[6]</a></li>
<li>Tox<a id="n7" href="#note7" class="note">[7]</a></li>
<li>WeChat<a id="n8" href="#note8" class="note">[8]</a></li>
<li>Discord<a id="n9" href="#note9" class="note">[9]</a></li>
</ul>
<p>There is some kind of good old irony here: all but two of these
platforms were meant to replace Tencent QQ for me (at least for the most
part). They came and went, but QQ just stuck there like a stubborn spot
of stain (except for a very short period of time in between).</p>
<p>The biggest pain of switching to a different IM platform is that one
has to convince all their friends they want to maintain contact with to
either migrate too, or revert to a less synchronous method of messaging.
I imagine there are probably few people out there who are willing to
monitor 7 different IM platforms simultaneously, so it’s very much
understandable that such effort may frequently amount to nothing. And I
certainly appreciate the people who got on to matrix just to keep in
touch with me a lot.</p>
<p>When we (my friends trying to find an alternative to QQ and I) found
Discord, I thought we were set for life. You could tell this by the fact
I have the early supporter badge there: I subscribed to Discord Nitro a
year after I got on the platform, and did not unsubscribe until
recently. I honestly don’t have a clue how I came to that conclusion
knowing that this is a proprietary, centralized platform.</p>
<p>Right now, Matrix is my main IM solution, using an account on the
matrix.org home server registered in 2019 for the purpose of testing out
the platform. I imagine I’d eventually move to a self-hosted home server
as well, but as of today that probably won’t be done any time soon.</p>
<p>I’m aware of the potential issues with Matrix<a id="n10" href="#note10" class="note">[10]</a>. To some people out there these issues are not just
“potential” but deal-breakers instead. But this is currently the
solution that supports all my use cases and requires the least amount of
messing around. And at least it’s not centralized. So I’ll stick with it
for now.</p>
<p>I’m still considering XMPP as a platform that I can use along side
Matrix.<a id="n11" href="#note11" class="note">[11]</a></p>
<p>I do not plan to delete my Discord account, at least for now. I’m
just going to stop monitoring it, starting in May. If you need me to
check out a message on Discord, shoot me an e-mail. I do not make any
guarantee that I’ll never delete the account though, especially if the
situation worsens somehow.</p>
<h3 id="tocanch10" class="tvis">Decentralized Networking</h3>
<p>Projects like <a href="https://dn42.eu/">DN42</a> and <a href="https://yggdrasil-network.github.io/">Yggdrasil network</a>
sparked my interest when I was researching e-mail self-hosting.</p>
<p>I considered joining one of these decentralized networks. But
ultimately I decided against doing it: while I can appreciate why people
want to build these networks, I simply don’t have the skills required
for this kind of things: I routinely mess up the configuration of my
OpenWRT routers, and my WireGuard VLAN is barely holding itself
together. I probably should avoid involving myself in such public
projects before I got over my skill issues, although I don’t have any
practical use of the skills I might gain from joining one either.</p>
<p>So no, I’m probably not joining any decentralized networks any time
soon (especially with my current schedule), but I’ll keep an eye on
it.</p>
<h2 id="tocanch11" class="tvis">Ten years! Now what?</h2>
<p>Yes the website you’re reading right now has occupied this domain for
a little over 10 years now. Probably not a huge deal in the grand scheme
of things. But consider how many websites come into existence each year
just to vanish forever in the next, I couldn’t help but wonder: has my
website exceeded the average lifespan of websites on the
Internet?<a id="n12" href="#note12" class="note">[12]</a></p>
<p>This is not going to turn into a piece on the history of the website.
There will be a post dedicated to that purpose, and I’m definitely NOT
writing it now.</p>
<p>And now what? I guess let’s see if I can keep this website online for
another decade then …</p>
<br><hr><div class="TText" id="notediv" style="font-size:80%;"><span class="TText"><a id="note1" href="#n1">[1]</a>: ~8.16 USD at the time<br></span><span class="TText"><a id="note2" href="#n2">[2]</a>: QZone, a
facebook knockoff by Tencent<br></span><span class="TText"><a id="note3" href="#n3">[3]</a>: It’s not zero, because I have
to keep an eye on potential abuse and intrusion (despite the low
probability).<br></span><span class="TText"><a id="note4" href="#n4">[4]</a>: I only truly left the platform for a year
or so before I realized I had to maintain some minimal presence there to
stay in touch with a small group of friends.<br></span><span class="TText"><a id="note5" href="#n5">[5]</a>: Left before Microslop killed it<br></span><span class="TText"><a id="note6" href="#n6">[6]</a>: Left before Microslop bought and killed
it<br></span><span class="TText"><a id="note7" href="#n7">[7]</a>: A less well-known platform built on a p2p protocol.
Left due to lack of offline messaging support.<br></span><span class="TText"><a id="note8" href="#n8">[8]</a>: phew<br></span><span class="TText"><a id="note9" href="#n9">[9]</a>: We are here.<br></span><span class="TText"><a id="note10" href="#n10">[10]</a>: The protocol,
major implementations, as well as the main driving force behind
it.<br></span><span class="TText"><a id="note11" href="#n11">[11]</a>: Currently I’m leaning towards adopting a permanent
presence on that platform too, but it must be on a self-hosted server.
So that’s going to put things off significantly.<br></span><span class="TText"><a id="note12" href="#n12">[12]</a>: It probably has, but so what?<br></span></div>]]></content></entry><entry><title>New Quest Unlocked!</title><link href="https://chrisoft.org/blog/post/2026-03-31.html" rel="alternate" type="text/html" title="New Quest Unlocked!"/><published>2026-03-31T00:00:00.000Z</published><updated>2026-03-31T03:04:37.267Z</updated><id>https://chrisoft.org/blog/post/2026-03-31.html</id><author><name>Chris Xiong</name><uri>https://chrisoft.org/</uri></author><content type="html" xml:base="https://chrisoft.org/blog/post/2026-03-31.html"><![CDATA[
<pre><code>╔═══════════════════════════════════════╗
║   New Quest Unlocked!                 ║
║  ───────────────────────────────────  ║
║  Description: Live your life to the   ║
║  fullest                              ║
║  Level: 29                            ║
║                                       ║
║  Subquests:                           ║
║    a) Get your dream job.             ║
║    b) Do something you love every     ║
║       day.                            ║
║    c) Become woman.                   ║
║                                       ║
║  ┌────────┐              ┌─────────┐  ║
║  │ Accept │              │ Decline │  ║
║  └────────┘              └─────────┘  ║
║                                       ║
╚═══════════════════════════════════════╝</code></pre>
<p>Become … woman? Why is that part of the quest?</p>
<p>(Hits accept without second thought)</p>
<hr class="trans">
<div class="ubx-container">
<div class="ubx">
<div class="ubx-ll">
<span class="crossed-out" title="3D model of oleic acid with a red X on it"><img src="/ubx-rsc/Oleic-acid-3D-vdW-sc.webp" class="hf" alt="3D model of cis-9-Octadecenoic acid" decoding="async"></span>
</div>
<div class="ubx-lr">
This user is <i>not</i> cis.
</div>
</div>
<div class="ubx">
<div class="ubx-ll">
<div style="width:100%;height:100%;background:linear-gradient(to bottom,#5bcefa 20%,#f5a9b8 20% 40%,#fff 40% 60%,#f5a9b8 60% 80%,#5bcefa 80%);" title="Transgender pride flag">

</div>
</div>
<div class="ubx-lr">
That means this user is trans, silly!
</div>
</div>
</div>
<p>It’s time to be true to myself. No more shame. No more hiding. No
more mental gymnastics.</p>
<p>Yes, you read that right. I am trans. Specifically
transfem<a id="n1" href="#note1" class="note">[1]</a>. This post, made public on the <a href="https://en.wikipedia.org/wiki/International_Transgender_Day_of_Visibility">International
Transgender Day of Visibility</a>, is one of the many major milestones
in my journey.</p>
<p>The post would be impossibly long if I just do an full lore drop
here. But long story short (a vast understatement), it’s been a
combination of messed up childhood, unsupportive parents, and insane
level of internalized transphobia.</p>
<p>But anyway, a cis male would not have more than 60 pages of muffled
screaming on her hard drive, right?</p>
<div style="text-align:center;max-width:90%;">
<a href="//filestorage.chrisoft.org/blog/img/tsprf.png"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/ssbsthumb_1536x1536_tsprf.png"></a> <br>Preface to the
muffled screaming.
</div>
<hr class="trans">
<p>It baffles me a little when I look back and realize I, somehow,
managed to hold it back for well over a decade. Sometimes however, I
felt guilty for being able to pull this off, as being trapped in a
cocoon for such a long time is absolutely life-threatening to many other
people. On the other hand though, I frequently fault myself for not
kicking start the changes when I was younger.</p>
<p>I can, however, no longer refer to myself as “pre-everything”, as I
probably still could a few months ago. I’m a few months into voice
training, which is my first attempt of doing something concrete to
address what is arguably by far my worst dysphoria: it was so bad that
HRT wasn’t even on my radar initially. So far I’ve been thoroughly
enjoying it, barring the anxiety. But the best part of it has to be
being able to speak without constantly telling myself to stfu because I
hate my own voice.</p>
<p>As I have already alluded to, there are other changes to come. But I
know one thing is not changing: I would still be Chris once I have
transitioned. I don’t have many nice things to say about myself; but if
there’s one thing that I’d say was pretty smart when I did it as an
8-year-old, it would be picking a unisex name that could survive a
transgender awakening.</p>
<p>Read this totally cis short story that I wrote two years ago: <a href="/blog/post/2024-03-02.html">The Station of Elyssima</a>.</p>
<br><hr><div class="TText" id="notediv" style="font-size:80%;"><span class="TText"><a id="note1" href="#n1">[1]</a>: Or MtF, depending on the terminology you
prefer.<br></span></div>]]></content></entry><entry><title>List of Special Pages</title><link href="https://chrisoft.org/blog/post/Special.html" rel="alternate" type="text/html" title="List of Special Pages"/><published>2026-03-20T00:00:00.000Z</published><updated>2026-06-24T04:32:49.937Z</updated><id>https://chrisoft.org/blog/post/Special.html</id><author><name>Chris Xiong</name><uri>https://chrisoft.org/</uri></author><content type="html" xml:base="https://chrisoft.org/blog/post/Special.html"><![CDATA[
<p>Here is a list of special pages hosted on this blog.</p>
<ul>
<li><a href="/blog/post/Special.html">List of Special Pages</a>
(Recursion!1!11!!!)</li>
<li><a href="/blog/post/Privacy.html">Privacy policies</a></li>
<li><a href="/blog/post/AI.html">Generative AI Statement</a></li>
<li><a href="/blog/post/ls-dev.html">ls /dev</a></li>
<li>ls /dev/music_gears (unfinished)</li>
<li>--about --verbose (unfinished)</li>
<li>About this website (unfinished)</li>
</ul>
<br><hr><div class="TText" id="notediv" style="font-size:80%;"></div>]]></content></entry><entry><title>SD-90: Tales Untold</title><link href="https://chrisoft.org/blog/post/2025-09-09.html" rel="alternate" type="text/html" title="SD-90: Tales Untold"/><published>2025-09-09T00:00:00.000Z</published><updated>2026-06-21T18:26:39.035Z</updated><id>https://chrisoft.org/blog/post/2025-09-09.html</id><author><name>Chris Xiong</name><uri>https://chrisoft.org/</uri></author><content type="html" xml:base="https://chrisoft.org/blog/post/2025-09-09.html"><![CDATA[
<h2 id="tocanch0" class="tvis">GenAI Statement</h2>
<p>No generative AI was used in any way, shape, or form during the
composition of this post. See the <a href="/blog/post/AI.html">Site-wide
Generative AI Statement</a> for details.</p>
<h2 id="tocanch1" class="tvis">Preamble</h2>
<p>Ah yes, the venerable EDIROL SD-90. The legendary (but weird) combo
of a ROMpler and an audio interface in a box<a id="n1" href="#note1" class="note">[1]</a> used by
the mastermind behind the Touhou Project.<a id="n2" href="#note2" class="note">[2]</a> Released more than two decades
ago, surely there can’t be anything new to learn about this thing out in
the wild, right?</p>
<p>Turns out, that is not the case. I’ve never found an electronic copy
of the service manual for this machine online. I’ve had my SD-90 for
nearly three years at that point (prior to that I was using my <a href="/blog/post/2019-04-25.html">SD-80</a>), but I have still yet to
figure out how to boot into its test mode despite having systematically
exhausted all possible three-button combinations. So when I saw a
listing on ebay for a physical copy earlier this year, I knew I just
have to get it.</p>
<p>As soon as the service manual arrived, I was shocked to find out that
the correct button combination for booting into test mode is among the
first few combinations I tried – I just didn’t wait until the boot
animation finished playing because I simply didn’t have the patience for
that: there are <em>a lot</em> of three-button combinations to try when
you have 19 buttons!</p>
<p>But I didn’t stop there: I flipped through the entire 50 pages of the
service manual, making comparisons against its little brother SD-80’s
service manual throughout the way. In the process I found a lot of
curiosities about the design of this machine that clearly indicates its
transitional and experimental nature. And the purpose of this blog post
is to share these curiosities (mostly design details of the
PCB<a id="n3" href="#note3" class="note">[3]</a>, as well as a few other interesting things) with the
world.</p>
<p>And of course just a few months after me paying the ransom, <s><a href="https://archive.org/details/edirol-sd-90-service-notes">a scanned
version of the service manual</a></s> popped up on archive.org.
Sigh.<a id="n4" href="#note4" class="note">[4]</a>
<a id="n5" href="#note5" class="note">[5]</a></p>
<p>As usual, this article will be written in a way such that readers
with little knowledge in electronics will hopefully not find themselves
totally lost. However, please do keep in mind that the author of this
article is not a professional either. Corrections are always
welcome.</p>
<h2 id="tocanch2" class="tvis">Firmware Revisions</h2>
<p>My SD-90 seems to be a pretty early specimen. Its main board was
manufactured on 2001-10-27, still fairly early into the product’s life
cycle: some official documentation of the SD-90 was dated later than
that, e.g.&nbsp;the MIDI Implementation version 1.02 was dated 2001-12-12,
and the service manual was dated 2001-11. The MIDI Implementation
document shipped in the driver CD of my SD-90 is however version 1.00,
and dated 2001-10-11. Earliest snapshot of SD-90’s Japanese product
information page on the Wayback Machine is dated 2001-10-31. Mine came
to me with firmware version 1.02, which I assume is the version it was
shipped with and the previous owner never bothered to apply <a href="https://static.roland.com/support_archive/jp/SD-90_1811927_readme_jp.html">the
1.03 update</a> which is available on Roland’s website.<a id="n6" href="#note6" class="note">[6]</a></p>
<p>However as other people started checking the firmware version of
their SD-90, news of units on odd firmware versions started pouring in:
A few people’s units are running version 1.05. One even has a unit
that’s running 1.06. Unsurprisingly, these units were all manufactured
later than mine. But I could find no information on these newer firmware
versions online<a id="n7" href="#note7" class="note">[7]</a>, and
they apparently do not provide any new functionality. So I’d assume that
these are factory firmware versions for later hardware revisions. And as
you will see later, there <em>are</em> actual hardware differences
between early and late units. So these newer firmware revisions might
have been made to accommodate these hardware differences.</p>
<p>Since I updated the firmware of my SD-90 to 1.03 before I knew how to
check the build information, I would never know when was version 1.02
built. However archived supported pages for the SD-90 on the Wayback
Machine indicates that version 1.02 was made public on 2001-12-10. The
same page has a reference to version 1.01 which I have never seen
running on an actual unit. The service manual shows version 1.00 in what
the version check would look like in test mode, however there is no way
for me to tell whether that information is accurate or just a
placeholder for demonstration purposes.</p>
<p>If you want to check the firmware version of your SD-90, you might
want to try the following button combinations (hold these buttons while
powering on the unit):</p>
<ul>
<li>inst/drum set + exit + preview</li>
<li>mic/guitar + inst/drum set + system</li>
</ul>
<p>All SD-90 firmware versions that I was able to personally confirm are
listed below:</p>
<table>
<colgroup>
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
</colgroup>
<thead>
<tr class="header">
<th class="c">
</th>
<th class="c">
1.02
</th>
<th class="c">
1.03
</th>
<th class="c">
1.05
</th>
<th class="c">
1.06
</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td class="c">
<b>Updater info</b>
</td>
<td class="l">
<pre>Ttl :---- [1655] 1.02
Prog:00FA [00FA] 1.02
Boot:AA1D [AA1D] 1.02
Updt:1284 [1284] 1.01</pre>
</td>
<td class="l">
<pre>Ttl :---- [05A5] 1.03
Prog:1F50 [1F50] 1.03
Boot:AA1D [AA1D] 1.02
Updt:4B60 [4B60] 1.03
</pre>
</td>
<td class="l">
<pre>Ttl :---- [8488] 1.05
Prog:01D2 [01D2] 1.05
Boot:AA1D [AA1D] 1.02
Updt:4B60 [4B60] 1.03</pre>
</td>
<td class="l">
<pre>Ttl :---- [11E9] 1.06
Prog:DAD7 [DAD7] 1.06
Boot:A9E6 [A9E6] 1.03
Updt:FAE5 [FAE5] 1.04</pre>
</td>
</tr>
<tr class="even">
<td class="c">
<b>Build Number</b>
</td>
<td class="c">
?
</td>
<td class="c">
0038
</td>
<td class="c">
0041
</td>
<td class="c">
0042
</td>
</tr>
<tr class="odd">
<td class="c">
<b>Build Time</b><br>(YYYY/MM/DD HH:MM)
</td>
<td class="c">
?<br>(Not after 2001/12/10)
</td>
<td class="c">
2002/06/06 10:00
</td>
<td class="c">
2002/12/12 21:35
</td>
<td class="c">
2005/03/17 14:00
</td>
</tr>
<tr class="even">
<td class="c">
<b>Main board mfg. date &amp;<br>Specimen courtesy of</b>
</td>
<td class="c">
2001-10-27 (me)
</td>
<td class="c">
(No confirmed units shipped with this version)
</td>
<td class="c">
2004-12-09 (Palto)
</td>
<td class="c">
2005-01-24 (Eason20000)
</td>
</tr>
</tbody>
</table>
<h2 id="tocanch3" class="tvis">PCB Madness</h2>
<p>Even before obtaining my copy of the service manual, I was already
fairly familiar with the internals of the SD-90: I disassembled mine as
soon as it arrived to photograph its internals, noticing striking
similarities to the SD-80 in the power supply section, and later
performed a partial recap to address its noisy synthesizer output.
However, getting my hands on the full schematics and the parts list has
definitely revealed a few very interesting things about this
machine.</p>
<h3 id="tocanch4" class="tvis">To buffer, or not to buffer</h3>
<p>While we were trying to figure out the firmware versions, Palto
noticed that there is a chip missing in a PCB photo of my SD-90 while
the same position is populated on their board. This got me deeply
hooked, because this confirms the existence of different hardware
revisions.</p>
<p>Determined to figure out what is going on, I asked for more PCB
photos from Palto. Here is a list of differences I spotted:</p>
<ul>
<li><code>IC30</code>, <code>IC31</code>, <code>IC32</code>,
<code>IC33</code>, <code>IC34</code>, <code>IC35</code> and supporting
components for these chips (mostly output resistors) are not populated
on my board. These are populated on Palto’s board.</li>
<li><code>RA41</code>, <code>RA42</code>, <code>RA44</code>,
<code>RA46</code>, <code>RA47</code> are not populated on Palto’s board
while these are populated on my board.
<code>RA41</code>/<code>IC30</code>,
<code>RA44</code>/<code>IC31</code>,
<code>RA42</code>/<code>IC32</code>,
<code>RA46</code>/<code>IC35</code>, <code>RA47</code>/<code>IC34</code>
have overlapping footprints on the PCB so they cannot be populated at
the same time. These are all 0 Ohm resistor arrays (0 Ohm links).</li>
</ul>
<p>All but one of the chips mentioned above are bus transceiver chips
(74LV245). The exception is <code>IC33</code>, which is four 2-input AND
gates in a single package (74LV08).</p>
<p>As a bus transceiver, the 245 connects 8 pairs of inputs and outpus
together. It allows the signals to flow in a set direction, which can be
flipped on demand with the <code>DIR</code> pin. More specifically, it
has two “ports” A and B, each with 8 pins A1-A8 and B1-B8. Depending on
the setting on the <code>DIR</code> pin, signals are allowed to travel
from port A to its corresponding pin on port B or vice versa. It can
also “disconnect” the ports altogether. This is controlled by the pin
marked <code>/OE</code>. When <code>/OE</code> is set to high, signals
are neither allowed to travel from port A to port B, nor from port B to
port A regardless of the setting on the <code>DIR</code> pin. This
effectively disconnects the ports.</p>
<p>Beside being used as a bus transceiver, some variants of the 245 chip
is also often used as a logic level shifter, especially in older
electronics that has mixed logic levels<a id="n8" href="#note8" class="note">[8]</a>. Initially this sounds like a
plausible reason why these 245 chips were put there: the XV chip is
powered by both 3.3V and 5V, therefore it might have used mixed logical
levels; and in the schematics the ports connected to these 245 chips are
named <code>WA</code>/<code>3WA</code> (for the wave memory address bus)
or <code>WD</code>/<code>3WD</code> (for the wave memory data bus), as
if it’s converting between 3V logic and a certain other logical level.
However, this is where the variant of the chip comes into play. The “LV”
in its part number <code>74LV245</code> is the variant. In this case,
the LV variant is not suitable for use as a level shifter. This is
rendered even less possible by the fact that in earlier production units
that lacked these 245 chips, signals that would pass through these 245
chips are connected directly through 0 Ohm links. If logical level
shifting is in fact required, that would be a bad design and might even
damage the XV and / or the wave memory chips.</p>
<p>Now here is the fun part: 3 of these 5 245 chips have their enable
pins and direction pins tied to the ground as well! This means that they
are always enabled, and fixed to a specific direction, which means they
are connecting each output to its corresponding input all the time, as
long as the chips are powered. These chips are all on the wave address
bus, which means the two designs are not that different as far as the
address bus of the wave bus is concerned<a id="n9" href="#note9" class="note">[9]</a>.</p>
<p>The other two 245 chips are on the wave data bus and have their
enable pins connected to the <code>/WOE</code> (likely stands for “wave
output enable”) pins of the XV chips. This means that the XV chips in
late revision SD-90 units would have the ability to disconnect the data
output of the wave ROM chips from them. Why it would need to do that is
something I couldn’t answer: the <code>/WOE</code> pins of the XV chips
are also connected the the <code>/OE</code> pins of all the wave ROM
chips as well, meaning the same signal already have direct control over
the output right at the source. Their <code>DIR</code> pins are
controlled by the XV chips’ <code>/WWE</code> signal, which means
besides receiving data on the wave data bus from the ROM chips, the XV
chips would also be able to send data to the wave data bus. This would
only make sense if the flash memory (more details later) on the wave bus
is used, as that would be the only occasion where the XV chips need to
write to the wave bus.</p>
<p><code>IC33</code> implements glue logic for these two bus transceiver
chips on the wave data bus, as well as the unused flash memory on the
wave bus.</p>
<p>The SD-80 doesn’t have design variants when it comes to how the wave
ROM chips are attached to the DSP (i.e.&nbsp;its PCB does not have
unpopulated positions for alternative designs). It’s consistently using
the design of later SD-90 hardware revisions.</p>
<p>Those are all the differences we’ve spotted between our production
SD-90 units. Below are a few other interesting components marked “Not In
Use” in the schematics, but they were never populated on any production
boards that I’ve seen.</p>
<p><code>IC37</code> and <code>IC45</code> are simple OR gates that
would allow the SH3 CPU (in addition to the XV chips) to control whether
the effect RAM chips’ output are enabled. These seem to be still missing
from later units, and are completely absent in XV-5080 and SD-80’s board
designs. In those models, only the XV chips control whether the outputs
of its effect RAM chip are enabled.</p>
<p><code>IC38</code> is an AND gate that connects the <code>WBACK</code>
and <code>WBREQ</code> pins of the XV chips together, but exposes one of
its inputs as a test point with a pull-up resistor that’s also not
installed. If this block is functional, the <code>WBACK</code> and
<code>WBREQ</code> pins of the XV chips would be effectively
disconnected when the test point is shorted to ground. This is possibly
just a leftover for internal testing. In production units, which lack
this chip on its board, a 0 Ohm link is installed to connect them
directly. In XV-5080 and SD-80’s design, all this extra testing
circuitry is completely absent and said pins are linked directly by
circuit board traces.</p>
<p><code>IC19</code> and <code>CN7</code> seem to be related to an
additional encoder that’s removed in the final design. It was referred
to as <code>ENC0</code> in the design while the 3 encoders that are
actually present on the SD-90 are referred to as <code>ENC1</code> to
<code>ENC3</code>.<a id="n10" href="#note10" class="note">[10]</a> This seem to suggest
that the SD-90 was initially designed with a single value knob (like the
SC-8850) instead of 3.</p>
<p><code>Q13</code> and <code>Q14</code> (close to the LCD backlight
power socket) are used for driving the LCD backlight with a PWM signal
from the XV chip. This suggests the SD-90 is designed with adjustable
backlight brightness (not just contrast, as available in production
units), but was scrapped before it reached production.</p>
<h3 id="tocanch5" class="tvis">Robbed memory</h3>
<p>There are footprints (<code>IC42</code>, <code>IC43</code>) for two
additional memory chips that are on the wave bus. These two chips are
never populated in any of the SD-90 specimens that I’ve seen.</p>
<p><code>IC42</code> is just another 128 Mbit Mask ROM chip, very much
like the two that are installed on all SD-90 units (<code>IC40</code>
and <code>IC41</code>). This position is not reserved as a simple
substitute for either <code>IC40</code> or <code>IC41</code>, as it has
its own, distinct chip select line. Therefore its capacity would be
added to the total wave ROM capacity were it installed. The only notable
difference is that the part number designated for <code>IC42</code> is
23C128000LGY, while <code>IC40</code> and <code>IC41</code> has the part
number 23C128040LGY. The difference between these two parts is that
000LGY does not have the page access mode that 040LGY
has.<a id="n11" href="#note11" class="note">[11]</a> I’ve never seen Roland
using the version of this ROM chip that doesn’t have page access
mode.</p>
<p>This chip does not have its “mask code”<a id="n12" href="#note12" class="note">[12]</a>
printed in the service manual. This could mean the content of this chip
was never finalized and passed on to the mask manufacturing process.</p>
<p>If <code>IC42</code> was used alongside <code>IC40</code> and
<code>IC41</code>, the SD-90 would have 48 MB of compressed samples (96
MB uncompressed using Roland’s calculus). <del>Roland never released a
sample-based synth that came with 48 MB of compressed samples (that I’m
aware of).</del> Update 2026-04-28: Actually, they did. Both the VA-7
and VA-5 had 48 MB of wave ROM. These models are extremely interesting
in their own right<a id="n13" href="#note13" class="note">[13]</a>, but I
don’t have the time to go too deep into that right now. Now back to your
scheduled programming: although they did release ones with 24 MB of
compressed samples, which include the SC-8820 and its relatives. In
later sound modules with bigger wave ROMs, they jumped straight to 64 MB
of compressed samples.</p>
<p>Imagine what they could have done with 16 MB of additional space for
samples. They could probably squeeze the entire RD-700 piano in there
(equivalent to the piano found in SuperQuartet), alongside possibly not
crippling a few sample sets (e.g.&nbsp;keeping all velocity layers of the XV
acoustic guitar samples). Oh the missed opportunities!</p>
<p>The specific parts used for <code>IC40</code> and <code>IC41</code>
had changed throughout the production too. In fact, there are at least 3
equivalent set of ROM chips used seemingly interchangeably through the
entire lifespan of the Studio Canvas product line (The R-number is
Roland’s internal part number):</p>
<table>
<colgroup>
<col style="width: 17%">
<col style="width: 40%">
<col style="width: 42%">
</colgroup>
<thead>
<tr class="header">
<th style="text-align: center;">Source</th>
<th style="text-align: center;">First ROM chip<br>(Normal Bend)</th>
<th style="text-align: center;">Second ROM chip<br>(Reverse Bend)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: center;">SD-90 Parts List &amp; Schematics</td>
<td style="text-align: center;">UPD23C128040LGY-529-MJH (R02678601)</td>
<td style="text-align: center;">UPD23C128040LGY-535-MKH (R02678612)</td>
</tr>
<tr class="even">
<td style="text-align: center;">My SD-90<br>(MB mfg. 2001-10-27)</td>
<td style="text-align: center;">R02678601 23C128L-529J 0127E7001</td>
<td style="text-align: center;">R02678612 23C128L-535K 0127E7002</td>
</tr>
<tr class="odd">
<td style="text-align: center;">Palto’s SD-90<br>(MB mfg.
2004-12-09)</td>
<td style="text-align: center;">R03010612 23C128AL525J 0419E7001</td>
<td style="text-align: center;">R03010623 23C128AL526K 0420E7001</td>
</tr>
<tr class="even">
<td style="text-align: center;">Eason20000’s SD-90<br>(MB mfg.
2005-01-24)</td>
<td style="text-align: center;">R03010612 23C128AL525J 0451E7001</td>
<td style="text-align: center;">R03010623 23C128AL526K 0451E7002</td>
</tr>
<tr class="odd">
<td style="text-align: center;">SD-80 Parts List &amp; Schematics</td>
<td style="text-align: center;">UPD23C128040ALGY-525-MJH
(R03010612)</td>
<td style="text-align: center;">UPD23C128040ALGY-526-MKH
(R03010623)</td>
</tr>
<tr class="even">
<td style="text-align: center;">My SD-80<br>(MB mfg. 2004-08-27)</td>
<td style="text-align: center;">R02678601 23C128L-529J 0224E7007</td>
<td style="text-align: center;">R02678612 23C128L-535K 0222E7005</td>
</tr>
<tr class="odd">
<td style="text-align: center;">Palto’s SD-80<br>(MB mfg.
2005-07-25)</td>
<td style="text-align: center;">R03010612 23C128BL832J 0525K7002</td>
<td style="text-align: center;">R03010623 23C128BL833K 0525K7003</td>
</tr>
<tr class="even">
<td style="text-align: center;">My SD-20<br>(unknown mfg. date)</td>
<td style="text-align: center;">R03010612 23C128BL832J 0620K7002</td>
<td style="text-align: center;">R03010623 23C128BL833K 0620K7005</td>
</tr>
<tr class="odd">
<td style="text-align: center;">Palto’s SD-20<br>(unknown mfg.
date)</td>
<td style="text-align: center;">R03010612 23C128AL525J 0229E7005</td>
<td style="text-align: center;">R03010623 23C128AL526K 0229E7006</td>
</tr>
</tbody>
</table>
<p>… what a mess.</p>
<p>Keep in mind that a different mask code for chips in <em>a slightly
different series</em> does not necessarily translate to different data
stored. This can be back by the fact that the BLGY-version of the chips
and ALGY-version have different mask codes, but identical Roland part
numbers, which indicates that Roland saw them as exact equivalents and
should therefore have identical data.</p>
<p>It’s plausible that the last line of the engraving on these ROM chips
is a date code in the format of
<code>&lt;last two digits of year&gt;&lt;week&gt;</code> (which seems
consistent with other lines of evidence). If that’s the case we can
easily reconstruct a time of these parts: they started production using
the LGY-version of the chip, switching to the ALGY-version and then the
BLGY-version later. But they definitely grabbed whatever was available
on their production lines at times too.</p>
<p>If that already sounds wacky, I have more in store for you: the story
of <code>IC43</code> might be way more interesting, because it’s a 32
Mbit (4 MiB) flash memory chip directly connected to the wave bus!</p>
<p>If this sounds crazy, it’s because it is: No other Roland
sample-based synth from this era that I’ve seen has a flash memory chip
directly hooked up to the main synthesizer DSP chips. All models with
sample playback / sampling capability that I’m aware of must first load
the samples into some form of RAM first. The flash chip they had chosen
however, does seem fast enough to behave like all the other ROM chips on
the wave bus. The write enable input of the flash memory chip is
connected to the <code>/WWE</code> pin of the XV chips through some
trivial logic, so this chip can definitely be written to, making it
unlikely that it’s just a factory configuration vessel that is only
written once when the product leaves the factory.<a id="n14" href="#note14" class="note">[14]</a> The same <code>/WWE</code> signal
is also used to flip the direction of the transceiver chips on the wave
data bus so that the XV chip can send data to the flash chip. In the
XV-5080, the <code>/WWE</code> pin of the XV chips are connected to pin
47 (<code>/WE</code>) of the RAM socket, although the way it control the
signal direction on its wave data bus is much more complicated. This
<code>/WWE</code> pin is completely unused in all other Roland devices
that use the XV chip that I’m aware of (XV-5050, SD-80, FA-76 and
MV-8x00), with the notable exception of the Fantom-S/S88 and MC-909. In
these two models the <code>/WWE</code> signal would go into a 74LV245
chip (used in the same way as those always-enabled unidirectional level
shifters in later SD-90 units), and then seemingly vanish right there.
Coincidentally both the Fantom-S and the MC-909 do support sampling into
a wave RAM, but they use a dedicated “BA” chip as their memory
controller.</p>
<p>One plausible explanation of why this existed in the design is Roland
wanted to make the SD-90 their response to Yamaha’s MU2000, which also
has sampling capabilities, and coincidentally has 4 MiB of temporary
sample storage<a id="n15" href="#note15" class="note">[15]</a>. Despite having multiple audio inputs, the
MU2000 does not serve as a USB audio interface. In addition Roland’s
design would have made the SD-90 a completely self-contained solution:
no additional storage would be required to permanently retain the
samples. Sounds like the SD-90 would be a all-around winner if had this
chip, right? Unfortunately their design also makes loading samples a
pain: it would probably involve some kind of proprietary communication
over MIDI or USB. So maybe the MU2000 would still have a chance if the
SD-90 was released with sampling capabilities<a id="n16" href="#note16" class="note">[16]</a>.</p>
<p><del>But at the end of the day this would be pretty useless, just
like MU2000’s sampling feature:</del> <del>4 MiB of storage is not even
enough for one minute of 44100 Hz single channel audio.</del></p>
<p>The way the chip select lines for these wave memory chips are
connected is interesting when compared against the XV-5080. In the
XV-5080, the two built-in wave ROM chips are selected first as a single
entity using <code>/WCS0</code>, then selected between these two using
<code>WA23</code> with a small 2-to-3 line demuxer. In the SD-90, a
similar approach is implemented with a 3-to-8 line 74LV138 demuxer. The
SD-80 uses yet another different design, but it is basically a
simplified version found in the XV-5080. This commonality between these
different approaches is that this way the XV chip could treat the two
built-in wave ROM chips as one big 32 MiB chip, rather than two
individual 16 MiB chips.</p>
<p>This could make sense for the XV-5080, since in a fully expanded
XV-5080, it has to be able to address 2 * 16 MiB + 4 * 8 MiB + 4 * 32
MiB = 192 MiB of wave ROM. It could be important to save a few chip
select lines, as the XV chip only has 8 of them. But in the SD-90 /
SD-80, they might as well treat the built-in wave ROM chips as separate
chips to reduce the complexity of the hardware. The only reason that I
can think of that explains why they did it this way is to maintain some
level of hardware compatibility with the XV-5080 so they can reuse some
of the software code written for the XV-5080.</p>
<h3 id="tocanch6" class="tvis">Which port we using again?</h3>
<p>The XV chip seems to have 6 output channels and 2 input channels for
serial digital audio data. They are marked
<code>SDO0, SDO3, SDO4, SDO5, SDO6, SDO7</code> and
<code>SDI1, SDI2</code> respectively in service manuals for various
products<a id="n17" href="#note17" class="note">[17]</a>. Among these, <code>SDO0</code> and
<code>SDI1</code> seem to double as <em>part</em> of a 6-bit wide full
duplex communication channel (<code>PO5</code> and <code>PI5</code> of
<code>PO[0..5]</code> and <code>PI[0..5]</code>
respectively)<a id="n18" href="#note18" class="note">[18]</a>
between the XV chips when a pair is used in a design<a id="n19" href="#note19" class="note">[19]</a>, and they are always used this
way. Even in models where there’s only one lone XV chip, these are never
used as audio I/O ports.</p>
<p>The other ports are used in an interesting, consistently inconsistent
fashion, summarized in the table below.</p>
<table>
<colgroup>
<col style="width: 12%">
<col style="width: 14%">
<col style="width: 14%">
<col style="width: 14%">
<col style="width: 14%">
<col style="width: 14%">
<col style="width: 14%">
</colgroup>
<thead>
<tr class="header">
<th style="text-align: center;">Model</th>
<th style="text-align: center;"><code>SDO3</code></th>
<th style="text-align: center;"><code>SDO4</code></th>
<th style="text-align: center;"><code>SDO5</code></th>
<th style="text-align: center;"><code>SDO6</code></th>
<th style="text-align: center;"><code>SDO7</code></th>
<th style="text-align: center;"><code>SDI2</code></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: center;">SD-90</td>
<td style="text-align: center;">(unused)</td>
<td style="text-align: center;">INST<br>(MR3 <code>AD0</code>)</td>
<td style="text-align: center;">(unused)</td>
<td style="text-align: center;">(unused)</td>
<td style="text-align: center;">(unused)</td>
<td style="text-align: center;">(unused)</td>
</tr>
<tr class="even">
<td style="text-align: center;">XV-5080</td>
<td style="text-align: center;">Coaxial / Optical Output</td>
<td style="text-align: center;">Output A<br>(Analog &amp;
R-BUS)<br>Front Panel Phones Jack</td>
<td style="text-align: center;">Output B<br>(Analog &amp; R-BUS)</td>
<td style="text-align: center;">Output C<br>(Analog &amp; R-BUS)</td>
<td style="text-align: center;">Output D<br>(Analog &amp; R-BUS)</td>
<td style="text-align: center;">Input A<br>(R-BUS)<a id="n20" href="#note20" class="note">[20]</a></td>
</tr>
<tr class="odd">
<td style="text-align: center;">SD-80</td>
<td style="text-align: center;">Coaxial / Optical Output</td>
<td style="text-align: center;">Output A<br>(Analog)<br>Front Panel
Phones Jack</td>
<td style="text-align: center;">Output B<br>(Analog)</td>
<td style="text-align: center;">(unused)</td>
<td style="text-align: center;">(unused)</td>
<td style="text-align: center;">(unused)</td>
</tr>
<tr class="even">
<td style="text-align: center;">XV-5050</td>
<td style="text-align: center;">Coaxial / Optical Output</td>
<td style="text-align: center;">Output A<br>(Analog)<br>Front Panel
Phones Jack</td>
<td style="text-align: center;">Output B<br>(Analog)</td>
<td style="text-align: center;">(unused)</td>
<td style="text-align: center;">(unused)</td>
<td style="text-align: center;">(unused)</td>
</tr>
<tr class="odd">
<td style="text-align: center;">Fantom-S</td>
<td style="text-align: center;">To CPU?<br>(SY-PPC
<code>PPC-SD</code>)<a id="n21" href="#note21" class="note">[21]</a></td>
<td style="text-align: center;">(unused)<a id="n22" href="#note22" class="note">[22]</a></td>
<td style="text-align: center;">Output B<br>(Analog)</td>
<td style="text-align: center;">Output A<br>(Analog)<br>Phones Jack</td>
<td style="text-align: center;">Coaxial / Optical Output</td>
<td style="text-align: center;">Analog Input L/R</td>
</tr>
<tr class="even">
<td style="text-align: center;">MC-909</td>
<td style="text-align: center;">To CPU?<br>(SY-PPC
<code>PPC-SD</code>)</td>
<td style="text-align: center;">Direct 2 Output</td>
<td style="text-align: center;">Direct 1 Output</td>
<td style="text-align: center;">Mix Output<br>Front Panel Phones
Jack</td>
<td style="text-align: center;">Coaxial / Optical Output</td>
<td style="text-align: center;">Analog Input L/R</td>
</tr>
<tr class="odd">
<td style="text-align: center;">MV-8800<a id="n23" href="#note23" class="note">[23]</a></td>
<td style="text-align: center;">Coaxial / Optical Output</td>
<td style="text-align: center;">MV8-OP1<br>Output 1-2<br>(Analog &amp;
R-BUS)</td>
<td style="text-align: center;">MV8-OP1<br>Output 3-4<br>(Analog &amp;
R-BUS)</td>
<td style="text-align: center;">MV8-OP1<br>Output 5-6<br>(Analog &amp;
R-BUS)</td>
<td style="text-align: center;">MV8-OP1<br>Output 7-8<br>(R-BUS)</td>
<td style="text-align: center;">MV8-OP1<br>Coaxial / Optical
Input<br>R-BUS Input 1-2</td>
</tr>
</tbody>
</table>
<h3 id="tocanch7" class="tvis">Power supply</h3>
<p>As already mentioned, the SD-90 has an extremely similar power supply
section to the SD-80, even just by looking at the circuit board. In fact
they are built around exactly identical core components.</p>
<p>I find the design based around a buck regulator used in the SD-90 and
SD-80 much more reliable than the linear regulator design used in, say,
the XV-5080.</p>
<h3 id="tocanch8" class="tvis">Display module</h3>
<p>When I was looking for display modules for an unrelated project, I
realized displays that are almost identical to the one used in the SD-90
are still being manufactured, for example <a href="https://www.digikey.com/en/products/detail/newhaven-display-intl/NHD-C12864WO-B1TMI-M/2625202">NHD-C12864WO-B1TMI#-M</a>.
This particular one seems to be a drop-in replacement. In fact, it seems
to be possible to rewire any 128×64 LCD display that can use the
parallel 8080 interface into the SD-90.</p>
<p>Although the SD-90 does indeed have a terrible display, I’m not in a
rush to buy one of these to replace the factory LCD. Primarily because
all drop-in replacements are also STN displays, so it’s likely that they
are going to look equally terrible. Replacing it with an OLED module
would be an interesting idea and will certainly look much nicer, but
would also require rewiring, and right now I simply don’t have time for
that.</p>
<h3 id="tocanch9" class="tvis">“Emurator”</h3>
<p>There is an unpopulated D-SUB connector<a id="n24" href="#note24" class="note">[24]</a> near
the CPU, which the SD-80 also has. In SD-90’s service manual, it is
marked “Emurator”<em>[sic]</em>. Signals connected to this port directly
correspond to those used by the Hitachi User-Debugging Interface from
the CPU. The datasheet of the CPU describes the Hitachi User-Debugging
Interface as a JTAG-compatible interface. The section on the H-UDI does
mention an emulator, as well as Advanced User Debugger (AUD), which
appears in the names of multiple signals going into the port, confirming
this port is used during development of these products. Hitachi’s E10A
emulator kit (both the hardware and software) is the official tool used
with this port.</p>
<h3 id="tocanch10" class="tvis">Shared lineage with SC-D70</h3>
<p>The SD-90 shares two major ICs with the SC-D70, which can be
considered a weaker predecessor of the SD-90. They are both sound module
/ audio interface combo devices. The shared major ICs are all in the USB
/ Audio interface section.</p>
<table>
<colgroup>
<col style="width: 34%">
<col style="width: 23%">
<col style="width: 23%">
<col style="width: 19%">
</colgroup>
<thead>
<tr class="header">
<th style="text-align: center;">Shared IC</th>
<th style="text-align: center;">In SD-90</th>
<th style="text-align: center;">In SC-D70</th>
<th style="text-align: left;">Notes</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: center;">M37641M8-106</td>
<td style="text-align: center;">IC48</td>
<td style="text-align: center;">IC21</td>
<td style="text-align: left;">Mitsubishi 7600 series microcontroller
with 32KiB ROM, used as USB controller</td>
</tr>
<tr class="even">
<td style="text-align: center;">TC203G08AF-1017 (“KD chip”)</td>
<td style="text-align: center;">IC47</td>
<td style="text-align: center;">IC25</td>
<td style="text-align: left;">Roland ASIC made by Toshiba, 6 in / 6 out
USB audio interface</td>
</tr>
</tbody>
</table>
<p>Note that this USB controller chip runs code stored in its built-in
mask ROM, the 106 in its part number being the mask code. This means the
USB controller in the SC-D70 and SD-90 are running exactly the same
firmware.</p>
<p>The USB audio interface seems an overkill for both. 4 inputs and 4
outputs are simply wasted, making me speculate it might have been used
in other Roland products. However I couldn’t find such a product.</p>
<p>The SC-D70 is interesting in its own right. Besides the obvious
shared components with the SC-8820 and SC-8850 (as its tone generator
section is essentially a SC-8820), it somehow also has an ESP4 chip
inside, making it the earliest Roland product to use this chip to my
knowledge. This chip is also used as a secondary DSP inside Fantom-S,
MC-909, and MV-8800. It’s the primary DSP chip in the V-Synth. As I
don’t own one to test how is the ESP4 connected to the other chips, nor
do I have access to its service manual, I currently do not have any idea
what this chip is used for in the SC-D70.</p>
<h3 id="tocanch11" class="tvis">SC-8850 expansion</h3>
<p>Speaking of the SC-8850, here is something I must address despite it
being completely off-topic for this post. As soon as I saw the circuit
board of SC-8850’s main board, I noticed a big, unpopulated 50-pin
header near one of its XP6 chips. It’s only marked as <code>CN8</code>
on the circuit board. But its proximity to the DSP section and one of
the wave ROM chips made me speculate that it might be some kind of
expansion slot.</p>
<p>Turns out, that is exactly the case. The component is labelled
“50P-Header Expansion-Slot (NIU)”, and is a Molex 52411 plug, which is
the same series of socket used for the SR-JV80 expansion boards. However
SR-JV80 expansion boards use a narrower version of the socket that only
has 40 pins.</p>
<p>This got me interested in comparing Roland’s sound expansion formats
used over the years …</p>
<table>
<colgroup>
<col style="width: 10%">
<col style="width: 8%">
<col style="width: 8%">
<col style="width: 8%">
<col style="width: 8%">
<col style="width: 10%">
<col style="width: 16%">
<col style="width: 32%">
</colgroup>
<thead>
<tr class="header">
<th class="c">
Format
</th>
<th class="c">
# of pins
</th>
<th class="c">
# of CS lines
</th>
<th class="c">
# of address signals
</th>
<th class="c">
# of data signals
</th>
<th class="c">
Theoretical max capacity
</th>
<th class="l">
Used in
</th>
<th class="l">
Notes
</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td class="c">
SN-U110
</td>
<td class="c">
34
</td>
<td class="c">
1
</td>
<td class="c">
19
</td>
<td class="c">
8
</td>
<td class="c">
512 KiB
</td>
<td class="l">
U-110 &amp; U-220
</td>
<td class="l">
</td>
</tr>
<tr class="even">
<td class="c">
SN-R8
</td>
<td class="c">
34
</td>
<td class="c">
1
</td>
<td class="c">
19
</td>
<td class="c">
8
</td>
<td class="c">
512 KiB
</td>
<td class="l">
R-8
</td>
<td class="l">
Electrically identical to SN-U110 cards
</td>
</tr>
<tr class="odd">
<td class="c">
SO-JD80
</td>
<td class="c">
40
</td>
<td class="c">
1
</td>
<td class="c">
21
</td>
<td class="c">
8
</td>
<td class="c">
2 MiB
</td>
<td class="l">
JD-800/990
</td>
<td class="l">
</td>
</tr>
<tr class="even">
<td class="c">
SO-PCM1
</td>
<td class="c">
40
</td>
<td class="c">
1
</td>
<td class="c">
21
</td>
<td class="c">
8
</td>
<td class="c">
2 MiB
</td>
<td class="l">
JD-990, JV-80, JV-880, most JV-series
</td>
<td class="l">
Electrically identical to SO-JD80 cards
</td>
</tr>
<tr class="odd">
<td class="c">
SR-JV80
</td>
<td class="c">
40
</td>
<td class="c">
4
</td>
<td class="c">
21
</td>
<td class="c">
8
</td>
<td class="c">
8 MiB
</td>
<td class="l">
JD-990, JV-80, JV-880, many others
</td>
<td class="l">
</td>
</tr>
<tr class="even">
<td class="c">
“SC-8850”
</td>
<td class="c">
50
</td>
<td class="c">
4
</td>
<td class="c">
21
</td>
<td class="c">
16
</td>
<td class="c">
16 MiB
</td>
<td class="l">
Never released
</td>
<td class="l">
</td>
</tr>
<tr class="odd">
<td class="c">
SRX
</td>
<td class="c">
80
</td>
<td class="c">
8
</td>
<td class="c">
23
</td>
<td class="c">
16
</td>
<td class="c">
128 MiB
</td>
<td class="l">
XV-88, XV-3080, many others
</td>
<td class="l">
Theoretical max capacity never reached.<br>Commercial releases all had
32 MiB ROM or less.<br> Has additional serial EEPROM, likely for board
identification purposes
</td>
</tr>
<tr class="even">
<td class="c">
ARX
</td>
<td class="c">
70
</td>
<td class="c" colspan="4">
Not a simple ROM expansion.
</td>
<td class="l">
Fantom-G
</td>
<td class="l">
Has a full synth engine built into it.<br> Connects to host CPU via UART
(likely based on MIDI protocol), and I<sup>2</sup>S-style serial digital
audio.<br> Akin to Yamaha’s PLG-100 / PLG-150 boards.
</td>
</tr>
</tbody>
</table>
<p>The theoretical max capacity is calculated using the formula 2<sup>(#
of address signals)</sup>×(# of data signals)×(# of CS lines)÷8. The
result is the theoretical max capacity in bytes.</p>
<p>So Roland apparently attempted to develop a new expansion format just
for the SC-8850 eh? Seems a weird thing to do for a product category on
its way out … Oh that’s probably why it was scrapped.</p>
<p>Again <a href="https://www.dtech.lv/techarticles_roland_exp.html">this excellent
article from Edward D-tech</a> has more details on the actual content of
these expansions.</p>
<h3 id="tocanch12" class="tvis">IC listing</h3>
<p>See below for a list of all ICs as installed on the main board (as
well as select ICs installed on the analog board) inside my SD-90.</p>
<p>Please note that this information is NOT from the service manual.
It’s read off actual PCB of my SD-90. Some remarks contain information
derived from my reading of the schematics. If you skipped straight to
this section, you may want to refer back to early sections where
unpopulated chips are discussed in more detail.</p>
<details>
<summary>
IC listing
</summary>
<table>
<tbody><tr>
<th>
Label
</th>
<th>
Engraving
</th>
<th>
Remark
</th>
</tr>
<tr>
<td>
IC1
</td>
<td>
62292 071
</td>
<td>
Voltage regulator with dual output
</td>
</tr>
<tr>
<td>
IC2
</td>
<td>
SH-3 1E3 F100B HD6417709A JAPAN
</td>
<td>
CPU, SH7709A
</td>
</tr>
<tr>
<td>
IC3
</td>
<td>
LH28F320BFE-PBTL80 SHARP JAPAN 0132 32B
</td>
<td>
Flash memory, 4 MiB
</td>
</tr>
<tr>
<td>
IC4 IC6
</td>
<td>
SAMSUNG 120 K4S161622D-TC80 T7D049A3 KOREA
</td>
<td>
CPU SDRAM, 2 MiB
</td>
</tr>
<tr>
<td>
IC5
</td>
<td>
7W04F 1E
</td>
<td>
Inverter
</td>
</tr>
<tr>
<td>
IC7 IC8 IC9
</td>
<td>
P14A0 VT245A
</td>
<td>
Bus transceiver
</td>
</tr>
<tr>
<td>
IC10 IC13
</td>
<td>
01 42 H LVXC3245
</td>
<td>
Bus transceiver
</td>
</tr>
<tr>
<td>
IC11
</td>
<td>
(illegible)
</td>
<td>
OR gate
</td>
</tr>
<tr>
<td>
IC12
</td>
<td>
0134H VHCT139A
</td>
<td>
Demuxer
</td>
</tr>
<tr>
<td>
IC14
</td>
<td>
VHC T04A 130
</td>
<td>
Inverter
</td>
</tr>
<tr>
<td>
IC15
</td>
<td>
(illegible)
</td>
<td>
AND gate
</td>
</tr>
<tr>
<td>
IC16 IC49
</td>
<td>
1C26 LV 00A
</td>
<td>
NAND gate
</td>
</tr>
<tr>
<td>
IC17
</td>
<td>
E1
</td>
<td>
NAND gate
</td>
</tr>
<tr>
<td>
IC18
</td>
<td>
1D36 LV 04A
</td>
<td>
Inverter
</td>
</tr>
<tr>
<td>
IC19
</td>
<td>
(Not installed)
</td>
<td>
DQ flip-flop for additional encoder knob.
</td>
</tr>
<tr>
<td>
IC20
</td>
<td>
1G26 LV 14A
</td>
<td>
Schmitt trigger inverter
</td>
</tr>
<tr>
<td>
IC21 IC23 IC24
</td>
<td>
0M16 LV 74A
</td>
<td>
DQ flip-flop
</td>
</tr>
<tr>
<td>
IC22
</td>
<td>
(Not installed)
</td>
<td>
DQ flip-flop in encoder circuitry. Bypassed in production units.
</td>
</tr>
<tr>
<td>
IC25 IC27
</td>
<td>
1D16 LV 08A
</td>
<td>
AND gate
</td>
</tr>
<tr>
<td>
IC26
</td>
<td>
BOSS R02565501 RA0B-B01 JAPAN 0130EPI F0006ZA8
</td>
<td>
DSP (“MR3”). Found in many BOSS effects (BF-3, DD-20, etc.), EDIROL
portable recorders (<a href="http://studiorepair.com/gallery/Edirol/R-1/slides/Edirol_R-1_DSP__STUDIOREPAIR_09101501_0910207816.html">R-1</a>,
<a href="https://static.roland.com/assets/media/pdf/r4_block_diagram.pdf">R-4</a>,
etc.), among other things (SP-404 etc.).<br>4 pairs of
I<sup>2</sup>S-style digital serial audio I/O (8ch in, 8ch out).
Curiously does not require external RAM despite many effects implemented
with it typically require a buffer.
</td>
</tr>
<tr>
<td>
IC28 IC50
</td>
<td>
1A4S LV 138A
</td>
<td>
Demuxer
</td>
</tr>
<tr>
<td>
IC29 IC39
</td>
<td>
Roland R01455956 RA08-503 JAPAN 0124EAI F0013ZBB
</td>
<td>
DSP (“XV”)
</td>
</tr>
<tr>
<td>
IC30
</td>
<td>
(Not installed)
</td>
<td>
Bus transceiver on wave address bus. <code>/OE</code> and
<code>DIR</code> tied to ground. Present in newer units.
</td>
</tr>
<tr>
<td>
IC31
</td>
<td>
(Not installed)
</td>
<td>
Bus transceiver on wave address bus. <code>/OE</code> and
<code>DIR</code> tied to ground. Present in newer units.
</td>
</tr>
<tr>
<td>
IC32
</td>
<td>
(Not installed)
</td>
<td>
Bus transceiver on wave data bus. Present in newer units.
</td>
</tr>
<tr>
<td>
IC33
</td>
<td>
(Not installed)
</td>
<td>
AND gate. Glue logic for unused bus transceiver chips on the wave data
bus, as well as the unused flash memory on the wave bus. Present in
newer units.
</td>
</tr>
<tr>
<td>
IC34
</td>
<td>
(Not installed)
</td>
<td>
Bus transceiver on wave address bus. <code>/OE</code> and
<code>DIR</code> tied to ground. Present in newer units.
</td>
</tr>
<tr>
<td>
IC35
</td>
<td>
(Not installed)
</td>
<td>
Bus transceiver on wave data bus. Present in newer units.
</td>
</tr>
<tr>
<td>
IC36 IC44
</td>
<td>
ESMT M11B16161A- 45T DZW1H8929 0126
</td>
<td>
XV EDO DRAM, 2 MiB
</td>
</tr>
<tr>
<td>
IC37
</td>
<td>
(Not installed)
</td>
<td>
OR gate. Lets CPU disable the effect RAM of the first XV chip.
</td>
</tr>
<tr>
<td>
IC38
</td>
<td>
(Not installed)
</td>
<td>
AND gate. Presumably used during development.
</td>
</tr>
<tr>
<td>
IC40
</td>
<td>
Roland R02678601 23C128L-529J 0127E7001
</td>
<td>
Wave ROM, 16 MiB. Later units used mask ROM of a slightly different
series (23C128<strong>A</strong>L525J, Roland P.N.
R03010612).<a id="n25" href="#note25" class="note">[25]</a>
</td>
</tr>
<tr>
<td>
IC41
</td>
<td>
Roland R02678612 23C128L-535K 0127E7002
</td>
<td>
Wave ROM, 16 MiB. Later units used mask ROM of a slightly different
series (23C128<strong>A</strong>L526K, Roland P.N. R03010623).
</td>
</tr>
<tr>
<td>
IC42
</td>
<td>
(Not installed)
</td>
<td>
Third 16 MiB wave ROM.
</td>
</tr>
<tr>
<td>
IC43
</td>
<td>
(Not installed)
</td>
<td>
4 MiB flash memory connected directly to the wave bus.
</td>
</tr>
<tr>
<td>
IC45
</td>
<td>
(Not installed)
</td>
<td>
OR gate. Lets CPU disable the effect RAM of the first XV chip.
</td>
</tr>
<tr>
<td>
IC46 IC51
</td>
<td>
129100 M5M5256DFP -55LL
</td>
<td>
SRAM, 32 KiB
</td>
</tr>
<tr>
<td>
IC47
</td>
<td>
Roland R02234767 203G08F1017 JAPAN 0052EAI B0196DAB
</td>
<td>
USB Audio Interface ASIC. 3 pairs of I<sup>2</sup>S-style audio I/O (6ch
in, 6ch out).<br>Seems to have pins for the <a href="#n18">ESP bus</a>
too, but are left unused in the SD-90. This chip is also found in
SC-D70.
</td>
</tr>
<tr>
<td>
IC48
</td>
<td>
Roland R02451356 106 052100
</td>
<td>
USB controller. Also found in SC-D70.
</td>
</tr>
<tr>
<td>
IC52
</td>
<td>
P145B 74VHC4046
</td>
<td>
PLL/VCO
</td>
</tr>
<tr>
<td>
IC53
</td>
<td>
0131H 9246
</td>
<td>
PLL
</td>
</tr>
<tr>
<td>
IC54
</td>
<td>
A E
</td>
<td>
Voltage regulator
</td>
</tr>
<tr>
<td>
IC55
</td>
<td>
7WU04F 1H
</td>
<td>
PLL
</td>
</tr>
<tr>
<td>
IC7 @ Analog Board
</td>
<td>
AKM AK4524VF 090A0123N
</td>
<td>
ADC/DAC
</td>
</tr>
<tr>
<td>
IC11 @ Analog Board
</td>
<td>
PCM1728E L0004E
</td>
<td>
DAC
</td>
</tr>
<tr>
<td>
IC18 @ Analog Board
</td>
<td>
CS8420-CS EP ZYAFMD0102
</td>
<td>
Digital audio sample rate converter
</td>
</tr>
</tbody></table>
</details>
<h2 id="tocanch13" class="tvis">Test Mode</h2>
<p>I don’t have much to say about the test mode in the SD-90 except it
does what it’s supposed to do and is in many ways extremely similar to
the test mode in the SD-80.</p>
<p>If you’re wondering what the key combination to enter test mode is
for the SD-90, it’s already referenced at an earlier point in this
post.</p>
<h2 id="tocanch14" class="tvis">A repair that nobody wanted
to do</h2>
<p>I daily drove my SD-90 as my primary soundcard for roughly two years,
mainly to combine the sound output of two computers. Later in this
period, the synthesizer portion of my SD-90 started generating noise in
its output, which sounds quite like <a href="https://gearspace.com/board/electronic-music-instruments-and-electronic-music-production/1133675-xv-5080-quot-static-noise-quot-solution.html">what
some people have heard from their XV-5080</a><a id="n26" href="#note26" class="note">[26]</a>. It was even heard in the USB output, so the fault
has to be in the DSPs.</p>
<p>As I didn’t have a oscilloscope, I couldn’t check the ripple on the
power rails. I didn’t have a replacement for the regulator used in the
SD-90 on hand, but I already had a bunch of capacitors (which were
bought for my XV-5080 repair), and fully convinced that SD-90’s voltage
regulator design is more reliable than the one in the XV-5080, I decided
to try just a partial recap first.</p>
<p>I focused on the main power rails and bypass capacitors close to the
DSP chips first. These are the capacitors I replaced:</p>
<pre><code>C1, C3, C4, C139, C150, C173, C205, C232</code></pre>
<p>I didn’t have access to the schematics when I did the recap. But if I
did, I’d also have replaced <code>C157</code>, <code>C187</code>,
<code>C191</code>, <code>C212</code>, and <code>C219</code>.</p>
<p>The original capacitors taken off the board seem to hold their
original capacitance pretty well (all &gt; 60% their designated
capability), but some had terrible ESR<a id="n27" href="#note27" class="note">[27]</a> (almost 100 Ω for 16V 10µF ones).</p>
<p>Most SD-90 units are produced during the height of the infamous <a href="https://en.wikipedia.org/wiki/Capacitor_plague">capacitor
plague</a>. Although all electrolytic capacitors specified in SD-90’s
parts list are made by Japanese manufacturers and therefore
theoretically weren’t affected by the plague, it’s not guaranteed that
they didn’t switch supplier later into the production: Roland is known
to switch supplier for non-critical parts like DRAM chips throughout a
product’s life time. As an example, my SD-90 (early production, mfg.
2001-10-27) was built with DRAM chips specified in the parts list
(Samsung DRAM chips for CPU RAM, ESMT DRAM chips for XV effect buffer).
But Palto’s SD-90 (mid-late production, mfg. 2004-12-09) uses Sanyo DRAM
chips for the CPU and ICSI DRAM chips for the XV chips. I am however not
familiar enough with how surface mount electrolytic capacitors are
marked to tell the actual manufacturer of the capacitors installed in my
unit. What I <em>can</em> tell is that: a) the markings for capacitors
in Palto’s unit seem identical to those in mine, b) I do recognize the
markings on the bigger capacitors I swapped out (“SVP”): those are the
SVP series of Sanyo’s OS-CON capacitors (as specified in the parts
list), and c) of all the capacitors I swapped that had pretty terrible
ESR values, they showed no sign of electrolyte leakage.</p>
<p>I haven’t heard noise from the internal synthesizer since the recap.
I also ceased daily driving my SD-90 as my main soundcard. Be nice to
your obsolete audio equipment, folks!</p>
<h2 id="tocanch15" class="tvis">Driver issues</h2>
<p>Starting from a certain insider release of Microslop Windows 11 from
late 2024, the Windows 8 driver for the SD-90 would no longer function
properly on my computer. All audio recordings produced from the device
(regardless of the API used, be it WASAPI, ASIO, or WINMM) were either a
choppy mess, or just complete noise.</p>
<p>I tried a bunch of workarounds, the only thing that worked was
forcing UA-25EX’s Windows 10 driver onto the SD-90. This however leaves
only the primary MIDI port accessible (as the UA-25EX only has one MIDI
port), so it’s not a perfect solution.</p>
<p>I’ve asked around when I first saw the issue, and apparently nobody
at the time had the same issue as me. I suspect that it might have
something to do with me being on the Winblows 11 insider dev channel
(now the canary channel). I haven’t asked anybody since, but now that
ZUN himself is also experiencing driver issues, I guess this has become
a more wide-spread issue.</p>
<p>On Linux, the generic USB Audio/MIDI driver is used just like the
audio-less SD-80, even when the USB Driver is set to Vendor on the
SD-90. As I’ve also noticed for the SD-80, the MIDI performance on Linux
is noticeably worse than on Windows with an official driver. However at
least it never produced unusable recordings.</p>
<h2 id="tocanch16" class="tvis">Other random rants</h2>
<p>I had much to say about SD-90’s built-in sound module and the
differences between it and the SD-80 in my second SD-80 post. <a href="/blog/post/2020-11-20.html#tocanch31">Click here to read that
rant.</a></p>
<p>I got my SD-90 for 150 USD, and it arrived with all the original
paperwork and software discs, a bargain if I do say so myself.</p>
<p>The built-in audio effects of the SD-90 is not a feature I used very
often. In fact I think I might have used it exactly once in
production.</p>
<p>If you came here for a generic review of the SD-90, and have been
tremendously disappointed to find out it’s anything but: well, first of
all I’m surprised that you’re still here reading this. And why don’t
check out these other excellent posts?</p>
<ul>
<li><a href="https://www.soundonsound.com/reviews/edirol-sd90">Sound On
Sound’s original SD-90 review from July 2002</a></li>
<li><a href="http://www.gigadenza.co.uk/?page_id=5116&amp;all=1">Gigadenza’s
page on the SD-90</a></li>
<li>Or even my two original posts on the SD-80 … they’re not excellent
but they are something!! (<a href="/blog/post/2019-04-25.html">first
post</a>, <a href="/blog/post/2020-11-20.html">second post</a>)</li>
</ul>
<p>That’s about it. Phew! That went on for much longer than I
thought!</p>
<h2 id="tocanch17" class="tvis">Gallery</h2>
<p>All photos in this gallery are taken by me. I offer my apologies in
advance for all the shaky and out of focus photos.</p>
<table class="gallery">
<tbody><tr>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/sd-90/peak.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/sd-90/ssbsthumb_1536x1536_peak.jpg"></a> <br>“This is peak.”
</div>
</td>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/sd-90/xgl.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/sd-90/ssbsthumb_1536x1536_xgl.jpg"></a> <br>“XGL.”
</div>
</td>
</tr>
<tr>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/sd-90/mac.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/sd-90/ssbsthumb_1536x1536_mac.jpg"></a> <br>Connected to a
PowerBook G4 and disguised as XV-5080.
</div>
</td>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/sd-90/s80emac.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/sd-90/ssbsthumb_1536x1536_s80emac.jpg"></a> <br>Least buggy
MacOS X software.
</div>
</td>
</tr>
<tr><td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/sd-90/acc.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/sd-90/ssbsthumb_1536x1536_acc.jpg"></a> <br>Accessories.
</div>
</td>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/sd-90/pcb-overview.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/sd-90/ssbsthumb_1536x1536_pcb-overview.jpg"></a>
<br>Disassembled in the first hour?!
</div>
</td>
</tr>
<tr>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/sd-90/pcb-date.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/sd-90/ssbsthumb_1536x1536_pcb-date.jpg"></a> <br>Early
specimen. Multiple unpopulated components visible.
</div>
</td>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/sd-90/pcb-cpu.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/sd-90/ssbsthumb_1536x1536_pcb-cpu.jpg"></a> <br>CPU.
</div>
</td>
</tr>
<tr>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/sd-90/pcb-smem.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/sd-90/ssbsthumb_1536x1536_pcb-smem.jpg"></a> <br>CPU RAM and
flash memory.
</div>
</td>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/sd-90/pcb-usb.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/sd-90/ssbsthumb_1536x1536_pcb-usb.jpg"></a> <br>USB
controller and audio interface.
</div>
</td>
</tr>
<tr>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/sd-90/pcb-xv0.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/sd-90/ssbsthumb_1536x1536_pcb-xv0.jpg"></a> <br>First XV
chip.
</div>
</td>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/sd-90/pcb-xv1.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/sd-90/ssbsthumb_1536x1536_pcb-xv1.jpg"></a> <br>Second XV
chip and one of the two sample ROM chips.
</div>
</td>
</tr>
<tr>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/sd-90/pcb-ic41.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/sd-90/ssbsthumb_1536x1536_pcb-ic41.jpg"></a> <br>Second
sample ROM chip and effect RAM chips.
</div>
</td>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/sd-90/pcb-pwrmr3.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/sd-90/ssbsthumb_1536x1536_pcb-pwrmr3.jpg"></a> <br>Power
supply section and MR3 DSP chip.
</div>
</td>
</tr>
<tr>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/sd-90/pcb-analog.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/sd-90/ssbsthumb_1536x1536_pcb-analog.jpg"></a> <br>Major
chips on the analog board.
</div>
</td>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/sd-90/rcp.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/sd-90/ssbsthumb_1536x1536_rcp.jpg"></a> <br>Power supply
section with new capacitors. Go ahead and roast my terrible solder
joints.
</div>
</td>
</tr>
<tr>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/sd-90/rcx.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/sd-90/ssbsthumb_1536x1536_rcx.jpg"></a> <br>Other swapped
capacitors. Red capacitors are 100% more powerful when they blow up.
</div>
</td>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/sd-90/rcr.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/sd-90/ssbsthumb_1536x1536_rcr.jpg"></a> <br>How I did the
recap. There’s no flux in sight …
</div>
</td>
</tr>
<tr>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/sd-90/sn.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/sd-90/ssbsthumb_1536x1536_sn.jpg"></a> <br>Service manual.
Mystical 1.00 firmware. Yes I was reading it on the road.
</div>
</td>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/sd-90/ver.jpg"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/sd-90/ssbsthumb_1536x1536_ver.jpg"></a> <br>Version
information.
</div>
</td>
</tr>
</tbody></table>
<h2 id="tocanch18" class="tvis">Acknowledgements</h2>
<p>Major thanks go to KRPalto47 for the PCB photos and morale support.
I’d also like to thank Eason20000 and others in the Touhou Instrument
Sources Research server for providing firmware version information of
their units.</p>
<p>Special thanks to archive.org’s Wayback Machine for preserving the
Internet’s history.</p>
<br><hr><div class="TText" id="notediv" style="font-size:80%;"><span class="TText"><a id="note1" href="#n1">[1]</a>: There aren’t a
whole lot of Roland products that share this feature set: SC-D70, SD-90,
SD-50 and SonicCell. That’s all that I can recall.<br></span><span class="TText"><a id="note2" href="#n2">[2]</a>: ZUN alluded to the
idea of finally stop using it altogether in a recent event, citing
difficulties installing the driver. Curse you, Microslop! But one thing
he said when they were on this topic is very true: it is not the
instrument he used that made his music distinctively “his”. Also <a href="https://youtu.be/cLauPiICjOA?t=9460">here’s a link to the event
live stream with the timestamp</a>, if you want to check out in detail
what they were talking about.<br></span><span class="TText"><a id="note3" href="#n3">[3]</a>: PCB: Printed Circuit Board, not Perfect Cherry
Blossom.<br></span><span class="TText"><a id="note4" href="#n4">[4]</a>: Until you noticed who uploaded it…<br></span><span class="TText"><a id="note5" href="#n5">[5]</a>: Well, it seems that Archive.org decided to take it down before
this post is even made public. Contact me if you want to see portions of
the manual I guess?<br></span><span class="TText"><a id="note6" href="#n6">[6]</a>: Well,
it’s been on the Japanese website, and it’s still there as I’m writing
this blog post. However I couldn’t find anything about this update on
their English website.<br></span><span class="TText"><a id="note7" href="#n7">[7]</a>: Except this <a href="https://www.vogons.org/viewtopic.php?t=56163">vogons post</a>,
which unfortunately doesn’t provide much insight either.<br></span><span class="TText"><a id="note8" href="#n8">[8]</a>: For example, some chips
take any voltages &gt; 2V as a logical 1, while others take voltages
&gt; 3.5V as a logical 1.<br></span><span class="TText"><a id="note9" href="#n9">[9]</a>: It can be argued that
since there are some amount of semiconductors between the XV chip and
the wave ROM chips in the newer design, these cheap transceiver chips
may serve as some form of protection for the ROM chips if things ever go
wrong, i.e.&nbsp;if the XV chips go crazy, instead of damaging the ROM chips,
the transceiver chips get damaged, protecting the ROM chips. But that’s
certainly not guaranteed to happen.<br></span><span class="TText"><a id="note10" href="#n10">[10]</a>: These are the names used for their signals
on the main board. On the analog board where these encoders actually sit
on they are actually referred to as <code>EN2</code>, <code>EN3</code>,
and <code>EN4</code> for some reason.<br></span><span class="TText"><a id="note11" href="#n11">[11]</a>: Having support for page access basically means accessing
consecutive addresses can be faster.<br></span><span class="TText"><a id="note12" href="#n12">[12]</a>: Each mask ROM has a
“mask code” assigned by its manufacturer. You can think of this as an ID
for the master copy of the content stored in that chip. All mask ROM
chips from a given series bearing the same “mask code” should
theoretically have exactly the same data. Conversely, chips in the same
series but different data usually have different mask codes.<br></span><span class="TText"><a id="note13" href="#n13">[13]</a>: For example, evidence from their owner’s
manual and service manuals seem to suggest that these are designed in
Italy instead of Japan, by Roland Europe s.p.a. Internals of these
models also show an extremely different design philosophy, one that’s
almost too modular. This entity seems to be the one responsible for a
few lower-end / education-oriented / oddball products from that era,
like the EM-10/20, EM-2000, VA-76 and VA-7/5. I’ll stop there before
this post gets hijack into a post on Roland Europe.<br></span><span class="TText"><a id="note14" href="#n14">[14]</a>: Although this
doesn’t rule out the possibility that it’s only written to when a system
update is being installed.<br></span><span class="TText"><a id="note15" href="#n15">[15]</a>: Since the MU2000 loads / records samples into a
4 MiB wave RAM, permanent sample storage requires a SmartMedia card to
be installed.<br></span><span class="TText"><a id="note16" href="#n16">[16]</a>: Also keep in mind
that the MU2000 was released almost two years before the
SD-90!<br></span><span class="TText"><a id="note17" href="#n17">[17]</a>: I have no idea why <code>SDO1</code>,
<code>SDO2</code>, and <code>SDI0</code> are skipped, but they seem to
be completely absent from any pin function markings that I’ve seen. A
similar situation exists in the XP series of chips, but they have one
fewer output port, and the ports are named differently too:
<code>SDOA[0..5]</code> and <code>SDIA[0..5]</code> for the “ESP bus”,
and then <code>SDOB, SDOC, SDOD</code> plus the input named
<code>SDIB</code>.<br></span><span class="TText"><a id="note18" href="#n18">[18]</a>: This mechanism existed since the original XP
chip. It’s sometimes referred to as the “ESP bus”, even in models that
doesn’t use the ESP4 chip. In addition to point-to-point communication,
it also supports a ring topology, e.g.&nbsp;in the Fantom-G: WX chip -&gt;
first WSP chip -&gt; second WSP chip -&gt; back to WX chip.<br></span><span class="TText"><a id="note19" href="#n19">[19]</a>: Sometimes
between it and another type of DSP chip as well, e.g.&nbsp;the ESP4 chip in
Fantom-S, MC-909, and MV-8800.<br></span><span class="TText"><a id="note20" href="#n20">[20]</a>: The XV-5080
doesn’t seem to do anything meaningful with this input.<br></span><span class="TText"><a id="note21" href="#n21">[21]</a>: Fantom-S and MC-909 have menus that
display a real-time VU meter. If I’m going to make a blind guess, that
is probably what the signal is used for.<br></span><span class="TText"><a id="note22" href="#n22">[22]</a>: Goes into the AK4527B
DAC, but the output doesn’t go anywhere. This output is used in the
MC-909, which shares basically the same main board.<br></span><span class="TText"><a id="note23" href="#n23">[23]</a>: MV-8800 handles its
onboard analog audio through the ESP4 chip.<br></span><span class="TText"><a id="note24" href="#n24">[24]</a>: The indicated part
number is actually a Miniature High Density connector.<br></span><span class="TText"><a id="note25" href="#n25">[25]</a>: Because it’s a chip from a different series, a
different mask code does not necessarily indicate changed
content.<br></span><span class="TText"><a id="note26" href="#n26">[26]</a>: This was
unfortunately also <a href="https://chrisoft.org/nks-twm/?post=1665126382">experienced by
me</a>.<br></span><span class="TText"><a id="note27" href="#n27">[27]</a>: equivalent series
resistance<br></span></div>]]></content></entry><entry><title>再写一次高考作文（高中毕业十周年特别版）</title><link href="https://chrisoft.org/blog/post/2025-06-18.html" rel="alternate" type="text/html" title="再写一次高考作文（高中毕业十周年特别版）"/><published>2025-06-18T00:00:00.000Z</published><updated>2026-03-25T03:45:02.075Z</updated><id>https://chrisoft.org/blog/post/2025-06-18.html</id><author><name>Chris Xiong</name><uri>https://chrisoft.org/</uri></author><content type="html" xml:base="https://chrisoft.org/blog/post/2025-06-18.html"><![CDATA[
<p>This post is in Chinese only. Sorry in advance. <a id="n1" href="#note1" class="note">[1]</a></p>
<p>Wait! There is now an English version of the second essay in this
post. It is part of the generative AI statement of this website. <a href="/blog/post/AI.html">Check it out here.</a></p>
<h2 id="tocanch0" class="tvis">前言</h2>
<p>用英文提前道完一个歉，还得用汉语再道一个。二〇二四年初我曾经在一篇<a href="https://chrisoft.org/blog/post/2024-02-29.html#tocanch0">博文</a>里写过，我的博客上以后主要会发一些技术相关的内容，并且也会尽可能压低政治相关内容的含量。本文是一篇与技术八竿子打不着的文章<a id="n2" href="#note2" class="note">[2]</a>，甚至可能包含我的个人琐事和对一些观念的认识。读者若对此类内容不感兴趣，请接受我诚挚的道歉并关闭此页面。</p>
<p>又是一年高考季。我自从高中毕业以来一直有观察高考写作部分题目的“传统”，甚至还做过几次和广大考生一起写高考作文这样的事情（<a href="/blog/post/2018-06-07.html">这里</a>有一篇公开的——提前提醒一下，写得很烂）。如果你是在我高中时期认识的我，你会感觉这简直是无法想象的事情：写作可以说是中国式应试教育中我最痛恨的一环（以至于我当时专门写过<a href="/blog/post/2014-11-14.html">一篇文章</a>来发泄。没错，现在终于可以把它正式公之于众了）。但是自从没有了分数的威逼利诱之后，我有时候还是会随便写点东西，倒倒脑子里的垃圾——毕竟那么多的“内心独白”，不把它记录下来感觉多少有点浪费。二〇二五年对我来说又是比较特别的一年：高考这件事，到现在已经被我抛在脑后十年有余了。再加上个人感觉最近几年的写作题目比较有趣，于是打算尝试一下……（你没有看错——我要来答近两年的高考作文题目！天哪我这是哪根神经搭错了竟然要这么虐待自己……）</p>
<p>但是作为不瞎扯两句就难受的我，肯定不能这么快就切入主题。每次我打开我的博客文章目录，都会看到许多烂尾文在屏幕上十分扎眼，比如下面这篇，日期是差不多恰好四年前的……</p>
<pre><code>（尝试）好好用中文写一篇文章
2021-06-26

闲来无事 （假话。4天之后还有两篇报告要交。）看了看自己以
往的博客。发现中文内容语无伦次，让人血压升高；英文内容也
好不到哪里去。加上发现自己上次用中文写博客已经有3年多了。
于是毫无征兆地给自己加了一个任务。

那么写点什么呢……毕竟这个标题简直就不像个标题。

想到哪写到哪好了。

## 为什么我的博客文章大部分是英文

## 我到底想「研究」些什么样的东西

## 我到底是不是一个自私自利的混蛋

## 如何看待以前的我（含各种版本）

## 如果现在回去高考，能拿到多少分

## 再喷当代中国大陆的考场作文制度

## 幻想跟现实之间究竟哪一个更荒诞

## 这篇文章半年之内有可能写完吗？</code></pre>
<p>先回答一下其中的最后一个问题：当然没有可能。这种基本只有一个提纲但没有任何实际内容的烂尾材料，在我的博客目录里还有一大把。自然我没有公布这其中的任何一篇。希望这篇能逃过成为它们之中的一篇的命运（不要说完就成真了啊%#$!……）。</p>
<p>（不过我有可能在本文的后记部分回答一些这份有四年年头的提纲里的某些问题？）</p>
<p>另外顺嘴提一句，<a href="/blog/post/2018-06-07.html">二〇一八年的那篇文章</a>，我在某种程度上还是给当成高考写作来写的（尽管毫无疑问是零分作文）：主要是篇幅上当时决定不要超过八百字太多。这次我的计划是彻底放开——无视一切高考写作的规则，单纯就我看到的题目谈我自己的想法。另外，照惯例我使用的是我高考时的省区使用的试题。</p>
<p>那么，差不多是时候开始了……！</p>
<h2 id="tocanch1" class="tvis">二〇二五</h2>
<p>阅读下面的材料，根据要求写作。<del>（60分）</del></p>
<p>他想要给孩子们唱上一段，可是心里直翻腾，开不了口。
——老舍《鼓书艺人》</p>
<p>假如我是一只鸟， 我也应该用嘶哑的喉咙歌唱 ——艾青《我爱这土地》</p>
<p>我要以带血的手和你们一一拥抱， 因为一个民族已经起来
——穆旦《赞美》</p>
<p>以上材料引发了你怎样的联想和思考？请写一篇文章。</p>
<p><del>要求：选准角度，确定立意，明确文体，自拟标题；不要套作，不得抄袭；不得泄露个人信息；不少于800字。</del></p>
<hr>
<p>«爱国的三驾马车»</p>
<p>出题人的小心思，自然不是只有天才才能看出来的。我在见识过中国跟美国两个爱国在主流观念中都占上风，但具体表现极不相同的国家（和它们的国民）之后，自然在这个主题上有很多想说的话。惟恐这些话可能不是出题人想要听到的……</p>
<p>首先界定一下我对爱国这个概念的定义：爱国是指一个人对一些与国家相关的特征的偏好或者情感牵挂。这些特征包括但不限于：</p>
<ul>
<li>物理位置及物理位置衍生的属性（比如气候，物产等等）</li>
<li>文化（包括语言），历史，以及生活方式</li>
<li>政体和与之相关的意象</li>
</ul>
<p>我把这三条称为“爱国的三驾马车”。因为以上特征有一些（主要包括在第二条内）实际上是属于民族的特征（而不是国家的），爱国主义跟民族主义往往有难以切割的联系。出题人能把最后一条材料夹带进来，多少也反映了这一点。</p>
<p>人总是会偏向于选择自己熟悉的东西，这是人之常情。因此以上所列的前两条并不难以理解。材料中的第二条所想表达的，其实也被涵盖在这个范围内。如果其他变量完全保持不变，我想应该很少有人会背井离乡，迁徙到自己不熟悉的地方——我自己也不例外。但是“其他变量完全保持不变”是一个很难在真实世界中找到实例的假想情形。这也是为什么现实生活中每天都有许多人像材料一中的鼓书艺人一样被迫踏上离开故土的路。在兵荒马乱的地方，小人物能够做出的选择往往都是很有限的。我与数位自己国家深陷战火的同事交谈过。其中有的来自侵略国，有的来自被侵略国。他们之中无一例外没有一个认为他们国家被卷入的战争对任何普通人有任何好处。不过就战争而言，对攻守两方我的观点还有一些差别，在后面可以读到。</p>
<p>事实上仅就前两条来看，中美两国的普通民众似乎并无太大差异。一边有“祖国那么多大好河山，为什么要去外国旅游？”的说法，另一边有誓死不离开自己农场半步的红脖。一边有执着于“五千年文明”的痴狂，另一边是一场场的独立战争著名战斗的带妆表演。尽管形式各异，如果究其根本，就会发现驱动这些行为的都是类似的思维模式。</p>
<p>很多时候，对以上前两类特征的情感牵挂经常被用来培养对第三类特征的偏好。比方说类似“保卫江山，踊跃参军”这样的说法在两个国家都不少见。这最后一条，也正是我认为比较有问题的一条。</p>
<p>在开始解释为什么我认为将爱国和政体捆绑在一起是一种比较有问题的操作之前，我首先需要点明一个问题：这并不是某一个国家独有的问题。一边是“五千年悠久历史”之正统继承的迷之自信，另一边是民主国家舍我其谁的傲慢。所以仅从表面来看，好像分不出个高下。</p>
<p>所以为什么说将爱国与爱政府等同是有问题的呢？我认为原因有二：一是因为与地理因素不同，政体是全然由人为因素决定的；二是因为在客观层面上政体有能力在极短时间内对人群造成不可逆转的危害（例如对外战争或者对内镇压）<a id="n3" href="#note3" class="note">[3]</a>。因此我认为把对政府的绝对拥护当作人的义务是与人性相悖的。如果还不能理解这个观点的话，可以试想一下如果你处在上世纪30到40年代，你的国家正在践踏别国的土地，滥杀无辜。在这种情况下一个人还怎么可能拥护自己的政府而不伤害自己的良心？</p>
<p>马克・吐温说过，真正理性的爱国主义，是“始终忠于国家，但只在政府值得被效忠时忠于政府”。
(“… the modern patriotism, the true patriotism, the only rational
patriotism, is loyalty to the Nation ALL the time, loyalty to the
Government when it deserves it”)
我想这是最接近我自己的观点的说法。即使抛开战争不谈，我也不能无条件地支持我的国家的政府：假设我的国家的政府擅自把整个国家的尊严出卖了（就像历史上发生过无数次的那样），我还如何支持它？<footnotee>插一句半不相关的话，我认为子女对父母的尊重其实某种程度上是一个非常类似的议题：虽然社会主流上也存在“孝敬父母天经地义”的说法，包括中国的君臣父子“五伦”之流，以及受基督教影响的西方”honora
patrem tuum et matrem tuam, ut sis longaevus super terram, quam dominus
deus tuus dabit
tibi.”（当敬尔父母、以致延年于尔上帝耶和华所赐之地）一说。但持有这些观点的人在见识到真正对孩子不负责任的父母（很不幸的是在当下的世界这种父母变得没那么罕见了）之后很难自圆其说。因此我认为无条件地尊敬父母也是一种不理性、对自己以至于对父母都不负责任的观点。</footnotee></p>
<p>有时爱国会伴随着一种排他性，或者导致一种优越感。在我看来这是有危险性的爱国主义的开端。如果一个民族真的相信自己高人一等，或者视某些其他群体为低贱之人的话，那么他就不再受到他的良心的束缚，而变得一切惨无人道的事情都做得出了。这也是为什么我极度反感“世代相传绵延不绝的中华文明”或者言必称三权分立的民主灯塔云云。</p>
<p>回到对比两国的主题上来。将爱国与政治挂钩，也不是其中某一个国家独有的特点。实际上在这两个国家中，爱国与政治都被紧紧地绑定在一起，只是风格迥异：教育上一边从小灌输烈士事迹、爱党爱国，另一边从小学起就要每天念诵《忠诚宣誓》(Pledge
of
Allegiance)；生活上一边是随处可见的爱国标语，另一边几乎可以把国旗贴到从公交车到地铁车厢等等的一切表面上。实话说，这些行为在我看来多少都有些俗气。但是可能是由于亲身从小经历过，我还是认为在俗气程度上其中的一方是稳操胜券的。</p>
<p>当然，我也不是一味地反对政治性的爱国。题目所给的三则材料，都取自外来军队入侵时期的作品。我想如果现今我的国家遭到了外敌的侵略，我也会竭尽我的所能来帮助我的国家——毕竟我想除了所谓的“浪人”，很少有人能在还存在这种牵挂时忍心看着承载着自己记忆、自己在乎的人生活着的土地任人蹂躏。反过来，因为我懂得“己所不欲，勿施于人”的道理，所以我也不会支持我的国家发动的侵略战争。</p>
<p>写到这里，也得有八百字了。评卷老师怎么看我的想法，恐怕我是不得而知了。但屏幕前的你是怎么想的呢？</p>
<h2 id="tocanch2" class="tvis">二〇二四</h2>
<p>阅读下面的材料，根据要求写作。<del>（60分）</del></p>
<p>随着互联网的普及、人工智能的应用，越来越多的问题能很快得到答案。那么，我们的问题是否会越来越少？</p>
<p>以上材料引发了你怎样的联想和思考？请写一篇文章。</p>
<p><del>要求：选准角度，确定立意，明确文体，自拟标题；不要套作，不得抄袭；不得泄露个人信息；不少于800字。</del></p>
<hr>
<p>«一个看不起人工智能的人看人工智能»</p>
<p>不会。</p>
<p>（完）</p>
<p>开玩笑的。自然我不会只写这么一点，尤其是在这个材料跟我的专业多少有点沾边的情况下。</p>
<p>相信很多人都听过下面的这个比方：假设一张无穷大的白纸表示宇宙间所有的知识，白纸上有一个圆圈，圈内表示全人类已知的知识，而圈外表示的是未知。当这个圆圈不断变大时，它与未知接触的界面的度量值也就越大。事实上，这个比喻不仅适用于作为一个整体的全人类<a id="n4" href="#note4" class="note">[4]</a>，也适用于作为个体的每一个人。</p>
<p>请注意到这个比方里完全没有提到互联网和人工智能。也就是说它们的发展并不会影响我的回答。所以下面我就来漫谈一下关于互联网和人工智能的事情。</p>
<p>想必如果在大众群体中做个调查的话，大多数人都会认为人工智能的发展比互联网晚得多。事实却正好相反：当人工智能在经历第一个“寒冬”的时候（上世纪70年代中期的”<a href="https://en.wikipedia.org/wiki/AI_winter">AI
winter</a>“），现代互联网的概念都没有诞生——甚至它所用的协议都还在摇篮中<a id="n5" href="#note5" class="note">[5]</a>。很多机器学习的基础算法，它们的数学背景在上世纪60-70年代就被人研究过了，只是因为当时算力的匮乏，无法得到广泛的应用。这也是为什么最近十多年，得益于计算机硬件性能的急剧提升，这些技术的成果被一个接一个地展现在公众眼前。</p>
<p>在正式开始扯淡之前，我想我有必要先声明一下我的专业背景：虽然我是计算机科学专业出身，但是我研究的方向跟互联网和人工智能都毫不沾边——事实上我在选择方向时是有意避开了一切人工智能强相关的方向的。原因之一是我一直对人工智能持谨慎态度——即便是近三年生成式人工智能的爆炸式发展也没有改变我的观点。事实上我反而非常反感现在“人工智能”被植入一切软件的做法：我认为这不过是为了迎合资本而搞的噱头；并且我认为目前的人工智能行业里有相当大程度的水分——不过这其实不是重点：因为我也清楚，就像2000年初互联网泡沫破裂之后我们仍然有互联网一样，如果人工智能行业的泡沫在未来的任何时间破裂，人工智能（甚至生成式人工智能）也不会从我们的身边消失。我们再也不会回到没有生成式人工智能的年代了<a id="n6" href="#note6" class="note">[6]</a>。</p>
<p>至于为什么生成式人工智能没有改变我的想法，其根本还是在于因为我认为生成式人工智能并没有真正“创新”的能力。早期的生成式人工智能在基本数学问题上闹出的笑话已经说明了究其根本作为一个统计学模型，当前的生成式人工智能缺乏逻辑思维的能力。虽然后来的模型在此方面有所改进，但是如果去问它们从未出现在它们的训练数据中的概念的话，得到的回答仍然让人摸不着头脑。就当前的情况来说，这同样也适用于被设计来模仿人类创作的的生成式人工智能。这就是我认为现阶段的生成式人工智能缺乏创新的能力的原因。</p>
<p>作为资本的产物，想必从资本的视角来说，生成式人工智能一定是个宝贝了吧？倒也不一定。以前就多次听说过某些大厂的高层人士发出“AI界的各种基准测试、甚至‘AGI’的概念本身，都是无意义的攀比”这种观点。如果说这只是商业公司为了安抚自己的投资人情绪而发表的嘲讽竞争对手的言论的话，没多久前在生成式人工智能方面投入巨额资源的微软，他们的首席执行官还刚刚感叹过“AI还没给我们创造过什么真正的价值”<a id="n7" href="#note7" class="note">[7]</a>，这又是怎么回事呢？尽管如此，他仍然重申微软对人工智能的投资不会因此停止——不知道是真正看到了希望呢，还是陷入了沉没成本的陷阱？</p>
<p>不过话说回来，人工智能作为一个工具，应当像对待其他工具一样客观合理地对待它：既不能像因为恐惧从油灯换电灯而传播电灯伤眼的谣言，也不能像拿到锤子之后看到个东西就会想去拿它砸一下一样。冷静地分析人工智能的长处和短处，才能更有效地利用这个工具。考虑到如今很多人大都多少对人工智能的长处有些许了解，比如提高生产力之类的（当然这也不是完全没有争议的，比如<a href="https://arxiv.org/abs/2506.08872">这个麻省理工学院的研究声称使用人工智能进行辅助写作会导致“认知债”的积累，而不会真正提高生产力</a>），下面主要讨论几个我认为比较重要的人工智能的短板，尤其是我认为目前关注度似乎不够高的。</p>
<p>首先是对用户的过度肯定。在大语言模型还经常犯下“45是质数”这种低级错误还对其判断特别有自信的时代（其实也不算太久以前吧），人们意识到不能给大语言模型太多的自信。在一番折腾之后，这种情况有所改善，但又出现了新的问题：现在它们经常走向另一个极端，把用户的指令奉为圣旨，用户不管问什么问题都会回应“这个问题问得好极了”。对于商业大模型来说，这种讨好用户的行为甚至是合人意的。但是我认为这非常容易加强用户的现有观点——不论它们正确与否，从而把用户困于一种新式的“回音壁”中。</p>
<p>提示词注入也是个危险性不低的问题。先前公众对此的了解也不多，但最近因为一些曝光率比较高的事件的发生（比如<a href="https://www.nature.com/articles/d41586-025-02172-y">在论文里藏隐匿的提示词让用大模型“帮忙”审阅论文的评审自动给出正面评价</a>啦，在<a href="https://0din.ai/blog/phishing-for-gemini">电子邮件里藏白字诱导总结邮件内容功能输出特定文字</a>啦），提示词注入也来到了公众的视野中。比较令人担忧的是，最有可能使用这些面向普罗大众的“AI总结”功能的人，有很大可能对这种潜在的风险一无所知。当然对此我也给出不了除了在此功能的入口处加上红色大号警告文字之外特别有效的应对方案。但是渴望吸引更多用户的公司是绝不可能主动加入这种提示的。</p>
<p>我想专门提到的，就是以上这两点。当然人工智能有争议的地方远远不止这些，比如生成式人工智能的创作能否被称为“艺术”、训练数据的版权问题以及相关设施的能源消耗等等。这些议题已经有其他人做了充分的讨论，我也没有什么特别独特的观点，所以在此不再赘述。</p>
<p>最后，我想引用科幻作品《沙丘》里的一句话结尾：</p>
<blockquote>
<p>Once men turned their thinking over to machines in the hope that this
would set them free. But that only permitted other men with machines to
enslave them.</p>
</blockquote>
<blockquote>
<p>以前人们把思考的工作交给机器，以为这样可以使自己得到解放。结果最后他们成了拥有机器的人的奴隶。</p>
</blockquote>
<h2 id="tocanch3" class="tvis">后记</h2>
<h3 id="tocanch4" class="tvis">写后感</h3>
<p>这写得也太[expletive]垃圾了……</p>
<p>怕不是随便丢给AI一写都比我写出的这堆破烂强千百倍。<a id="n8" href="#note8" class="note">[8]</a>不过这也从侧面证明了我的这堆破烂不是用AI写的……未尝不是一件好事。</p>
<h3 id="tocanch5" class="tvis">不红了？</h3>
<p>说实话当时最初看到二〇二四年的题目的时候有种耳目一新的感觉：出题人竟然不把主旋律宣传放到第一位了，而且出了一个我上高中十多年以来的第一个真正让我有想写点什么的冲动的题目。当时一时冲动，就下了一定要找时间写一写的决心。</p>
<p>当然我这么想也未免有些理想主义的天真幼稚。写完我的文章之后我去网上搜了一下对这个题目的分析……看到搜索结果里排名第一的一上来这么写道：</p>
<blockquote>
<p>一、命题意图</p>
<p>习近平总书记曾经引用爱因斯坦的话来说明发现真正的问题的重要性：“提出一个问题往往比解决一个问题更重要。”新课标I卷写作试题据此设题。</p>
</blockquote>
<p>……我错了。</p>
<p>当然二〇二五年的题目就又直接原形毕露了。还是多少有点失望。（不过你的希望到底是哪里来的啊啊啊……）</p>
<p>（刚才又看了下网上的范文……依旧是那种卖弄辞藻的八股文，还是熟悉的配方、熟悉的味道……呕）</p>
<h3 id="tocanch6" class="tvis">烂尾文收场</h3>
<p>借这里为开头提到的那篇四年多的烂尾文收个场。</p>
<blockquote>
<p>为什么我的博客文章大部分是英文</p>
</blockquote>
<p>很简单。因为我感觉目前我用英语表达自己的想法比用汉语更流利。</p>
<blockquote>
<p>我到底想「研究」些什么样的东西</p>
</blockquote>
<p>当时问这个问题的时候我还没有一个明确的答案。不过现在已经有了，这个问题也就没有问的意义了。</p>
<blockquote>
<p>我到底是不是一个自私自利的混蛋</p>
</blockquote>
<p>我认为自私是人类作为动物的天性和本能。一些表面上看似无私或者牺牲自我的行为，实质上是不会对实施这个行为的个体是百害而无一利的。</p>
<p>说到底这还是一个利弊分析的问题。人类作为认知有限的实体，只能依靠自己所知的利弊来做出选择。如果我所知的利大于我所知的弊的话，我也会做出这些外界看起来无私的行为的。</p>
<blockquote>
<p>如何看待以前的我（含各种版本）</p>
</blockquote>
<p>话说你这么多年真的变了吗……不还是一个胆小鬼</p>
<blockquote>
<p>如果现在回去高考，能拿到多少分</p>
</blockquote>
<p>多么愚蠢的问题。下一个！</p>
<p>（偷偷瞄过一眼数学试卷，情况不容乐观……大概只能考个300分上蓝翔了）</p>
<blockquote>
<p>再喷当代中国大陆的考场作文制度</p>
</blockquote>
<p>前面喷过了，不喷了。</p>
<blockquote>
<p>幻想跟现实之间究竟哪一个更荒诞</p>
</blockquote>
<p>我想今年到现在发生的一切已经回答了这个问题。</p>
<blockquote>
<p>这篇文章半年之内有可能写完吗？</p>
</blockquote>
<p>当然没有可能，想什么呢！</p>
<p>唉……</p>
<p>2025-07-23 01:01:28</p>
<br><hr><div class="TText" id="notediv" style="font-size:80%;"><span class="TText"><a id="note1" href="#n1">[1]</a>: Seems
unnecessary for a blog that’s barely visited by anyone, doesn’t
it?<br></span><span class="TText"><a id="note2" href="#n2">[2]</a>: 呃……有一半话题是跟人工智能相关的，所以也不能算完全打不着吧？<br></span><span class="TText"><a id="note3" href="#n3">[3]</a>: 如果你要说“地震也会致人死亡，为什么我还要爱这土地？”那我也无话可说——毕竟现阶段人类还无法阻止地震的发生。当然如果未来人类有这种能力了的话就是另一回事了。<br></span><span class="TText"><a id="note4" href="#n4">[4]</a>: 假设“全部的知识”确实是无穷的情况下。<br></span><span class="TText"><a id="note5" href="#n5">[5]</a>: <a href="https://en.wikipedia.org/wiki/Internet_Protocol#Version_history">IP协议的简短历史背景见于维基百科</a><br></span><span class="TText"><a id="note6" href="#n6">[6]</a>: 在不考虑世界毁灭的前提下<br></span><span class="TText"><a id="note7" href="#n7">[7]</a>: <a href="https://economictimes.indiatimes.com/news/international/us/microsoft-ceo-satya-nadella-shocks-industry-by-admitting-ai-has-yet-to-deliver-real-value-despite-massive-investment/articleshow/121912600.cms">原报道见此</a><br></span><span class="TText"><a id="note8" href="#n8">[8]</a>: 顺便感叹下，为什么我读高中的时候没有大语言模型这种东西……要是有的话当时我就可以更有效地气死语文老师了。<br></span></div>]]></content></entry><entry><title>Someone has to do it: Reverse engineering Intel&#39;s OLED panel driver</title><link href="https://chrisoft.org/blog/post/2025-04-28.html" rel="alternate" type="text/html" title="Someone has to do it: Reverse engineering Intel&#39;s OLED panel driver"/><published>2025-04-28T00:00:00.000Z</published><updated>2026-04-01T00:08:16.513Z</updated><id>https://chrisoft.org/blog/post/2025-04-28.html</id><author><name>Chris Xiong</name><uri>https://chrisoft.org/</uri></author><content type="html" xml:base="https://chrisoft.org/blog/post/2025-04-28.html"><![CDATA[
<p>Finally, I managed to put my non-existent reverse engineering skills
to good use for once.</p>
<p>I recently <a href="/blog/post/2025-04-16.html">upgraded my ThinkPad
X1 Yoga 1st gen to an OLED panel</a>. And I was actually stunned to find
out that a) Support for proper PWM OLED brightness control for this
laptop, which is almost a decade old at this point, is still completely
absent in the Linux kernel, and b) Nobody has bothered to do anything
about it yet. People have been recommending solutions like
<code>xrandr --brightness</code> or <a href="https://github.com/udifuchs/icc-brightness">icc-brightness</a>,
which are both software color filters and will reduce the effective
color depth of the display. I have tried out the first solution, and the
color banding it causes on lower brightness settings drives me
absolutely nuts.</p>
<p>I guess some one has to do it, and that person happens to be me this
time. Challenge accepted.</p>
<p>Before someone cries out “but reverse engineering is illegal”, I’ll
say this: Yes it is. But I <a href="https://forum.level1techs.com/t/consumer-bill-of-rights-draft/229124#h-4-the-right-to-reverse-engineer-4">don’t
believe that it should be</a>.</p>
<p>So let’s get started!</p>
<h2 id="tocanch0" class="tvis">Obtaining the Windows driver</h2>
<p>The official Windows driver can be easily obtained from Lenovo under
the entry “Intel OLED Panel Driver for Windows 10 (64-bit) - ThinkPad X1
Yoga (Machine types: 20FQ, 20FR)”. The file name is
<code>n1fdl04w.exe</code>. This is just a simple installer that extracts
the driver files to <code>C:\DRIVERS</code> and wine runs it without any
issue.<a id="n1" href="#note1" class="note">[1]</a></p>
<p>When I looked through the driver files, I found all the usual
suspects: a Windows kernel driver binary, a <code>.inf</code> file, a
file containing the code signing information, and … a <code>.pdb</code>
file? Is that what I think it is? Turns out that it is precisely what I
thought it was: <strong>Lenovo somehow published the debugging symbols
in this driver package</strong>. This immediately lowered the challenge
level from intermediate to low or even trivial. But this also makes me
even more baffled: why did nobody take on this task when it’s actually
this easy?</p>
<h2 id="tocanch1" class="tvis">First look at the decompiled
code</h2>
<p>My go-to reverse engineering tool was <a href="https://cutter.re/">Cutter</a>. I chose it because it’s small, has
a proper native user interface, and has seemingly good Ghidra
integration. It was in fact the only reverse engineering tool when I was
taking a class on software security. However when I loaded the driver in
Cutter, I found the result to be very underwhelming: notably its Ghidra
integration failed to apply any data structure information to the
decompiled code.</p>
<p>So I decided to give the full Java monstrosity a try. Without a doubt
it worked much better. The decompiled code is already pretty close to
readable, what’s left to me is to simply name a bunch of local
variables, and rewrite the whole thing.</p>
<p>It seems that the Windows driver is communicating with the panel
using private APIs provided by the Intel graphics driver, obtained from
a call to <code>ExCreateCallback</code> with <code>ObjectName</code>
being <code>\CallBack\IGD_PanelDriverShareObj</code>. The communications
seem to happen with a function called
<code>AuxOrI2cOverAuxAccess</code>, which from its name and usage
pattern appears to do something very similar to the
<code>drm_dp_dpcd_read</code> / <code>drm_dp_dpcd_write</code> family of
functions in the DRM infrastructure of the Linux kernel, which happens
to be used by the brightness control logic for newer OLED panels
connected to an Intel GPU in the i915 driver as well.</p>
<p>I compared how the hardware is accessed between the Windows driver
and the existing OLED brightness control code in the Linux kernel
(<code>intel_dp_aux_backlight.c</code>), with the hope that they could
share some similarities. It quickly became very obvious to me that they
are in fact, extremely different. The Windows driver has lots and lots
of hard-coded constants inside the driver, as well as a bunch of nested
loops, both of which are absent from the existing driver inside the
Linux kernel. It also reads a variety of information from the Windows
registry, which is set by the <code>.inf</code> file when the driver is
installed. Basically the only commonality that I was able to find
between these two drivers is that both contain references to the term
“tcon”<a id="n2" href="#note2" class="note">[2]</a>. That’s it.</p>
<p>Right in the middle of the reverse engineering process, I decided to
find out whether there are other laptops that have used a similar
driver. I found the X1 Yoga 2nd gen, which wasn’t really surprising at
all because it’s using the same panel. But notably that driver doesn’t
come with debug symbols. I also found the Alienware 13 R3, which has an
extremely similar driver in terms of overall structure. One significant
difference is that it does not have the biggest chunk of hard-coded
magic numbers found in the Lenovo driver. It instead reads these values
from the registry, which are also set by the driver’s <code>.inf</code>
file. HP also provides a download to a very similar driver for an
alleged model of Spectre x360. However I was unable to find information
on a OLED version of this model from that era. The driver itself is
practically the same as Dell’s driver.</p>
<h2 id="tocanch2" class="tvis">Developing the reverse
engineered driver</h2>
<p>I do not have much experience in kernel development at all. I really
had no idea where to put this reverse engineered driver. But after
surveying the kernel source code surrounding the stuff that I might use,
I realized that the code may have to sit in the i915 driver, the primary
reason being I couldn’t find a way to get hold of the DP AUX
communication channel used by <code>drm_dp_dpcd_*</code> outside of it.
I decided to develop the prototype code within the i915 driver first. If
later I was told there’s a way to make it a standalone module, I should
be able to do it still.</p>
<p>The Windows driver starts nearly all communications with the panel
with a handshake. I decided to implement the handshake first so that I
can confirm that <code>drm_dp_dpcd_*</code> is the correct family of
functions that I should use. Soon enough I got the confirmation that I
wanted.</p>
<p>The driver then requests a bunch of data from the panel. This was
also decently easy to implement. In the Windows driver these values
would be written to the registry so that they don’t have to be retrieved
from the hardware again, which can be used to verify the correctness of
my driver. But I have actually just removed the Windows installation
from this machine a few weeks ago, which wasn’t booted into since I
restored it in 2023, so I skipped that step for now.</p>
<p>Then there just two functions left to implement. A function that
calculates interpolated brightness values, and the actual process to set
the brightness. A lot inside the driver seems to imply that they only
have 33 distinct brightness levels ([0,32]) which is later interpolated
to the range of [0,100]. The function that performs this interpolation
is gigantic, and contains an immense amount of repetitive logic, with
only the numbers changing all the time. These numbers for the
interpolation procedure are also different in Dell’s driver. The
function that sets the brightness is also huge, but it’s because it
starts with two big arrays filled with magic numbers. And as noted
above, in other vendors’ implementation of this driver, these values are
read from the registry. Once I got over that, the rest of this function
is actually not very difficult at all.</p>
<p>So at this point I have all the critical functions for setting the
brightness implemented. But I’m still in a very early prototyping stage:
every bit of code I wrote was piggybacking on the existing dpcd
backlight code, and everything is only run once when backlight control
is being initialized. Anyway as a first test of actual functionality I
tried to set the brightness to 30% as soon as the backlight control
initializes.</p>
<p>Nothing happened. The screen is still at full brightness. Devastated,
I dug up an external disk and put a new installation of Windows 10 on it
in order to compare the values I read from the hardware and the Windows
driver read from the hardware.</p>
<p>Unsurprisingly, <strong>every single one of them was wrong</strong>.
I went back to the code, comparing the magic numbers in my code to those
in the decompiled code one by one … and found that I had
<code>0xa</code> in one position where it should have been
<code>'\a'</code>. D’oh!</p>
<p>Once that has been fixed, the values read by my driver were now
correct<a id="n3" href="#note3" class="note">[3]</a>. But the
driver still did nothing. In fact I even checked all the values that
have been written into the DPCD registers, and they all appeared to be
correct when compared against the simulated result from the Windows
driver.</p>
<p>At this point I truly had no idea what I could have done wrong. So I
decided to plug all the functions into their proper backlight control
interface anyway and try my luck, and lo and behold: it worked
instantly. You cannot not imagine my surprise when I saw the screen
dimming down when <a href="https://cgit.chrisoft.org/lightsd.git/">lightsd</a> kicked in to
automatically turn the brightness down when the system is booting.</p>
<p>So yes, that was when I got the first working prototype of the
driver. Keep in mind that I have no idea what the hardware protocol used
is. I don’t know what any of the addresses the driver is reading from /
writing to does. I have my own guesses, but I have no idea whether they
are correct or not.</p>
<p>With the intention of reducing the amount of magic numbers inside the
code, I extracted them into a firmware blob. I also extracted the values
used in the interpolation process into the firmware, leaving just one
instance of the repetitive interpolation logic in the code. Once I have
also removed all the static global variables, I declared the driver
finished.</p>
<p>There’s one notable weirdness: when the brightness is set to lower
levels, the screen brightness sometimes fluctuates randomly. This
behavior is also observed on Windows with the official driver. Since my
machine is modded to use the OLED panel, it is not yet known to me that
whether this is a hardware issue or a software issue.</p>
<h2 id="tocanch3" class="tvis">If you want to use this
driver</h2>
<h3 id="tocanch4" class="tvis">Will this work for my panel?</h3>
<p>If your computer has an OLED screen and meets one of the following
criteria, there’s a chance that this driver might work for you:</p>
<ul>
<li>Your computer is released between 2015-2018, has an Intel processor
and uses its integrated GPU.</li>
<li>The <code>.inf</code> file in the Windows driver package for your
computer’s OLED panel contains the following text: <code>AOR</code>,
<code>DELVSS</code>, <code>BrightLevelForACL</code>, and
<code>ACLCutoffAndDelta</code>.</li>
</ul>
<h3 id="tocanch5" class="tvis">Using the driver</h3>
<p>The patch and required tools can be obtained here:</p>
<p><a href="https://cgit.chrisoft.org/linux-legacy-oled-brightness.git/">https://cgit.chrisoft.org/linux-legacy-oled-brightness.git/</a></p>
<p>Instructions for ThinkPad X1 Yoga 1st gen / 2nd gen:</p>
<ol type="1">
<li>Run <code>gen_panel_prop.py</code> to generate
<code>intel_legacy_panel_data.bin</code>. Put the generated file in your
firmware directory (and if needed, your initramfs).</li>
<li>Patch the sources for the kernel of your choice. Compile and install
it.</li>
<li>Boot your new kernel with an additional kernel parameter
<code>i915.enable_dpcd_backlight=99</code>. Check if it works.</li>
</ol>
<p>If you have a different laptop, I would not recommend using the
firmware for the ThinkPad panel. You can still try it, but I don’t know
what will happen. Your computer may explode. To properly make use of
this driver you’ll have to modify <code>gen_panel_prop.py</code>
according to the values inside the Windows driver for you panel.
Properly filling out the values requires some reverse engineering. I may
add more detailed instructions here or in the repo later.</p>
<h2 id="tocanch6" class="tvis">That’s it?</h2>
<p>Uhh, so that’s the first time I actually used my reverse engineering
skills for something useful. Was it hard? No, not really. Will I do it
to myself again? No, but knowing myself I’m positive that I absolutely
will do it to myself again some time in the future. Will it ever be
upstreamed? You tell me! If you want to see this abomination sent to the
upstream, please let me know by contacting me using one of the methods
listed <a href="https://chrisoft.org/#contact">here</a>.</p>
<h2 id="tocanch7" class="tvis">Timeline of the project</h2>
<ul>
<li>2025-04-18: Looking through the debugging symbols just for fun.</li>
<li>2025-04-19: Actually started walking through the decompiled code and
naming things.</li>
<li>2025-04-20: Started the prototype driver. Successful handshake with
the hardware.</li>
<li>2025-04-21: Looking through the same driver from a few other
vendors. Finished implementing 3 of 5 critical functions.</li>
<li>2025-04-22: Biggest function reverse engineered. Output values
verified against the Windows driver.</li>
<li>2025-04-23: Finished implementation of all required functions. First
prototype that has working brightness adjustment.</li>
<li>2025-04-24: Parameterized a lot of the code. Magic numbers moved
into firmware files.</li>
<li>2025-04-25: Global static variables removed from the prototype.
Driver declared complete.</li>
<li>Now: <del>awaiting a lawsuit from Lenovo or Intel.</del></li>
</ul>
<br><hr><div class="TText" id="notediv" style="font-size:80%;"><span class="TText"><a id="note1" href="#n1">[1]</a>: I deleted the Windows installation on this computer a
couple of weeks ago in order to reclaim some disk space. This would come
back to bite me a little bit later…<br></span><span class="TText"><a id="note2" href="#n2">[2]</a>: “Timing controller”? Could make sense because it’s
controlling the brightness with a PWM signal…<br></span><span class="TText"><a id="note3" href="#n3">[3]</a>: Actually there was an additional detour of one row in
the interpolated values being wrong. It was again just a single wrong
number that costed me a <em>mere</em> hour to debug.<br></span></div>]]></content></entry><entry><title>My first and only CTF post</title><link href="https://chrisoft.org/blog/post/2025-04-26.html" rel="alternate" type="text/html" title="My first and only CTF post"/><published>2025-04-26T00:00:00.000Z</published><updated>2025-10-20T04:46:11.653Z</updated><id>https://chrisoft.org/blog/post/2025-04-26.html</id><author><name>Chris Xiong</name><uri>https://chrisoft.org/</uri></author><content type="html" xml:base="https://chrisoft.org/blog/post/2025-04-26.html"><![CDATA[
<h2 id="tocanch0" class="tvis">Preface</h2>
<p>Ah yes, CTF. The green pasture that I never bothered to explore in
the first two decades of messing around with computing.</p>
<p>However, when I took <a href="https://syllabi.engineering.osu.edu/syllabi/cse_5474">CSE 5474 at
OSU</a> last year, which is a course that revolves around these CTF
challenges, I was instantly hooked. I went absolutely crazy over them,
solving the entire dozen in one ultra-extended (and sleep-deprived)
night, which was supposed to be solved over the span of roughly two
weeks each set. I guess it was my old competitiveness getting to me… I
believe I ended up being the only person in that class who solved every
single CTF challenge <a id="n1" href="#note1" class="note">[1]</a>.</p>
<p>As I said, security research is an area that I’ve never explored
before. I came to this class with little prior experience: I’ve never
written any serious code in assembly. In fact, I don’t know much
assembly in the first place: I have to look up what the <code>LEA</code>
instruction does almost every time I see them. While I do have some
decent experience debugging with GDB, but it’s limited to programs with
source code available, and I have little knowledge in reverse
engineering. But thanks to the thoughtfully designed structure of the
course, as well as its introductory nature, I was able to get accustomed
to the basics pretty fast.</p>
<div style="text-align:center;">
<a href="//filestorage.chrisoft.org/blog/img/stackcorruption.png"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/ssbsthumb_1024x1024_stackcorruption.png"></a><br>My first
conscious encounter with a stack corruption bug. That was almost ten
years ago. The “good old times”.
</div>
<p>Towards the end of the class, we were asked to design our own CTF
challenges to be included in the final challenge set. The challenge
you’re about to see is what I submitted. It was based around a huge
detour that I got myself into when I was solving the last homework
assignment challenge set, which is on the topic of heap vulnerabilities
and was in fact, the only challenge set that I spent <em>two</em> nights
instead of one working on thanks to said detour. Because it was quite a
trip for me, I made it into a full challenge with the hope that it could
be a nice learning experience for everyone. That unfortunately did not
happen, because no one even took a crack at it, even after me posting a
pretty thorough walk-through of the challenge in the class discussion
board, which is included in the “Guide” section of this post.</p>
<p>I have later had a discussion with the TA of the class, who’s
responsible for the entire CTF component of the class, to make my
challenge and the write-up public. The request was approved, and this
post you’re reading right now is the result of it.</p>
<p>Please note that this is the first ever (and probably last, as will
be explained soon) CTF challenge designed by me, it might be fatally
flawed and / or laughably easy. I’d appreciate any constructive
criticism though.</p>
<p>If you read the title right, you might be wondering why would this be
the only post on CTF from me, after all it seems to be the case that I
enjoy solving them. But unfortunately, despite enjoying the dopamine
rush after solving a challenge, I ultimately only have limited time.
Plus security is at best only tangentially related to my area of
research. So I have decided that it’s probably better for me to keep
this adventure as a leaflet of memory in my diary to be cherished
forever.</p>
<h2 id="tocanch1" class="tvis">The challenge</h2>
<p><a href="//filestorage.chrisoft.org/blog/data/babyheap10.tar.gz">Download
the archive for the challenge.</a></p>
<p>This challenge is designed for the AMD64 platform. It specifically
required the <strong>vulnerable</strong> glibc version
2.27-3ubuntu1.2_amd64 to work. You may download this
<strong>vulnerable</strong> glibc version from launchpad.net using <a href="https://launchpad.net/ubuntu/bionic/amd64/libc6/2.27-3ubuntu1.2">this
link</a>.</p>
<p>The archive includes the following folders:</p>
<ul>
<li><code>bin</code>: the binary you would have received on a CTF
platform.</li>
<li><code>src</code>: contains the source code for the challenge.</li>
<li><code>sol</code>: contains a wacky reference solution.</li>
</ul>
<p>Your goal, as usual, is to obtain a root shell by interacting with
this program (when it is owned by root and has the suid bit set).</p>
<h2 id="tocanch2" class="tvis">Guide</h2>
<p>Below is the walk-through I posted to the class discussion board,
nearly in verbatim. For this reason it will contain references to
materials used in the class.</p>
<p>Please note that all GDB interactions in this guide are for
illustration purposes only. The address you get will differ.</p>
<h2 id="tocanch3" class="tvis">Key
differences between this challenge and Level 6 in the heap challenge
set</h2>
<ul>
<li>tcache bins are disabled.</li>
<li>No Christmas present (must find the libc base address on your
own).</li>
<li>The executable was compiled with PIC on. You’ll have to find the run
time base address of the executable somehow.</li>
<li>All calls to <code>exit()</code> have been replaced by
<code>abort()</code>. The 5th menu option was replaced with one that
calculates integer division.</li>
</ul>
<h2 id="tocanch4" class="tvis">Getting the hints
hidden in the executable</h2>
<p>There were two separate hints hidden in the challenge executable. It
is strongly recommended that you read both of them before proceeding.
These hints are printed out by the <code>give_hint</code> and
<code>give_another_hint</code> functions, respectively.</p>
<p>By reverse engineering the function called <code>main_func</code>, it
quickly becomes apparent that the first hint can be triggered by
entering <code>1024</code> at the choice prompt.</p>
<p>The only reference to the second hint function is this call:
<code>__sysv_signal(8, give_another_hint);</code>. Checking the manual
page of <code>signal(2)</code> reveals that this call sets the signal
handler of signal with numerical value 8 to the function that gives the
second hint. The signal with numerical value of 8 is
<code>SIGFPE</code>. In glibc, when the <code>div()</code> function
(which is used by the integer division option) generates a division by
zero error, it raises the <code>SIGFPE</code> signal. So to get the
second hint, simply choose option 5 at the prompt and enter a divisor of
0.</p>
<h2 id="tocanch5" class="tvis">Leaking the executable base
address</h2>
<p>Let’s just do what the first hint told us: “search for it in the heap
before you start doing anything”. Run the program with GDB and interrupt
it as soon as the prompt shows up.</p>
<pre><code>
What do you want to do?
1. add a chunk
2. edit a chunk
3. delete a chunk
4. show the content of a chunk
5. calculate integer division
6. there's no exit ;)
Choice:^C
Program received signal SIGINT, Interrupt.
...
pwndbg&gt; vis
...
0x55f3163a1660  0x0000000000000000      0x0000000000000031      ........1.......         &lt;-- fastbins[0x30][0]
0x55f3163a1670  0x0000000000000000      0x000055f3157f27ca      .........'...U..
0x55f3163a1680  0x000055f3157f2860      0x000055f3157f2970      `(...U..p)...U..
0x55f3163a1690  0x0000000000000000      0x0000000000020971      ........q.......         &lt;-- Top chunk
pwndbg&gt; bins
tcachebins
empty
fastbins
0x20: 0x0
0x30: 0x55f3163a1660 ◂— 0x0
...</code></pre>
<p>Whoa! Why is there already a freed chunk before we have done
anything? Also those huge values in the heap look awfully like pointers!
Let’s verify that…</p>
<pre><code>pwndbg&gt; x/4i 0x000055f3157f27ca
   0x55f3157f27ca &lt;edit_chunk&gt;: push   rbp
   0x55f3157f27cb &lt;edit_chunk+1&gt;:       mov    rbp,rsp
   0x55f3157f27ce &lt;edit_chunk+4&gt;:       sub    rsp,0x10
   0x55f3157f27d2 &lt;edit_chunk+8&gt;:       call   0x55f3157f25b7 &lt;read_idx&gt;</code></pre>
<p>We get our pointer into the executable. It was that easy! But how did
this come to be? Turns out the programmer was doing silly things in
<code>main_func()</code>…</p>
<pre><code>    give_libc_ptr();
    ppcVar2 = (code **)malloc(0x20);
    *ppcVar2 = add_chunk;
    ppcVar2[1] = edit_chunk;
    ppcVar2[2] = delete_chunk;
    ppcVar2[3] = show_chunk;
    free(ppcVar2);</code></pre>
<p>This code allocates some memory, places a few function pointers there
and immediately frees it. The code is vulnerable because sensitive data
aren’t erased before the space they occupy is deallocated – the
<code>free()</code> function won’t erase everything for you. To make the
program spit out these pointers on its own, we simply allocate the same
amount of space, get the exact same location previously occupied by
these pointers, and use the show content command to get the
pointers.</p>
<p>Note that the first pointer was overwritten by 0’s though. Knowing
that <code>puts()</code> will stop printing on the first
<code>'\0'</code> it encounters, you should craft the content of the new
chunk carefully so that the pointer to <code>edit_chunk</code> can be
printed out.</p>
<p>Also note that due to the same reason (how <code>puts()</code>
works), if the base address of the executable contains a zero byte, we
will unfortunately get an incorrect pointer from the output. In that
case simply rerun the program and hope for a better luck.</p>
<h2 id="tocanch6" class="tvis">Leaking libc base address</h2>
<p>We will do the same thing as what we did for level 8 in the heap
challenge set: using the unsorted bin to leak the arena address. This
procedure was described in detail in Section 2 of Lecture Notes 21 and
won’t be repeated here.</p>
<h2 id="tocanch7" class="tvis">Use-after-free
on fastbins, but with tcache bins disabled</h2>
<p>Here comes the most interesting part of the challenge.</p>
<p>First we would perform the triple-alloc-triple-free ritual as
described in Section 3 of Lecture Notes 21. Tcache bins don’t exist, so
there’s no need to fill them up.</p>
<pre><code>pwndbg&gt; bins
tcachebins
empty
fastbins
0x20: 0x0
...
0x70: 0x5563057f8770 —▸ 0x5563057f8700 —▸ 0x5563057f8690 ◂— 0x0
0x80: 0x0</code></pre>
<p><em>The fast bins after the triple-alloc-triple-free ritual.</em></p>
<p>Then we would edit the second chunk so that the third chunk points to
a location we want to overwrite. Since this challenge is similar to
Level 6 of the heap challenge set, presumably we would also want to
overwrite the GOT. So why don’t we just do that?</p>
<pre><code>pwndbg&gt; bins
tcachebins
empty
fastbins
0x20: 0x0
...
0x70: 0x5563057f8770 —▸ 0x5563057f8700 —▸ 0x55d47dac6000 (free@got.plt) —▸ 0x55d47dac3056 (__stack_chk_fail@plt+6) ◂— push   3
0x80: 0x0</code></pre>
<p><em>The fast bins after editing the second chunk.</em></p>
<p>Then we allocate twice, getting rid of the first two items in the
linked list. After that the next allocation <em>should</em> give us the
location we wrote in the last step. Note that since tcache bins don’t
exist, the content of fast bins won’t get transferred into tcache bins.
So we don’t have to worry about filling the fast bin linked list to a
specific length.</p>
<pre><code>pwndbg&gt; bins
tcachebins
empty
fastbins
0x20: 0x0
...
0x70: 0x55d47dac6000 (free@got.plt) —▸ 0x55d47dac3056 (__stack_chk_fail@plt+6) ◂— push   3
0x80: 0x0</code></pre>
<p><em>Fast bins after two allocations.</em></p>
<p>Indeed this looks correct, and the next allocation should give us a
pointer into the GOT. However in reality, all we get is a CRASH:</p>
<pre><code>-------------------------
What do you want to do?
1. add a chunk
2. edit a chunk
3. delete a chunk
4. show the content of a chunk
5. calculate integer division
6. there's no exit ;)
Choice: 1
Size of the chunk?: (whatever size you're using)
malloc(): memory corruption (fast)</code></pre>
<p><em>Dramatic reenactment of the crash.</em></p>
<p>The first hint comes in clutch: it provides us with the source code
snippet that raises this error:</p>
<pre><code>if (__builtin_expect (victim_idx != idx, 0))
    malloc_printerr ("malloc(): memory corruption (fast)");</code></pre>
<p>So <code>victim_idx</code> better be the same as <code>idx</code>
here or we will get this error. Let’s take a look at how these values
are calculated:</p>
<pre><code>#define chunksize(p) (chunksize_nomask (p) &amp; ~(SIZE_BITS))
#define chunksize_nomask(p)         ((p)-&gt;mchunk_size)
...
#define fastbin_index(sz) \
  ((((unsigned int) (sz)) &gt;&gt; (SIZE_SZ == 8 ? 4 : 3)) - 2)
...
idx = fastbin_index (nb);
...
victim = *fb;
...
size_t victim_idx = fastbin_index (chunksize (victim));</code></pre>
<p><code>victim</code> is the chunk from the fast bin that we are about
to reuse. <code>fastbin_index</code> will find the index of the bin
where chunks of a certain size should be placed. This check pretty much
makes sure the chunk we’re about to reuse really has the proper size
that allows it to go into the fast bin that it is currently in. The size
of the chunk is a 64-bit integer located at 8 bytes after the chunk
address (see figure 2 in Lecture Notes 21). We have to find an address
that will satisfy this condition. Note that fast bins will only be used
for chunks with the size of at most 128 bytes by default. So the chunk
size cannot exceed 0x80.</p>
<p>Let’s inspect the memory before the GOT table.</p>
<pre><code>pwndbg&gt; got
GOT protection: Partial RELRO | GOT functions: 17
[0x555555558000] free@GLIBC_2.2.5 -&gt; 0x7ffff7a7b9c0 (free) ◂— push   r15
...
pwndbg&gt; x/20gx 0x555555558000-0x40
0x555555557fc0: 0x0000000000000000      0x0000000000000000
0x555555557fd0: 0x00007ffff7a271d0      0x0000000000000000
0x555555557fe0: 0x00007ffff7a275d0      0x0000000000003dd0
0x555555557ff0: 0x00007ffff7ffe170      0x00007ffff7dec7a0
0x555555558000 &lt;free@got.plt&gt;:  0x00007ffff7a7b9c0      0x0000555555555046
...</code></pre>
<p>There’s no valid values in sight. But don’t give up just yet! The
address don’t have to be aligned to 64-bit boundaries (yes there is no
checks regarding that), so let’s try shifting things around…</p>
<pre><code>pwndbg&gt; x/20gx 0x555555558000-0x40+13
0x555555557fcd: 0xfff7a271d0000000      0x000000000000007f
0x555555557fdd: 0xfff7a275d0000000      0x0000003dd000007f
0x555555557fed: 0xfff7ffe170000000      0xfff7dec7a000007f
0x555555557ffd: 0xfff7a7b9c000007f      0x555555504600007f
0x55555555800d &lt;abort@got.plt+5&gt;:       0x5555555056000055      0x5555555066000055</code></pre>
<p>Immediately we find our valid fast bin size 0x7f. So why don’t we
just target this address (0x3fcd + executable base address)?</p>
<p>Do note that our size passed to <code>malloc</code> has to have the
same <code>fastbin_index</code> value as this fake size (0x7f) due to
how this check works.</p>
<h2 id="tocanch8" class="tvis">Targeting the GOT</h2>
<p>Don’t celebrate just yet. After finding out our working target
address, the previously crashing <code>malloc</code> call successfully
returns with our desired address. But we’re immediately greeted with
this message:</p>
<pre><code>adding into that section is not allowed...</code></pre>
<p>Pesky! Some simple reverse engineering reveals that if
<code>malloc</code> returns an address within offset range
<code>[0x3000, 0x4000)</code> from the executable base address, it will
quit immediately, and our working address is right at the higher end of
this range (you’ll have a hard time finding another one that comes after
it while still allowing you to overwrite the entire GOT, trust me).</p>
<p>Here is where the second hint comes into play. It suggests writing
into the GOT indirectly, specifically using the edit command. Remember
that there is a global variable at <code>0x40c0</code> that keeps track
of the address of each chunk and whether they are in use or not. So our
new plan of attack would be:</p>
<ol type="1">
<li>Using the UAF vulnerability to overwrite the global variable that
keeps track of the chunks, redirecting a chunk pointer to the GOT</li>
<li>Using the edit command on that chunk to fill the GOT with our
modified function entries</li>
</ol>
<p>We will now repeat the process to find a target address near the
aforementioned global variable with a valid chunk size described in the
last section. I’ll not repeat the procedure here, except giving this
potentially useful figure (all addresses are offsets from the
executable, it’s pretty safe to assume libc will always be loaded to an
address in the form of <code>0x7f??????????</code>):</p>
<pre><code>offset  00 01 02 03 04 05 06 07   08 09 0a 0b 0c 0d 0e 0f
0x4090  00 .....
0x40a0  xx xx xx xx xx 7f 00 00   00 .....
        &lt;-- stdout in glibc --&gt;
                        ^ this value is 0x7f when interpreted as uint64_t
                            and is a valid size for fast bins. So 0x409d is the
                            address we need to target. (offset of stdout - 3)
0x40b0  00 .....
0x40c0  xx xx xx xx .....         01 00 .....
        &lt;--- chunks[0].ptr ---&gt;   &lt;-- chunks[0].inuse --&gt;
        ^ this is the address you want to overwrite</code></pre>
<h2 id="tocanch9" class="tvis">Tackling
<code>malloc_usable_size</code></h2>
<p>Now that we have made the first chunk point to the GOT, can we simply
enter the new GOT data by issuing the edit command? Sadly, the answer is
no. The program will, again, crash, this time inside
<code>malloc_usable_size</code>.</p>
<p>The offending instruction and relevant values are shown below:</p>
<pre><code>&lt;malloc_usable_size+64&gt;    test   byte ptr [rdi + rdx - 8], 1
    with rdi = 0x55af1df47000 (free@got.plt) —▸ 0x7f1bbd8a59c0 (free) ◂— push   r15
    and  rdx = 0x7f1bbdc167a0 (_dl_runtime_resolve_xsavec) ◂— push   rbx</code></pre>
<p>Why don’t we follow the advice and start tracing where these values
are from? Let’s start by taking a look at our chunks global
variable:</p>
<pre><code>pwndbg&gt; x/20gx 0x55af1df43000 + 0x40a0
0x55af1df470a0 &lt;stdout@GLIBC_2.2.5&gt;:    0x00007f1bbdbfa760      0x6161610000000000
0x55af1df470b0: 0x0000000000000000      0x0000000000000000
0x55af1df470c0 &lt;chunks&gt;:        0x000055af1df47000      0x0000000000000001
0x55af1df470d0 &lt;chunks+16&gt;:     0x000055af1eda8780      0x0000000000000001</code></pre>
<p>This looks correct. The first chunk now points to the first function
entry of the GOT table, which is what we intended. <code>rdi</code> is
pointing to the same address, indicating it still holds the parameter to
<code>malloc_usable_size</code> (because <code>edit_chunk</code> is
trying to find out how many heap bytes is supposedly available at this
address).</p>
<p>Now let’s take a look at memory near the GOT.</p>
<pre><code>pwndbg&gt; x/20gx 0x000055af1df47000 - 0x40
0x55af1df46fc0: 0x0000000000000000      0x0000000000000000
0x55af1df46fd0: 0x00007f1bbd8511d0      0x0000000000000000
0x55af1df46fe0: 0x00007f1bbd8515d0      0x0000000000003dd0
0x55af1df46ff0: 0x00007f1bbde28170      0x00007f1bbdc167a0
0x55af1df47000 &lt;free@got.plt&gt;:  0x00007f1bbd8a59c0      0x000055af1df44046
0x55af1df47010 &lt;__stack_chk_fail@got.plt&gt;:      0x000055af1df44056      0x00007f1bbd872f00
0x55af1df47020 &lt;fputs@got.plt&gt;: 0x00007f1bbd88d260      0x00007f1bbd99d1c0
0x55af1df47030 &lt;close@got.plt&gt;: 0x00007f1bbd91e9d0      0x00007f1bbd895850</code></pre>
<p>Interesting! <code>rdx</code> seems to have the 64-bit value right
before the targeted address. Of course here <code>rdx + rdi - 8</code>
would be a huge value that won’t ever be in the process virtual memory
space and will crash the program.</p>
<p>Let’s try the good old trick of moving the target address around. How
about moving it back by 32 bytes? This way the targeted address still
points to a value that seems to be a pointer into libc, but
<code>rdx</code> would be 0, which would be perfect for us because
<code>rdx + rdi - 8</code> would very likely be a valid memory
location!</p>
<h2 id="tocanch10" class="tvis">Capturing the flag</h2>
<p>And it turns out, this works. <code>malloc_usable_size</code> now
returns without crashing the program. Although the return value seems a
bit less desirable – it returns 0. However this is not an issue for us
because our victim program would subtract 1 from this value, making it
huge (the value is unsigned). This allows us to read unlimited number of
bytes into our target address, which allows us to successfully replace
the GOT.</p>
<p>After that, it’s just crafting the new GOT and passing it to the
program. We would then use the appropriate commands to obtain our sweet,
sweet root shell!</p>
<p>As a final note, since there is no <code>exit</code> in the GOT,
we’ll have to find another place for <code>setuid</code>.
<code>div</code> seems to be a perfect candidate because it takes more
parameters and we have full control over the value of the
parameters.</p>
<p>Hope you learned something from this challenge :)</p>
<br><hr><div class="TText" id="notediv" style="font-size:80%;"><span class="TText"><a id="note1" href="#n1">[1]</a>: Most likely because one doesn’t have to
solve all of them to get all possible points in this
class.<br></span></div>]]></content></entry><entry><title>Refurbishing my X1 Yoga</title><link href="https://chrisoft.org/blog/post/2025-04-16.html" rel="alternate" type="text/html" title="Refurbishing my X1 Yoga"/><published>2025-04-16T00:00:00.000Z</published><updated>2026-04-07T02:18:49.697Z</updated><id>https://chrisoft.org/blog/post/2025-04-16.html</id><author><name>Chris Xiong</name><uri>https://chrisoft.org/</uri></author><content type="html" xml:base="https://chrisoft.org/blog/post/2025-04-16.html"><![CDATA[
<p>Breaking news: I did something horrible to my ThinkPad X1 Yoga once
again (shocker…)!</p>
<p>If you want to play catch-up, here are the previous episodes: <a href="https://chrisoft.org/blog/post/2016-05-12.html">The initial
review</a>, and <a href="https://chrisoft.org/blog/post/2017-01-09.html">The first screen
replacement</a>. Note that both posts are ancient and in Chinese.</p>
<p>If you just want a quick recap, or in case you couldn’t read
Chinese:</p>
<ul>
<li>The laptop in question is a ThinkPad X1 Yoga first gen purchased in
the US in early 2016, and delivered into my hands mid 2016.</li>
<li>It was completely maxed out in the specs department, except the
screen and WWAN. The WWAN and OLED screen options were unavailable at
the time of purchase.</li>
<li>Roughly half a year into its lifetime, I broke the screen digitizer
through a deliberate and evil act: attempting to replace the factory
anti-glare coating with a tempered glass protector. The screen glass
cracked in the process, completely disabling all functionality of the
digitizer layer. I spent the equivalent of $150 purchasing a replacement
LCD assembly and installed it myself, because the authorized service
provider quoted me more than $500 for a repair. I failed to properly
close the clips on one side of the LCD assembly. So I had to give in and
ask for a repair from a Lenovo authorized service provider. But
unfortunately they don’t know how to properly install the LCD assembly
either, leaving the screen flapping around in the breeze for many
years.</li>
<li>In its second year of service, I accidentally spilled half a glass
of hot (!!) water into the keyboard. As pretty much all ThinkPads are,
this machine is designed to be spill-proof. So I did not pay too much
attention to it once I’ve let most of the water out through the drainage
holes. However, roughly three months after the incident, the fingerprint
reader on the machine decided to quit. There were a few corrosion marks
near the fingerprint reader connector on the motherboard. However later
through some basic probing, it was determined that the actual cause is a
broken flex cable.</li>
</ul>
<h2 id="tocanch0" class="tvis">What is it about <em>this
time</em>?</h2>
<p>Well, blame <a href="https://www.reddit.com/r/thinkpad/comments/9nhxdf/upgrading_oled_in_x1_yoga/">this
reddit post</a>.</p>
<p>I randomly decided to look up a few reviews of the OLED version of
this laptop one day, and I came across that post. Someone upgraded
<a id="n1" href="#note1" class="note">[1]</a> their LCD X1 Yoga to an OLED
display. It’s really a fascinating idea, isn’t it? After looking up the
price to source the required parts (prior to all the tariff insanity), I
found out that the upgrade is actually very affordable in 2025. So not
only did I commit myself to the upgrade, I decided to perform a total
exterior refurbishment as well: my battle-scarred X1 yoga has definitely
seen better days, and it deserves better.</p>
<p>Here is a list of parts that I ordered:</p>
<pre><code>PART                                      SOURCE           PRICE
OLED display assembly 01AW977         aliexpress          $66.20
OLED driver board 00NY439             aliexpress          $28.13
Lenovo Wacom Active Pen 00HN897       aliexpress          $20.55
Sensor &amp; Camera Module Cable 01AW980  aliexpress          $ 9.67
Fingerprint Module 01AX699            aliexpress          $12.58
OLED Screen Cable 01AW979             aliexpress          $15.88
Base Cover 01AW995                    aliexpress          $39.91
OLED Rear Cover 01AW978               aliexpress          $39.06
Palmrest Cover 00JT863                ebay                $26.99
Keyboard 00JT864                      ebay                $50.34
LCD Misc Kit 01AW987                  Lenovo              $38.57
Base Misc Kit 01AW964                 Lenovo              $66.98
----------------------------------------------------------------
Grand Total                                              $414.86</code></pre>
<p>The part number of the fingerprint module I ordered wasn’t for the X1
Yoga, but instead for the T470, which uses a physically compatible
sensor with USB ID <code>138a:0097</code> instead of
<code>138a:0090</code> found on the X1 Yoga. The newer module seems to
have match-on-chip as its only operation mode, which provides better
security (in theory).<a id="n2" href="#note2" class="note">[2]</a></p>
<p>“LCD Misc Kit” provides the missing rubber plug over the screen
screw. “Base Misc Kit” provides replacement for the broken fingerprint
reader cable, as well as the degraded rubber on what Lenovo calls the
“rising rubber foot”. These parts are incredibly difficult to source,
and the only reliable provider I could find is Lenovo itself, which
sells them at completely delusional prices. But alas, to carry out the
perfect refurbishment, I had to pay the ransom.<a id="n3" href="#note3" class="note">[3]</a> It’s also notable that the price of the OLED panel
itself is incredibly low these days<a id="n4" href="#note4" class="note">[4]</a>.</p>
<p>As always I fell victim to the much dreaded scope creep: If I have to
swap the rear cover for the OLED screen anyway<a id="n5" href="#note5" class="note">[5]</a>,
why don’t I swap the base and palm rest cover too? Why not the keyboard
as well? Why should I live with degraded rubber parts once all of this
has been swapped? Am I some kind of monster that doesn’t get things all
the way done? Just like this, I spent more than $400 before I could
notice it, while my original budget for the job was &lt;$200. That
amount of money will probably get me 4 of these machines in the used
market, but the upside is that mine will effectively look brand new
after the swap.</p>
<h2 id="tocanch1" class="tvis">Installing the parts</h2>
<p>The installation wasn’t super complicated. The palm rest cover
arrived one day late, so I decided to be done with the display assembly
first.</p>
<p>I haven’t disassembled the machine to this extent for years. There
were parts of the machine that were absolutely filthy. There were quite
a few pretty bad looking corrosion marks under the camera / sensor hub
flex cable. Both the connector and the cable are in a horrific state. I
was actually amazed that the machine was able to power on at all in that
state. I cleaned up the corrosion with rubbing alcohol and proceeded
with the modding.</p>
<p>There was a small issue with the official hardware maintenance manual
– it has no mention of the OLED model at all. It does not mention the
special parts used in the OLED version, nor does it contain instructions
on how to install them. But ultimately it’s not super complicated: the
OLED driver board would go under a small plastic clip under the rear
cover, then two small plastic protrusion would hold the board in place
through two holes on the board. These anchor points for the OLED driver
board are the only differences between the OLED rear cover and the LCD
rear cover, as far as I can tell. There are no fasteners involved.
Cabling for the OLED panel is quite different from the LCD panel, but
connecting them up is mostly just a matter of “adult LEGO”.</p>
<p>The other thing that costed me some significant amount of time was
transferring the antennae for WiFi and WWAN. I have almost forgotten
that I still have the WWAN antennae dangling in the base of the machine.
Improper antennae installation was the reason why I wasn’t able to close
the rear cover in my last screen repairing attempt, so I tried to be
extra careful this time. Unfortunately I still messed up: I partially
tore the copper tab on one of the WiFi antennae. Since I don’t really
have a proper fix for it, I simply glued the thing back together. I did
not bother to shove all the antenna cables into cable channels on the
rear cover, because last time I spent an unholy amount of time on it and
still failed – I just taped the cables down using some Kapton tape. I
was ultimately able to make the rear cover fit this time – still not
perfectly, but much better than the display panel falling off at any
time. And as a bonus, WiFi reception wasn’t meaningfully impacted after
this whole ordeal. Yay!</p>
<p>I turned the computer on for the first time with the new screen.
Nothing exploded. The Lenovo logo showed up on the screen and I was
immediately stunned – the colors were absolutely poppin’ on this panel.
In fact, it may even feel like a bit over saturated on the default
settings. There was another thing that was immediately apparent: screen
brightness control no longer worked. The screen is stuck at highest
brightness. I was kind of expecting this because I have read before that
OLED screen brightness control on Linux is janky. So I tried a bunch of
fixes that were found on various places<a id="n6" href="#note6" class="note">[6]</a>, including setting
<code>i915.enable_dpcd_backlight</code> to all possible values, and none
of them worked. So I had to set a software “brightness” by using
something like <code>xrandr --output eDP-1 --brightness 0.5</code>. This
however, is not a valid long-term solution, because it effectively
reduces the available color depth of the display, and will cause visible
banding on low settings (comparison shown below). So I’ll have to find a
proper solution.</p>
<p>The panel identified itself as “ATNA40JU01-0” in its EDID, which
seems to be the correct model number from Samsung. One pretty big
annoyance that I was able to notice pretty soon after the installation
was that the screen sometimes has a green-ish horizontal bar flash
across it when it turns off.</p>
<p>I switched to a pitch black color scheme for my desktop environment,
and reduced the amount of completely static elements on screen, with the
hope to reduce OLED burn-in. The editor theme I’m using however, does
not have a pitch black text background. And while I was writing some
code later that night, I realized that there’s a vertical band in the
dead middle of the panel that is very slightly brighter compared to the
rest of the screen. This happened when I was on a software brightness of
25%, so realistically this would only be (very subtly) visible when the
color displayed is extremely dark while not being pitch black, and the
text editor background shouldn’t cause any visual disturbance once I
have proper PWM brightness control working<a id="n7" href="#note7" class="note">[7]</a>.</p>
<p>The palm rest cover arrived on the following day, and I transferred
the entire machine into the new base shell. All I can say is that the
process was surprisingly short. At this point, my X1 Yoga was fully
refurbished, with nearly all user-facing components replaced. The only
exceptions are the touchpad (which I don’t use anyway), and the hinges
(which still work perfectly after some cleaning).</p>
<p>Apologies for not having any photos of the actual refurbishment
process. I got too excited to take any pictures.</p>
<table class="gallery">
<tbody><tr>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/C1u1UdsUsAEc7Wb.jpg"><img style="width: 48%;" src="//filestorage.chrisoft.org/blog/img/ssbsthumb_800x800_C1u1UdsUsAEc7Wb.jpg"></a> <br>Consequence
of the first failed screen repair. Photo taken 2017-01-09.
</div>
</td>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/IMG_20250412_165522.jpg"><img style="width: 48%;" src="//filestorage.chrisoft.org/blog/img/ssbsthumb_800x800_IMG_20250412_165522.jpg"></a>
<br>(Slightly NSFW) The battle-scarred laptop.
</div>
</td>
</tr>
<tr>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/IMG_20250412_165216.jpg"><img style="width: 48%;" src="//filestorage.chrisoft.org/blog/img/ssbsthumb_800x800_IMG_20250412_165216.jpg"></a> <br>Parts
and things.
</div>
</td>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/IMG_20250412_171347.jpg"><img style="width: 48%;" src="//filestorage.chrisoft.org/blog/img/ssbsthumb_800x800_IMG_20250412_171347.jpg"></a>
<br>Corrosion on the camera / sensor board connector.
</div>
</td>
</tr>
<tr>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/IMG_20250412_172231.jpg"><img style="width: 48%;" src="//filestorage.chrisoft.org/blog/img/ssbsthumb_800x800_IMG_20250412_172231.jpg"></a> NSFW: Filthy
screen, filthy hinge, filthy everything.
</div>
</td>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/IMG_20250412_172301.jpg"><img style="width: 48%;" src="//filestorage.chrisoft.org/blog/img/ssbsthumb_800x800_IMG_20250412_172301.jpg"></a> NSFW: The
other filthy hinge, and corroded connector.
</div>
</td>
</tr>
<tr>
<td colspan="2">
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/IMG_20250412_195125.jpg"><img style="width: 48%;" src="//filestorage.chrisoft.org/blog/img/ssbsthumb_1000x1000_IMG_20250412_195125.jpg"></a> <br>First
power up!
</div>
</td>
</tr>
<tr>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/IMG_20250414_195911.jpg"><img style="width: 48%;" src="//filestorage.chrisoft.org/blog/img/ssbsthumb_800x800_IMG_20250414_195911.jpg"></a> <br>The
husk.
</div>
</td>
<td>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/IMG_20250414_194903.jpg"><img style="width: 48%;" src="//filestorage.chrisoft.org/blog/img/ssbsthumb_800x800_IMG_20250414_194903.jpg"></a> <br>And the
brand new laptop.
</div>
</td>
</tr>
<tr>
<td colspan="2">
<div style="text-align:center;max-width:98%;">
<video width="90%" src="https://filestorage.chrisoft.org/blog/data/VID_20250414_193203.mp4" controls="">
</video>
<br>“The Peel” (video)
</div>
</td>
</tr>
</tbody></table>
<h2 id="tocanch2" class="tvis">A super-long-term review</h2>
<p>In a few days, it’s going to be nine years since I’ve owned this
machine. It’s probably just in time for a super long-term review from
me.</p>
<h3 id="tocanch3" class="tvis">Durability</h3>
<p>As you are probably already aware at this point, I don’t exactly
treat my machines with exceeding care, and my laptops usually have a
pretty rough life (and look like absolute sh*t after a few years of use)
for that reason. But this computer somehow managed to survive all of my
nonsense over all these years: botched screen repairs, hot water in
chassis, countless instances of rough handling. Even though the exterior
was all scratched up, the core functionality of all the internals stayed
rock solid.</p>
<p>Below is a list of functionalities that has degraded in an unusual
manner over the years (unsurprisingly, it’s all my fault):</p>
<ul>
<li>Pen drift. There was a small area (roughly the size of an ordinary
oreo cookie) on the display where pen hover does not work properly. When
the pen is hovered above that area, the position reported by the
digitizer would jump all over the place, performing clicks at random.
Presumably this is because of the uneven screen surface caused by the
botched screen repairs.</li>
<li>The left shift key on the keyboard cracked. Although the key hasn’t
completely split in half, the crack was very easily visible with the
keyboard backlight turned on.</li>
<li>The flex cable that connects the fingerprint scanner to the
motherboard had a broken trace. The broken trace was in fact on pin 1,
which is the positive power supply.</li>
<li>There were a few “blobs” on the LCD near the center where the
backlight appears quite a bit brighter than their surrounding areas.
They don’t show up very well on camera, but are very visible to human
eyes. Probably has the same root cause as the next point.</li>
<li>There were bubbles forming below the coating of the base cover
roughly 3 years into its life, which coincides with the time when I
started regularly packing it with a giant steel box (<a href="/blog/post/2019-04-25.html">the EDIROL SD-80</a>) in my backpack.
They all went burst later and peeled off from the cover, exposing the
magnesium alloy. There were similar things happening to the palmreset
side of the base, except that there were no bubbles. Those started as
small dents, and later “grew” larger.</li>
<li>The CMOS was completely dead some time in 2022. Whenever the main
battery runs out, the system immediately loses its RTC time and all BIOS
settings. I ordered a replacement (together with two main batteries for
the machine. Yes, two.) from Lenovo to get the issue fixed.</li>
</ul>
<h3 id="tocanch4" class="tvis">Performance</h3>
<p>Did it hold up? No, of course not. It’s a Skylake ULV dual core, what
would you expect? And you’d be a little bit crazy if you disagree
(unless you’re a true terminal dweller – in that case, you might as well
stick with a Core 2 Duo).</p>
<p>With the highly bloated modern web, this machine frequently chugs in
Chromium. Yes I know, I use Chromium. Shame on me. But I do often leave
hundreds of tabs open, and I do use Firefox too. It holds up slightly
better on this machine, but not by a lot.</p>
<p>I’ve noticed the graphics power in the feeble Intel HD 520 not
keeping up with what I do in 2019, when I realized that it could barely
run Minecraft in 1080p at 60 FPS. In fact, I never relied on this
machine for any intensive graphics work. I used the abomination that was
the mobile Sandy Bridge quad core slapped on a Supermicro motherboard
with a 2080 FE for that purpose until I got my current workstation.</p>
<p>In terms of pure CPU performance, this machine now takes about an
hour to build stage2 rustc together with the standard library. This is
quite unacceptable because my current day job involves frequent rustc
builds (although it’s stage0 only). In comparison, it takes my Framework
13 (i7-1185G7) ~20 minutes to build the stage2. The non-upgradable 16
GiB of RAM has also become a problem, when <code>rust-analyzer</code>
will happily use half of it with a large project loaded. Not to mention
that Skylake was among the most impacted micro-architectures by
mitigation for various CPU vulnerabilities such as Spectre and
Meltdown.</p>
<p>But of course it still handles simple tasks just fine. This machine
is still being used every day for the following things:</p>
<ul>
<li>E-mail and other general communications.</li>
<li>Word processing. Only <em>real</em> word processing though,
e.g.&nbsp;LaTeX.</li>
<li>Web development. Since I’m not using any fancy new tech for my
website, everything that you saw on this website was typed out on this
almost decade-old laptop, including this post.</li>
<li>Development of my personal projects, most of them. My personal
projects are all on the smaller side, and as long as they don’t require
any specialized hardware, this platform can still handle it.</li>
</ul>
<p>Battery life is still surprisingly good. I swapped the main battery
once in 2022<a id="n8" href="#note8" class="note">[8]</a>,
and right now the battery holds ~92% of its designed capacity. I haven’t
measured the actual run time after the panel swap, but before it can
still regularly last for more than 10 hours of light, continuous usage,
thanks to the stripped down nature of my setup. Since the OLED panel
does consume more power, it remains to be seen how long the battery
would last with this new screen.</p>
<h3 id="tocanch5" class="tvis">Other criticisms</h3>
<p>(… that was absent from my original review)</p>
<p>The speakers are abysmal and to be honest, an embarrassment.
Apparently Lenovo thought business machine owners must live with
speakers of such garbage quality.</p>
<p>No thunderbolt or even USB Type-C. Not seen as a big deal by me back
then, when USB Type-C was still quite new. However this has become a
more and more prevalent issue over the years. If I want to use my
external SSD, which only has a Type-C connector, with both my ThinkPad
and my Framework, I’d have to bring two different cables.<a id="n9" href="#note9" class="note">[9]</a></p>
<p>Poor software integration on the Linux side, even after almost ten
years. I will not comment on tablet mode support, because I haven’t been
running a full desktop environment for more than five years, and the
kernel interface has been present for years now, which is more than
enough for me. And the touch screen experience on Linux has been “meh”
all along<a id="n10" href="#note10" class="note">[10]</a>, but I’m not here to bitch about that either. It’s
the fingerprint reader still not receiving proper support after all this
time that baffles me. There are a bunch of third-party forks and
standalone projects, all barely working (or not working at
all)<a id="n11" href="#note11" class="note">[11]</a>. And apparently due to some <a href="https://gitlab.freedesktop.org/libfprint/libfprint/-/issues/276">dev
drama</a> there’s practically no hope that these reverse engineered
drivers can be upstreamed to libfprint. Now, I know the problems with
reverse engineered drivers. But not coming up with a satisfying solution
for a problem that impacts so many users (keep in mind that the ever so
popular T480 series uses a very similar sensor) in nearly a decade seems
a little bit too much. And yes, fingerprint is not a secure method for
user authentication, and I will not enable it even if proper upstream
support is present (see my Framework laptop). But for people who do use
it, this has become an unnecessary obstacle.</p>
<h3 id="tocanch6" class="tvis">Other non-criticisms</h3>
<p>As noted in my original post, soon after my initial purchase, I added
the WWAN option to my machine myself. It worked well under Windows, but
I didn’t figure out a way to use it under Linux back then. Later I found
<a href="https://github.com/dmikushin/modem-manager-gui/">this
project</a>, which not only allowed me to connect to the Internet via
WWAN, but see all the connection details that Windows hides from the
user and manage SMS text messages as well. Ironically in later versions
of Windows 10, Microsoft removed the ability to send (and later receive)
SMS messages using the WWAN adapter from the built-in messaging app,
which made Linux my only way to manage these messages.</p>
<p>I upgraded the built-in wifi adapter from the Intel 8260 to the AX200
in early 2021. There is no wifi whitelist in the BIOS for this machine,
so the upgrade was as simple as removing the old card and dropping the
card in.</p>
<p>I replaced the thermal paste with a phase-change sheet in 2023, at
the same time when I <a href="/blog/post/2023-11-13.html">replaced the
thermal paste in my Framework</a>. I got a similar improvement on the
thermal performance. Single-threaded loads would no longer cause the
temperature to shoot up beyond 70 degrees, and fully-loaded thermals
also improved by ~5 degrees (from 85 degrees to 79 degrees).</p>
<p>I swapped the trackpoint rubber dome a bunch of times. Not very
surprising because it’s a consumable that is subjected to regular wear
and tear. One notable thing was that in my original post, I stated that
I did not like these low-profile trackpoints. But once I have become
used to it, it works just as well as the older iterations. The
sensitivity issue only requires a simple tweak of the numbers (in
<code>/sys/devices/platform/i8042/serio1/serio2/speed</code> and
<code>/sys/devices/platform/i8042/serio1/serio2/sensitivity</code>).</p>
<h3 id="tocanch7" class="tvis">Comparison against my
Framework 13</h3>
<p>I’ve spent nearly 4 years with my original Framework 13 at the time
of this writing, and I can confidently say that it has given me a lot
more grief over its lifespan. From the <a href="https://community.frame.work/t/clock-stuck-at-39ghz/4835/201">0.4
GHz CPU (the BDPROCHOT issue)</a>, <a href="/blog/post/2022-07-23.html#tocanch11">system instability and USB
port interference</a>, <a href="https://framework.kustomer.help/articles/-SJQQqt2Hu">bad
touchpad</a>, and <a href="https://community.frame.work/t/rework-instructions-for-11th-gen-mainboards-to-enable-powering-the-rtc-circuit-from-the-main-battery/26922">flawed
RTC battery design</a>, to the fried USB port (okay this one is probably
my fault). My experience with this computer is certainly much less
trouble-free compared to that with my X1 Yoga (as long as I’m not the
one causing the problem). Most of the problems mentioned above have
later been resolved, but it’s still notable that I never had to deal
with such issues on my X1 Yoga.</p>
<p>Just a few days ago, my Framework decided to publicly humiliate me.
Its fan randomly decided to start making noise akin to a dying spinning
rust hard drive, only 10x louder, in the midst of a meeting. I had to
shut off the computer because of the sheer embarrassment it cause
me.</p>
<p>Framework forum user gringrind put it in the perfect way in the
following post: <a href="https://community.frame.work/t/fw13-fan-noise-scraping-rattling/40548">FW13
Fan Noise (Scraping/rattling)</a>:</p>
<blockquote>
<p>I like having a laptop that I can repair, I don’t want a laptop that
I have to repair twice a year.</p>
</blockquote>
<p>This post is quite alarming, because according to the discussion
there it seems that their newer thermal solutions still suffer from a
similar issue. I am certainly aware that Framework is a pretty new
company. And their radical new approach of designing a repairable laptop
in this weight class certainly put them into a disadvantaged position
compared to more established vendors. And to be clear: I do support
their design philosophy (and how they operate<a id="n12" href="#note12" class="note">[12]</a>) in general. But if they are repeating the same mistakes
on their newer products, it will be perceived as a pattern and become
less forgivable over time. I hope they can get the Strix Point
generation Framework 13 right.</p>
<p>They do have much better customer service than Lenovo though,
speaking from my personal experience. I have, however, seen different
opinions on this matter too… So don’t let my experience speak for
you.</p>
<p>To conclude this section: my Framework 13 quickly assumed the
position of being my “work” laptop since I got it, leaving my personal
computing tasks to the X1 Yoga. This role assignment does seem fit for
both machines, and I have no intention of changing it any time soon.</p>
<h3 id="tocanch8" class="tvis">Conclusion</h3>
<p>It would not be an exaggeration if I say this is still that laptop
that brought me the least trouble and the most joy. Yes, I did not win
the silicon lottery and my unit doesn’t undervolt as well as other
people’s units. Yes, there are features of this machine that I’ve never
made working with my software setup. Yes, it is not as capable as it was
back in the day. But people are still rocking their X201 and T420 in
2025. There’s no reason why my X1 Yoga, now a Frankenstein’s monster
with an all-new exterior, can’t serve me for yet another decade.</p>
<h2 id="tocanch9" class="tvis">What’s next?</h2>
<ul>
<li>Reverse engineer the OLED Panel driver for Windows. <a href="/blog/post/2025-04-28.html">It’s done!</a></li>
<li>Treat the machine with slightly more respect and don’t scratch it up
within a year.</li>
<li><del><a href="https://mikejmoffitt.com/articles/0042-x61-60.html">Flash the
EEPROM in my X60 Tablet so that it can finally hit 60Hz refresh
rate?</a></del></li>
<li>Not buying any new machines from Lenovo before they stop f*cking
around with the keyboard design and most importantly, start offering a
keyboard option without the stupid copilot key.</li>
</ul>
<br><hr><div class="TText" id="notediv" style="font-size:80%;"><span class="TText"><a id="note1" href="#n1">[1]</a>: There will inevitably be some debate around the use of the
word “upgrade” here. Some people avoid OLED screens like a plague,
citing concerns of burn-in and PWM brightness control. I however, always
preferred the perfect black reproduction and vibrant colors of OLED
displays. Plus, since owning my first OLED device in 2012, I have never
really had an issue with screen burn-in. Although it’s pretty obvious
that computer screens are used in very different ways. I guess let’s see
how it goes in terms of burn-in.<br></span><span class="TText"><a id="note2" href="#n2">[2]</a>: But still, neither is going to work with
unmodified libfprint, even in the year of our Lord 2025.<br></span><span class="TText"><a id="note3" href="#n3">[3]</a>: Arguably it’s
probably much better to get a broken machine sold for parts on ebay for
this purpose… But the idea of putting parts from a machine with an
unknown history into my most beloved ThinkPad makes me
flinch.<br></span><span class="TText"><a id="note4" href="#n4">[4]</a>: Not for much longer, I
would assume.<br></span><span class="TText"><a id="note5" href="#n5">[5]</a>: As noted in the
reddit post, the OLED version uses a different rear cover.<br></span><span class="TText"><a id="note6" href="#n6">[6]</a>: It’s mainly the
ArchWiki. Let’s admit it. Pretty every Linux user refers to the ArchWiki
from time to time, regardless of the actual distro they use. In the page
for my model (X1 Yoga 1st gen), it says “Currently, a very small amount
of OLED screens are supported by the kernel, and unfortunately, as of
Sept.&nbsp;2021, the X1 Yoga gen 1 is not one of them. Currently, the
workaround is, to use some color filter on your screen, …”. On the page
for a considerably newer ThinkPad (P1 Gen 4), it says “OLED screens have
no backlight, brightness cannot be controlled by changing backlight
power in the traditional way. Instead, it can be controlled using PWM by
… add[ing] <code>i915.enable_dpcd_backlight=1</code> as a kernel
parameter.”<br></span><span class="TText"><a id="note7" href="#n7">[7]</a>: but that’s a pretty
big if!<br></span><span class="TText"><a id="note8" href="#n8">[8]</a>: The replacement battery is a standard 52Wh part
for the LCD model obtained from an official Lenovo provider.<br></span><span class="TText"><a id="note9" href="#n9">[9]</a>: To
those who would tell me just use a C-to-A cable for both computers, I
usually only one USB Type-A port installed on my Framework and it’s
usually occupied by the receiver dongle of the ThinkPad wireless
keyboard…<br></span><span class="TText"><a id="note10" href="#n10">[10]</a>: I mean, if you never leave Chromium or Firefox for
that matter, it is actually not that bad. But as soon as you leave the
browser…<br></span><span class="TText"><a id="note11" href="#n11">[11]</a>: Probably because the developers were tired of fighting the
upstream.<br></span><span class="TText"><a id="note12" href="#n12">[12]</a>: Maybe not the
“going public” part, but that’s a discussion for another
day…<br></span></div>]]></content></entry><entry><title>&quot;Landfill in the Clouds&quot; devlog</title><link href="https://chrisoft.org/blog/post/2024-09-30.html" rel="alternate" type="text/html" title="&quot;Landfill in the Clouds&quot; devlog"/><published>2024-09-30T00:00:00.000Z</published><updated>2024-10-01T06:08:16.183Z</updated><id>https://chrisoft.org/blog/post/2024-09-30.html</id><author><name>Chris Xiong</name><uri>https://chrisoft.org/</uri></author><content type="html" xml:base="https://chrisoft.org/blog/post/2024-09-30.html"><![CDATA[
<p>This post inevitably contains spoiler for the level <a href="https://chrisoft.org/ballance/levels/landfill/index.html">“Landfill
in the Clouds”</a>. Proceed with caution.</p>
<h2 id="tocanch0" class="tvis">Hints</h2>
<p>General hints for the first few sectors:</p>
<ul>
<li>Maintain a high speed if possible. You will reach the end before
even noticing it.</li>
<li>Remember how versatile the paper ball is.</li>
<li>There’s always another way.</li>
<li>You have infinite attempts. Don’t be afraid of restarting.</li>
</ul>
<p>For the final section:</p>
<ul>
<li>Heed the warnings. HEED THE WARNINGS!</li>
<li>If you ignore the last hint, you get what you deserve.</li>
<li>Like in many real life situations, not everything around you is
useful.</li>
<li>Sometimes taking a dangerous step is mandatory.</li>
</ul>
<h2 id="tocanch1" class="tvis">New gameplay mechanics in
this level</h2>
<h3 id="tocanch2" class="tvis">Random level generation</h3>
<p>Arguably the biggest contribution of this level. Despite using a very
crude approach, the result is still pretty satisfying – both to look at
and to navigate through.</p>
<p>A more methodical approach is definitely possible (e.g.&nbsp;randomly
generated labyrinth), but I’ll leave that for someone else to
implement.</p>
<h3 id="tocanch3" class="tvis">Dumpster fire</h3>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/lf_m0.png"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/ssbsthumb_1000x1000_lf_m0.png"></a>
</div>
<p>A weird Americanism (but one that makes sense if you think about it)
that somehow made into this level in its literal form.</p>
<p>Purely cosmetic. Won’t even burn the paper if you somehow manage to
fall into the dumpster.</p>
<h3 id="tocanch4" class="tvis">The switch</h3>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/lf_m1.png"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/ssbsthumb_1000x1000_lf_m1.png"></a>
</div>
<p>Literally the most cliché mechanism in countless platformers. Now
it’s also been introduced to Ballance. Triggers an annoying buzzer alarm
when pressed.</p>
<h3 id="tocanch5" class="tvis">“Energized Core”</h3>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/lf_m2.png"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/ssbsthumb_1000x1000_lf_m2.png"></a>
</div>
<p>This thing is here only because I don’t want to waste the explosion
script that I wrote back in 2019. Animated in the exact way as an
explosive found in Minecraft.</p>
<p>Mostly for decoration only. This idea may or may not be reused in the
future. If so, it will be given a more useful role.</p>
<h3 id="tocanch6" class="tvis">“Skill trolling”</h3>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/lf_m4.png"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/ssbsthumb_1000x1000_lf_m4.png"></a>
</div>
<p>This is the revenge of an unskilled player against the “competitive
player camarilla” (don’t take this too seriously).</p>
<h3 id="tocanch7" class="tvis">Vanishing balloon</h3>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/lf_m3.png"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/ssbsthumb_1000x1000_lf_m3.png"></a>
</div>
<p>Don’t know why but I’ve wanted to make something like this for quite
a while now.</p>
<h3 id="tocanch8" class="tvis">Animation sequence</h3>
<p>A redo of the closing sequence in my previous custom level. This time
improved and fully revamped.</p>
<h3 id="tocanch9" class="tvis">Random facts on landfills</h3>
<p>To be honest I don’t know why I added these. Maybe just to annoy the
player LOL.</p>
<h2 id="tocanch10" class="tvis">More background story</h2>
<p>The idea of a randomly generated level has been in the text file
referred to in the information page for quite a while. But it was deemed
a pretty difficult one to execute – it’s not easy to generate levels
that’s not completely trivial but still fun to play at the same time. To
make things worse, implementing anything that’s mathematically intense
in pure Virtools behavior script is a nightmare.</p>
<p>I’ve always been planning to make a level with a theme of decay too,
not because the state of the game – that’s just a coincidence (by the
way, I don’t even think this game is in a full downward spiral,
especially not on the technical front where the game is being completely
dissected and rebuilt), but instead because I’ve been fascinated by
scenes showing decay of a formerly prospering place for quite some time.
The event that kick started this project is me realizing that these two
ideas could be combined. A state of chaos may be interpreted as a state
of decay – something I told myself when I was trying to fit these two
unrelated ideas together. At this point, I wasn’t even sure how well
would this weird combination work: maybe randomly generated levels won’t
be playable at all. So my initial plan is to play with this idea to make
sure that it really works. But in my usual fashion, I was way ahead of
myself and soon finding myself designing the level.</p>
<p>The first thing I decided on is the theme: if I’m going to randomly
place things in the level, it will probably look pretty chaotic, and
let’s be honest, crappy. All trash finally goes to the landfill (well,
not literally but you get the point). That couldn’t be a more fitting
theme. I gave the final section a twist so that the level doesn’t feel
the same all the way through, and the twist plays into one idea listed
in my collection perfectly: pressing the switch emptying the final
landfill, revealing a hidden path that leads to the end of the level. I
threw in a few other ideas in my collection for good measure. These are
the ideas that I used for this level from the file:</p>
<ul>
<li>physical switch</li>
<li>random level generation</li>
<li>something from Minecraft (you know which one appeared in the
level)</li>
<li>“skill trolling”</li>
<li>puzzle with unused elements / elements deliberately causing
confusion</li>
</ul>
<p>When I was modeling the floor for the puzzle that appeared in the
final section, I realized the original design I had in mind is
completely unsolvable. So I had to improvise a little bit. This
unintentionally upped the complexity of the puzzle by a notch. Anyway,
despite my best efforts, to me this puzzle still seems “forced” and a
little bit out of place. This is barely surprising because my brain is
dysfunctional enough that the vast majority of puzzle games on the
market is out of reach for me… let alone the idea of designing a good
puzzle of my own. This entire level still feels like a bunch of discrete
ideas loosely put together anyway.</p>
<p>In addition to all that there is at least one alternative solution to
puzzle, which I didn’t bother blocking. That solution would require
pushing a box over unguarded path, so if you pulled it off, you fully
deserve it.</p>
<p>The modeling process is completely done in Blender on my workstation
computer which runs Linux. Thanks to the Blender plugin made by
yyc12345, this process was relatively free from pain and suffering
besides some minor annoyances. I provided my feedback to the developer
regarding how to fix these annoyances. Everything can now be done within
Blender without requiring any external tools (If you’re not making a
scripted level, you don’t even need to postprocess the exported file in
Virtools – the exported NMO file itself will simply work in the game as
long as you have all the required elements.) – this has been a massive
improvement compared to when I first started experimenting making
renders of the game in 2018. That was a time when even importing a level
into Blender would inflict major emotional damage. I still have to
relearn all the key bindings in Blender every time I use it though. But
I genuinely hope the Blender workflow can be more popular in the future,
and I would strongly encourage anyone who’s new to level making and
looking into getting started to at least try out the Blender workflow,
as it has become extremely streamlined nowadays.</p>
<p>Tuning the difficulty of the randomly generated parts was an
extremely tedious and painful process. I must have spent more than 20
hours just tweaking the numbers used by the level generator. Due to the
random nature of the level, I had to go through the level multiple times
every time I changed something just to make sure that I wasn’t only
getting the easiest generation. When I made the first beta release, the
consensus was that the level was still way too annoying to play through.
Therefore I took the axe to the generator, lowering the overall
difficulty significantly and hopefully made it much less irritating.</p>
<p>The level was simply called “Landfill” during its design phase. As
the name of my last level caused some confusion, I decided against
naming the level in an obscure fashion this time. For that reason it was
almost released as “Landfill”. However I couldn’t resist the temptation
to scorn the concept of “cloud computing”, so I stuck “in the clouds” to
the end and called it a day.</p>
<h2 id="tocanch11" class="tvis">Other noteworthy
technical information</h2>
<h3 id="tocanch12" class="tvis">Sound compression</h3>
<p>Virtools uses the WINMM’s ACM (Audio Compression Manager)
infrastructure to decode sound data. Therefore it supports any format
that are registered with the ACM. Windows provides out-of-the-box
support for a compressed format called ADPCM (adaptive differential
pulse code modulation). Files compressed by this codec is usually around
a quarter of their original uncompressed size – not great but still a
very significant reduction in size. It was the use of this codec that
made it possible to ship this level with high quality sound effects
while keeping the file size down.</p>
<p>The down side of this is that some so-called “light weight”
installations of Windows have this ACM component stripped out. For this
reason if you do not have <code>msadp32.acm</code> in your
<code>System32</code> folder (or <code>SysWOW64</code> folder for 64-bit
systems), these sound effects cannot be loaded.</p>
<h3 id="tocanch13" class="tvis">Running Ballance in Virtools
Dev</h3>
<p>It is now possible to run Ballance within Virtools Dev. This is
incredibly useful for debugging scripted levels.</p>
<p>This was made possible thanks to DoyaGu’s work, which completely
suppressed my prior work “CK2Shim” done in 2020-2021. My previous work
allowed behavior building blocks defined in libraries built for older
versions of Virtools to be inserted using a newer version of Virtools
Dev, but none of these building blocks can be executed correctly because
the lack of translation of virtual function calls. This new work is
based on a complete set of reverse engineered source code of these
building block libraries, and they are nearly fully functional when
built against a newer version of the Virtools SDK. Right now there are
still some imperfections in this process, but still the value it
provides cannot be understated.</p>
<p>The procedure for doing this hasn’t yet been made public at this
time. I’ll update this post once the full instructions are made
public.</p>
<h2 id="tocanch14" class="tvis">Useless stuff</h2>
<p>Content of the random messages in this level are based on information
from the following articles:</p>
<p><a href="https://greencoast.org/facts-about-landfills/">https://greencoast.org/facts-about-landfills/</a><br>
<a href="https://www.bvtrashvalet.com/5-facts-about-landfills/">https://www.bvtrashvalet.com/5-facts-about-landfills/</a><br>
<a href="https://en.wikipedia.org/wiki/Landfill">https://en.wikipedia.org/wiki/Landfill</a><br>
<a href="https://en.wikipedia.org/wiki/Landfill_restoration">https://en.wikipedia.org/wiki/Landfill_restoration</a><br>
<a href="https://en.wikipedia.org/wiki/Landfill_mining">https://en.wikipedia.org/wiki/Landfill_mining</a><br>
<a href="https://en.wikipedia.org/wiki/Landfill_tax">https://en.wikipedia.org/wiki/Landfill_tax</a><br>
<a href="https://en.wikipedia.org/wiki/Atari_video_game_burial">https://en.wikipedia.org/wiki/Atari_video_game_burial</a></p>
<h2 id="tocanch15" class="tvis">中文版</h2>
<p>本文不可避免地包含大量对<a href="https://chrisoft.org/ballance/levels/landfill/index_zh.html">《云端掩埋场》</a>的剧透。请谨慎阅读。</p>
<h2 id="tocanch16" class="tvis">提示</h2>
<p>前数小节：</p>
<ul>
<li>尽可能保持比较高的速度。这样在你还没注意到的时候你就可能已经到终点了。</li>
<li>记得纸球的超强越野特性。</li>
<li>总有别的路可以走。</li>
<li>你有无数次机会，别害怕重开。</li>
</ul>
<p>最后一小节：</p>
<ul>
<li>听劝。<strong>听劝！！</strong></li>
<li>如果你无视上一条提示，你会得到应有的报应。</li>
<li>和现实一样，不是每样你周围的东西都是有用的。</li>
<li>有时除了走一条险路你别无他法。</li>
</ul>
<h2 id="tocanch17" class="tvis">本关卡中的新游戏机制</h2>
<h3 id="tocanch18" class="tvis">随机路面生成</h3>
<p>大概是本关最有特色的内容。尽管使用的生成方法十分原始，最终结果仍然看起来很有意思（玩起来也是——至少从某种程度上来说）。</p>
<p>更有规则的生成当然是可以做的，比如随机生成迷宫之类。但是这个点子我还是留给其他人来实现吧。</p>
<h3 id="tocanch19" class="tvis">起火的垃圾桶</h3>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/lf_m0.png"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/ssbsthumb_1000x1000_lf_m0.png"></a>
</div>
<p>「Dumpster
fire」，一个奇怪的美国表达方式。用以指代极其混乱的场面或者无可救药的情景。不过想想看还是挺形象的。以其字面意思出现在了本关卡中。</p>
<p>纯装饰作用。甚至不会烧坏你的纸球。</p>
<h3 id="tocanch20" class="tvis">开关</h3>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/lf_m1.png"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/ssbsthumb_1000x1000_lf_m1.png"></a>
</div>
<p>在无数平台游戏中被用烂了的老套机制，现在也被引入到Ballance里来了。按下去时会发出很吵的警报声。</p>
<h3 id="tocanch21" class="tvis">「充能核心」</h3>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/lf_m2.png"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/ssbsthumb_1000x1000_lf_m2.png"></a>
</div>
<p>这个东西在这里的唯一原因是因为我不想浪费2019年写的爆炸脚本。动效是直接从Minecraft的一个爆炸物那边照搬来的。</p>
<p>基本只有装饰作用。这个机关将来可能还会再次出现，不过到时候会给它一点实际的作用。</p>
<h3 id="tocanch22" class="tvis">「高技巧玩家诱饵」</h3>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/lf_m4.png"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/ssbsthumb_1000x1000_lf_m4.png"></a>
</div>
<p>这是一个低技术力玩家对高水平玩家精英会的复仇（不是</p>
<h3 id="tocanch23" class="tvis">消失的飞船</h3>
<div style="text-align:center;max-width:98%;">
<a href="//filestorage.chrisoft.org/blog/img/lf_m3.png"><img style="width: 50%;" src="//filestorage.chrisoft.org/blog/img/ssbsthumb_1000x1000_lf_m3.png"></a>
</div>
<p>不知道为什么，但是我想做类似这个的东西也已经有段时间了。</p>
<h3 id="tocanch24" class="tvis">退场动画</h3>
<p>《彷徨梦》的退场动画的改进版本。</p>
<h3 id="tocanch25" class="tvis">掩埋场主题的随机小知识</h3>
<p>我也不知道我为什么想加这个。可能是单纯想找不自在吧（笑</p>
<h2 id="tocanch26" class="tvis">制作花絮</h2>
<p>做一个随机生成的关卡的想法躺在我的那个脚本图点子文档里已经很久了。但是我意识到想把这个点子做好是比较有难度的——生成难度合适并且有可玩性的关卡没有那么简单。而且在Virtools中使用纯脚本实现任何包含数学运算的过程都无异于自虐，这使得我一直没有把这个想法转换为成品。</p>
<p>我还一直想做一个以衰败为主题的地图。倒不是因为这个游戏本身所处的状态——这顶多算一个巧合（顺便，我甚至觉得这游戏没有完全处在衰落模式里，尤其是在彻底解剖游戏的内部原理这个方面。）。实际原因是我一直对展示一个曾经繁荣的地方如今逐渐衰败的场景有一种来历不明的迷恋。我突然意识到这个主题和前面的点子是可以结合到一起的。“混乱的状态也可以算是衰败的一种吧”，我如此说服了我自己将这两个本无关联的想法结合到了一起。当时我甚至不知道这个奇怪的组合会搞出什么样的结果，也许随机生成的关卡是根本没法玩的。所以我一开始的计划是先测试一下随机生成方案，至少证明它有可行性。但是如同我的一贯作风，在我意识到这点之前我已经把关卡设计做了一半了。</p>
<p>我第一个决定的事项是地图的主题：如果是随机生成的关卡的话，关卡本身看起来大概会很混乱，而且坦白来讲的话，会很垃圾。所有垃圾最终都去了掩埋场（不是完全准确的描述，但是大致就是怎么个意思）。这个主题简直不能更合适了。为了不让整个关卡从头到尾玩起来感觉都一样，我在最后一个小节里加了一点「料」。这个「料」正好符合我的一个点子：按下开关之后最后一个掩埋场会被倒空，露出隐藏的引向终点的路。在正式开始设计之前我又顺便从我的那堆点子里面随便挑了几个加进去。以下是我最后采用的所有点子：</p>
<ul>
<li>物理开关</li>
<li>随机关卡生成</li>
<li>随便一个来自Minecraft的东西</li>
<li>「高技巧玩家诱饵」</li>
<li>有未使用/刻意引起迷惑的机关的谜题</li>
</ul>
<p>当我在为最后小关的谜题的路面建模的时候，我意识到我一开始在图纸上画的设计完全无解，所以我被迫即兴作出了一点修改。这无意间略微提高了这个谜题的难度。即使我已经尽力地让这个谜题更好地融入这个关卡了，某种程度上这个小节还是有点不合情理的感觉。其实对此也没什么可惊讶的，毕竟对我的不争气的脑子来说，市面上大多数解谜类游戏都超出了我的能力范围，更别说设计我自己的谜题了。不过话说回来整个关卡也不过就是一堆离散的点子被强行堆在了一起罢了。</p>
<p>除此之外这段还有至少一个非预期解法。我没有刻意禁掉这个解，因为这个路线需要在没有保护措施的路面上推着箱子走相当长的一段距离。如果你用这种解法成功通关了的话，你的通关也完全是应得的。</p>
<p>整个建模过程都是在我的运行Linux的工作站电脑上使用Blender完成的。多亏了yyc12345的插件，现在这个过程整体上没有太多痛点——顶多有几个小问题。关于如何解决这些小问题，我已经向开发者提供了我的反馈。现在一切建模工作均可在Blender一个软件内完成（如果你不是在制作脚本图的话，你甚至不需要用Virtools处理从Blender里导出的NMO文件。只要所有必需的基本元素都在，这个NMO文件可以直接在游戏中使用。）——相比2018年我刚开始在Blender里研究制作Ballance渲染图时这简直是翻天覆地的变化。当时甚至将Virtools数据导入Blender中都是一个痛苦的过程。虽然现在每次用Blender时我还是得重新学习它的快捷键，但是我还是真心希望Blender制图工作流未来可以更加主流，尤其是在国内社区中。对于正在考虑制作第一张地图的制图新人来说，我会强烈建议尝试一下Blender制图工作流，因为如今这个工作流已经非常易用了。</p>
<p>在设计阶段这个关卡的名字就叫「掩埋场」。因为我的上一张图的命名导致了一些程度上的迷惑（比如STERD在其通关视频描述中写道「我没彷徨也没梦，我懵」），这次我不打算在名字里搞奇怪的名堂了。因此这个关卡差点被最终定名「掩埋场」。不过我无法阻止我的嘲讽「云计算」概念的冲动，所以最后在前面加上了「云端」。</p>
<h2 id="tocanch27" class="tvis">关卡内文字中英文对照</h2>
<blockquote>
<p>Welcome to the Landfill.</p>
<p>It’s easy to get stuck here. If you need a lifeguard who moves you
back to your last checkpoint when you haven’t moved in 10 seconds, press
F now. You may dismiss these messages by pressing Enter.</p>
</blockquote>
<p>欢迎来到垃圾掩埋场。</p>
<p>在这里冒险很容易把球卡住。如果你需要在本关内启用10秒内不动则自动将你送回上一个出生点的功能，请现在按下F键。<br>
按回车可以立即关闭本关卡中的消息。</p>
<blockquote>
<p>Landfills are the oldest form of waste management.</p>
</blockquote>
<p>掩埋场是最古老的废弃物处置方式。</p>
<blockquote>
<p>Landfills emit decomposition gases over time, roughly half of which
in volume is methane.</p>
</blockquote>
<p>随着时间的推移，掩埋场会排放分解气体。该气体的按容积计大约一半是甲烷。</p>
<blockquote>
<p>Due to its high methane content, landfill gas is flammable. It can be
used to generate electricity.</p>
</blockquote>
<p>由于掩埋场废气中甲烷的占比高，它可以燃烧，并且可以用来发电。</p>
<blockquote>
<p>Depending on its material, the time required for waste to decompose
in a landfill varies. This usually lies within the range of decades and
centuries.</p>
</blockquote>
<p>垃圾在掩埋场中分解所需的时间取决于其材料。通常这个时间在数十年到数百年之间。</p>
<blockquote>
<p>Some sources report that it could take more than 4000 years if glass
were dumped into a landfill.</p>
</blockquote>
<p>有资料声称如果玻璃被埋入掩埋场，其可能需要超过4000年才能完全分解。</p>
<blockquote>
<p>Once filled up to its max capacity, a landfill can be capped and
restored with vegetation.</p>
</blockquote>
<p>在达到其最大容量后，掩埋场可以被封顶并用植被恢复地表外貌。</p>
<blockquote>
<p>Vectors (in the pathological sense, not the mathematical quantity)
can thrive near mismanaged landfills, spreading infectious diseases.</p>
</blockquote>
<p>携带传染病的生物可在管理不善的掩埋场周围肆虐，传播传染性疾病。</p>
<blockquote>
<p>Landfills were mined to reclaim valuable resources such as
aluminum.</p>
</blockquote>
<p>有的掩埋场由于为了回收铝之类的有价值的材料而被挖开过。</p>
<blockquote>
<p>Landfills are taxed in a few countries.</p>
</blockquote>
<p>有的国家对掩埋场征税。</p>
<blockquote>
<p>ATARI buried thousands of cartridges for games that are widely
considered failures in a landfill during the infamous video game crash
of 1983.</p>
</blockquote>
<p>ATARI在著名的1983年美国电子游戏大萧条中将数千张被人广泛认为是失败作品的游戏卡带埋入了掩埋场。</p>
<blockquote>
<p>Legend has it that rails around here are cursed… You have been
warned.</p>
</blockquote>
<p>传说这附近的钢轨有邪气……</p>
<blockquote>
<p>How dare you mess around on my monorail!</p>
</blockquote>
<p>大胆！竟然敢在我的单轨上放肆！</p>
<h2 id="tocanch28" class="tvis">其他值得一提的技术信息</h2>
<h3 id="tocanch29" class="tvis">音频压缩</h3>
<p>Virtools使用WINMM的ACM（音频压缩管理器，Audio Compression
Manager）设施来解码音频数据。于是所有在ACM中登记的格式Virtools都可以支持。Windows自带了针对一个叫做ADPCM格式的有损音频编码的解码器。这个编码大致可以将未压缩数据压缩到原大小的四分之一——对音频数据来说不算多好的压缩率，但是仍然能显著减小文件大小。因为使用了ADPCM编码的音效，本关卡才有可能在使用了高音质的音效数据的同时把文件大小控制在合理的范围之内。</p>
<p>使用ADPCM编码的音频的坏处是有些所谓「轻量」的Windows安装会移除这个ACM组件。因此如果你的系统的<code>System32</code>目录（64位系统下则是<code>SysWOW64</code>目录）中没有<code>msadp32.acm</code>这个文件，你将无法听到本关卡中的音效。</p>
<h3 id="tocanch30" class="tvis">在Virtools
Dev环境中运行Ballance</h3>
<p>现在Ballance可以运行在Virtools
Dev环境中了。这对脚本地图的开发有着极大的价值。</p>
<p>这背后的工作是由DoyaGu完成的，同时这个项目也取代了我2020-2021年的CK2Shim工程。CK2Shim可以让新版本的Virtools加载为旧版本Virtools编译的逻辑块库并插入其中定义的逻辑块，但是因为没有处理虚函数调用，这些逻辑块在Virtools
Dev中均无法正常执行。这个新工程则是基于原游戏逆向出的源代码，完全用新版本的Virtools
SDK重新编译的。现在运行的效果还不是百分之百完美，但是它的意义依然不可小觑。</p>
<p>相关的步骤在本文完成时还没有公开。在公开时本文此处会加入相应的信息。</p>
<h2 id="tocanch31" class="tvis">无聊内容</h2>
<p>游戏中的随机消息是基于以下几篇文章中的内容撰写的：</p>
<p><a href="https://greencoast.org/facts-about-landfills/">https://greencoast.org/facts-about-landfills/</a><br>
<a href="https://www.bvtrashvalet.com/5-facts-about-landfills/">https://www.bvtrashvalet.com/5-facts-about-landfills/</a><br>
<a href="https://en.wikipedia.org/wiki/Landfill">https://en.wikipedia.org/wiki/Landfill</a><br>
<a href="https://en.wikipedia.org/wiki/Landfill_restoration">https://en.wikipedia.org/wiki/Landfill_restoration</a><br>
<a href="https://en.wikipedia.org/wiki/Landfill_mining">https://en.wikipedia.org/wiki/Landfill_mining</a><br>
<a href="https://en.wikipedia.org/wiki/Landfill_tax">https://en.wikipedia.org/wiki/Landfill_tax</a><br>
<a href="https://en.wikipedia.org/wiki/Atari_video_game_burial">https://en.wikipedia.org/wiki/Atari_video_game_burial</a></p>
<br><hr><div class="TText" id="notediv" style="font-size:80%;"></div>]]></content></entry></feed>