<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>arp242.net</title>
    <description>Website of Martin Tournoij/arp242. I write about stuff, occasionally.</description>
    <link>https://www.arp242.net/</link>
    <atom:link href="https://www.arp242.net/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Fri, 10 Jul 2026 20:18:57 +0000</pubDate>
    <lastBuildDate>Fri, 10 Jul 2026 20:18:57 +0000</lastBuildDate>
    <item>
      <title>I like using scrollbars</title>
      <pubDate>Fri, 10 Jul 2026 00:00:00 +0000</pubDate>
      <link>https://www.arp242.net/scrollbar.html</link>
      <guid isPermaLink="true">https://www.arp242.net/scrollbar.html</guid>
      <category>Web</category>
      <description>&lt;p&gt;I like to use scrollbars. I move my mouse cursor to the scrollbar handle and
move it about – scrolling like this is so much faster than with the wheel if you
want to scroll more than a few lines.&lt;/p&gt;

&lt;p&gt;I didn’t notice how often I did this until sites started breaking this. It seems
to be fashionable to put a margin around the entire document, resulting in the
scrollbar no longer being aligned to the edge of the screen.&lt;/p&gt;

&lt;p&gt;This is an absolute bollockache because now you can no longer just move the
mouse to the edge of the screen: you need to place it &lt;em&gt;exactly&lt;/em&gt; on the
scrollbar, requiring far greater precision.&lt;/p&gt;

&lt;p&gt;You can see an example at &lt;a href=&quot;/scrollmargin.html&quot;&gt;/scrollmargin.html&lt;/a&gt;. You need a
device with a mouse cursor to see the problem of course.&lt;/p&gt;

&lt;p&gt;Some real examples, all of which started doing it in the last year or so:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;FastMail&lt;/li&gt;
  &lt;li&gt;Outlook365&lt;/li&gt;
  &lt;li&gt;Stack Overflow “Beta Redesign”, although that was aborted.&lt;/li&gt;
  &lt;li&gt;Spotify (&lt;a href=&quot;https://open.spotify.com/artist/0Savi9s4Zz0g4hENYhUP0F&quot;&gt;example&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;GitLab (&lt;a href=&quot;https://gitlab.gnome.org/GNOME/vte/-/issues/253&quot;&gt;example&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Couchsurfing now &lt;em&gt;completely hides the scrollbar&lt;/em&gt; (&lt;a href=&quot;https://www.couchsurfing.com/c/locations/places&quot;&gt;example&lt;/a&gt;)
(aside: I am not saying that Couchsurfing has been infiltrated by AirBnB
agents to run this communist woke libtard competition in to the ground, but I
am saying that nothing Couchsurfing has done over the last 10 years
contradicts that hypothesis.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is of course also the perennial “inappropriate scrollbar styling” problem,
and recently ultra low contrast have also gained popularity; for example:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Substack (&lt;a href=&quot;https://therealitydrift.substack.com/p/reality-drift-in-everyday-life&quot;&gt;example&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;MariaDB documentation (&lt;a href=&quot;https://mariadb.com/docs/server/server-management/variables-and-modes/sql_mode&quot;&gt;example&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’m not against &lt;em&gt;any&lt;/em&gt; scrollbar styling; in some cases it’s appropriate. Some
more playful websites, some very specific and rare webapp use cases, things like
that. I’m not a “system behaviour in every single last scenario” purist. The
MariaDB reference docs is very much not one of those cases.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;I like using scrollbars. I can’t be the only person who does. You’re frustrating
users for no reason other than following some fad that makes things look ever so
marginally nicer. No one pays that much attention to your design and they won’t
notice. In ten years time everyone will look back at this in the same way we
look back that extremely low contrast text fad from a decade ago: “yeah, that
was rather silly”.&lt;/p&gt;
</description>
    </item>
    <item>
      <title>Chesterton&apos;s middle finger</title>
      <pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate>
      <link>https://www.arp242.net/chestersons-finger.html</link>
      <guid isPermaLink="true">https://www.arp242.net/chestersons-finger.html</guid>
      <category>Programming</category>
      <category>Workplace</category>
      <description>&lt;p&gt;The &lt;a href=&quot;https://fs.blog/chestertons-fence/&quot;&gt;Chesterton’s fence&lt;/a&gt; analogy says that you need to be careful to change
things if you don’t understand why things are the way they are, because there
may be good reasons you haven’t considered yet. This is broadly good advice in
many different contexts, including programming where it can be easy to “fix”
some weird code only to discover there was a reason for the weirdness later on
when things break.&lt;/p&gt;

&lt;p&gt;This is Chesterson’s middle finger:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;# List all commit body content (but not the subject line)
% git log --no-merges --format=format:&apos;%b&apos; | sed &apos;/^$/d&apos; | wc -l
295
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;That’s 295 lines of commit text over the last 13 years. In total. The commit
subjects are usually just “fix page A” – even for huge changes – and are
pointless. If I manually remove some dependabot, “revert commit”, and “fix typo”
commit bodies it’s just 167 lines. That’s about one line a month.&lt;/p&gt;

&lt;p&gt;There is no other documentation. There are barely any comments in the code.&lt;/p&gt;

&lt;p&gt;This is Chesterson’s middle finger: “Yes, we did all these weird things and
we’re not telling anyone why. Haha fuck you.”&lt;/p&gt;

&lt;p&gt;This is not the first time I’ve seen a less-than-useful commit log, but it is
the first time I’m hired after everyone else left. There is no one to ask.&lt;/p&gt;

&lt;p&gt;In theory there was a three week handover period from the previous developer,
but that was just as communicative as the commit log. Never have I sympathized
more with Jack Bauer’s methods to extract information and I regret not employing
some of them.&lt;/p&gt;

&lt;p&gt;There are unfinished refactors. There are left-overs from removed features.
There are features that were added but never linked/used but are still in the
code. There are features no one seems to be using. Overall it seems to suffer
quite badly from &lt;a href=&quot;https://stephantul.github.io/blog/unfence/&quot;&gt;Chesterton’s gap&lt;/a&gt; (“There isn’t a fence yet? Lets build
fences!” without asking if a fence is actually needed).&lt;/p&gt;

&lt;p&gt;Everything is just a big fat middle finger.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Writing &lt;em&gt;well&lt;/em&gt; is hard. Writing something vaguely passable is not. By and large
there are three questions to answer: “What are you changing?”, “why are you
changing it?”, and “why is this a good solution?”&lt;/p&gt;

&lt;p&gt;Sometimes “Implement new feature X” is enough, although it’s rare there is
nothing more to say – most of the time there is &lt;em&gt;something&lt;/em&gt; to say about how/why
it’s added as it is.&lt;/p&gt;

&lt;p&gt;If you’re fixing a bug, refactoring or changing things, or making some other
substantial change then it’s very rare there isn’t at least a paragraph or two
to comment on the “what’s changing, why change it, and why is this good?”
questions.&lt;/p&gt;

&lt;p&gt;Writing this is not an optional extra; it’s part of the job. If you’re not doing
it then you’re not doing your job as a software developer. It doesn’t need to be
eloquent. It doesn’t need to be perfect English. It doesn’t need to be a
comprehensive essay on the nature of reality. It’s okay to forget something
(although better if you don’t). It just needs … &lt;em&gt;something&lt;/em&gt;. Any half-way
serious attempt will be infinitely better than &lt;em&gt;nothing at all&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;If you do &lt;em&gt;nothing at all&lt;/em&gt; then you’re just giving everyone after you the
finger.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Dependencies should be fetched directly from VCS</title>
      <pubDate>Fri, 12 Jun 2026 00:00:00 +0000</pubDate>
      <link>https://www.arp242.net/deps-vcs.html</link>
      <guid isPermaLink="true">https://www.arp242.net/deps-vcs.html</guid>
      <category>Security</category>
      <category>Ruby</category>
      <description>&lt;p&gt;I’ve been writing Ruby at my new $dayjob in the last month. After spending most
of the last decade writing Go it’s been a fun change of scenery. I did Ruby
before (years ago) and I can’t really tell you which is “better” – Ruby is very
different from Go in almost every respect and I find both quite effective in
getting stuff done in their own way.&lt;/p&gt;

&lt;p&gt;One aspect where I do feel Go is clearly better is dependency management;
specifically the security aspect thereof. Go is not magically immune to
malicious dependencies, but it is a lot more resistant to them chiefly because
there is no “publish a package” step.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;In Go dependencies are identified by URL, e.g. github.com/user/pkg. Go
identifies which VCS is being used (git in this case) and fetches the tag or
commit you specified in your go.mod file. The go.mod file serves as both a
dependency specification and “lock file”. It lists exact versions; there is no
~&amp;gt;1.1. It includes both direct and indirect dependencies and lists your full
dependency tree (the go command writes to go.mod).&lt;/p&gt;

&lt;p&gt;A hash of all files is checked against known hashes on sum.golang.org to prevent
tags from being replaced, and it uses a proxy to prevent repos from being
left-pad’d.&lt;/p&gt;

&lt;p&gt;There are more aspects to Go Modules, including security features, but I will
skip over them for the purpose of this article. The relevant bit is
“dependencies are identified by URL, the code is fetched directly from the VCS,
and it does this for both direct and indirect dependencies”.&lt;/p&gt;

&lt;p&gt;Auditing and updating dependencies is easy: I do &lt;code&gt;git log -p old..new&lt;/code&gt; (usually
via a forge web UI), read all the commits, and update the go.mod file. I don’t
have many dependencies and those I do have don’t change much. It’s usually
pretty fast. I don’t need to do careful in-depth reviews here; just look for
suspicious stuff. Something like &lt;code&gt;exec.Command(..)&lt;/code&gt; or &lt;code&gt;http.Post(..)&lt;/code&gt; in a
globbing library would stand out. It’s hard to really hide stuff.&lt;/p&gt;

&lt;p&gt;I’ve been doing this for years for every dependency. As a solo developer. It’s
easy. Some projects have much larger dependency trees and this becomes more
time-consuming, but not hard or confusing. It’s still easy, just takes a bit of
time.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;For Ruby things are different as it has a “publish a package” step: you create a
.gem archive and upload that to rubygems.org. You can put anything in there – no
guarantee the .gem contents correspond to the source repo. To audit it I need to
do something like:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;curl -s https://rubygems.org/downloads/example-2.7.5.gem &amp;gt;old.gem
curl -s https://rubygems.org/downloads/example-2.8.2.gem &amp;gt;new.gem

mkdir old new
tar xf old.gem -C old
tar xf new.gem -C new

(cd old &amp;amp;&amp;amp; tar xf data.tar.gz)
(cd new &amp;amp;&amp;amp; tar xf data.tar.gz)

diff -urN old new
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;It works, I guess, but is far from easy. The individual commits are lost and is
generally harder to audit. In some cases the diff is small enough that it’s
okay. In other cases it’s huge and not having access to the commits is a pain. I
can also totally see myself get confused about what I did and didn’t audit. I
guess the number of people doing this sort of auditing is very low because it’s
just such a pain.&lt;/p&gt;

&lt;p&gt;This is not unique to Ruby: this is how many (most?) package systems work.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Almost all of the “side-channel attacks” I’ve seen are perhaps more accurately
described as “package publishing attacks”. They rely on injecting something in
the “publish a package” step. Whether that’s RubyGems, npm, PyPI, .tar.gz FTP
downloads, or something else is a relatively minor detail. It’s rare that the
actual source repo gets compromised as it’s just too visible. You need to at
least slightly hide your exploit for it to be effective.&lt;/p&gt;

&lt;p&gt;The recent npm compromises all relied on gaining access to the npm account and
injecting something in the published package. xz had some exploit code in the
source repo but was inert, hidden in a binary test file and only activated in
the modified .tar.gz release. Back in 2018 event-stream added a dependency on
flatmap-stream, which had nefarious code in index.min.js only on the published
npm package.&lt;/p&gt;

&lt;p&gt;Which points to a second problem: packages containing “compiled” resources. The
JavaScript that TypeScript generates is not completely unreadable, it’s
certainly much &lt;em&gt;less&lt;/em&gt; readable and auditable than the original TypeScript. To
say nothing of minified files or binary blobs. This is less of a problem in
Ruby, but a far bigger problem in npm.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Last week RubyGems &lt;a href=&quot;https://blog.rubygems.org/2026/06/03/cooldown-let-new-gems-be-vetted.html&quot;&gt;added a cooldown option&lt;/a&gt; and “AI-assisted vulnerability
scanning against the most critical gems”. Not a bad thing to do as a short-term
move, but I feel a more appropriate solution would be to reconsider the entire
“publish a package” model. It just lacks the required transparency. AI tools are
not going to magically fix that.&lt;/p&gt;

&lt;p&gt;I probably would have created something similar to RubyGems myself twenty years
ago. Distributing .tar.gz files on SourceForge was by and large how things
worked and many projects did not have a publicly accessible VCS repo, or did not
have one at all. Generally this worked fairly well at the time. I’m not blaming
any one here – I would have done the same. RubyGems is just designed in a
different world.&lt;/p&gt;

&lt;p&gt;I’m not saying RubyGems and npm need to copy what Go does &lt;em&gt;exactly&lt;/em&gt; in every
respect and I’m not saying all of this is completely perfect either. But as far
as I know, it’s the best anyone has come up with thus far. Some other aspects of
Go modules (such as &lt;a href=&quot;https://go.dev/ref/mod#minimal-version-selection&quot;&gt;Minimal Version Selection&lt;/a&gt;) are less important.&lt;/p&gt;

&lt;p&gt;I appreciating that completely changing how this works is hard and potentially
disruptive. But dealing with this endless stream of hijacked packages is also
hard and disruptive. So…&lt;/p&gt;

&lt;p&gt;In the specific case of Bundler, there is already &lt;em&gt;some&lt;/em&gt; support for this. You
can do:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;gem &apos;rails&apos;, git: &apos;https://github.com/rails/rails.git&apos;, tag: &apos;v8.1.2&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;And it will fetch the rails gem from git, but will still fetch dependencies from
rubygems.org. Maybe there is some way to cajole Bundler in to using git for
everything, but it’s an uphill battle and easy to accidentally use rubygems.org.&lt;/p&gt;

&lt;p&gt;Just thinking out loud here, but something like this would probably go a long
way, and won’t break existing Gemfiles:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;# Do not allow fetching anything from rubygems.org;
# changes gem() behaviour to use git.
must &apos;use-git&apos;

gem &apos;github.com/rails/rails&apos;, &apos;v8.1.2&apos;

# Indirect (automatically written and updated by &quot;bundle install&quot; and similar)
indirect do
    gem &apos;github.com/rails/actionview&apos;, &apos;v8.1.2&apos;
    gem &apos;github.com/fxn/zeitwerk&apos;,     &apos;v2.8.2&apos;
    # ... etc...
end
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Gemfile.lock can still be used for hashes (similar to go.sum), but is otherwise
not all that useful.&lt;/p&gt;

&lt;p&gt;There’s a bunch of details to be sorted out here and I’m not pretending those
are straight-forward to sort out well, but there does seem to be a reasonable
path there, I think?&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Or maybe something else entirely. I don’t know. The main point is: &lt;strong&gt;I want to
reliably audit my dependencies like a responsible developer and RubyGems makes
it too hard&lt;/strong&gt;, as do other package managers (but I don’t care about them).&lt;/p&gt;

&lt;div class=&quot;postscript&quot;&gt;&lt;strong&gt;Elsewhere on the interwebs&lt;/strong&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://nesbitt.io/2025/12/25/cursed-bundler-using-go-get-to-install-ruby-gems.html&quot;&gt;Cursed Bundler: Using go get to install Ruby Gems&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;
</description>
    </item>
    <item>
      <title>Comparing compression tools</title>
      <pubDate>Tue, 14 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://www.arp242.net/cmp-compress.html</link>
      <guid isPermaLink="true">https://www.arp242.net/cmp-compress.html</guid>
      <category>Unix</category>
      <description>&lt;p&gt;I’d like to know what the “best” compression tool is for storing archives
(backups, data files). So I wrote a tool to compare them: &lt;a href=&quot;https://github.com/arp242/cmp-compress&quot;&gt;cmp-compress&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My tl;dr take-away is:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;zstd -3&lt;/code&gt; for fast compression (the default).&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;xz -7&lt;/code&gt; for the best ratio use; &lt;code&gt;-8&lt;/code&gt; or &lt;code&gt;-9&lt;/code&gt; &lt;em&gt;sometimes&lt;/em&gt; compress better but
often don’t. You probably want to test this.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;zstd -12&lt;/code&gt; for a good trade-off that leans towards fast.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;zstd -17&lt;/code&gt; for a good trade-off that leans towards better ratio.&lt;/li&gt;
  &lt;li&gt;Add &lt;code&gt;-T0&lt;/code&gt; to zstd to use all cores if nothing else is running on the system
(or use the &lt;code&gt;zstdmt&lt;/code&gt; wrapper).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For other use-cases all of this may of course be different.&lt;/p&gt;

&lt;p&gt;Some tools like xz and zstd offer many differs knobs and levers; I’m sure these
are useful but did not bother with them and tested only the presets. I’m not
looking for an incantation to eek out the absolute best – I just want an
informed practical decision on which tool to use with which flags to compress
common stuff.&lt;/p&gt;

&lt;p&gt;For the same reason I’m only comparing reasonably common compression tools
already in wide-spread use. There are many others that are very similar to those
listed here with minor differences (e.g. zip, 7zip), not considered stable, or
just not widespread for one reason or the other.&lt;/p&gt;

&lt;p&gt;If you’re interested in other tools or sets of flags then you can run
cmp-compress yourself.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Full Results as HTML table: &lt;a href=&quot;/compress.html&quot;&gt;/compress.html&lt;/a&gt;.
Or as JSON: &lt;a href=&quot;/compress.json&quot;&gt;/compress.json&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I ran this on the following files:&lt;/p&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;vim&lt;/td&gt;
      &lt;td&gt;5.5M&lt;/td&gt;
      &lt;td&gt;Statically linked binary&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;qemu-system-x86_64&lt;/td&gt;
      &lt;td&gt;29M&lt;/td&gt;
      &lt;td&gt;Dynamically linked binary&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;dockerd&lt;/td&gt;
      &lt;td&gt;87M&lt;/td&gt;
      &lt;td&gt;Statically linked binary&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;dickens&lt;/td&gt;
      &lt;td&gt;9.7M&lt;/td&gt;
      &lt;td&gt;English text, no tags&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;enwik8&lt;/td&gt;
      &lt;td&gt;95M&lt;/td&gt;
      &lt;td&gt;English text, with XML and Wiki tags&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;enwik9&lt;/td&gt;
      &lt;td&gt;950M&lt;/td&gt;
      &lt;td&gt;English text, with XML and Wiki tags&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;yt-dlp-2026.03.17.tar&lt;/td&gt;
      &lt;td&gt;12M&lt;/td&gt;
      &lt;td&gt;Python code&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;coreutils-9.10.tar&lt;/td&gt;
      &lt;td&gt;63M&lt;/td&gt;
      &lt;td&gt;C code&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;go1.26.1.src.tar&lt;/td&gt;
      &lt;td&gt;150M&lt;/td&gt;
      &lt;td&gt;Go code&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;go1.26.1.linux-amd64.tar&lt;/td&gt;
      &lt;td&gt;233M&lt;/td&gt;
      &lt;td&gt;Mix of Go code and statically linked binaries&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The enwik files are from the &lt;a href=&quot;https://mattmahoney.net/dc/text.html&quot;&gt;Large Text Compression Benchmark&lt;/a&gt;; dickens is from
the &lt;a href=&quot;https://sun.aei.polsl.pl/~sdeor/index.php?page=silesia&quot;&gt;Silesia compression corpus&lt;/a&gt;; QEMU is version 10.2.0; Docker is version
29.4.0, and vim is a private modified fork that’s not easy to exactly reproduce
but a statically linked huge build of Vim 9.1 should be close.&lt;/p&gt;

&lt;p&gt;All of this is run on Void Linux with Linux 6.19.10 on a ThinkPad x13 with AMD
Ryzen 7 7840U (8 cores/16 threads) and 32G of memory. I ran all the tests twice
and discarded the first run (&lt;code&gt;cmp-compress compare &amp;gt;/dev/null &amp;amp;&amp;amp; cmp-compress compare &amp;gt;a.json&lt;/code&gt;).&lt;/p&gt;

&lt;h2 id=&quot;gzip&quot;&gt;gzip &lt;a href=&quot;#gzip&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The -# flag has a small effect and becomes useless above -6. Even the difference
between -5 and -6 is quite small, and arguably -5 would be a better default than
-6. -4 is a good setting if you want to be faster as it compresses a bit more
than -1, -2, and -3, but isn’t too much slower.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;-#&lt;/code&gt; flag makes no meaningful difference for decompression times.&lt;/p&gt;

&lt;p&gt;In short, use only -4, -5, or -6.&lt;/p&gt;

&lt;h2 id=&quot;pigz&quot;&gt;pigz &lt;a href=&quot;#pigz&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The -# behaves identical to gzip: it has no meaningful difference from gzip
(in most cases pigz compresses slightly better – by a negligible amount but
still). It’s often faster when using multiple threads, but single-thread
compression is a bit slower.&lt;/p&gt;

&lt;p&gt;It also adds a -11 flag to use the zopfli algorithm. The manpage says it “gives
a few percent better compression at a severe cost in execution time”. It’s not
exaggerating about the “severe cost”: it’s always the slowest of &lt;em&gt;any&lt;/em&gt; tool by a
huge margin. I suppose that it can be useful if you’re serving things at
Google-scale, but you’re probably not, and other algorithms (brotli, zstd) are
now widely-implemented. I’m going to say that in 2026 it’s useless: bzip2, xz,
and zstd can all achieve better compression while being much much faster.&lt;/p&gt;

&lt;p&gt;Aside: I kind of wish /bin/gzip would be replaced by pigz, or that gzip would
use zlib and incorporate multi-threaded code. pigz seems stable, reasonably
coded, and generally fairly good. I can’t find any reasons to &lt;em&gt;not&lt;/em&gt; s/gzip/pigz/
other than vague concerns about “it’s different”, which is not entirely invalid
but pigz has been around for a long time and at some point things need to move
forward.&lt;/p&gt;

&lt;h2 id=&quot;bzip2&quot;&gt;bzip2 &lt;a href=&quot;#bzip2&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The classic “I’m okay waiting a bit longer than gzip to get better ratios”. As a
rule, compression is ~1.5 times slower, and decompression is ~3 times slower. It
practically always gets a better compression ratio than gzip. More so on text
than on binary files.&lt;/p&gt;

&lt;p&gt;The -# flags make a small difference in compression ratio or time. There is no
reason to use anything other than &lt;code&gt;-9&lt;/code&gt; (the default). bzip3 did away with -#
flags entirely.&lt;/p&gt;

&lt;p&gt;Both xz and zstd compress better than bzip2, but do take longer. Only on the
dickens file does bzip2 give the same compression ratio as xz, but bzip2 is an
order of a magnitude faster. Overall I’m going to say that bzip2 is rarely worth
it, except &lt;em&gt;perhaps&lt;/em&gt; if your data is primary text with minimal formatting (e.g.
Markdown files, or similar). I don’t know to what degree this holds true for
other languages or scripts as I only tested English.&lt;/p&gt;

&lt;h2 id=&quot;xz&quot;&gt;xz &lt;a href=&quot;#xz&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The newer “I’m okay waiting a bit longer than gzip to get better ratios”. Both
compression and decompression is fairly slow, but delivers the best ratios of
the common compression tools (zstd comes close, but xz is still meaningfully
better).&lt;/p&gt;

&lt;p&gt;The -# flag does little above -5, albeit more than gzip and there is a small but
meaningful difference between the levels. For many files -8 and -9 are much
slower than -7 while having little to no effect on ratio. As a rule you want to
stick with the default of -6 unless you’ve tested it makes a meaningful
difference on your data.&lt;/p&gt;

&lt;p&gt;On binary files it outperforms bzip2 even on -0, on code at around -1, and on
natural language it tends to outperform bzip2 at around -4 or -5,&lt;/p&gt;

&lt;p&gt;Decompression speed decreases with higher -# flags, although this is not quite
linear and sometimes higher -# flags have &lt;em&gt;faster&lt;/em&gt; decompression speeds.
Sometimes it’s &lt;em&gt;very&lt;/em&gt; slow (e.g. enwik9 is almost two minutes to decompress,
which is by far the slowest other than &lt;code&gt;pigz -11&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;zstd&quot;&gt;zstd &lt;a href=&quot;#zstd&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Unlike gzip and xz the -# flag makes a meaningful difference at any level. The
progression from -1 to -19 isn’t linear, but comes closer than anything else. In
particular, the gap between -12 to -13 is consistently large with -13 being two
to three times slower than -12.&lt;/p&gt;

&lt;p&gt;Decompression is fast, although does take a noticeable hit at higher -# levels,
but even at -19 it’s always faster than xz -0.&lt;/p&gt;

&lt;p&gt;It’s almost always faster than gzip -1 at lower -# levels while delivering a
better compression ratio. -12 is usually faster than gzip -6 (and -13 is slower,
due to aforementioned jump).&lt;/p&gt;

&lt;p&gt;For natural language files it compresses better than bzip2 at around -16, while
being much slower. For code and binary files it’s around -6, while being faster.
xz still compresses ~10% better than zstd -19, but xz is slower (often a lot
slower).&lt;/p&gt;

&lt;p&gt;The --ultra flags eeks out a slightly amount of extra compression ratio at the
expense of being a lot slower, although not quite as dramatic as pigz -11. The
compression ratio differences are small enough that for most cases it’s rarely
useful.&lt;/p&gt;

&lt;p&gt;--fast is not that much faster than -1 and compression ratio suffers greatly,
and decompression performance is not significantly faster. There’s probably
&lt;em&gt;some&lt;/em&gt; uses cases where you really want to get the maximum compression
performance, but by and large, I’m going to say it’s not very useful for most
cases, outside of some streaming perhaps.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a href=&quot;#conclusion&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I’m going to say that “just use zstd” is probably decent advice: it doesn’t have
the absolute best compression ratio or performance, but for many use-cases the
trade-of it makes are quite good, and it has very fast decompression which is
nice. zstd also has a bewildering number of configuration options, making it
very flexible: e.g. for smaller files the ability to pre-train a dictionary will
make a big difference (not tested in this overview: based on previous
experience).&lt;/p&gt;

&lt;p&gt;If you do need absolute maximum compression ratios then xz is often better, at
the expense of being much slower. In some cases that’s a good trade-off.&lt;/p&gt;

&lt;p&gt;For natural language text bzip2 might still be worth it, but compression seems
to suffer if there’s too much formatting (which is often the case in real-world
files).&lt;/p&gt;

&lt;p&gt;I want to stress once more that all of this may depend on your specific data,
system you’re using, planetary alignments, etc. “Most of the time”, “often”, and
“usually” do &lt;em&gt;not&lt;/em&gt; equal “always”.&lt;/p&gt;
</description>
    </item>
    <item>
      <title>Against best practices</title>
      <pubDate>Sat, 16 Nov 2024 00:00:00 +0000</pubDate>
      <link>https://www.arp242.net/best-practices.html</link>
      <guid isPermaLink="true">https://www.arp242.net/best-practices.html</guid>
      <category>Programming</category>
      <description>&lt;p&gt;I have come to believe that by and large “best practices” are doing more harm
than good. Not necessarily because they’re bad advice as such, but because
they’re mostly pounded by either 1) various types of zealots, idiots, and
assholes who abuse these kind of “best practices” as an argument from authority,
or 2) inexperienced programmers who lack the ability to judge the applicability,&lt;/p&gt;

&lt;p&gt;Everyone else just says “X is better because Y”. This is an actual argument that
can be engaged with, and engaging with actual arguments is what discovers the
best solution for the situation at hand.&lt;/p&gt;

&lt;p&gt;This is not unique to programming; even the best of ideas becomes silly once you
start uncritically applying it to everything – one can find many examples in
politics. There, too, it’s mostly pounded by various types of zealots, idiots,
and assholes.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;All of this doesn’t mean that “best practice” are bad advice. Many are worth
reading, and some should always be followed. But many are little more “this
thing someone said” and/or “just someone’s opinion”.&lt;/p&gt;

&lt;p&gt;Take Postel’s “law” – what if I break this “law”? Will the police come and
arrest me? Will I get the Nobel prize in physics as I’ve discovered new laws of
nature? It’s not a “law” in any meaningful sense: it’s just a thing this Postel
guy said in 1980, which may or may not be applicable to whatever you’re doing.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Postel’s quip&lt;/em&gt; was probably good advice in 1980 in the context of TCP. There
were tons of little incompatibilities between existing implementations and
Postel was working on the first effort to actually standardize it all. Back then
it was typically harder to write fully correct implementations (no internet with
tons of examples, little to no testing, less access to other implementations,
etc.)&lt;/p&gt;

&lt;p&gt;There are other cases where it’s the reasonable and practical thing to do. But
as a general concept applied to all protocols or all software development I find
it’s mostly a bad idea. I’m hardly the first to critique it, and it’s been
controversial for decades.&lt;/p&gt;

&lt;p&gt;In spite of that, people citing &lt;em&gt;Postel’s quip&lt;/em&gt; as if it’s somehow a “law” are
still plentiful. My law is that everyone who does so is an idiot, asshole, or
some combination of both.&lt;/p&gt;

&lt;p&gt;There are many more examples:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;“Don’t use globals” is obviously good, but “never use globals under any
circumstances” is just silly. I’ve seen people throw a hissy fit over
“globals” in simple CLI tools with a few functions. Whoop-dee-doo.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Don’t use unstructured GOTO” has turned into “never use goto”, “don’t use
continue because it’s like a hidden goto”, and “use single point of exit”, and
that kind of absolute insane bollocks.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Don’t Repeat Yourself” (DRY) is basically good advice, but sometimes just
copy/pasting things is just the more pragmatic thing to do, and not really a
big deal.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I think “12 factor app” has some okay ideas, some dubious ideas, and some
outright bad ideas.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;While the basic ideas of “SOLID” are okay, I generally find that strong
adherence does not lead to particularly good code. It’s typically 80% fucking
about with &lt;em&gt;how&lt;/em&gt; to do things, and 20% actually doing the stuff you want to do
(which makes things harder to understand and change, not easier).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;etc. etc. etc.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can disagree with any of the above, and that’s fine. But citing “laws” or
“best practices” are just a fallacious arguments from authority.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;One of the difficulties with argueing against “best practices” is that it often
goes something like this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;em&gt;“X is not according to best practices”&lt;/em&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;em&gt;“I think X is better because Y”&lt;/em&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;em&gt;“But it’s not following best practice Z! Here is a book about it! Why don’t
you follow it?! Do you want to write bad code?!”&lt;/em&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is kind of a gaslighting effect here, because it’s very natural to assume
that maybe you &lt;em&gt;should&lt;/em&gt; be following the “best practice”, especially when
declared with great arrogance.&lt;/p&gt;

&lt;p&gt;This is the same fallacious argument people use for safety:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;em&gt;“I don’t think X will improve safety.”&lt;/em&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;em&gt;“What?! You don’t care about safety?!”&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;or: &lt;em&gt;“Better safe than sorry!”&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which is why safety regulation only grows and never shrinks, even when the
regulation just makes no sense at all: arguing against it is hard, because the
look of things is against you. I call this the &lt;em&gt;Safety Fallacy&lt;/em&gt;.&lt;/p&gt;
</description>
    </item>
    <item>
      <title>Jia Tanning Go code</title>
      <pubDate>Mon, 28 Oct 2024 00:00:00 +0000</pubDate>
      <link>https://www.arp242.net/jia-tan-go.html</link>
      <guid isPermaLink="true">https://www.arp242.net/jia-tan-go.html</guid>
      <category>Go</category>
      <category>Security</category>
      <description>&lt;p&gt;The Go compiler skips files ending with &lt;code&gt;_test.go&lt;/code&gt; during normal compilation.
They are compiled with &lt;code&gt;go test&lt;/code&gt; command (together will all other .go files),
which also inserts some chutney to run the test functions. The standard way to
do testing is to have a &lt;code&gt;foo.go&lt;/code&gt; and &lt;code&gt;foo_test.go&lt;/code&gt; file next to each other.&lt;/p&gt;

&lt;p&gt;If you have a file that appears to end with &lt;code&gt;_test.go&lt;/code&gt; but doesn’t actually end
with &lt;code&gt;_test.go&lt;/code&gt;, then it will get compiled for a regular build. For example:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;a_test\uFE0E.go
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;U+FE0E is a variation selector. These are typically very invisible. Or more
traditional homoglyph trickery:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;b_t\u0435\u0455t.go&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Those Cyrillic letters appear virtually identical on my machine: b_tеѕt.go /
b_test.go, or as monospace: &lt;code&gt;b_tеѕt.go&lt;/code&gt; / &lt;code&gt;b_test.go&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This can also be done with zero-width space, zero-width joiner, and perhaps a
few other codepoints, but variation selectors tend to be the “most invisible” of
them.&lt;/p&gt;

&lt;p&gt;This is pretty sneaky, something like this:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-go&quot;&gt;&lt;code&gt;// user.go
var bcryptCost = bcrypt.DefaultCost

func hashPassword(pwd []byte) []byte {
    pwd, _ := bcrypt.GenerateFromPassword(pwd, bcryptCost)
    return pwd
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-go&quot;&gt;&lt;code&gt;// user_test.go
func init() { // The special &quot;init&quot; function gets run on import.
    // Lower bcrypt cost in tests, because otherwise any test will take
    // well over a second as it&apos;s so slow.
    bcryptCost = bcrypt.MinCost
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Is perfectly valid, but with a doctored “non-test” &lt;code&gt;user_test.go&lt;/code&gt; it will now
lower the security of &lt;em&gt;all&lt;/em&gt; passwords, and effectively inserts a backdoor.&lt;/p&gt;

&lt;p&gt;There are many variants one can think of: code that looks innocent and would
probably pass many reviews, but will backdoor the codebase by weakening the
security, or adds in “test users”, “test keys”, “default passwords”, and things
like that.&lt;/p&gt;

&lt;p&gt;Who is carefully auditing tests for security in the first place? I’ve audited a
bunch of packages over the years, but I’ve never paid much attention to the test
files.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Most tools don’t display this: Vim, VSCode, macOS finder, Windows Explorer,
/bin/ls, GitHub, GitLab, etc. – they all just display &lt;code&gt;user_test.go&lt;/code&gt; with no
indication there’s something more there. Take a look at the &lt;a href=&quot;https://github.com/arp242/test&quot;&gt;test repo&lt;/a&gt;; all
seems fairly innocent. Arguably, that’s how it should be, at least for some of
these programs. Variation selectors are a Unicode feature and required to
display certain types of text. That said, filenames are not really “normal
text”, certainly not in the context of code.&lt;/p&gt;

&lt;p&gt;The major exception is the git CLI with &lt;code&gt;core.quotePath=true&lt;/code&gt; (the default),
which displays the byte sequences for anything that’s not ASCII. You need to
enable that setting to have &lt;em&gt;any&lt;/em&gt; non-ASCII path display correctly, and
depending on locality it’s probably a somewhat common setting. And how many
people use the git CLI to review files (instead of GitHub web UI, VSCode
integration, etc?) I’m a pretty heavy git CLI user, but typically don’t use “git
diff” or “git log” for viewing differences between releases, and even if I did,
there’s a good chance I’d miss this in a “git diff”.&lt;/p&gt;

&lt;p&gt;Another way to detect this is to carefully pay attention to the URL in e.g.
GitHub when viewing files, which displays escapes for some – but not all – of
the variants. But this isn’t displayed in the PR view, only when browsing files,
and is very easy to miss.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;I reported this to GitHub, GitLab, and BitBucket back in June. None of them
considered this to be a security issue. I don’t really understand why, because
it doesn’t seem too dissimilar to &lt;a href=&quot;https://github.com/nickboucher/trojan-source&quot;&gt;LTR trickery to hide code&lt;/a&gt;. Also GitHub will
display a warning for PRs with this sort of trickery:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;The head ref may contain hidden characters: &quot;a\uFE0Eb&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Crafted branch names is an issue but crafted files are not? Well okay 🤷&lt;/p&gt;

&lt;p&gt;Also emailed the security@golang.org, but they never got back to me, so I guess
they don’t consider it an issue either.&lt;/p&gt;

&lt;p&gt;Is it viable to do a real-world attack with this? I don’t know, I haven’t tried.
I am not employed at the University of Minnesota so I don’t go around sending
malicious patches just to see what would happen.&lt;/p&gt;

&lt;p&gt;But if I were tasked to Jia Tan a Go codebase, this seems a promising method.
There’s very little obfuscation and with a bit of careful design from a
malicious actor everything can seem legitimate test code that’s there for valid
reasons, being only malicious because it gets compiled in the regular program,
and because it still gets compiled in the test program the tests will still
work. Seems like a great way to hide malicious code in plain sight.&lt;/p&gt;

&lt;p&gt;Doing a full Jia Tan and compromising ssh is still tricky, because that requires
doing the sort of stuff that typically has no business being in a test file.
That’s why in xz it was hidden in binary test data. Still, with the right
project and a bit of creativity I could envision this as a step in a similar
scheme, especially when done by the maintainer itself.&lt;/p&gt;
</description>
    </item>
    <item>
      <title>Safe terminal escape codes</title>
      <pubDate>Wed, 22 May 2024 00:00:00 +0000</pubDate>
      <link>https://www.arp242.net/safeterm.html</link>
      <guid isPermaLink="true">https://www.arp242.net/safeterm.html</guid>
      <category>Unix</category>
      <description>&lt;style&gt;
th                         { text-align:left; }
tbody &gt;tr &gt;td:nth-child(1),
tbody &gt;tr &gt;td:nth-child(2) { font-family:monospace; white-space:pre; }
&lt;/style&gt;

&lt;p&gt;A long time ago when the Unix greybeards were slightly less grey beards everyone
was using hardware terminals to talk to some mainframe. Those terminals had
wildly different feature sets and ways to implement them, which you needed to
know about if you wanted your program to run on more than one type of terminal.
Hence systems like terminfo and termcap.&lt;/p&gt;

&lt;p&gt;The TeleVideo 955 used &lt;code&gt;\x1b[=5l&lt;/code&gt; for bold text, and the IBM 3161 used &lt;code&gt;\x1b4H&lt;/code&gt;.
Both were introduced in 1985. The TeleVideo 950 from 1980 didn’t support bold
text at all.&lt;/p&gt;

&lt;p&gt;Today everyone is using software terminal emulators, and they all just implement
ANSI escape codes for the common stuff. No one is sending &lt;code&gt;\x1b[=5l&lt;/code&gt; or
&lt;code&gt;\x1b4H&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Do you still need terminfo? It depends. If you just want to style some text and
maybe do some basic cursor operations: probably not. If you want to use more
advanced operations or read key input: probably yes. If you want maximum
compatibility (there’s probably someone using a hardware Wyse terminal, or SunOS
4 with CDE): absolutely.&lt;/p&gt;

&lt;p&gt;Here is a list of terminal escape sequences that should always work on any
vaguely modern terminal, where “modern” means “since the mid-90s or so”.&lt;/p&gt;

&lt;p&gt;This was generated by looking at every terminfo file I could find with my
&lt;a href=&quot;https://github.com/arp242/termfo&quot;&gt;termfo&lt;/a&gt; tool (specifically, &lt;code&gt;termfo find-cap&lt;/code&gt;) and occasionally testing some
things to verify it works. I don’t care what some spec says; I care about what
works.&lt;/p&gt;

&lt;p&gt;The escape character is always omitted; so &lt;code&gt;[0m&lt;/code&gt; is &lt;code&gt;\x1b[0m&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;graphics&quot;&gt;Graphics &lt;a href=&quot;#graphics&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;These can be combined in a single sequence by joining with a &lt;code&gt;;&lt;/code&gt;. For example
&lt;code&gt;\x1b[1;4m&lt;/code&gt; for underlined bold text. These can also be combined with colours;
for example &lt;code&gt;\x1b[1;38;2;0;255;0;41m&lt;/code&gt; to set bold, foreground colour with true
colour, and background colour with 16 colour.&lt;/p&gt;

&lt;p&gt;Just using &lt;code&gt;\x1b[1m\x1b[4m&lt;/code&gt; also works.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Code&lt;/th&gt;
      &lt;th&gt;Terminfo (short, long)&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;[0m&lt;/td&gt;
      &lt;td&gt;sgr0,  exit_attribute_mode&lt;/td&gt;
      &lt;td&gt;Turn off all attributes and colours.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[1m&lt;/td&gt;
      &lt;td&gt;bold,  enter_bold_mode&lt;/td&gt;
      &lt;td&gt;Enter bold mode.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[2m&lt;/td&gt;
      &lt;td&gt;dim,   enter_dim_mode&lt;/td&gt;
      &lt;td&gt;Enter half-bright mode.&lt;sup id=&quot;fnref:dim&quot;&gt;&lt;a href=&quot;#fn:dim&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[3m&lt;/td&gt;
      &lt;td&gt;sitm,  enter_italics_mode&lt;/td&gt;
      &lt;td&gt;Enter italic mode.&lt;sup id=&quot;fnref:sitm&quot;&gt;&lt;a href=&quot;#fn:sitm&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[4m&lt;/td&gt;
      &lt;td&gt;smul,  enter_underline_mode&lt;/td&gt;
      &lt;td&gt;Enter underline mode.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[5m&lt;/td&gt;
      &lt;td&gt;blink, enter_blink_mode&lt;/td&gt;
      &lt;td&gt;Enter blinking mode.&lt;sup id=&quot;fnref:blink&quot;&gt;&lt;a href=&quot;#fn:blink&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[7m&lt;/td&gt;
      &lt;td&gt;rev,   enter_reverse_mode&lt;/td&gt;
      &lt;td&gt;Enter reverse video mode.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[8m&lt;/td&gt;
      &lt;td&gt;invis, enter_secure_mode&lt;/td&gt;
      &lt;td&gt;Enter blank mode.&lt;sup id=&quot;fnref:invis&quot;&gt;&lt;a href=&quot;#fn:invis&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[9m&lt;/td&gt;
      &lt;td&gt;smxx&lt;/td&gt;
      &lt;td&gt;Enter strikeout mode.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Colours are set with &lt;code&gt;setab&lt;/code&gt; / &lt;code&gt;set_a_background&lt;/code&gt; and &lt;code&gt;setaf&lt;/code&gt; /
&lt;code&gt;set_a_foreground&lt;/code&gt;; the format depends on which colour scheme you want to use.&lt;/p&gt;

&lt;h3 id=&quot;16-colours&quot;&gt;16-colours &lt;a href=&quot;#16-colours&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Pretty much everything supports 16 colours, unless explicitly disabled by the
user. The exact shade is determined by the terminal.&lt;/p&gt;

&lt;p&gt;Escapes as «regular» «bright»:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Foreground&lt;/th&gt;
      &lt;th&gt;Background&lt;/th&gt;
      &lt;th&gt;Colour&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;[30m  [90m&lt;/td&gt;
      &lt;td&gt;[40m [100m&lt;/td&gt;
      &lt;td&gt;black&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[31m  [91m&lt;/td&gt;
      &lt;td&gt;[41m [101m&lt;/td&gt;
      &lt;td&gt;red&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[32m  [92m&lt;/td&gt;
      &lt;td&gt;[42m [102m&lt;/td&gt;
      &lt;td&gt;green&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[33m  [93m&lt;/td&gt;
      &lt;td&gt;[43m [103m&lt;/td&gt;
      &lt;td&gt;yellow&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[34m  [94m&lt;/td&gt;
      &lt;td&gt;[44m [104m&lt;/td&gt;
      &lt;td&gt;blue&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[35m  [95m&lt;/td&gt;
      &lt;td&gt;[45m [105m&lt;/td&gt;
      &lt;td&gt;magenta&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[36m  [96m&lt;/td&gt;
      &lt;td&gt;[46m [106m&lt;/td&gt;
      &lt;td&gt;cyan&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[37m  [97m&lt;/td&gt;
      &lt;td&gt;[47m [107m&lt;/td&gt;
      &lt;td&gt;white&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;256-colours&quot;&gt;256-colours &lt;a href=&quot;#256-colours&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Choose a colour from a &lt;a href=&quot;https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit&quot;&gt;pre-defined table of 256 colours&lt;/a&gt;. This is
supported on almost all current terminal emulators, unless explicitly disabled
by the user.&lt;/p&gt;

&lt;p&gt;Where «C» is a number from 0 to 255:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;[38;5;«C»m   Foreground
[48;5;«C»m   Background
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h3 id=&quot;true-colours&quot;&gt;True colours &lt;a href=&quot;#true-colours&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Use “true” RGB colours; this is supported on almost all current terminal
emulators, unless explicitly disabled by the user.&lt;/p&gt;

&lt;p&gt;Where «R», «G», «B» are the red, green and blue values as decimal, 0 to 255:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;[38;2;«R»;«G»;«B»m   Foreground
[48;2;«R»;«G»;«B»m   Background
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h2 id=&quot;cursor-movement&quot;&gt;Cursor movement &lt;a href=&quot;#cursor-movement&quot;&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Code&lt;/th&gt;
      &lt;th&gt;Terminfo (short, long)&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;[«R»;«C»H&lt;/td&gt;
      &lt;td&gt;cup,  cursor_address&lt;/td&gt;
      &lt;td&gt;Set cursor position to «R», «C».&lt;sup id=&quot;fnref:cup&quot;&gt;&lt;a href=&quot;#fn:cup&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[«N»A&lt;/td&gt;
      &lt;td&gt;cuu,  parm_up_cursor&lt;/td&gt;
      &lt;td&gt;Move «N» lines up.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[«N»B&lt;/td&gt;
      &lt;td&gt;cud,  parm_down_cursor&lt;/td&gt;
      &lt;td&gt;Move «N» lines down.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[«N»C&lt;/td&gt;
      &lt;td&gt;cuf,  parm_right_cursor&lt;/td&gt;
      &lt;td&gt;Move «N» characters to the right.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[«N»D&lt;/td&gt;
      &lt;td&gt;cub,  parm_left_cursor&lt;/td&gt;
      &lt;td&gt;Move «N» characters to the left.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;7&lt;/td&gt;
      &lt;td&gt;sc,   save_cursor&lt;/td&gt;
      &lt;td&gt;Save current cursor position.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;8&lt;/td&gt;
      &lt;td&gt;rc,   restore_cursor&lt;/td&gt;
      &lt;td&gt;Restore cursor to position of last save_cursor.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[6n&lt;/td&gt;
      &lt;td&gt;u7,   user7&lt;/td&gt;
      &lt;td&gt;Request cursor, returned as &lt;code&gt;\x1b[«R»;«C»R&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The «N» can be omitted in the above, in which case it will default to 1. For
&lt;code&gt;cup&lt;/code&gt; the «R» and/or «C» can be omitted, and will default to 1.&lt;/p&gt;

&lt;h2 id=&quot;inserting-and-deleting-text&quot;&gt;Inserting and deleting text &lt;a href=&quot;#inserting-and-deleting-text&quot;&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Code&lt;/th&gt;
      &lt;th&gt;Terminfo (short, long)&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;[J&lt;/td&gt;
      &lt;td&gt;ed,  clr_eos&lt;/td&gt;
      &lt;td&gt;Clear from cursor to end of screen.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[K&lt;/td&gt;
      &lt;td&gt;el,  clr_eol&lt;/td&gt;
      &lt;td&gt;Clear from cursor to end of line.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[1K&lt;/td&gt;
      &lt;td&gt;el1, clr_bol&lt;/td&gt;
      &lt;td&gt;Clear from cursor to beginning of line.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[«N»M&lt;/td&gt;
      &lt;td&gt;dl,  parm_delete_line&lt;/td&gt;
      &lt;td&gt;Delete «N» lines, moving all the lines below up.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[«N»P&lt;/td&gt;
      &lt;td&gt;dch, parm_dch&lt;/td&gt;
      &lt;td&gt;Delete «N» characters, moving all afterwards left.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[«N»L&lt;/td&gt;
      &lt;td&gt;il,  parm_insert_line&lt;/td&gt;
      &lt;td&gt;Insert «N» lines, moving all lines below down.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[«N»@&lt;/td&gt;
      &lt;td&gt;ich, parm_ich&lt;/td&gt;
      &lt;td&gt;Insert «N» characters, moving all after right.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The «N» can be omitted in the above, in which case it will default to 1.&lt;/p&gt;

&lt;h2 id=&quot;misc&quot;&gt;Misc &lt;a href=&quot;#misc&quot;&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Code&lt;/th&gt;
      &lt;th&gt;Terminfo (short, long)&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;[?25l&lt;/td&gt;
      &lt;td&gt;civis, cursor_invisible&lt;/td&gt;
      &lt;td&gt;Make cursor invisible.&lt;sup id=&quot;fnref:civis&quot;&gt;&lt;a href=&quot;#fn:civis&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[?25h&lt;/td&gt;
      &lt;td&gt;cnorm, cursor_normal&lt;/td&gt;
      &lt;td&gt;Make cursor appear normal (undo civis).&lt;sup id=&quot;fnref:cnorm&quot;&gt;&lt;a href=&quot;#fn:cnorm&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;keys&quot;&gt;Keys &lt;a href=&quot;#keys&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Key input handling is kind of a mess, and this is where I really recommend using
a terminfo library if at all possible. This also avoids the whole
backspace/delete key confusion (less of an issue today than it used to be, but
still exists).&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Codes&lt;/th&gt;
      &lt;th&gt;terminfo (short, long)&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;[A OA&lt;/td&gt;
      &lt;td&gt;kcuu1 key_up&lt;/td&gt;
      &lt;td&gt;Arrow up&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[B OB&lt;/td&gt;
      &lt;td&gt;kcud1 key_down&lt;/td&gt;
      &lt;td&gt;Arrow down&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[C OC&lt;/td&gt;
      &lt;td&gt;kcuf1 key_right&lt;/td&gt;
      &lt;td&gt;Arrow right&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[D OD&lt;/td&gt;
      &lt;td&gt;kcub1 key_left&lt;/td&gt;
      &lt;td&gt;Arrow left&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[5~ [I&lt;/td&gt;
      &lt;td&gt;kpp key_ppage&lt;/td&gt;
      &lt;td&gt;PageUp&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[6~ [G&lt;/td&gt;
      &lt;td&gt;knp key_npage&lt;/td&gt;
      &lt;td&gt;PageDown&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[2~ [L&lt;/td&gt;
      &lt;td&gt;kich1 key_ic&lt;/td&gt;
      &lt;td&gt;Insert key&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[1~ [H OH [7~&lt;/td&gt;
      &lt;td&gt;khome key_home&lt;/td&gt;
      &lt;td&gt;Home key&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[4~ [F OF [8~&lt;/td&gt;
      &lt;td&gt;kend key_end&lt;/td&gt;
      &lt;td&gt;End key&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[11~ [M [[A OP&lt;/td&gt;
      &lt;td&gt;key_f1&lt;/td&gt;
      &lt;td&gt;F1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[12~ [N [[B OQ&lt;/td&gt;
      &lt;td&gt;key_f2&lt;/td&gt;
      &lt;td&gt;F2&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[13~ [O [[C OR&lt;/td&gt;
      &lt;td&gt;key_f3&lt;/td&gt;
      &lt;td&gt;F3&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[14~ [P [[D OS&lt;/td&gt;
      &lt;td&gt;key_f4&lt;/td&gt;
      &lt;td&gt;F4&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[15~ [Q [[E&lt;/td&gt;
      &lt;td&gt;key_f5&lt;/td&gt;
      &lt;td&gt;F5&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[17~ [R&lt;/td&gt;
      &lt;td&gt;key_f6&lt;/td&gt;
      &lt;td&gt;F6&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[18~ [S&lt;/td&gt;
      &lt;td&gt;key_f7&lt;/td&gt;
      &lt;td&gt;F7&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[19~ [T&lt;/td&gt;
      &lt;td&gt;key_f8&lt;/td&gt;
      &lt;td&gt;F8&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[20~ [U&lt;/td&gt;
      &lt;td&gt;key_f9&lt;/td&gt;
      &lt;td&gt;F9&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[21~ [V&lt;/td&gt;
      &lt;td&gt;key_f10&lt;/td&gt;
      &lt;td&gt;F10&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[23~ [W&lt;/td&gt;
      &lt;td&gt;key_f11&lt;/td&gt;
      &lt;td&gt;F11&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;[24~ [X&lt;/td&gt;
      &lt;td&gt;key_f12&lt;/td&gt;
      &lt;td&gt;F12&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;\x7f \b&lt;/td&gt;
      &lt;td&gt;kbs key_backspace&lt;/td&gt;
      &lt;td&gt;Backspace key&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;\x7f [3~&lt;/td&gt;
      &lt;td&gt;kdch1 key_dc key_delete&lt;/td&gt;
      &lt;td&gt;Delete key; &lt;em&gt;usually&lt;/em&gt; sends [3~, but some send \x7f&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;\x09&lt;/td&gt;
      &lt;td&gt;-&lt;/td&gt;
      &lt;td&gt;Tab key&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;\x0d&lt;/td&gt;
      &lt;td&gt;-&lt;/td&gt;
      &lt;td&gt;Enter key&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;div class=&quot;postscript&quot;&gt;&lt;strong&gt;Elsewhere on the interwebs&lt;/strong&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/termstandard/colors&quot;&gt;github.com/termstandard/colors&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;postscript&quot; role=&quot;doc-endnotes&quot;&gt;&lt;strong&gt;Footnotes&lt;/strong&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:dim&quot;&gt;
      &lt;p&gt;Except FreeBSD system console where it’s &lt;code&gt;[30;1m&lt;/code&gt;. &lt;a href=&quot;#fnref:dim&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:sitm&quot;&gt;
      &lt;p&gt;Not super-widely supported, sometimes displays as reverse. &lt;a href=&quot;#fnref:sitm&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:blink&quot;&gt;
      &lt;p&gt;Often doesn’t do anything (because it’s annoying). &lt;a href=&quot;#fnref:blink&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:invis&quot;&gt;
      &lt;p&gt;Characters can still be copy/pasted. &lt;a href=&quot;#fnref:invis&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:cup&quot;&gt;
      &lt;p&gt;Top-left is 1;1 &lt;a href=&quot;#fnref:cup&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:civis&quot;&gt;
      &lt;p&gt;mtm terminfo doesn’t include the question mark, and Linux console adds another escape (&lt;code&gt;\x1b[?1c&lt;/code&gt;). But for both this also works. &lt;a href=&quot;#fnref:civis&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:cnorm&quot;&gt;
      &lt;p&gt;Has &lt;code&gt;[?12l&lt;/code&gt; for some, but works without that on all; for FreeBSD terminfo has &lt;code&gt;[=0C&lt;/code&gt; but that doesn’t work (&lt;code&gt;[?25h&lt;/code&gt; does) &lt;a href=&quot;#fnref:cnorm&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    <item>
      <title>s/bash/zsh/g</title>
      <pubDate>Wed, 20 Oct 2021 00:00:00 +0000</pubDate>
      <link>https://www.arp242.net/why-zsh.html</link>
      <guid isPermaLink="true">https://www.arp242.net/why-zsh.html</guid>
      <category>zsh</category>
      <category>Unix</category>
      <description>&lt;p&gt;You would expect this to work, no?&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;bash% echo $(( .1 + .2 ))
bash: .1 + .2 : syntax error: operand expected (error token is &quot;.1 + .2 &quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Well, bash says no, but zsh just works:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;zsh% echo $(( .1 + .2 ))
0.30000000000000004      # Well, &quot;works&quot; insofar IEEE-754 works.
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;There is simply no way you can do calculations with fractions in bash without
relying on &lt;code&gt;bc&lt;/code&gt;, &lt;code&gt;dc&lt;/code&gt;, or some hacks. Compared to simply being able to use &lt;code&gt;a +
b&lt;/code&gt; it’s ugly, slow, and difficult.&lt;/p&gt;

&lt;p&gt;There are other pretty frustrating omissions in bash; NUL bytes is another fun
one:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;zsh% x=$(printf &apos;N\x00L&apos;); printf $x | xxd -g1 -c3
00000000: 4e 00 4c  N.L

bash% x=$(printf &apos;N\x00L&apos;); printf $x | xxd -g1 -c3
bash: warning: command substitution: ignored null byte in input
00000000: 4e 4c     NL
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;It looks like bash added a warning recently-ish (&lt;a href=&quot;https://github.com/bminor/bash/commit/280bd77d8d3e7f7c90c9fa07de3d1e8f8e18ac29&quot;&gt;4.4-patch 2&lt;/a&gt;); this one
bit me pretty hard a few years ago; back then it would just get silently
discarded without warning; I guess a warning is an “improvement” of sorts
(fixing it, however, would be an actual improvement&lt;sup id=&quot;fnref:n&quot;&gt;&lt;a href=&quot;#fn:n&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;).&lt;/p&gt;

&lt;p&gt;NUL bytes aren’t &lt;em&gt;that&lt;/em&gt; uncommon, think of e.g. &lt;code&gt;find -print0&lt;/code&gt;, &lt;code&gt;xargs -0&lt;/code&gt;, etc.
That all works grand, right up to the point you try to assign it to a variable.
You can use NUL bytes for array assignments though, if you evoke the right
incantation:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;bash% read -rad arr &amp;lt; &amp;lt;(find . -type f -print0)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;There are all sorts of edge-cases where you need to resort to &lt;code&gt;read&lt;/code&gt; or
&lt;code&gt;readarray&lt;/code&gt; rather than being able to just assign in. In zsh it’s:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;zsh% arr=(**/*(.))

zsh% IFS=&apos;\x00&apos; arr=($(find . -type f -print0)) # If you must use find (rarely needed)

zsh% arr=( &quot;${(0)$(find . -type f -print0)}&quot; )
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;That &lt;code&gt;(.)&lt;/code&gt; is a “glob qualifier” to include only regular files – more on that
later.&lt;/p&gt;

&lt;p&gt;Don’t even think of doing something like:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-zsh&quot;&gt;&lt;code&gt;img=$(curl https://example.com/image.png)
if [[ $cond ]]; then
    optpng &amp;lt;&amp;lt;&amp;lt;&quot;$img&quot; &amp;gt; out.png
else
    cat &amp;lt;&amp;lt;&amp;lt;&quot;$img&quot; &amp;gt; out.png
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Of course you can refactor this to avoid the variable (and the example is a bit
contrived), but it really ought to work. I once wrote a script to import emails
from the Mailgun API. It worked great, yet sometimes images were mangled and I
just couldn’t figure out why. Turns out Mailgun “helpfully” decodes attachments
(i.e. removes the base64) and sends binary content, which bash (at the time)
would silently discard. It took me a very long time to figure out. I forgot why,
but it was hard to avoid storing the response in a variable. I ended up
rewriting it to Python because of this, which was just a waste of time really.
It’s this, specifically, that really soured me on bash and prompted &lt;a href=&quot;/shell-scripting-trap.html&quot;&gt;The shell
scripting trap&lt;/a&gt; in 2017. However, many items listed there are solved by
zsh, including this one.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;zsh also fixes &lt;em&gt;most&lt;/em&gt; of the quoting:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;zsh% for f in *; ls -l $f
-rw-rw-r-- 1 martin martin 0 Oct 19 06:51 asd.txt
-rw-rw-r-- 1 martin martin 0 Oct 19 06:51 with space.txt

bash% for f in *; do ls -l $f; done
-rw-rw-r-- 1 martin martin 0 Oct 19 06:51 asd.txt
ls: cannot access &apos;with&apos;: No such file or directory
ls: cannot access &apos;space.txt&apos;: No such file or directory
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;It’s not POSIX compatible, but who cares? bash doesn’t &lt;a href=&quot;https://tiswww.case.edu/php/chet/bash/POSIX&quot;&gt;follow POSIX in all
sorts of ways by default&lt;/a&gt; either because it just makes more sense, and with
both you can still tell them to be POSIX-compatible if you must for one reason
or the other.&lt;/p&gt;

&lt;p&gt;Also note the convenient short version of the
&lt;code&gt;for&lt;/code&gt; loop: no need for &lt;code&gt;do&lt;/code&gt;, &lt;code&gt;done&lt;/code&gt; and muckery with &lt;code&gt;;&lt;/code&gt; before the &lt;code&gt;done&lt;/code&gt;,
which is much more convenient for quick one-liners you interactively type in.
You can still do word splitting, but you need to do it explicitly:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;zsh% for i in *; ls -l $=i
-rw-rw-r-- 1 martin martin 0 Oct 19 06:51 asd.txt
ls: cannot access &apos;with&apos;: No such file or directory
ls: cannot access &apos;space.txt&apos;: No such file or directory
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&lt;code&gt;[[&lt;/code&gt; is supposed to fix &lt;code&gt;[&lt;/code&gt;, but it still has weird quoting quirks:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;zsh% a=foo; b=*;
zsh% if [[ $a = $b ]]; then
       print &apos;Equal!&apos;
     else
       print &apos;Nope!&apos;
     fi
Nope!

bash% a=foo; b=*
bash% if [[ $a = $b ]]; then
        echo &apos;Equal!&apos;
      else
        echo &apos;Nope!&apos;
      fi
Equal!
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;This is equal because without quotes it still interprets the right-hand side as
a pattern (i.e. glob). In zsh you need to use &lt;code&gt;$~var&lt;/code&gt; to explicitly enable
pattern matching, which is a much better model than remembering when you do and
don’t need to quote things – sometimes you &lt;em&gt;do&lt;/em&gt; want the pattern matching and
then you don’t want quotes; it’s not always immediately obvious if an &lt;code&gt;if [[ ...&lt;/code&gt;
statement is correct if it’s lacking quotes.&lt;/p&gt;

&lt;p&gt;“But Martin, you should always quote things, you’re being disingenuous!” Well, I
could make a comfortable living if I were paid to add quotes to other people’s
shell scripts. Telling people to “always quote things” is what we’ve been doing
for 40 years now and irrefutable observational evidence has demonstrated that it
&lt;em&gt;just does not work&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Most people aren’t shell scripting wizards; they make a living writing Python or
C or Go or PHP programs, or maybe they’re sysadmins or scientists, and oh,
occasionally they also write a shell script. They just see something that works
and assume it has sane behaviour and don’t realize the subtle differences
between &lt;code&gt;$@&lt;/code&gt;, &lt;code&gt;$*&lt;/code&gt;, and &lt;code&gt;&quot;$@&quot;&lt;/code&gt;. I think that’s actually quite reasonable,
because the behaviour is odd, surprising, and confusing.&lt;/p&gt;

&lt;p&gt;It’s also a lot more complex than just “quote your variables”, especially if you
use &lt;code&gt;$(..)&lt;/code&gt; since command substitution often needs quotes too, as well as any
variables inside it. Before you know it you’ve got double, triple, or more
levels of nested quotes and if you forget one set of them you’re in trouble.&lt;/p&gt;

&lt;p&gt;It’s such a fertile source of real-world bugs that it would merit entomologist
examination. If there’s a system that causes this many bugs then that system is
at fault, and not the people using it. Computers and software should adapt to
humans, not the other way around.&lt;/p&gt;

&lt;p&gt;And “always quote things!” isn’t even right either, because you should always
quote things except when you shouldn’t:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;zsh% a=foo; b=.*;
zsh% if [[ &quot;$a&quot; =~ &quot;$b&quot; ]]; then
       print &apos;Equal!&apos;
     else
       print &apos;Nope!&apos;
     fi
Equal!

bash% a=foo; b=.*
bash% if [[ &quot;$a&quot; =~ &quot;$b&quot; ]]; then
        echo &apos;Equal!&apos;
      else
        echo &apos;Nope!&apos;
      fi
Nope!
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;If there are quotes around a regexp then it’s treated as a literal string. I
mean, it’s consistent with &lt;code&gt;=&lt;/code&gt; pattern matching, but also confusing because I
explicitly use &lt;code&gt;=~&lt;/code&gt; to match a regular expression.&lt;/p&gt;

&lt;p&gt;Another famous quoting trap is &lt;code&gt;$@&lt;/code&gt; vs. &lt;code&gt;&quot;$@&quot;&lt;/code&gt; vs. &lt;code&gt;$*&lt;/code&gt; vs. &lt;code&gt;&quot;$*&quot;&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;zsh% cat args
echo &quot;unquoted @:&quot;
for a in $@; do echo &quot;  =&amp;gt; $a&quot;; done

echo &quot;quoted @:&quot;
for a in &quot;$@&quot;; do echo &quot;  =&amp;gt; $a&quot;; done

echo &quot;quoted *:&quot;
for a in $*; do echo &quot;  =&amp;gt; $a&quot;; done

echo &quot;quoted *:&quot;
for a in &quot;$*&quot;; do echo &quot;  =&amp;gt; $a&quot;; done
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;!-- --&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;bash% bash args hello world &apos;test space&apos; &apos;*&apos;
unquoted @:
  =&amp;gt; hello
  =&amp;gt; world
  =&amp;gt; test
  =&amp;gt; space
  =&amp;gt; Guust1129.jpg
  =&amp;gt; IEEESTD.2019.8766229.pdf
  [.. rest of my $HOME ..]
quoted @:
  =&amp;gt; hello
  =&amp;gt; world
  =&amp;gt; test space
  =&amp;gt; *
unquoted *:
  =&amp;gt; hello
  =&amp;gt; world
  =&amp;gt; test
  =&amp;gt; space
  =&amp;gt; Guust1129.jpg
  =&amp;gt; IEEESTD.2019.8766229.pdf
  [.. rest of my $HOME ..]
quoted *:
  =&amp;gt; hello world test space *
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Experiences shellers will know to (almost) always use &lt;code&gt;&quot;$@&quot;&lt;/code&gt;, but how often do
you see it being done wrong? It’s not that strange people do it wrong either; if
you learned about quoting and word splitting then &lt;code&gt;$@&lt;/code&gt; without quotes is
actually the &lt;em&gt;logical&lt;/em&gt; thing to use because you would expect &lt;code&gt;&quot;$@&quot;&lt;/code&gt; to be
treated as one argument (as &lt;code&gt;&quot;$*&quot;&lt;/code&gt;). You tell people to “always add quotes to
prevent word splitting and treat things as a single argument”, and then you tell
them “oh, except in this one special case when the addition of quotes &lt;em&gt;invokes&lt;/em&gt;
a special kind of splitting and doesn’t follow any of the rules we previously
told you about”.&lt;/p&gt;

&lt;p&gt;In zsh, &lt;code&gt;$@&lt;/code&gt; and &lt;code&gt;$*&lt;/code&gt; (and &lt;code&gt;$argv&lt;/code&gt;) are all (read-only) arrays and it all
behaves identical as you would expect with no surprises:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;zsh% zsh args hello world &apos;test space&apos; &apos;*&apos;
unquoted @:
  =&amp;gt; hello
  =&amp;gt; world
  =&amp;gt; test space
  =&amp;gt; *
quoted @:
  =&amp;gt; hello
  =&amp;gt; world
  =&amp;gt; test space
  =&amp;gt; *
unquoted *:
  =&amp;gt; hello
  =&amp;gt; world
  =&amp;gt; test space
  =&amp;gt; *
quoted *:
  =&amp;gt; hello world test space *
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Actually in bash you can do &lt;code&gt;argv=(&quot;$@&quot;)&lt;/code&gt; and then you have an array. This is
really how it should work by default.&lt;/p&gt;

&lt;p&gt;You still need to loop over it with:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;bash% for a in &quot;${argv[@]}&quot;; do
        echo &quot;=&amp;gt; $a&quot;
      done
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Rather than just &lt;code&gt;for a in $argv&lt;/code&gt; like in zsh. Aside from the pointless &lt;code&gt;[@]&lt;/code&gt;,
why would you ever want to word-split every element of an array? There is
probably some use case somewhere, but it’s exceedingly rare. Better to just skip
all of that by default unless explicitly enabled with &lt;code&gt;=&lt;/code&gt; and/or &lt;code&gt;~&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Oh, here’s another interesting tidbit:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;zsh% n=3; for i in {1..$n}; print $i
1
2
3

bash% n=3; for i in {1..$n}; do echo &quot;$i&quot;; done
{1..3}

bash% n=3; for i in {1..3}; do echo &quot;$i&quot;; done
1
2
3
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Why does it work like that? That’s left as an exercise for the reader ;-)&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Aside from all sorts caveats that are handled much better, a lot of common
things are just much easier in zsh:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;zsh% arr=(123 5 1 9)
zsh% echo ${(o)arr}     # Lexical order
1 123 5 9
zsh% echo ${(on)arr}    # Numeric order
1 5 9 123

bash% IFS=$&apos;\n&apos;; echo &quot;$(sort &amp;lt;&amp;lt;&amp;lt;&quot;${arr[*]}&quot;)&quot;; unset IFS
1 123 5 9
bash% IFS=$&apos;\n&apos;; echo &quot;$(sort -n &amp;lt;&amp;lt;&amp;lt;&quot;${arr[*]}&quot;)&quot;; unset IFS
1 5 9 123
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;I had to look up how to do it in bash; the Stack Overflow answer for that one
starts with “you don’t really need all that much code”. lol? I guess that’s in
reply to some of the other horrendously complex answers which implement “pure
bash” sorting algorithms and the like. I guess compared to that this is “not
that much code”. And of course the entire thing is a minefield of expansion
again; and if you forget a set of nested quotes you end up in trouble.&lt;/p&gt;

&lt;p&gt;Arrays in general are just awkward in bash:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;bash% arr=(first second third fourth)

bash% echo ${arr[0]}
first
bash% echo ${arr[@]::2}
first second
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;I mean, it works, and it’s not that much typing, but why do I need that &lt;code&gt;[@]&lt;/code&gt;?
Probably some (historical) reason, but zsh implements it much more readable and
easier:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;zsh% arr=(first second third fourth)

zsh% print ${arr[1]}        # Yeah, it&apos;s 1-based. Deal with it.
first
zsh% print ${arr[1,2]}
first second
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;The bash array syntax was copied from ksh; so I guess we have to blame David
Korn (zsh supports it too, if you must use it). But regular subscripts are just
so much easier.&lt;/p&gt;

&lt;p&gt;And then there’s the useful features:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;zsh% ls *.go
format.go  format_test.go  gen.go  old.go  uni.go  uni_test.go

zsh% ls *.go~*_test.go
format.go  gen.go  old.go  uni.go

zsh% ls *.go~*_test.go~f*
gen.go  old.go  uni.go
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&lt;code&gt;*.go&lt;/code&gt; gets expanded, and filters the pattern after the &lt;code&gt;~&lt;/code&gt;; &lt;code&gt;*_test.go&lt;/code&gt; in this
case. Looks a bit obscure at first glance, but bash’s ksh-style extglobs are far
harder:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;bash% ls !(*_test).go
format.go  gen.go  old.go  uni.go

bash% ls !(*_test|f*).go
gen.go  old.go  uni.go
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&lt;code&gt;!(..)&lt;/code&gt; is “match anything except the pattern”; the &lt;code&gt;*&lt;/code&gt; is implied here (zsh
supports &lt;code&gt;!(..)&lt;/code&gt; if you set &lt;code&gt;ksh_glob&lt;/code&gt;). While it works, the the
&lt;code&gt;pattern~filter~filter&lt;/code&gt; model is much easier, and also more flexible since you
don’t need to start with all matches.&lt;/p&gt;

&lt;p&gt;There are many useful things you can do with globbing; you can replace many uses
of &lt;code&gt;find&lt;/code&gt; with it, and you don’t need to worry about the caveats, &lt;code&gt;-print0&lt;/code&gt;
hacks, etc. For example to recursively list all regular files:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;zsh% ls **/*(.)
LICENSE         go.sum           unidata/gen.go             wasm/make*
README.md       old.go           unidata/gen_codepoints.go  wasm/srv*
[..]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Or directories:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;zsh% ls -d /etc/**/*(/)
/etc/OpenCL/                      /etc/runit/runsvdir/default/dnscrypt-proxy/log/
/etc/OpenCL/vendors/              /etc/runit/runsvdir/default/ntpd/log/
/etc/X11/                         /etc/runit/runsvdir/default/postgresql/supervise/
[..]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Or files that were changed in the last week:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;zsh% ls -l /etc/***(.m-7)    # *** is a shortcut for **/*; needs GLOB_STAR_SHORT
-rw-r--r-- 1 root root 28099 Oct 13 03:47 /etc/dnscrypt-proxy.toml.new-2.1.1_1
-rw-r--r-- 1 root root    97 Oct 13 03:47 /etc/environment
-rw-r--r-- 1 root root 37109 Oct 17 10:34 /etc/ld.so.cache
-rw-r--r-- 1 root root 77941 Oct 19 01:01 /etc/public-resolvers.md
-rw-r--r-- 1 root root  6011 Oct 19 01:01 /etc/relays.md
-rw-r--r-- 1 root root   142 Oct 19 07:57 /etc/shells
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;You can even order them by modified date with &lt;code&gt;om&lt;/code&gt; (&lt;code&gt;***(.m-7om)&lt;/code&gt;), although
that’s a bit pointless here as &lt;code&gt;ls&lt;/code&gt; will reorder them again, but if you’re
looping over files it’s useful.&lt;/p&gt;

&lt;p&gt;There is no way to do any of this in bash, you’ll have to use something like:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;bash% find /etc -type f -mtime 7 -exec ls -l {} +
find: ‘/etc/sv/docker/supervise’: Permission denied
find: ‘/etc/sv/docker/log/supervise’: Permission denied
find: ‘/etc/sv/bluetoothd/log/supervise’: Permission denied
find: ‘/etc/sv/postgresql/supervise’: Permission denied
find: ‘/etc/sv/runsvdir-martin/supervise’: Permission denied
find: ‘/etc/wpa_supplicant/supervise’: Permission denied
find: ‘/etc/lvm/cache’: Permission denied
-rw-rw-r-- 1 root root  167 Oct 12 22:17 /etc/default/postgresql
-rw-r--r-- 1 root root  817 Oct 12 09:11 /etc/fstab
-rw-r--r-- 1 root root 1398 Oct 12 22:19 /etc/passwd
-rw-r--r-- 1 root root 1397 Oct 12 22:19 /etc/passwd.OLD
-rw-r--r-- 1 root root  307 Oct 12 23:10 /etc/public-resolvers.md.minisig
-rw-r--r-- 1 root root  297 Oct 12 23:10 /etc/relays.md.minisig
-r-------- 1 root root  932 Oct 12 09:57 /etc/shadow
-rwxrwxr-x 1 root root  397 Oct 12 22:23 /etc/sv/postgresql/run
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Not sure how to make it ignore these errors too without redirecting stderr (more
typing!) And if you think adding single letters in &lt;code&gt;(..)&lt;/code&gt; after a pattern is
hard then try understanding &lt;code&gt;find&lt;/code&gt;’s weird flag syntax. Glob qualifies are
great.&lt;/p&gt;

&lt;p&gt;csh-style parameter substitution is pretty useful:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;zsh% for f in ~/photos/*.png; convert $f ${f:t:r}.jpeg
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&lt;code&gt;:t&lt;/code&gt; to get the tail, and &lt;code&gt;:r&lt;/code&gt; to get the root (without extension). csh could do
this before I was even born, but bash can’t (it can for history expansion, but
not variables). According to the bash FAQ “Posix has specified a more powerful,
albeit somewhat more cryptic, mechanism cribbed from ksh”, which I find a
somewhat curious statement as the above in bash is:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;bash% for f in ~/photos/*.png; do convert &quot;$f&quot; &quot;$(basename &quot;${f%%.*}&quot;)&quot;; done
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Technically “more powerful” in the sense that you can do other things with it,
but not really “more useful for common operations” (zsh, of course, implements
&lt;code&gt;%&lt;/code&gt; and &lt;code&gt;#&lt;/code&gt; as well).&lt;/p&gt;

&lt;p&gt;Note you can’t nest &lt;code&gt;${..}&lt;/code&gt; in bash; e.g. &lt;code&gt;&quot;${${f%%.*}##*/}&quot;&lt;/code&gt; is an error:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;zsh% f=~/asd.png; print &quot;${${f%%.*}##*/}&quot;
asd

bash% f=~/a.png; echo &quot;${${f%%.*}##*/}&quot;
bash: ${${f%%.*}##*/}: bad substitution
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;While this can quickly lead to very unreadable ASCII vomit, it’s useful on
occasion, when used with care and wisdom. You can click below for a more
advanced example if the children are already in bed.&lt;/p&gt;

&lt;div id=&quot;nsfw&quot;&gt;
&lt;a href=&quot;#&quot;&gt;Click to see NSFW content. &lt;strong&gt;Not suitable for children under 18!&lt;/strong&gt;&lt;/a&gt;
&lt;p&gt;For example, this can be used to show the longest element in an array:&lt;/p&gt;
&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;print ${array[(r)${(l.${#${(O@)array//?/X}[1]}..?.)}]}&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Cribbed from the &lt;a href=&quot;https://zsh.sourceforge.io/Guide/zshguide05.html&quot;&gt;zsh User&apos;s Guide&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;

&lt;style&gt;
    #nsfw { text-align: center; }
    #nsfw pre, #nsfw p { display: none; }
&lt;/style&gt;

&lt;script&gt;
nsfw.onclick = (e) =&gt; {
    e.preventDefault()
    nsfw.querySelectorAll(&apos;pre,p&apos;).forEach((e) =&gt; e.style.display = &apos;block&apos;)
} 
&lt;/script&gt;

&lt;hr /&gt;

&lt;p&gt;There are many more things. I’m not going to list them all here. None of this is
new; much (if not all?) of this has around for 20 years, if not longer. I don’t
know why bash is the de-facto default, or why people spend time on complex
solutions to work around bash problems when zsh solves them. I guess because
Linux used a lot of GNU stuff and bash was came with it, and GNU stuff was (and
is) using bash. Not a very good reason, certainly not one 30 years later.&lt;/p&gt;

&lt;p&gt;zsh still has plenty of limitations; the syntax isn’t always something you’d
want to show your mother for starters, as well as a number of other things.
Still, it’s &lt;em&gt;clearly&lt;/em&gt; better. I genuinely can’t find a single thing bash does
better beyond “it’s installed on many systems already”.&lt;/p&gt;

&lt;p&gt;Ubiquitousness is overrated anyway; zsh has no dependencies beyond libc and
curses and is 970K on my system&lt;sup id=&quot;fnref:size&quot;&gt;&lt;a href=&quot;#fn:size&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; and is available for pretty much all
systems. Compared to most other interpreters it’s tiny, with only Lua being
smaller (275K). “Stick to POSIX sh for compatibility” was good advice in 1990
when you had some SunOS system with some sun-sh and that’s what you were stuck
with. Those days are long gone, and while there are a few poor souls still suck
on those systems (sometimes even with csh!) chances are they’re not going to try
and run your Docker or Arch Linux shell script or whatnot on those systems
anyway.&lt;/p&gt;

&lt;p&gt;Contorting yourself in all sorts of strange bends to perhaps possibly maybe make
it work for a tiny fraction of users who are unlikely to use your script anyway
does not seem like a good trade-off, especially since these kind of limitations
tend to be organisational rather than technical, which is not my problem anyway
to be honest.&lt;/p&gt;

&lt;p&gt;Using zsh is also &lt;em&gt;more&lt;/em&gt; portable; since it allows you to avoid many shell tools
and the (potential) incompatibilities, and by explicitly setting  &lt;code&gt;zsh&lt;/code&gt; as the
interpreter you can rely on zsh behaviour, rather than hoping the &lt;code&gt;/bin/sh&lt;/code&gt; on
$random_system behaves the same (even dash has some extensions to POSIX sh, such
as &lt;code&gt;local&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;What I typically do is save files as &lt;code&gt;script.zsh&lt;/code&gt; with:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env zsh
[ &quot;${ZSH_VERSION:-}&quot; = &quot;&quot; ] &amp;amp;&amp;amp; echo &amp;gt;&amp;amp;2 &quot;Only works with zsh&quot; &amp;amp;&amp;amp; exit 1
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;This makes sure it gets run by zsh when used as &lt;code&gt;./script.zsh&lt;/code&gt;, and gives an
error in case people type &lt;code&gt;sh script.zsh&lt;/code&gt; or &lt;code&gt;bash script.zsh&lt;/code&gt; in case the .zsh
extension isn’t enough of a clue.&lt;/p&gt;

&lt;p&gt;So in conclusion: &lt;code&gt;s/bash/zsh/g&lt;/code&gt; and everything will be just a little bit
better.&lt;/p&gt;

&lt;p style=&quot;font-size: .9rem; font-style: italic&quot;&gt;
P.S. maybe fish is even better by the way, but I could never get over the bright
colouring and all these things popping in and out of my screen; it&apos;s such a
&quot;busy&quot; chaotic experience! I&apos;d have to spend time disabling all that stuff to
make it usable for me, and I never bothered – and if you&apos;re disabling the key
selling points of a program then it&apos;s probably not intended for you anyway.
Maybe I&apos;ll have a crack at it at some point though.
&lt;/p&gt;
&lt;div class=&quot;postscript&quot; role=&quot;doc-endnotes&quot;&gt;&lt;strong&gt;Footnotes&lt;/strong&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:n&quot;&gt;
      &lt;p&gt;Which I assume isn’t so easy, otherwise it would have been done already.
  The reason it doesn’t work is an artifact from C’s NUL-terminated strings,
  but this kind of stuff really shouldn’t be exposed in a high-level language
  like the shell. It’s also a bit ironic since one of Stephen Bourne’s
  original goals with his shell was to get rid of arbitrary size limits on
  strings, which were common at the time. &lt;a href=&quot;#fnref:n&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:size&quot;&gt;
      &lt;p&gt;A full install if ~8M, mostly in the optional completion functions it
     ships with. Bash is about 1.3M by the way. &lt;a href=&quot;#fnref:size&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    <item>
      <title>Getting started with RimWorld modding on Linux</title>
      <pubDate>Wed, 29 Sep 2021 00:00:00 +0000</pubDate>
      <link>https://www.arp242.net/rimworld-mod-linux.html</link>
      <guid isPermaLink="true">https://www.arp242.net/rimworld-mod-linux.html</guid>
      <category>RimWorld</category>
      <description>&lt;p&gt;This describes how to create RimWorld mods on Linux; this is an introduction to
both RimWorld modding and developing C♯ with Mono; it’s essentially the steps I
followed to get started.&lt;/p&gt;

&lt;p&gt;This doesn’t assume any knowledge of Unity, Mono, or C♯ but some familiarity
with Linux and general programming is assumed; if you’re completely new to
programming then this probably isn’t a good resource. A lot of this will work on
Windows or macOS too; it’s just the C♯ build steps that are really
Linux-specific, as are various pathnames etc.&lt;/p&gt;

&lt;p&gt;RimWorld mods consist of two parts:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;A set of XML definitions (“Defs”) which defines everything from items, actions
you can take, research projects, weather, etc. This is the “glue” that
actually makes stuff appear in the game, applies effects, etc.&lt;/p&gt;

    &lt;p&gt;For (very) simple mods this may actually be enough, and no “real” coding is
required. You can use XML files to both add new stuff, and RimWorld has
facilities to patch existing in-game content.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;C♯ code which either adds entire new stuff, or monkey-patches existing code.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As an example we’ll make a little mod that makes it rain blood. Why? It seemed
easy enough to do while also exploring some of the core concepts. Also, I was
playing Slayer when I started on this. The &lt;a href=&quot;https://github.com/arp242/RimWorld-RainingBlood&quot;&gt;complete example mod is on
GitHub&lt;/a&gt;, but I encourage people to modify things manually (and maybe play
around with things a bit) rather than copy/paste stuff from there; it’s just a
better way to learn things.&lt;/p&gt;

&lt;h2 id=&quot;getting-started&quot;&gt;Getting started &lt;a href=&quot;#getting-started&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before we start with the C♯ stuff let’s set up a basic mod which adds a new
weather type; we just need to edit some XML for this.&lt;/p&gt;

&lt;p&gt;Mods are located in the &lt;code&gt;Mods/&lt;/code&gt; directory in your RimWorld installation
directory; I’m using the version I bought from the RimWorld website and
extracted to &lt;code&gt;~/rimworld&lt;/code&gt; so that’s nice and simple. GOG.com games usually store
the actual game data in a &lt;code&gt;game/&lt;/code&gt; subdirectory. I don’t know where Steam
stores things 🤷&lt;/p&gt;

&lt;p&gt;This directory should already exist with a &lt;code&gt;Mods/Place mods here.txt&lt;/code&gt;. A mod
&lt;em&gt;must&lt;/em&gt; have an &lt;code&gt;About/About.xml&lt;/code&gt; file; a minimal version looks like:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;ModMetaData&amp;gt;
    &amp;lt;!-- Must contain a dot; usually &amp;lt;author&amp;gt;.&amp;lt;modname&amp;gt; --&amp;gt;
    &amp;lt;packageId&amp;gt;arp242.RainingBlood&amp;lt;/packageId&amp;gt;
    &amp;lt;name&amp;gt;Raining blood&amp;lt;/name&amp;gt;

    &amp;lt;!-- Game versions this mod supports. More on game versions later. --&amp;gt;
    &amp;lt;supportedVersions&amp;gt;
        &amp;lt;li&amp;gt;1.1&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;1.2&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;1.3&amp;lt;/li&amp;gt;
    &amp;lt;/supportedVersions&amp;gt;
&amp;lt;/ModMetaData&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;See &lt;code&gt;ModUpdating.txt&lt;/code&gt; in the RimWorld installation directory for a full
description of the &lt;code&gt;About.xml&lt;/code&gt; fields. For now, this is enough.&lt;/p&gt;

&lt;aside class=&quot;warn&quot;&gt;&lt;strong title=&quot;Warning&quot;&gt;⚠&lt;/strong&gt;
&lt;p&gt;Pathnames are &lt;em&gt;case-sensitive&lt;/em&gt;; &lt;code&gt;about/about.xml&lt;/code&gt; will not work. If a pathname
has a capital in it then chances are you are &lt;em&gt;required&lt;/em&gt; to write it with a
capital. Many pathnames start with a capital, as seems common in the C♯ world.
Keep this in mind if something doesn’t work!&lt;/p&gt;
&lt;/aside&gt;

&lt;p&gt;The official content uses the same structure as a mod except that it’s in the
&lt;code&gt;Data/&lt;/code&gt; directory; e.g. &lt;code&gt;Data/Core/&lt;/code&gt; contains the base game, &lt;code&gt;Data/Royalty&lt;/code&gt; the
Royalty expansion, etc. To find the weather definitions I just used:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-none&quot;&gt;&lt;code&gt;[~/rimworld/Data/Core]% ls (#i)**/*weather*.xml
Defs/WeatherDefs/Weathers.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;(#i)&lt;/code&gt; makes things case-insensitive in zsh, FYI. zsh is nice. You can also
use &lt;code&gt;find -iname&lt;/code&gt; if you enjoy more typing.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Weathers.xml&lt;/code&gt; seems to define all the weather types. I copied the definition of
“rain” to &lt;code&gt;Mods/RainingBlood/Defs/WeatherDefs/RainingBlood.xml&lt;/code&gt; with some
modifications:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?&amp;gt;
&amp;lt;Defs&amp;gt;
&amp;lt;WeatherDef&amp;gt;
    &amp;lt;defName&amp;gt;RainingBlood&amp;lt;/defName&amp;gt;
    &amp;lt;label&amp;gt;raining blood&amp;lt;/label&amp;gt;
    &amp;lt;description&amp;gt;It&apos;s raining blood; what the hell?!&amp;lt;/description&amp;gt;

    &amp;lt;!-- ThoughtDefs/RainingBlood.xml --&amp;gt;
    &amp;lt;!-- &amp;lt;exposedThought&amp;gt;SoakingWet&amp;lt;/exposedThought&amp;gt; --&amp;gt;
    &amp;lt;exposedThought&amp;gt;BloodCovered&amp;lt;/exposedThought&amp;gt;

    &amp;lt;!-- Copied from rain --&amp;gt;
    &amp;lt;temperatureRange&amp;gt;0~100&amp;lt;/temperatureRange&amp;gt;
    &amp;lt;windSpeedFactor&amp;gt;1.5&amp;lt;/windSpeedFactor&amp;gt;
    &amp;lt;accuracyMultiplier&amp;gt;0.8&amp;lt;/accuracyMultiplier&amp;gt;
    &amp;lt;favorability&amp;gt;Neutral&amp;lt;/favorability&amp;gt;
    &amp;lt;perceivePriority&amp;gt;1&amp;lt;/perceivePriority&amp;gt;

    &amp;lt;rainRate&amp;gt;1&amp;lt;/rainRate&amp;gt;
    &amp;lt;moveSpeedMultiplier&amp;gt;0.9&amp;lt;/moveSpeedMultiplier&amp;gt;
    &amp;lt;ambientSounds&amp;gt;
        &amp;lt;li&amp;gt;Ambient_Rain&amp;lt;/li&amp;gt;
    &amp;lt;/ambientSounds&amp;gt;
    &amp;lt;overlayClasses&amp;gt;
        &amp;lt;li&amp;gt;WeatherOverlay_Rain&amp;lt;/li&amp;gt;
    &amp;lt;/overlayClasses&amp;gt;
    &amp;lt;commonalityRainfallFactor&amp;gt;
        &amp;lt;points&amp;gt;
            &amp;lt;li&amp;gt;(0, 0)&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;(1300, 1)&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;(4000, 3.0)&amp;lt;/li&amp;gt;
        &amp;lt;/points&amp;gt;
    &amp;lt;/commonalityRainfallFactor&amp;gt;

    &amp;lt;!-- Colours modified to be reddish; just a crude effect. --&amp;gt;
    &amp;lt;skyColorsDay&amp;gt;
        &amp;lt;sky&amp;gt;(0.8,0.2,0.2)&amp;lt;/sky&amp;gt;
        &amp;lt;shadow&amp;gt;(0.92,0.2,0.2)&amp;lt;/shadow&amp;gt;
        &amp;lt;overlay&amp;gt;(0.7,0.2,0.2)&amp;lt;/overlay&amp;gt;
        &amp;lt;saturation&amp;gt;0.9&amp;lt;/saturation&amp;gt;
    &amp;lt;/skyColorsDay&amp;gt;

    &amp;lt;skyColorsDusk&amp;gt;
        &amp;lt;sky&amp;gt;(1,0,0)&amp;lt;/sky&amp;gt;
        &amp;lt;shadow&amp;gt;(0.92,0.2,0.2)&amp;lt;/shadow&amp;gt;
        &amp;lt;overlay&amp;gt;(0.6,0.2,0.2)&amp;lt;/overlay&amp;gt;
        &amp;lt;saturation&amp;gt;0.9&amp;lt;/saturation&amp;gt;
    &amp;lt;/skyColorsDusk&amp;gt;

    &amp;lt;skyColorsNightEdge&amp;gt;
        &amp;lt;sky&amp;gt;(0.35,0.10,0.15)&amp;lt;/sky&amp;gt;
        &amp;lt;shadow&amp;gt;(0.92,0.22,0.22)&amp;lt;/shadow&amp;gt;
        &amp;lt;overlay&amp;gt;(0.5,0.1,0.1)&amp;lt;/overlay&amp;gt;
        &amp;lt;saturation&amp;gt;0.9&amp;lt;/saturation&amp;gt;
    &amp;lt;/skyColorsNightEdge&amp;gt;

    &amp;lt;skyColorsNightMid&amp;gt;
        &amp;lt;sky&amp;gt;(0.35,0.20,0.25)&amp;lt;/sky&amp;gt;
        &amp;lt;shadow&amp;gt;(0.92,0.22,0.22)&amp;lt;/shadow&amp;gt;
        &amp;lt;overlay&amp;gt;(0.5,0.2,0.2)&amp;lt;/overlay&amp;gt;
        &amp;lt;saturation&amp;gt;0.9&amp;lt;/saturation&amp;gt;
    &amp;lt;/skyColorsNightMid&amp;gt;
&amp;lt;/WeatherDef&amp;gt;
&amp;lt;/Defs&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;The location where you store this doesn’t matter as long as it’s in &lt;code&gt;Defs&lt;/code&gt;;
&lt;code&gt;Defs/xxx.xml&lt;/code&gt; will work too. Internally all XML files in &lt;code&gt;Defs/&lt;/code&gt; are scanned in
the same data structure; it just recursively searches for &lt;code&gt;*.xml&lt;/code&gt; files and uses
&lt;code&gt;&amp;lt;defName&amp;gt;RainingBlood&amp;lt;/defName&amp;gt;&lt;/code&gt; to identify them rather than the path.&lt;/p&gt;

&lt;p&gt;We also need a new “exposed thought”; that’s the mood modifier that shows up in
the “needs” tab; “Soaking wet” doesn’t really seem applicable if you’re “soaking
wet in blood” 🙃&lt;/p&gt;

&lt;p&gt;Let’s grep for it:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-cli&quot;&gt;&lt;code&gt;[~/rimworld/Data/Core]% rg SoakingWet
Defs/TerrainDefs/Terrain_Water.xml
12:    &amp;lt;traversedThought&amp;gt;SoakingWet&amp;lt;/traversedThought&amp;gt;

Defs/ThoughtDefs/Thoughts_Memory_Misc.xml
297:    &amp;lt;defName&amp;gt;SoakingWet&amp;lt;/defName&amp;gt;

Defs/WeatherDefs/Weathers.xml
98:    &amp;lt;exposedThought&amp;gt;SoakingWet&amp;lt;/exposedThought&amp;gt;
202:    &amp;lt;exposedThought&amp;gt;SoakingWet&amp;lt;/exposedThought&amp;gt;
267:    &amp;lt;exposedThought&amp;gt;SoakingWet&amp;lt;/exposedThought&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&lt;code&gt;Thoughts_Memory_Misc.xml&lt;/code&gt; seems to be what we want, so make a copy of that to
&lt;code&gt;Mods/RainingBlood/Defs/ThoughtDefs/RainingBlood.xml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?&amp;gt;
&amp;lt;Defs&amp;gt;
&amp;lt;ThoughtDef&amp;gt;
    &amp;lt;defName&amp;gt;BloodCovered&amp;lt;/defName&amp;gt;
    &amp;lt;durationDays&amp;gt;0.1&amp;lt;/durationDays&amp;gt;
    &amp;lt;stackLimit&amp;gt;1&amp;lt;/stackLimit&amp;gt;
    &amp;lt;stages&amp;gt;
        &amp;lt;li&amp;gt;
            &amp;lt;label&amp;gt;blood covered&amp;lt;/label&amp;gt;
            &amp;lt;description&amp;gt;I&apos;m covered in blood; yuk!&amp;lt;/description&amp;gt;
            &amp;lt;baseMoodEffect&amp;gt;-30&amp;lt;/baseMoodEffect&amp;gt;
        &amp;lt;/li&amp;gt;
    &amp;lt;/stages&amp;gt;
&amp;lt;/ThoughtDef&amp;gt;
&amp;lt;/Defs&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;The meaning of the fields in both XML files should be mostly self-explanatory,
but if you want to know what &lt;em&gt;exactly&lt;/em&gt; something does you’ll need to decompile
the game to read the source code. We’ll cover that later.&lt;/p&gt;

&lt;p&gt;At this point, the basic mod should be done; let’s test it.&lt;/p&gt;

&lt;h3 id=&quot;running-the-game&quot;&gt;Running the game &lt;a href=&quot;#running-the-game&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Start the name normally and select the mod in the Mods panel. After this you can
start the game with &lt;code&gt;./RimworldLinux -quicktest&lt;/code&gt;, which will start the game in a
new small map with the last selected mods.&lt;/p&gt;

&lt;p&gt;You can select “Development mode” in options, which will give you a few buttons
at the top, gives you a console with &lt;code&gt;`&lt;/code&gt;, you can speed up things a
wee bit more by pressing 4 (&lt;em&gt;ludicrous speed!&lt;/em&gt;) Most of the buttons etc. should
be self-explanatory; there’s some &lt;a href=&quot;https://rimworldwiki.com/wiki/Development_mode&quot;&gt;more information on the RimWorld
wiki&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Click the “debug actions” button at the top, which has “Change Weather” (filter
in the top-left corner; you may need to scroll down). After clicking
RainingBlood it takes a few seconds for the weather to transition and the status
to show up in your colonists.&lt;/p&gt;

&lt;h3 id=&quot;patching-the-biomes&quot;&gt;Patching the biomes &lt;a href=&quot;#patching-the-biomes&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;It’s all very good that we can select this from our magical debug actions, but
does it actually appear in a regular game? Let’s search where the
&lt;code&gt;RainyThunderstorm&lt;/code&gt; weather is referenced (as that’s a bit more unique than just
“rain”):&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-cli&quot;&gt;&lt;code&gt;[~/rimworld/Data/Core]% rg RainyThunderstorm
Defs/BiomeDefs/Biomes_Cold.xml
101:      &amp;lt;RainyThunderstorm&amp;gt;1&amp;lt;/RainyThunderstorm&amp;gt;
234:      &amp;lt;RainyThunderstorm&amp;gt;1&amp;lt;/RainyThunderstorm&amp;gt;
389:      &amp;lt;RainyThunderstorm&amp;gt;1&amp;lt;/RainyThunderstorm&amp;gt;
513:      &amp;lt;RainyThunderstorm&amp;gt;0&amp;lt;/RainyThunderstorm&amp;gt;
611:      &amp;lt;RainyThunderstorm&amp;gt;0&amp;lt;/RainyThunderstorm&amp;gt;

Defs/BiomeDefs/Biomes_Temperate.xml
104:      &amp;lt;RainyThunderstorm&amp;gt;1&amp;lt;/RainyThunderstorm&amp;gt;
262:      &amp;lt;RainyThunderstorm&amp;gt;1&amp;lt;/RainyThunderstorm&amp;gt;

Defs/BiomeDefs/Biomes_Warm.xml
109:      &amp;lt;RainyThunderstorm&amp;gt;1.7&amp;lt;/RainyThunderstorm&amp;gt;
277:      &amp;lt;RainyThunderstorm&amp;gt;1.7&amp;lt;/RainyThunderstorm&amp;gt;

Defs/BiomeDefs/Biomes_WarmArid.xml
79:      &amp;lt;RainyThunderstorm&amp;gt;1&amp;lt;/RainyThunderstorm&amp;gt;
204:      &amp;lt;RainyThunderstorm&amp;gt;1&amp;lt;/RainyThunderstorm&amp;gt;
312:      &amp;lt;RainyThunderstorm&amp;gt;1&amp;lt;/RainyThunderstorm&amp;gt;

Defs/WeatherDefs/Weathers.xml
193:    &amp;lt;defName&amp;gt;RainyThunderstorm&amp;lt;/defName&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;e.g. &lt;code&gt;Biomes_Cold.xml&lt;/code&gt; has:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;baseWeatherCommonalities&amp;gt;
    &amp;lt;Clear&amp;gt;18&amp;lt;/Clear&amp;gt;
    &amp;lt;Fog&amp;gt;1&amp;lt;/Fog&amp;gt;
    &amp;lt;Rain&amp;gt;2&amp;lt;/Rain&amp;gt;
    &amp;lt;DryThunderstorm&amp;gt;1&amp;lt;/DryThunderstorm&amp;gt;
    &amp;lt;RainyThunderstorm&amp;gt;1&amp;lt;/RainyThunderstorm&amp;gt;
    &amp;lt;FoggyRain&amp;gt;1&amp;lt;/FoggyRain&amp;gt;
    &amp;lt;SnowGentle&amp;gt;4&amp;lt;/SnowGentle&amp;gt;
    &amp;lt;SnowHard&amp;gt;4&amp;lt;/SnowHard&amp;gt;
&amp;lt;/baseWeatherCommonalities&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Let’s try adding our bloody rain with a high chance of spawning:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;baseWeatherCommonalities&amp;gt;
    &amp;lt;Clear&amp;gt;18&amp;lt;/Clear&amp;gt;
    &amp;lt;Fog&amp;gt;1&amp;lt;/Fog&amp;gt;
    &amp;lt;Rain&amp;gt;2&amp;lt;/Rain&amp;gt;
    &amp;lt;DryThunderstorm&amp;gt;1&amp;lt;/DryThunderstorm&amp;gt;
    &amp;lt;RainyThunderstorm&amp;gt;1&amp;lt;/RainyThunderstorm&amp;gt;
    &amp;lt;FoggyRain&amp;gt;1&amp;lt;/FoggyRain&amp;gt;
    &amp;lt;SnowGentle&amp;gt;4&amp;lt;/SnowGentle&amp;gt;
    &amp;lt;SnowHard&amp;gt;4&amp;lt;/SnowHard&amp;gt;

    &amp;lt;RainingBlood&amp;gt;64&amp;lt;/RainingBlood&amp;gt; &amp;lt;!-- References the defName --&amp;gt;
&amp;lt;/baseWeatherCommonalities&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Why 64? Well, the other numbers add up to 32 and if they’re relative weights
then 64 means a 2/3rd chance of our raining blood weather. “Trying it and seeing
what happens” is pretty much what I’m doing here. Throw enough macaroni at a
wall and sooner or later some of it will stick.&lt;sup id=&quot;fnref:weight&quot;&gt;&lt;a href=&quot;#fn:weight&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;The easiest way to override this is to copy the XML file to your
&lt;code&gt;Mods/[..]/Defs/&lt;/code&gt; directory. Again, the path doesn’t matter, it just looks at
the &lt;code&gt;defName&lt;/code&gt; attribute; the last one overrides any previous ones.&lt;/p&gt;

&lt;p&gt;This is pretty useful for testing, debugging, etc. as you can focus on just the
XML without worrying if it’s patched correctly. The obvious downside is that you
won’t include any future updates (which may break the game due to missing fields
etc.), and if someone decides to make a “RainingMen” mod then one will override
the other, and you can’t have both mods. You never want to do this in a
published mod, but for testing it’s useful.&lt;/p&gt;

&lt;p&gt;Testing this is a bit annoying, since you need to wait for it to take effect.
Also, it seems the game always sets the initial weather for at least 10 in-game
days, so you may want to load a save game instead of using &lt;code&gt;-quicktest&lt;/code&gt;.
Remember You can press &lt;code&gt;4&lt;/code&gt; for if you enabled the dev console, which speeds up
the game to 15× (&lt;code&gt;3&lt;/code&gt; is 6×). You can also make &lt;code&gt;4&lt;/code&gt; speed it up to a whopping
150× by going to the “TweakValues” developer menu and enabling
&lt;code&gt;TickManager.UltraSpeedBoost&lt;/code&gt;. I am disappointed this is called &lt;code&gt;UltraFast&lt;/code&gt; and
&lt;code&gt;UltraSpeedBoost&lt;/code&gt; instead of &lt;code&gt;RidiculousSpeed&lt;/code&gt; and &lt;code&gt;LudicrousSpeed&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After confirming that our “override it all”-method works let’s properly patch
stuff. There are &lt;a href=&quot;https://rimworldwiki.com/wiki/Modding_Tutorials/Modifying_defs&quot;&gt;several ways of patching XML resources&lt;/a&gt;; I’ll use XPath
here, which is the easiest if you just need to patch some XML. Any XML file in
&lt;code&gt;Patches/&lt;/code&gt; is treated as patch.&lt;/p&gt;

&lt;p&gt;Our patch will just all biomes in &lt;code&gt;Patches/Biomes.xml&lt;/code&gt;, but you can select for
&lt;code&gt;[defName=..]&lt;/code&gt; if you only want to patch specific ones. Remember to remove the
overrides if you have any.&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?&amp;gt;
&amp;lt;Patch&amp;gt;
&amp;lt;!-- Class, not class! --&amp;gt;
&amp;lt;Operation Class=&quot;PatchOperationAdd&quot;&amp;gt;
    &amp;lt;xpath&amp;gt;/Defs/BiomeDef/baseWeatherCommonalities&amp;lt;/xpath&amp;gt;
    &amp;lt;value&amp;gt;
        &amp;lt;RainingBlood&amp;gt;64&amp;lt;/RainingBlood&amp;gt;
    &amp;lt;/value&amp;gt;
&amp;lt;/Operation&amp;gt;
&amp;lt;/Patch&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;As previously mentioned all XML files in &lt;code&gt;Defs/&lt;/code&gt; are in the same data structure,
so don’t worry about the pathnames. There are a number of other operations you
can do; see &lt;a href=&quot;https://rimworldwiki.com/wiki/Modding_Tutorials/PatchOperations&quot;&gt;the full documentation&lt;/a&gt; for more details on how patching
works.&lt;/p&gt;

&lt;aside class=&quot;warn&quot;&gt;&lt;strong title=&quot;Warning&quot;&gt;⚠&lt;/strong&gt;
&lt;p&gt;Do not start XPath expressions with &lt;code&gt;//&lt;/code&gt; as &lt;a href=&quot;https://ludeon.com/forums/index.php?topic=32874.0&quot;&gt;it’s very slow&lt;/a&gt;.&lt;/p&gt;
&lt;/aside&gt;

&lt;p&gt;You can use &lt;code&gt;xmllint&lt;/code&gt; from libxml2 to test queries on the commandline:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;% xmllint --xpath &apos;/Defs/BiomeDef/baseWeatherCommonalities&apos; \
    Data/Core/Defs/BiomeDefs/Biomes_Cold.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h2 id=&quot;writing-c-code&quot;&gt;Writing C♯ code &lt;a href=&quot;#writing-c-code&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let’s expand the mod a bit by making cannibals &lt;em&gt;like&lt;/em&gt; raining blood and give
them a mood boost, rather than a mood penalty. There isn’t any way to express
that in the XML defs, so we need some code for that.&lt;/p&gt;

&lt;h3 id=&quot;decompiling-the-code&quot;&gt;Decompiling the code &lt;a href=&quot;#decompiling-the-code&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Some of the game’s source code is in the installation directory (e.g.
&lt;code&gt;~/rimworld/Source&lt;/code&gt;) but it’s not a lot; there’s
&lt;code&gt;Source/Verse/Defs/DefTypes/WeatherDef.cs&lt;/code&gt;, but it’s not all that useful. You
can more or less ignore this directory.&lt;/p&gt;

&lt;p&gt;We’ll need to decompile the C♯ code in
&lt;code&gt;RimWorldLinux_Data/Managed/Assembly-CSharp.dll&lt;/code&gt;.&lt;sup id=&quot;fnref:eula&quot;&gt;&lt;a href=&quot;#fn:eula&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; There are &lt;a href=&quot;https://rimworldwiki.com/wiki/Modding_Tutorials/Decompiling_source_code&quot;&gt;several
tools&lt;/a&gt; for this; I’ll use &lt;a href=&quot;https://github.com/icsharpcode/ILSpy&quot;&gt;ILSpy&lt;/a&gt;. This doesn’t seem &lt;a href=&quot;https://repology.org/project/ilspy&quot;&gt;packaged in most
distros&lt;/a&gt; but there are Linux binaries for the GUI available as
&lt;a href=&quot;https://github.com/icsharpcode/AvaloniaILSpy&quot;&gt;AvaloniaILSpy&lt;/a&gt;. This seems to work well enough, but I prefer to extract all the
code at once so I can use Vim and grep and whatnot, and the GUI doesn’t seem to
do that (there is “save code”, but that doesn’t seem to do anything).&lt;/p&gt;

&lt;p&gt;You need to build the &lt;code&gt;ilspycmd&lt;/code&gt; binary from source, there isn’t a pre-compiled
version as far as I can find. Basic instructions:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-cli&quot;&gt;&lt;code&gt;# The &quot;.NET home&quot;.
% export DOTNET_ROOT=$HOME/dotnet
% mkdir -p $DOTNET_ROOT

# Needs .NET SDK 6 and .NET Core 3.1; binaries from:
# https://dotnet.microsoft.com/en-us/download/dotnet/6.0
# Versions may be different; this is just indicative.
% tar xf dotnet-sdk-6.0.408-linux-x64.tar.gz -C $DOTNET_ROOT

# Add the dotnet path, the binaries we compile later will be in ~/.dotnet/tools
% export PATH=$PATH:$HOME/dotnet:$HOME/.dotnet/tools

# Just the &quot;source code&quot; tar.gz from the GitHub release:
# https://github.com/icsharpcode/ILSpy/archive/refs/tags/v7.2.1.tar.gz
% tar xf ILSpy-7.2.1.tar.gz
% cd ILSpy-7.2.1
% dotnet tool install ilspycmd -g

# Now decompile the lot to src.
% cd ~/rimworld
% mkdir src
% ilspycmd ./RimWorldLinux_Data/Managed/Assembly-CSharp.dll -p -o src

# Hurray!
% ls src
Assembly-CSharp.csproj       FleckUtility.cs         RimWorld/
ComplexWorker_Ancient.cs     HistoryEventUtility.cs  Verse/
ComplexWorker.cs             Ionic/                  WeaponClassDef.cs
DarknessCombatUtility.cs     Properties/             WeaponClassPairDef.cs
FleckParallelizationInfo.cs  ResearchUtility.cs
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;You only need to do this once. Note that the &lt;code&gt;DOTNET_ROOT&lt;/code&gt; is a runtime
dependency of &lt;code&gt;ilspycmd&lt;/code&gt;, so don’t remove it unless you’re sure you don’t need
to run it again.&lt;/p&gt;

&lt;p&gt;The decompiled source doesn’t have any comments, and some variables seem changed
from the original (e.g. &lt;code&gt;num1&lt;/code&gt;, &lt;code&gt;num2&lt;/code&gt;, &lt;code&gt;num3&lt;/code&gt;, etc.) but it’s mostly fairly
readable. The versions in &lt;code&gt;Source&lt;/code&gt; do have comments, but the paths don’t quite
match up (it seems many subdirs are lost in the decompile?) I considered copying
them over to &lt;code&gt;src&lt;/code&gt; but I’m not sure if the code in &lt;code&gt;Source&lt;/code&gt; matches the exact
version.&lt;/p&gt;

&lt;h3 id=&quot;building-the-assembly&quot;&gt;Building the Assembly &lt;a href=&quot;#building-the-assembly&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;“Assembly” is C♯ speak for any compiled output such as an executable (.exe) or
shared library (.dll). We need to set up a “build solution” (C♯ “Makefiles”) to
build them. Start by just setting up a basic example before we start actually
writing code.&lt;/p&gt;

&lt;p&gt;By convention the source code lives in &lt;code&gt;Mods/.../Source/&lt;/code&gt;, but I don’t think
this is required since the game doesn’t &lt;em&gt;do&lt;/em&gt; anything with it directly. The
resulting DLL files should be in &lt;code&gt;Mods/.../Assemblies/&lt;/code&gt;. Note that you will use
a .dll file on Linux as well – it’s just how Mono/C♯ on Linux works. They are
cross-platform, an assembly built on Linux should also work on Windows and
vice-versa.&lt;/p&gt;

&lt;p&gt;The game code lives in two namespaces: &lt;code&gt;Verse&lt;/code&gt; and &lt;code&gt;RimWorld&lt;/code&gt;. &lt;code&gt;Verse&lt;/code&gt; is the
game engine and RimWorld is the game built on that. At least, I &lt;em&gt;think&lt;/em&gt; that was
the intention at some point as there are all sort of RimWorld-specific things in
&lt;code&gt;Verse&lt;/code&gt; (which also references the &lt;code&gt;RimWorld&lt;/code&gt; namespace frequently) and there
isn’t really a clear dividing line, but mostly: general “engine-y things” are in
&lt;code&gt;Verse&lt;/code&gt; and “RimWorld-y things” are in &lt;code&gt;RimWorld&lt;/code&gt;, except when they’re not.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;Source/RainingBlood.cs&lt;/code&gt; we’ll add a simple example to log something to the
developer console:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-cs&quot;&gt;&lt;code&gt;namespace RainingBlood {
    [Verse.StaticConstructorOnStartup]
    public static class RainingBlood {
        static RainingBlood() {
            Verse.Log.Message(&quot;Hello, world!&quot;);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;[Verse.StaticConstructorOnStartup]&lt;/code&gt; annotation makes the code run when the
game starts; the game searches for all static constructors with this annotation
on startup and executes them. If you really want to know how it works you can
use something like &lt;code&gt;rg &apos;[^\[]StaticConstructorOnStartup&apos;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Another way is inheriting from the &lt;code&gt;Verse.Mod&lt;/code&gt; class, which allows some more
advanced things (most notably &lt;a href=&quot;https://rimworldwiki.com/wiki/Modding_Tutorials/ModSettings&quot;&gt;implementing settings&lt;/a&gt;), but I’m not
going to cover that here.&lt;/p&gt;

&lt;aside class=&quot;note&quot;&gt;&lt;strong title=&quot;Note&quot;&gt;🛈&lt;/strong&gt;
&lt;p&gt;A lot of mods add &lt;code&gt;using Verse&lt;/code&gt; and &lt;code&gt;using RimWorld&lt;/code&gt; so you can use
&lt;code&gt;StaticConstructorOnStartup&lt;/code&gt; instead of &lt;code&gt;Verse.StaticConstructorOnStartup&lt;/code&gt;. I’ve
never really liked this kind of implicit namespacing (in any language) and I’ll
avoid it here too. It clarifies that &lt;code&gt;StaticConstructorOnStartup&lt;/code&gt; is something
belonging to &lt;code&gt;Verse&lt;/code&gt;, rather than being some built-in C♯ or .NET thing or
something. Maybe I’ll change my mind later as I become more familiar with C♯ and
RimWorld, but certainly when learning I find it a lot easier to be explicit at
the expense of slightly more verbosity.&lt;/p&gt;
&lt;/aside&gt;

&lt;p&gt;To build this we’ll need to set up a “build solution”, which consists of a
.csproj XML file and a .sln file. This is something I mostly just copied and
modified from other projects; it seems that most people are auto-generating this
from Visual Studio or MonoDevelop, with little instructions on how to write
these things manually. There’s probably a better way of doing some things (not a
huge fan of the hard-coded paths instead of using some LDPATH analogue), but I
haven’t dived in to this yet.&lt;/p&gt;

&lt;p&gt;Here’s what I ended up with in &lt;code&gt;Mods/RainingBlood/RainingBlood.csproj&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;Project ToolsVersion=&quot;14.0&quot; DefaultTargets=&quot;Build&quot; xmlns=&quot;http://schemas.microsoft.com/developer/msbuild/2003&quot;&amp;gt;

    &amp;lt;Import
        Project=&quot;$(MSBuildExtensionsPath)/$(MSBuildToolsVersion)/Microsoft.Common.props&quot;
        Condition=&quot;Exists(&apos;$(MSBuildExtensionsPath)/$(MSBuildToolsVersion)/Microsoft.Common.props&apos;)&quot;
    /&amp;gt;

    &amp;lt;PropertyGroup&amp;gt;
        &amp;lt;RootNamespace&amp;gt;RainingBlood&amp;lt;/RootNamespace&amp;gt;
        &amp;lt;AssemblyName&amp;gt;RainingBlood&amp;lt;/AssemblyName&amp;gt;
        &amp;lt;!-- You probably want to modify this GUID for your mod, as it&apos;s supposed to be unique.
             This is also referenced in the .sln file.
             My system has &quot;uuidgen&quot; to generate UUIDs. --&amp;gt;
        &amp;lt;ProjectGuid&amp;gt;{7196d15e-d480-441a-a2e0-87b9696dd38f}&amp;lt;/ProjectGuid&amp;gt;

        &amp;lt;Configuration Condition=&quot; &apos;$(Configuration)&apos; == &apos;&apos; &quot;&amp;gt;Debug&amp;lt;/Configuration&amp;gt;
        &amp;lt;Platform Condition=&quot; &apos;$(Platform)&apos; == &apos;&apos; &quot;&amp;gt;AnyCPU&amp;lt;/Platform&amp;gt;
        &amp;lt;OutputType&amp;gt;Library&amp;lt;/OutputType&amp;gt;
        &amp;lt;AppDesignerFolder&amp;gt;Properties&amp;lt;/AppDesignerFolder&amp;gt;
        &amp;lt;TargetFrameworkVersion&amp;gt;v4.7.2&amp;lt;/TargetFrameworkVersion&amp;gt;
        &amp;lt;FileAlignment&amp;gt;512&amp;lt;/FileAlignment&amp;gt;
        &amp;lt;TargetFrameworkProfile /&amp;gt;
    &amp;lt;/PropertyGroup&amp;gt;

    &amp;lt;!-- Debug build --&amp;gt;
    &amp;lt;PropertyGroup Condition=&quot; &apos;$(Configuration)|$(Platform)&apos; == &apos;Debug|AnyCPU&apos; &quot;&amp;gt;
        &amp;lt;DebugSymbols&amp;gt;false&amp;lt;/DebugSymbols&amp;gt;
        &amp;lt;DebugType&amp;gt;none&amp;lt;/DebugType&amp;gt;
        &amp;lt;Optimize&amp;gt;false&amp;lt;/Optimize&amp;gt;
        &amp;lt;OutputPath&amp;gt;Assemblies/&amp;lt;/OutputPath&amp;gt;
        &amp;lt;DefineConstants&amp;gt;DEBUG;TRACE&amp;lt;/DefineConstants&amp;gt;
        &amp;lt;ErrorReport&amp;gt;prompt&amp;lt;/ErrorReport&amp;gt;
        &amp;lt;WarningLevel&amp;gt;4&amp;lt;/WarningLevel&amp;gt;
        &amp;lt;UseVSHostingProcess&amp;gt;false&amp;lt;/UseVSHostingProcess&amp;gt;
        &amp;lt;Prefer32Bit&amp;gt;false&amp;lt;/Prefer32Bit&amp;gt;
    &amp;lt;/PropertyGroup&amp;gt;
    &amp;lt;!-- Release build --&amp;gt;
    &amp;lt;PropertyGroup Condition=&quot; &apos;$(Configuration)|$(Platform)&apos; == &apos;Release|AnyCPU&apos; &quot;&amp;gt;
        &amp;lt;DebugType&amp;gt;none&amp;lt;/DebugType&amp;gt;
        &amp;lt;Optimize&amp;gt;true&amp;lt;/Optimize&amp;gt;
        &amp;lt;OutputPath&amp;gt;Assemblies/&amp;lt;/OutputPath&amp;gt;
        &amp;lt;DefineConstants&amp;gt;TRACE&amp;lt;/DefineConstants&amp;gt;
        &amp;lt;ErrorReport&amp;gt;prompt&amp;lt;/ErrorReport&amp;gt;
        &amp;lt;WarningLevel&amp;gt;3&amp;lt;/WarningLevel&amp;gt;
        &amp;lt;Prefer32Bit&amp;gt;false&amp;lt;/Prefer32Bit&amp;gt;
    &amp;lt;/PropertyGroup&amp;gt;

    &amp;lt;!-- Dependencies --&amp;gt;
    &amp;lt;ItemGroup&amp;gt;
        &amp;lt;!-- The main game code (RimWorld and Verse) --&amp;gt;
        &amp;lt;Reference Include=&quot;Assembly-CSharp&quot;&amp;gt;
            &amp;lt;HintPath&amp;gt;../../RimWorldLinux_Data/Managed/Assembly-CSharp.dll&amp;lt;/HintPath&amp;gt;
            &amp;lt;Private&amp;gt;False&amp;lt;/Private&amp;gt;
        &amp;lt;/Reference&amp;gt;

        &amp;lt;!-- C#/.NET stdlib --&amp;gt;
        &amp;lt;Reference Include=&quot;System&quot; /&amp;gt;
        &amp;lt;Reference Include=&quot;System.Core&quot; /&amp;gt;
        &amp;lt;Reference Include=&quot;System.Runtime.InteropServices.RuntimeInformation&quot; /&amp;gt;
        &amp;lt;Reference Include=&quot;System.Xml.Linq&quot; /&amp;gt;
        &amp;lt;Reference Include=&quot;System.Data.DataSetExtensions&quot; /&amp;gt;
        &amp;lt;Reference Include=&quot;Microsoft.CSharp&quot; /&amp;gt;
        &amp;lt;Reference Include=&quot;System.Data&quot; /&amp;gt;
        &amp;lt;Reference Include=&quot;System.Net.Http&quot; /&amp;gt;
        &amp;lt;Reference Include=&quot;System.Xml&quot; /&amp;gt;
    &amp;lt;/ItemGroup&amp;gt;

    &amp;lt;!-- File list --&amp;gt;
    &amp;lt;ItemGroup&amp;gt;
        &amp;lt;Compile Include=&quot;Source/RainingBlood.cs&quot; /&amp;gt;
    &amp;lt;/ItemGroup&amp;gt;

    &amp;lt;Import Project=&quot;$(MSBuildToolsPath)/Microsoft.CSharp.targets&quot; /&amp;gt;
&amp;lt;/Project&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;And &lt;code&gt;Mods/RainingBlood/RainingBlood.sln&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27703.2035
MinimumVisualStudioVersion = 10.0.40219.1
Project(&quot;{57073194-e8b4-4a20-b60c-ee0e10947af0}&quot;) = &quot;RainingBlood&quot;, &quot;RainingBlood.csproj&quot;, &quot;{7196d15e-d480-441a-a2e0-87b9696dd38f}&quot;
EndProject
Global
    GlobalSection(SolutionConfigurationPlatforms) = preSolution
        Debug|Any CPU = Debug|Any CPU
        Release|Any CPU = Release|Any CPU
    EndGlobalSection
    GlobalSection(ProjectConfigurationPlatforms) = postSolution
        {7196d15e-d480-441a-a2e0-87b9696dd38f}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {7196d15e-d480-441a-a2e0-87b9696dd38f}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {7196d15e-d480-441a-a2e0-87b9696dd38f}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {7196d15e-d480-441a-a2e0-87b9696dd38f}.Release|Any CPU.Build.0 = Release|Any CPU
    EndGlobalSection
    GlobalSection(SolutionProperties) = preSolution
        HideSolutionNode = FALSE
    EndGlobalSection
    GlobalSection(ExtensibilityGlobals) = postSolution
        SolutionGuid = {31005EA7-3F04-446F-80B2-016137708540}
    EndGlobalSection
EndGlobal
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;To build it you’ll need &lt;a href=&quot;https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild?view=vs-2019&quot;&gt;msbuild&lt;/a&gt;, which is not included in the Standard Mono
installation. It does have &lt;code&gt;xbuild&lt;/code&gt;, but that gives a deprecated warning
pointing towards msbuild. Maybe it works as well, but I didn’t try it. Luckily
&lt;code&gt;msbuild&lt;/code&gt; does seem commonly packaged, so I just installed it from there.&lt;/p&gt;

&lt;aside class=&quot;warn&quot;&gt;&lt;strong title=&quot;Warning&quot;&gt;⚠&lt;/strong&gt;
&lt;p&gt;At the time of writing Arch Linux and derivates ship with a broken msbuild due
to a version of &lt;code&gt;System.Reflection.Metadata.dll&lt;/code&gt; that is too old. See &lt;a href=&quot;https://github.com/arp242/RimWorld-RainingBlood/issues/1#issuecomment-1001152651&quot;&gt;this
issue&lt;/a&gt;
for a workaround.&lt;/p&gt;
&lt;/aside&gt;

&lt;p&gt;I put the solution files in the project root; other people prefer to put it in
the &lt;code&gt;Source/&lt;/code&gt; directory, but you’ll need to modify some of the paths if you put
it there. To build it, simply run &lt;code&gt;msbuild&lt;/code&gt; from the directory, or use &lt;code&gt;msbuild
Mods/RainingBlood&lt;/code&gt; to specify a path. After this you should have
&lt;code&gt;Assemblies/RainingBlood.dll&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After starting the game now and opening the developer console you should see
“Hello, world!” in there.&lt;/p&gt;

&lt;h3 id=&quot;writing-the-code&quot;&gt;Writing the code &lt;a href=&quot;#writing-the-code&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Alrighty, now that all the plumbing is working we can actually start doing some
stuff. Let’s see what grepping for &lt;code&gt;exposedThought&lt;/code&gt; gives us:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-cli&quot;&gt;&lt;code&gt;[~/rimworld/src]% rg exposedThought
Verse/AI/Pawn_MindState.cs
415: if (curWeatherLerped.exposedThought != null &amp;amp;&amp;amp; !pawn.Position.Roofed(pawn.Map))
417:     pawn.needs.mood.thoughts.memories.TryGainMemoryFast(curWeatherLerped.exposedThought);

Verse/WeatherDef.cs
35: public ThoughtDef exposedThought;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&lt;code&gt;Verse/AI/Pawn_MindState.cs&lt;/code&gt; seems to be what we want, and reading through
&lt;code&gt;MindStateTick()&lt;/code&gt; the logic seems straightforward enough:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-cs&quot;&gt;&lt;code&gt;namespace Verse.AI {
    public class Pawn_MindState : IExposable {
        // [..]

        public void MindStateTick() {
            // [..]

            if (Find.TickManager.TicksGame % 123 == 0 &amp;amp;&amp;amp;
                pawn.Spawned &amp;amp;&amp;amp; pawn.RaceProps.IsFlesh &amp;amp;&amp;amp; pawn.needs.mood != null
            ) {
                TerrainDef terrain = pawn.Position.GetTerrain(pawn.Map);
                if (terrain.traversedThought != null) {
                    pawn.needs.mood.thoughts.memories.TryGainMemoryFast(terrain.traversedThought);
                }

                WeatherDef curWeatherLerped = pawn.Map.weatherManager.CurWeatherLerped;
                if (curWeatherLerped.exposedThought != null &amp;amp;&amp;amp; !pawn.Position.Roofed(pawn.Map)) {
                    pawn.needs.mood.thoughts.memories.TryGainMemoryFast(curWeatherLerped.exposedThought);
                }
            }

            // [..]
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;So every 123rd “tick” it checks the terrain and weather and applies any mood
effects. Digging a bit deeper:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;The &lt;code&gt;Pawn&lt;/code&gt; class describes a person or animal (“pawn”) in the game; every Pawn
has a MindState attached to it.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;On every “tick” it calls the MindStateTick() method on the attached MindState
instsance as long as the pawn isn’t dead (as well as a number of other
things).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;One “tick” corresponds to 1/60th real second, which is 1.44 minutes in-game
time. This is the game’s Planck time: everything that happens will take at
least 1.44 minutes in-game.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;There are also “rare ticks” (= 250 ticks = 4.15 real seconds = 6 hours
in-game, or 1/4th of a day) and “long ticks” (= 1000 ticks = 33.33 real
seconds = 1 day in-game) that you can hook in to various places.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If you speed up the game then ticks are just emitted faster: 3× or 6×. So
instead of emitting a tick once every 1/60th second it becomes once every
1/180th second or 1/360th second.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find a bit more about this in &lt;code&gt;Verse/Tick*.cs&lt;/code&gt;. It’s not really needed
to know this for a simple mod like this, but it’s useful to know if you want to
write actual real mods.&lt;/p&gt;

&lt;p&gt;To add our custom logic first add a new “thought” we want to apply to
&lt;code&gt;Defs/ThoughtDefs/RainingBlood.xml&lt;/code&gt; we created earlier:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;ThoughtDef&amp;gt;
    &amp;lt;defName&amp;gt;BloodCoveredCannibal&amp;lt;/defName&amp;gt;
    &amp;lt;durationDays&amp;gt;0.1&amp;lt;/durationDays&amp;gt;
    &amp;lt;stackLimit&amp;gt;1&amp;lt;/stackLimit&amp;gt;
    &amp;lt;stages&amp;gt;
        &amp;lt;li&amp;gt;
            &amp;lt;label&amp;gt;blood covered&amp;lt;/label&amp;gt;
            &amp;lt;description&amp;gt;Reigning in blood!&amp;lt;/description&amp;gt;
            &amp;lt;baseMoodEffect&amp;gt;10&amp;lt;/baseMoodEffect&amp;gt;
        &amp;lt;/li&amp;gt;
    &amp;lt;/stages&amp;gt;
&amp;lt;/ThoughtDef&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;And in the &lt;code&gt;Defs/WeatherDefs/RainingBlood.xml&lt;/code&gt; let’s add some new fields next to
the &lt;code&gt;exposedThought&lt;/code&gt; we already have:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;modExtensions&amp;gt;
    &amp;lt;!-- Class, not class! --&amp;gt;
    &amp;lt;li Class=&quot;RainingBlood.WeatherDefExtension&quot;&amp;gt;
        &amp;lt;exposedThoughtCannibal&amp;gt;BloodCoveredCannibal&amp;lt;/exposedThoughtCannibal&amp;gt;
    &amp;lt;/li&amp;gt;
&amp;lt;/modExtensions&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;The way the XML maps to C♯ code is that every entry in the XML file is expected
to be a field in the &lt;code&gt;*Def&lt;/code&gt; class (inherits from &lt;code&gt;Verse.Def&lt;/code&gt;), for example for
the existing &lt;code&gt;exposedThought&lt;/code&gt; the &lt;code&gt;WeatherDef&lt;/code&gt; class has:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-cs&quot;&gt;&lt;code&gt;public ThoughtDef exposedThought;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;If you were to just add &lt;code&gt;exposedThoughtCannibal&lt;/code&gt; you’d get an error telling you
that &lt;code&gt;exposedThoughtCannibal&lt;/code&gt; isn’t a field in the class:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-NONE&quot;&gt;&lt;code&gt;&amp;lt;exposedThoughtCannibal&amp;gt;[...] doesn&apos;t correspond to any field in in type WeatherDef
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;RimWorld comes with the &lt;code&gt;modExtensions&lt;/code&gt; field to extend Defs. In this case we’re
adding it to an entire new Def, but you can also patch existing Defs with XPath
and &lt;code&gt;PatchOperationAddModExtension&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You’ll also need to add a new class inhereting from &lt;code&gt;Verse.DefModExtension&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-cs&quot;&gt;&lt;code&gt;namespace RainingBlood {
    public class WeatherDefExtension : Verse.DefModExtension {
        public RimWorld.ThoughtDef exposedThoughtCannibal;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;Class&lt;/code&gt; attribute in the XML links the XML fields to this class. The name
can be anything. We can get the value in C♯ with the &lt;code&gt;GetModExtension&amp;lt;T&amp;gt;()&lt;/code&gt;
method on any Def class, where &lt;code&gt;T&lt;/code&gt; is the type (class name) you want. For
example, &lt;code&gt;GetModExtension&amp;lt;WeatherDefExtension&amp;gt;()&lt;/code&gt; in this case. By using the
type system multiple mods can attach their own extensions and not conflict.&lt;/p&gt;

&lt;h3 id=&quot;using-harmony&quot;&gt;Using Harmony &lt;a href=&quot;#using-harmony&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To make this actually &lt;em&gt;do&lt;/em&gt; something we need to hook in some code; RimWorld
itself doesn’t really have a “mod system” for this, but we can use &lt;a href=&quot;https://github.com/pardeike/HarmonyRimWorld&quot;&gt;Harmony&lt;/a&gt;.
Harmony is a C♯ library to patch existing code and can do a number of things,
but the most useful (and least error-prone) is to run code before or after a
method. In our case, we want to run code &lt;em&gt;after&lt;/em&gt;
&lt;code&gt;Pawn_MindState.MindStateTick()&lt;/code&gt; to apply the &lt;code&gt;exposedThoughtCannibal&lt;/code&gt; thought.&lt;/p&gt;

&lt;p&gt;To use this we’ll need to register it as a dependency in our &lt;code&gt;About/About.xml&lt;/code&gt; file:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;modDependencies&amp;gt;
    &amp;lt;li&amp;gt;
        &amp;lt;packageId&amp;gt;brrainz.harmony&amp;lt;/packageId&amp;gt;
        &amp;lt;displayName&amp;gt;Harmony&amp;lt;/displayName&amp;gt;
        &amp;lt;steamWorkshopUrl&amp;gt;steam://url/CommunityFilePage/2009463077&amp;lt;/steamWorkshopUrl&amp;gt;
        &amp;lt;downloadUrl&amp;gt;https://github.com/pardeike/HarmonyRimWorld/releases/latest&amp;lt;/downloadUrl&amp;gt;
    &amp;lt;/li&amp;gt;
&amp;lt;/modDependencies&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;We’ll also have to add it to the &lt;code&gt;RainingBlood.csproj&lt;/code&gt; file as a dependency
&lt;em&gt;before&lt;/em&gt; the &lt;code&gt;Assembly-CSharp&lt;/code&gt; dependency:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- Dependencies --&amp;gt;
&amp;lt;ItemGroup&amp;gt;
    &amp;lt;!-- Harmony must be loaded first --&amp;gt;
    &amp;lt;Reference Include=&quot;0Harmony&quot;&amp;gt;
        &amp;lt;HintPath&amp;gt;../HarmonyRimWorld/Current/Assemblies/0Harmony.dll&amp;lt;/HintPath&amp;gt;
        &amp;lt;Private&amp;gt;False&amp;lt;/Private&amp;gt;
    &amp;lt;/Reference&amp;gt; 

    &amp;lt;!-- The main game code (RimWorld and Verse) --&amp;gt;
    &amp;lt;Reference Include=&quot;Assembly-CSharp&quot;&amp;gt;
        &amp;lt;HintPath&amp;gt;../../RimWorldLinux_Data/Managed/Assembly-CSharp.dll&amp;lt;/HintPath&amp;gt;
        &amp;lt;Private&amp;gt;False&amp;lt;/Private&amp;gt;
    &amp;lt;/Reference&amp;gt;

    [..]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;aside class=&quot;warn&quot;&gt;&lt;strong title=&quot;Warning&quot;&gt;⚠&lt;/strong&gt;
&lt;p&gt;If you previously registered/loaded the game with the mod enabled you should at
this point start the game and make sure the Harmony mod is loaded in the mod
list. Because we added this dependency later it won’t be loaded automatically
for us, and if you don’t do this &lt;em&gt;before&lt;/em&gt; actually using Harmony the game won’t
even load the main menu, with many confusing unclear errors in the log. This
took me forever to figure out.&lt;/p&gt;

&lt;p&gt;You can also add &lt;code&gt;brrainz.harmony&lt;/code&gt; to &lt;code&gt;~/.config/unity3d/Ludeon Studios/RimWorld
by Ludeon Studios/Config/ModsConfig.xml&lt;/code&gt; manually.&lt;/p&gt;
&lt;/aside&gt;

&lt;aside class=&quot;warn&quot;&gt;&lt;strong title=&quot;Warning&quot;&gt;⚠&lt;/strong&gt;
&lt;p&gt;There are two versions of Harmony: 1 and 2. Harmony 1 is used as &lt;code&gt;Harmony.Foo&lt;/code&gt;,
Harmony 2 as &lt;code&gt;HarmonyLib.Foo&lt;/code&gt;. A lot of resources for RimWorld patching use
Harmony &lt;em&gt;1&lt;/em&gt; and mixing the two is not going to end well. I recommend just using
&lt;a href=&quot;https://harmony.pardeike.net/articles/intro.html&quot;&gt;the documentation&lt;/a&gt;; it’s pretty good.&lt;/p&gt;

&lt;/aside&gt;

&lt;p&gt;Now we can use it to run some code after the &lt;code&gt;Pawn_MindState.MindStateTick()&lt;/code&gt;
method:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-cs&quot;&gt;&lt;code&gt;namespace RainingBlood {
    public class WeatherDefExtension : Verse.DefModExtension {
        public RimWorld.ThoughtDef exposedThoughtCannibal;
    }

    [Verse.StaticConstructorOnStartup]
    public static class Patch {
        static Patch() {
            // Get the method we want to patch.
            var m = typeof(Verse.AI.Pawn_MindState).GetMethod(&quot;MindStateTick&quot;);

            // Get the method we want to run after the original.
            var post = typeof(RainingBlood.Patch).GetMethod(&quot;PostMindStateTick&quot;,
                       System.Reflection.BindingFlags.Static|System.Reflection.BindingFlags.Public);

            // Patch stuff! The string passed to the Harmony constructor can be
            // anything, and can be used to identify/remove patches if need be.
            new HarmonyLib.Harmony(&quot;arp242.rainingblood&quot;).Patch(m,
                postfix: new HarmonyLib.HarmonyMethod(post));
        }

        // The special __instance parameter has the original class instance
        // we&apos;re extending. This is based on the argument name.
        public static void PostMindStateTick(Verse.AI.Pawn_MindState __instance) {
            var pawn = __instance.pawn;

            // Same condition as MindStateTick, but inversed for early return.
            if (Verse.Find.TickManager.TicksGame % 123 != 0 ||
                !pawn.Spawned || !pawn.RaceProps.IsFlesh || pawn.needs.mood == null)
                return;

            // Is this pawn a cannibal? If not, then there&apos;s nothing to do. You
            // can also expand this by checking for the Ideology cannibalism
            // memes, but this just checks the &quot;cannibalism&quot; trait on colonists.
            if (!pawn.story.traits.HasTrait(RimWorld.TraitDefOf.Cannibal))
                return;

            // Let&apos;s see if the current weather has our new exposedThoughtCannibal.
            var w = pawn.Map.weatherManager.CurWeatherLerped;
            if (!w.HasModExtension&amp;lt;WeatherDefExtension&amp;gt;())
                return;
            var t = w.GetModExtension&amp;lt;WeatherDefExtension&amp;gt;().exposedThoughtCannibal;
            if (t == null)
                return;

            // Remove any existing thought that was applied and apply our
            // cannibalistic thoughts.
            if (w.exposedThought != null)
                pawn.needs.mood.thoughts.memories.RemoveMemoriesOfDef(w.exposedThought);
            pawn.needs.mood.thoughts.memories.TryGainMemoryFast(t);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;I use the “manual method” here as that’s a bit easier to debug if you did
something wrong, but you can also use the annotations. Again, see the Harmony
documentation. One thing you need to watch out for is getting the
&lt;code&gt;BindingFlags.[..]&lt;/code&gt; right. If you don’t then the reflection library won’t find
your method and it’ll return &lt;code&gt;null&lt;/code&gt;. See the &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.type.getmethod?view=net-5.0&quot;&gt;GetMethod()&lt;/a&gt; documentation. This
part actually took me quite a bit to get working. Unfortunately RimWorld doesn’t
have a REPL or console (AFAIK?) but you can use some printf-debugging with
&lt;code&gt;Verse.Log.Message($&quot;{var}&quot;)&lt;/code&gt; and the like.&lt;/p&gt;

&lt;p&gt;I’m not going to step through the rest of the code in more detail here; I think
most of it should be obvious. I mostly just found this be looking through
various code and some strategic grepping. You can test this by using the Debug
Actions menu, which allows assigning the Cannibalism trait to a colonist.&lt;/p&gt;

&lt;h2 id=&quot;next-steps&quot;&gt;Next steps &lt;a href=&quot;#next-steps&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The above wasn’t really all that useful as such, and there are many more parts
of RimWorld modding – most of which I haven’t looked at in detail yet – but this
should at least give a decent base to get started with.&lt;/p&gt;

&lt;p&gt;I have to say that I found a lot of documentation and guides on the topic to be
of, ehm, less-than-stellar quality :-/ The RimWorld wiki has a whole bunch of
pages, but – with a few exceptions linked in the article here – I found many are
unclear, outdated, or both, and in a few cases just downright wrong. Keep that
in mind if something doesn’t work: usually it’s a mistake to assume the
documentation is wrong instead of you, but here it might actually be the case.
I’ll see if I’ll write some more if I keep up interest in this.&lt;/p&gt;

&lt;p&gt;Some additional reading for topics not covered:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://docs.google.com/document/d/e/2PACX-1vSOOrF961tiBuNBIr8YpUvCWYScU-Wer3h3zaoMrw_jc8CCjMjlMzNCAfZZHTI2ibJ7iUZ9_CK45IhP/pub&quot;&gt;Multi-version mods&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Details how to make a mod compatible with both 1.2 and 1.3. I elided this for
simplicity, and also because quite frankly I don’t really care as I’m just
interested in writing some mods that work for &lt;em&gt;me&lt;/em&gt; to fix/improve some things 🤷&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://ludeon.com/forums/index.php?topic=2325.0&quot;&gt;RimWorld art source&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;The original PSD files for all art in RimWorld. Useful if you want to use a
modified version in your mod.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://rimworldwiki.com/wiki/Modding_Tutorials/Mod_folder_structure&quot;&gt;Mod folder structure&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Covers some things not used in this example, such as sounds, textures, and
i18n.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://github.com/alextd/RimWorld-TDBug&quot;&gt;TDBug&lt;/a&gt; adds some debug things which
seem useful. Haven’t tried it yet.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;missing-parts&quot;&gt;Missing parts &lt;a href=&quot;#missing-parts&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;And some things I’d still like to improve/figure out:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;I would really really like a REPL, debugger, or some other way to speed up the
dev cycle. RimWorld takes fairly long to start (almost a minute on my laptop)
and toying around with things is kinda annoying and time-consuming.&lt;/p&gt;

    &lt;p&gt;The closest I found is &lt;a href=&quot;https://fluffy-mods.github.io//2020/08/13/debugging-rimworld/&quot;&gt;How I got RimWorld debugging to work&lt;/a&gt;; the CLI
works on Linux (run with &lt;code&gt;dnSpy.Console.exe&lt;/code&gt;, from the .NET download) but the
GUI doesn’t (and never will, as the Windows-specific GUI toolkit things aren’t
implemented on Linux), but this doesn’t support the debugger (just
decompilation).&lt;/p&gt;

    &lt;p&gt;I tried the generic &lt;a href=&quot;https://github.com/mono/sdb&quot;&gt;sdb&lt;/a&gt; Mono debugger, but the game doesn’t load directly
with Mono but rather via the 32M &lt;code&gt;UnityPlayer.so&lt;/code&gt;, so using that seems
difficult. Using &lt;a href=&quot;https://www.mono-project.com/docs/debug+profile/debug/#debugging-with-gdb&quot;&gt;gdb&lt;/a&gt; works, but actually doing useful stuff with it (i.e.
breakpoints, calling functions, displaying variable values) seems harder, but
I haven’t spent that much time with it yet.&lt;/p&gt;

    &lt;p&gt;Making the game start faster would help too, or an automatic “script” to run
on startup (i.e. to apply certain debug actions).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;On Linux the Assemblies are in &lt;code&gt;RimWorldLinux_Data/&lt;/code&gt;, but on Windows and macOS
this directory is &lt;code&gt;RimWorldWin64_Data/&lt;/code&gt; and &lt;code&gt;RimWorldMac_Data/&lt;/code&gt;. Right now the
build solution builds just on Linux, but I’d like to be able to make it build
on all systems.&lt;/p&gt;

    &lt;p&gt;Hard-coding this path seems common; to get other mods to build I had to
manually s/Win64/Linux/ some things, which is not ideal. I couldn’t figure out
how to make it cross-platform.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;postscript&quot; role=&quot;doc-endnotes&quot;&gt;&lt;strong&gt;Footnotes&lt;/strong&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:weight&quot;&gt;
      &lt;p&gt;Although I later did confirm that they’re relative weights, see
       &lt;code&gt;Verse.TryRandomElementByWeight()&lt;/code&gt;, which can be examined after
       decompiling the source. &lt;a href=&quot;#fnref:weight&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:eula&quot;&gt;
      &lt;p&gt;Explicitly allowed in &lt;a href=&quot;https://rimworldgame.com/eula/&quot;&gt;the EULA&lt;/a&gt; it
     turns out: &lt;em&gt;“You’re allowed to ‘decompile’ our game assets and look
     through our code, art, sound, and other resources for learning
     purposes, or to use our resources as a basis or reference for a Mod.
     However, you’re not allowed to rip these resources out and pass them
     around independently.”&lt;/em&gt; I wish they’d just make this easier by
     distributing more code, but ah well. &lt;a href=&quot;#fnref:eula&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    <item>
      <title>Stallman isn&apos;t great, but not the devil</title>
      <pubDate>Thu, 25 Mar 2021 00:00:00 +0000</pubDate>
      <link>https://www.arp242.net/rms.html</link>
      <guid isPermaLink="true">https://www.arp242.net/rms.html</guid>
      <category>Open source</category>
      <category>Community</category>
      <category>Politics</category>
      <description>&lt;p&gt;So Richard Stallman is back at the FSF, on the board of directors this time
rather than as President. I’m not sure how significant this position is in the
day-to-day operations, but I’m not sure if that’s really important.&lt;/p&gt;

&lt;p&gt;How anyone could have thought this was a good idea is beyond me. I’ve long
considered Stallman to be a poor representative of the community, and quite
frankly it baffles me that people do. I’m not sure what the politics were that
led up to this decision; I had hoped that after Stallman’s departure the FSF
would move forward and shed off some of the Stallmanisms. It seems this hasn’t
happened.&lt;/p&gt;

&lt;p&gt;To quickly recap why Stallman is a poor representative:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Actively turned many people off because he’s such a twat; one of the better
&lt;a href=&quot;https://youtu.be/cj02_UeUnGQ?t=1705&quot;&gt;examples I know of is from Keith Packard&lt;/a&gt;, explaining why X didn’t use the
GPL in spite of Packard already having used it for some of his projects
before:&lt;/p&gt;

    &lt;blockquote&gt;
      &lt;p&gt;Richard Stallman, the author of the GPL and quite an interesting individual
lived at 5405 DEC square, he lived up on the sixth floor I think? Had an
office up there; he did not have an apartment. And we knew him extremely
well. He was a challenging individual to get along with. He would regularly
come down to our offices and ask us, or kind of rail at us, for not using
the GPL.&lt;/p&gt;

      &lt;p&gt;This did not make a positive impression on me; this was my first
interaction with Richard directly and I remember thinking at the time,
“this guy is a little, you know, I’m not interesting in talking to him
because he’s so challenging to work with.”&lt;/p&gt;

      &lt;p&gt;And so, we should have listened to him then but we did not because, we know
him too well, I guess, and met him as well.&lt;/p&gt;

      &lt;p&gt;He really was right, we need to remember that!&lt;/p&gt;
    &lt;/blockquote&gt;

  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;His behaviour against women in particular is creepy. This is not a crime (he
has, as far as I know, never forced himself on anyone) but not a good quality
in a community spokesperson, to put it mildly.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;His personal behaviour in general is … odd, to put it mildly. Now, you can
be as odd as you’d like as far as I’m concerned, but I also don’t think
someone like that is a good choice to represent an entire community.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Caused a major and entirely avoidable fracture of the community with the &lt;em&gt;Open
Source&lt;/em&gt; movement; it’s pretty clear that Stallman, him specifically as a
person, was a major reason for the OSI people to start their own organisation.
Stallman still seems to harbour sour grapes over this more than 20 years
later.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Sidetracking of pointless issues (“GNU/Linux”, “you should not be using hacker
but cracker”, “Open Source misses the point”, etc.), as well as stubbornly
insisting on the term “Free Software” which is confusing and stands in the way
if communicating the ideals to the wider world. Everyone will think that an
article with “Free Software” in the title will be about software free of
charge. There is a general lack of priorities or pragmatism in almost
anything Stallman does.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Stallman’s views in general on computing are stuck somewhere about 1990.
Possibly earlier. The “GNU Operating System” (which does not exist, has never
existed, and most likely will never exist&lt;sup id=&quot;fnref:gnu&quot;&gt;&lt;a href=&quot;#fn:gnu&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;) is not how to advance Free
Software in modern times. Most people don’t give a rat’s arse which OS they’re
using to access GitHub, Gmail, Slack, Spotify, Netflix, AirBnB, etc. The world
has changed and the strategy needs to change – but Stallman is still stuck in
1990.&lt;/p&gt;

  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Insisting on absolute freedom to the detriment of &lt;em&gt;more&lt;/em&gt; freedom compared to
the status quo. No, people don’t want to run a “completely free GNU/Linux
operating system” if their Bluetooth and webcam doesn’t work and if they can’t
watch Netflix. That’s just how it is. Deal with it.&lt;/p&gt;

    &lt;p&gt;His views are quite frankly &lt;a href=&quot;https://www.gnu.org/philosophy/install-fest-devil&quot;&gt;ridiculous&lt;/a&gt;:&lt;/p&gt;

    &lt;blockquote&gt;
      &lt;p&gt;If [an install fest] upholds the ideals of freedom, by installing only free
software from 100%-free distros, partly-secret machines won’t become
entirely functional and the users that bring them will go away disappointed.
However, if the install fest installs nonfree distros and nonfree software
which make machines entirely function, it will fail to teach users to say no
for freedom’s sake. They may learn to like GNU/Linux, but they won’t learn
what the free software movement stands for.&lt;/p&gt;

      &lt;p&gt;[..]&lt;/p&gt;

      &lt;p&gt;My new idea is that the install fest could allow the devil to hang around,
off in a corner of the hall, or the next room. (Actually, a human being
wearing sign saying “The Devil,” and maybe a toy mask or horns.) The devil
would offer to install nonfree drivers in the user’s machine to make more
parts of the computer function, explaining to the user that the cost of this
is using a nonfree (unjust) program.&lt;/p&gt;
    &lt;/blockquote&gt;

    &lt;p&gt;Aside from the huge cringe factor of having someone dressed up as a devil to
install a driver, the entire premise is profoundly wrong; people &lt;em&gt;can&lt;/em&gt;
appreciate freedom while also not having absolute/maximum freedom. Almost the
entire community does this, with only a handful of purist exceptions. This
will accomplish nothing except turn people off.&lt;/p&gt;

  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Crippling software out of paranoia; for example Stallman &lt;a href=&quot;https://lists.gnu.org/archive/html/emacs-devel/2015-01/msg00015.html&quot;&gt;refused to make gcc
print the AST&lt;/a&gt; – useful for the Emacs completion and other tooling –
because he was afraid someone might “abuse” it. He comes off as a gigantic
twat in that entire thread (e.g. &lt;a href=&quot;https://lists.gnu.org/archive/html/emacs-devel/2015-01/msg00213.html&quot;&gt;this&lt;/a&gt;).&lt;/p&gt;

    &lt;p&gt;How do you get people to use Free Software? &lt;em&gt;By making great software people
want to use&lt;/em&gt;. Not by offering some shitty crippled product where you can’t do
some common things &lt;em&gt;you can already do in the proprietary alternatives&lt;/em&gt;.&lt;/p&gt;

  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;Luckily, the backlash against this has been significant, including &lt;a href=&quot;https://rms-open-letter.github.io/&quot;&gt;an open
letter to remove Richard M. Stallman from all leadership positions&lt;/a&gt;. Good.
There are many things in the letter I can agree with. If there are parliamentary
hearings surrounding some Free Software law then would you want Stallman to
represent you? Would you want Stallman to be left alone in a room with some
female lawmaker (especially an attractive one)? I sure wouldn’t; I’d be fearful
he’d leave a poor impression, or outright disgrace the entire community.&lt;/p&gt;

&lt;p&gt;But there are also a few things that bother me, as are there in the general
conversation surrounding this topic. Quoting a few things from that letter:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;[Stallman] has been a dangerous force in the free software community for a
long time. He has shown himself to be misogynist, ableist, and transphobic,
among other serious accusations of impropriety.&lt;/p&gt;

  &lt;p&gt;[..]&lt;/p&gt;

  &lt;p&gt;him and his hurtful and dangerous ideology&lt;/p&gt;

  &lt;p&gt;[..]&lt;/p&gt;

  &lt;p&gt;RMS and his brand of intolerance&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yikes! That sounds horrible. But closer examinations of the claims don’t really
bear out these strong claims.&lt;/p&gt;

&lt;p&gt;The transphobic claim seems to hinge entirely on his eclectic opinion regarding
gender-neutral pronouns; he prefers some &lt;a href=&quot;https://stallman.org/articles/genderless-pronouns.html&quot;&gt;peculiar set of neologisms&lt;/a&gt; (“per”
and “pers”) instead of the singular “they”. You can think about his pronoun
suggestion what you will – I feel it’s rather silly and pointless at best – but
a disagreement on how to best change the common use of language to be more
inclusive does not strike me as transphobic. Indeed, it strikes me as the &lt;em&gt;exact
opposite&lt;/em&gt;: he’s willing to spend time and effort to make language &lt;em&gt;more
inclusive&lt;/em&gt;. That he doesn’t do it in the generally accepted way is not
transphobia, a “harmful ideology”, or “dangerous”. It’s really not.&lt;/p&gt;

&lt;p&gt;Stallman is well known for his &lt;a href=&quot;https://www.gnu.org/philosophy/words-to-avoid.html&quot;&gt;excessive pedantry surrounding language&lt;/a&gt;;
he’s not singularly focused on the issue of pronouns and has &lt;a href=&quot;https://duckduckgo.com/?t=ffab&amp;amp;q=site%3Astallman.org+transgender&amp;amp;ia=web&quot;&gt;consistently
posted in favour of trans rights&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Stallman’s penchant to make people feel unconformable has long been known; and
should hardly come as a surprise to anyone. Many who met him in person did not
leave with an especially good impression of him for one reason or the other. His
behaviour towards women in particular is pretty bad; many anecdotes have been
published and they’re pretty 😬&lt;/p&gt;

&lt;p&gt;But … I don’t have the impression that Stallman dislikes or distrusts women,
or sees them as subservient to men. Basically, he’s just creepy. That’s not
good, but is it misogyny? His lack of social skills seem to be broad and not
uniquely directed towards women. He’s just a socially awkward guy in general. I
mean, this is a guy who will, &lt;em&gt;when giving a presentation&lt;/em&gt;, will take off his
shoes and socks – which is already a rather weird thing to do – will then
proceed to &lt;em&gt;rub his bare foot&lt;/em&gt; – even weirder – only to proceed to appear to
&lt;em&gt;eat something from his foot&lt;/em&gt; – wtf wtf wtf?!&lt;/p&gt;

&lt;p&gt;If he can’t understand that this is just … wtf, then how can you expect him to
understand that some comment towards a woman is wtf?&lt;/p&gt;

&lt;p&gt;Does all of this excuse bad behaviour? No. But it shines a rather different
light on things than phrases such as “misogynist”, “hurtful and dangerous
ideology”, and “his brand of intolerance” do. He hasn’t forced himself on
anyone, as far as I know, and most complaints are about him being creepy.&lt;/p&gt;

&lt;p&gt;I don’t think it’s especially controversial to claim that Stallman would have
been diagnosed with some form of autism if he had been born several decades
later. This is not intended as an insult or some such, just to establish him as
a neurodivergent&lt;sup id=&quot;fnref:ne&quot;&gt;&lt;a href=&quot;#fn:ne&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; individual. Someone like that is absolutely a poor choice
for a leadership position, but at the same time doesn’t diversity &lt;em&gt;also&lt;/em&gt; mean
diversity of neurodivergent people, or at the very least some empathy and
understanding when people’s exhibit a lack of social skills and behaviour
considered creepy?&lt;/p&gt;

&lt;p&gt;At what point is there a limit if someone’s neurodiversity drives people away? I
don’t know; there isn’t an easy answer to his. Stallman is &lt;em&gt;clearly&lt;/em&gt; unsuitable
for a leadership role; but “misogynist”? I’m not really seeing it in Stallman.&lt;/p&gt;

&lt;p&gt;The ableist claim seems to mostly boil down to a comment he posted on his
website regarding abortion of fetuses with Down’s syndrome:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;A new noninvasive test for Down’s syndrome will eliminate the small risk of
the current test.&lt;/p&gt;

  &lt;p&gt;This mind lead more women to get tested, and abort fetuses that have Down’s
syndrome. Let’s hope so!&lt;/p&gt;

  &lt;p&gt;If you’d like to love and care for a pet that doesn’t have normal human mental
capacity, don’t create a handicapped human being to be your pet. Get a dog or
a parrot. It will appreciate your love, and it will never feel bad for being
less capable than normal humans.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It was &lt;a href=&quot;https://stallman.org/archives/2016-sep-dec.html#31_October_2016_%28Down%27s_syndrome%29&quot;&gt;later edited&lt;/a&gt; to its current version:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;A noninvasive test for Down’s syndrome eliminates the small risk of the old
test. This might lead more women to get tested, and abort fetuses that have
Down’s syndrome.&lt;/p&gt;

  &lt;p&gt;According to Wikipedia, Down’s syndrome is a combination of many kinds of
medical misfortune. Thus, when carrying a fetus that is likely to have Down’s
syndrome, I think the right course of action for the woman is to terminate the
pregnancy.&lt;/p&gt;

  &lt;p&gt;That choice does right by the potential children that would otherwise likely
be born with grave medical problems and disabilities. As humans, they are
entitled to the capacity that is normal for human beings. I don’t advocate
making rules about the matter, but I think that doing right by your children
includes not intentionally starting them out with less than that.&lt;/p&gt;

  &lt;p&gt;When children with Down’s syndrome are born, that’s a different situation.
They are human beings and I think they deserve the best possible care.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;He also made a few other comments to the effect of “you should abort if you’re
pregnant with a fetus who has Down’s syndrome”.&lt;/p&gt;

&lt;p&gt;That last paragraph of the original version was … not great, but the new
version seems okay to me. It &lt;em&gt;is&lt;/em&gt; a women’s right to choose to have an abortion,
for any reason, including not wanting to raise a child with Down’s syndrome.
This is already commonplace in practice, with many women choosing to do so.&lt;/p&gt;

&lt;p&gt;Labelling an entire person as ableist based &lt;em&gt;only&lt;/em&gt; on this – and this is really
the only citation of ableism I’ve been able to find – seems like a stretch, at
best. It was a shitty comment, but he &lt;em&gt;did&lt;/em&gt; correct it which is saying a lot in
Stallman terms, as I haven’t seen him do that very often.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Phrases like “a dangerous force”, “dangerous ideology”, and “brand of
intolerance” make it sound like he’s crusading on these kind of issues. Most of
these are just short notes on his personal site which few people seem to read.&lt;/p&gt;

&lt;p&gt;Most of the issues surrounding Stallman seem to be about him thinking out loud,
not realizing when it is or is not appropriate to do so, being excessively
pedantic over minor details, or just severally lacking in social skills. This can
be inappropriate, offensive, or creepy – depending on the scenario – but that’s
just something different than being actively transphobic or dangerous. If
someone had read only this letter without any prior knowledge of Stallman they
would be left with the impression that Stallman is some sort of alt-right troll
writing for Breitbart or the like. This is hardly the case.&lt;/p&gt;

&lt;p&gt;I think Stallman &lt;em&gt;should&lt;/em&gt; resign of newly appointed post, and from GNU as well,
over his personal behaviour in particular. Stallman isn’t some random programmer
working on GNU jizamabob making the occasional awkward comment, he’s the face of
the entire movement. Appointing “a challenging individual to get along with” –
to quote Packard – is not the right person for the job. I feel the rest of the
FSF board has shown spectacular poor judgement in allowing Stallman to come
back.&lt;sup id=&quot;fnref:s&quot;&gt;&lt;a href=&quot;#fn:s&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;But I can not, in good conscience, sign the letter as phrased currently. It
vastly exaggerates things to such a degree that I feel it does a gross injustice
to Stallman. It’s  grasping at straws to portray Stallman as the most horrible
human being possible, and I don’t think he is that. He seems clueless on some
topics and social interactions, and find him a bit of a twat in general, but
that doesn’t make you a horrible and dangerous person. I find the letter lacking
in empathy and deeply unkind.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;In short, I feel Stallman’s aptitudes do not apply well for any sort of
leadership position and I would rather not have him represent the community I’m
a part of, even if he did start it and made many valuable contributions to it.
Just starting something does not give you perpetual ownership over it, and in
spite of all his hard work I feel he’s been very detrimental to the movement and
has been a net-negative contributor for a while. A wiser version of Stallman
would have realized his shortcomings and stepped down some time in the late 80s
to let someone else be the public face.&lt;/p&gt;

&lt;p&gt;Overall I feel he’s not exactly a shining example of the human species, but then
again I’m probably not either. He is not the devil and the horrible person that
the letter makes him out to be. None of these exaggerations are even &lt;em&gt;needed&lt;/em&gt; to
make the case that he should be removed, which makes it even worse.&lt;/p&gt;

&lt;p&gt;It’s a shame, because instead of moving forward with Free Software we’re
debating this. Arguably I should just let this go as Stallman isn’t &lt;em&gt;really&lt;/em&gt;
worth defending IMO, but on the other hand being unfair is being unfair, no
matter who the target may be.&lt;/p&gt;
&lt;div class=&quot;postscript&quot; role=&quot;doc-endnotes&quot;&gt;&lt;strong&gt;Footnotes&lt;/strong&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:gnu&quot;&gt;
      &lt;p&gt;A set of commandline utilities, libc, and a compiler are not an
    operating system. Linux (the kernel) is not the “last missing piece of
    the GNU operating system”. &lt;a href=&quot;#fnref:gnu&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:ne&quot;&gt;
      &lt;p&gt;Neurodivergency is, in a nutshell, the idea that “normal” is a wide
   range, and that not everyone who doesn’t fits with the majority should be
   labelled as “there is something wrong with them” such as autism. While
   some some people take this a bit too far (not every autist is
   high-functioning; for some it really is debilitating) I think there’s
   something to this. &lt;a href=&quot;#fnref:ne&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:s&quot;&gt;
      &lt;p&gt;I guess this shouldn’t come as that much of a surprise, as the only people
  willing and able to hang around Stallman’s FSF were probably similar-ish
  people. It’s probably time to just give up on the FSF and move forward
  with some new initiative (OSI is crap too, for different reasons). I swear
  we’ve got to be the most dysfunctional community ever. &lt;a href=&quot;#fnref:s&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
  </channel>
</rss>
