<?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>Tue, 14 Apr 2026 18:20:12 +0000</pubDate>
    <lastBuildDate>Tue, 14 Apr 2026 18:20:12 +0000</lastBuildDate>
    <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>
      <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 (e.g.
bzip3), 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). bzip3 seems an interesting improvement over bzip2, but the big bold
“decompression may not work” disclaimer is rather a turn-off at the moment. I
suppose one could use a wrapper which compares the decompression results with
the input. That said, out of the tools not included here, bzip3 seems the most
interesting. Also see: &lt;a href=&quot;https://purplesyringa.moe/blog/an-ode-to-bzip/&quot;&gt;An ode to bzip&lt;/a&gt;.&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>
    <item>
      <title>Go is not an easy language</title>
      <pubDate>Mon, 22 Feb 2021 00:00:00 +0000</pubDate>
      <link>https://www.arp242.net/go-easy.html</link>
      <guid isPermaLink="true">https://www.arp242.net/go-easy.html</guid>
      <category>Go</category>
      <category>Programming</category>
      <description>&lt;p&gt;Go is not an easy programming language. It &lt;em&gt;is&lt;/em&gt; simple in many ways: the syntax
is simple, most of the semantics are simple. But a language is more than just
syntax; it’s about doing useful &lt;em&gt;stuff&lt;/em&gt;. And doing useful stuff is not always
easy in Go.&lt;/p&gt;

&lt;p&gt;Turns out that combining all those simple features in a way to do something
useful can be tricky. How do you remove an item from an array in Ruby?
&lt;code&gt;list.delete_at(i)&lt;/code&gt;. And remove entries by value? &lt;code&gt;list.delete(value)&lt;/code&gt;. Pretty
easy, yeah?&lt;/p&gt;

&lt;p&gt;In Go it’s … less easy; to remove the index &lt;code&gt;i&lt;/code&gt; you need to do:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;list = append(list[:i], list[i+1:]...)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;And to remove the value &lt;code&gt;v&lt;/code&gt; you’ll need to use a loop:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;n := 0
for _, l := range list {
    if l != v {
        list[n] = l
        n++
    }
}
list = list[:n]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Is this unacceptably hard? Not really; I think most programmers can figure out
what the above does even without prior Go experience. But it’s not exactly
&lt;em&gt;easy&lt;/em&gt; either. I’m usually lazy and copy these kind of things from the &lt;a href=&quot;https://github.com/golang/go/wiki/SliceTricks&quot;&gt;Slice
Tricks&lt;/a&gt; page because I want to focus on actually solving the problem at
hand, rather than plumbing like this.&lt;/p&gt;

&lt;p&gt;It’s also easy to get it (subtly) wrong or suboptimal, especially for less
experienced programmers. For example compare the above to copying to a new array
and copying to a new pre-allocated array (&lt;code&gt;make([]string, 0, len(list))&lt;/code&gt;):&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-NONE&quot;&gt;&lt;code&gt;InPlace             116 ns/op      0 B/op   0 allocs/op
NewArrayPreAlloc    525 ns/op    896 B/op   1 allocs/op
NewArray           1529 ns/op   2040 B/op   8 allocs/op
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;While 1529ns is still plenty fast enough for many use cases and isn’t something
to excessively worry about, there are plenty of cases where these things &lt;em&gt;do&lt;/em&gt;
matter and having the guarantee to always use the best possible algorithm with
&lt;code&gt;list.delete(value)&lt;/code&gt; has some value.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Goroutines are another good example. “Look how easy it is to start a goroutine!
Just add &lt;code&gt;go&lt;/code&gt; and you’re done!” Well, yes; you’re done until you have five
million of those running at the same time and then you’re left wondering where
all your memory went, and it’s not hard to “leak” goroutines by accident either.&lt;/p&gt;

&lt;p&gt;There are a number of patterns to limit the number of goroutines, and none of
them are exactly easy. A simple example might be something like:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;var (
	jobs    = 20                 // Run 20 jobs in total.
	running = make(chan bool, 3) // Limit concurrent jobs to 3.
	wg      sync.WaitGroup       // Keep track of which jobs are finished.
)

wg.Add(jobs)
for i := 1; i &amp;lt;= jobs; i++ {
	running &amp;lt;- true // Fill running; this will block and wait if it&apos;s already full.

	// Start a job.
	go func(i int) {
		defer func() {
			&amp;lt;-running // Drain running so new jobs can be added.
			wg.Done() // Signal that this job is done.
		}()

		// &quot;do work&quot;
		time.Sleep(1 * time.Second)
		fmt.Println(i)
	}(i)
}

wg.Wait() // Wait until all jobs are done.
fmt.Println(&quot;done&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;There’s a reason I annotated this with some comments: for people not intimately
familiar with Go this may take some effort to understand. This also won’t ensure
that the numbers are printed in order (which may or may not be a requirement).&lt;/p&gt;

&lt;p&gt;Go’s concurrency primitives may be simple and easy to use, but combining them to
solve common real-world scenarios is a lot less simple. The original version of
the above example &lt;a href=&quot;https://lobste.rs/s/ee6nsc/go_is_not_easy_language#c_gdnw5e&quot;&gt;was actually incorrect&lt;/a&gt; 😅&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;In &lt;a href=&quot;https://www.infoq.com/presentations/Simple-Made-Easy/&quot;&gt;Simple Made Easy&lt;/a&gt; Rich Hickey argues that we shouldn’t confuse “simple”
with “it’s easy to write”: just because you can do something useful in one or
two lines doesn’t mean the underlying concepts – and therefore the entire
program – are “simple” as in “simple to understand”.&lt;/p&gt;

&lt;p&gt;I feel there is some wisdom in this; in most cases we shouldn’t sacrifice
“simple” for “easy”, but that doesn’t mean we can’t think at all about how to
make things easier. Just because concepts are simple doesn’t mean they’re easy
to use, can’t be misused, or can’t be used in ways that lead to (subtle) bugs.
Pushing Hickey’s argument to the extreme we’d end up with something like
&lt;a href=&quot;https://en.wikipedia.org/wiki/Brainfuck&quot;&gt;Brainfuck&lt;/a&gt; and that would of course be silly.&lt;/p&gt;

&lt;p&gt;Ideally a language should reduce the cognitive load required to reason about its
behaviour; there are many ways to increase this cognitive load: complex
intertwined language features is one of them, and getting “distracted” by
implementing fairly basic things from those simple concepts is another: it’s
another block of code I need to reason about. While I’m not overly concerned
about code formatting or syntax choices, I do think it can matter to reduce this
cognitive load when reading code.&lt;/p&gt;

&lt;p&gt;The lack of generics probably plays some part here; implementing a &lt;code&gt;slices&lt;/code&gt;
package which does these kind of things in a generic way is hard right now.
Generics make this possible and also makes things more complex (more language
features are used), but they also make things easier and, arguably, less complex
on other fronts.&lt;sup id=&quot;fnref:g&quot;&gt;&lt;a href=&quot;#fn:g&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;hr /&gt;

&lt;p&gt;Are these insurmountable problems? No. I still use (and like) Go after all. But
I also don’t think that Go is a language that you “could pick up in ~5-10
minutes”, which was the comment that prompted this post; a sentiment I’ve seen
expressed many times, although usually with less extreme timeframes (“1-2 days”,
“1 week”).&lt;/p&gt;

&lt;p&gt;As a corollary to all of the above; learning the language isn’t just about
learning the syntax to write your &lt;code&gt;if&lt;/code&gt;s and &lt;code&gt;for&lt;/code&gt;s; it’s about learning a way of
thinking. I’ve seen many people coming from Python or C♯ try to shoehorn
concepts or patterns from those languages in Go. Common ones include using
struct embedding as inheritance, panics as exceptions, “pseudo-dynamic
programming” with interface{}, and so forth. It rarely ends well, if ever.&lt;/p&gt;

&lt;p&gt;I did this as well when I was writing my first Go program; it’s only natural.
And when I started as a Ruby programmer I tried to write Python code in Ruby
(although this works a bit better as the languages are more similar, but there
are still plenty of odd things you can do such as using &lt;code&gt;for&lt;/code&gt; loops).&lt;/p&gt;

&lt;p&gt;This is why I don’t like it when people get redirected to the Tour of Go to
“learn the language”, as it just teaches basic syntax and little more. It’s nice
as a little, well, &lt;em&gt;tour&lt;/em&gt; to get a bit of a feel of the language and see how it
roughly works and what it can roughly do, but it’s ill-suited to actually learn
the language.&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:g&quot;&gt;
      &lt;p&gt;Contrary to popular belief the &lt;a href=&quot;https://research.swtch.com/generic&quot;&gt;Go team was never “against” generics&lt;/a&gt;;
  I’ve seen many comments to the effect of “the Go team doesn’t think
  generics are useful”, but this was never the case. &lt;a href=&quot;#fnref:g&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>Downsides of working remotely</title>
      <pubDate>Thu, 18 Feb 2021 00:00:00 +0000</pubDate>
      <link>https://www.arp242.net/remote.html</link>
      <guid isPermaLink="true">https://www.arp242.net/remote.html</guid>
      <category>Worklace</category>
      <description>&lt;p&gt;I love remote work, and I’ve been working remotely for the last five years, but
I think there are some serious downsides too. In spite what all the “remote work
is the future!” articles of the last year claim, it’s not all perfect, or
suitable for everyone.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Working remotely means less socializing with your coworkers: fewer chats over
the coffee machine, you don’t go out to the pub for a pint, that sort of thing.
I think you shouldn’t underestimate the value of these kind of things: it’s just
a lot easier to work with people you get along with well socially.&lt;/p&gt;

&lt;p&gt;Conflicts will inevitably happen; it’s just part of human nature. But the better
we get along socially the higher the chance is that we can resolve things
amicably without thinking (though not saying) “they’re just a bloody idiot!”, or
being left with a feeling of lingering resentment. The more you’ve socialized
with someone, the more “social goodwill” you’ve got to fall back on in times of
conflict.&lt;/p&gt;

&lt;p&gt;It’s hard to communicate over text &lt;em&gt;well&lt;/em&gt;. Even if we ignore international
cultural differences, the lack of body language and direct feedback makes it
easy for misunderstandings and miscommunication to happen. We all phrase things
awkwardly or too harshly on occasion, and we can all lose our temper a bit (some
more frequently than others). This is normal, but when you write it in text
&lt;em&gt;it’s there&lt;/em&gt;; there is no opportunity to immediately correct it based on your
conversation partner’s feedback; there is no body language to clarify the
meaning, there is no opportunity to immediately add nuance. The recipient will
just be left steaming over your shitty remark. It’s &lt;em&gt;much&lt;/em&gt; easier for things to
escalate.&lt;/p&gt;

&lt;p&gt;Video conferencing is a bit better, but in my opinion still a poor substitute
over an actual conversation, and being in video chats all day also isn’t really
an option. In practice, a lot of communication will happen over text (chats,
emails, issues, etc.).&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Especially for more junior people the lack of feedback and guidance can be a
very detrimental. You can’t just pop in and ask how they are doing, or sit next
to them and guide them through some things. When I guided an intern years ago I
would regularly just turn around (he was sitting behind me) and look on his
screen to see what he was doing and how he was doing it, and try to offer some
helpful guidance if needed. I felt it was very helpful for him (at least, I hope
so, although last I heard he didn’t pursue his career in IT and is the manager
of a McDonald’s now). This sort of thing is much harder to do well remotely.&lt;/p&gt;

&lt;p&gt;The lack of guidance for more junior programmers is already a big problem in the
IT industry specifically because for decades there have been many more junior
people than senior people, and remote work makes this worse. Generally I
wouldn’t recommend remote work for entry-level and junior jobs.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Another big downside is that remote work can get lonely; most of is don’t hang
out with friends every single day, and it’s not uncommon to not have a serious
conversation with anyone for days on end, especially if you live alone.
Depending on where you live and your personality, you can restructure your
social life a bit to compensate for this, which is what I ended up doing, but
it’s something that takes some amount of effort and isn’t necessarily for
everyone.&lt;/p&gt;

&lt;p&gt;For this reason alone, I feel that remote work on a grand scale might not really
be a good thing. Western society in general seems to have a bit of an issue with
social contact – how many of us talk to our neighbours?&lt;/p&gt;

&lt;p&gt;This isn’t something that can be ascribed to a single cause, or something I have
an answer to – but we already tore down the church and village communities for
many, and I’m not sure if it’s a good idea to tear down the “office community”
too. Not that I think these communities were necessarily the best (I’m not
religious), but tearing it all down without … something … as a replacement
may not be wise.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Don’t get me wrong, I love working remote: I have fewer headaches (why are so
many offices so badly ventilated?!), don’t have to mentally block out noise all
day long (which I find very draining), and can make my own schedule. In general,
I’m much more productive and happier.&lt;/p&gt;

&lt;p&gt;But in the torrent of enthusiasm of remote work in the COVID era, it’s probably
good to focus on some downsides, too. Personally, I’m skeptical that it’s “the
future of work” and will introduce a paradigm shift. And if it does, we should
be acutely aware of some of the downsides.&lt;/p&gt;

&lt;p&gt;Other people have listed some other downsides, such as a lack of structure or
work/life balance. These have not been any problems for me personally (I very
much &lt;em&gt;like&lt;/em&gt; the lack of structure, and feel the “eight hours bum-on-seat” model
is equally problematic). Some other perspectives:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://georgestocker.com/2015/11/17/eight-months-of-remote-work/&quot;&gt;Eight Months of Remote Work&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.bbc.com/worklife/article/20220616-the-people-who-hate-working-from-home&quot;&gt;The people who hate working from home&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://slite.com/blog/micromanagement-is-not-a-bad-word&quot;&gt;Why junior employees are more likely to fail in remote&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;(Probably more, I’ll add some when I find them; feel free to email them)&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    <item>
      <title>Bitmasks for nicer APIs</title>
      <pubDate>Thu, 10 Dec 2020 00:00:00 +0000</pubDate>
      <link>https://www.arp242.net/bitmask.html</link>
      <guid isPermaLink="true">https://www.arp242.net/bitmask.html</guid>
      <category>Programming</category>
      <category>Go</category>
      <description>&lt;p&gt;Bitmasks is one of those things where the basic idea is simple to understand:
it’s just &lt;code&gt;0&lt;/code&gt;s and &lt;code&gt;1&lt;/code&gt;s being toggled on and off. But actually “having it click”
to the point where it’s easy to work with can be a bit trickier. At least, it is
(or rather, was) for me 😅&lt;/p&gt;

&lt;p&gt;With a bitmask you hide (or “mask”) certain bits of a number, which can be
useful for various things as we’ll see later on. There are two reasons one might
use bitmasks: for efficiency or for nicer APIs. Efficiency is rarely an issue
except for some embedded or specialized use cases, but everyone likes nice APIs,
so this is about that.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;A while ago I added colouring support to my little &lt;a href=&quot;https://github.com/arp242/zli&quot;&gt;zli&lt;/a&gt; library. Adding
colours to your terminal is not very hard as such, just print an escape code:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-go&quot;&gt;&lt;code&gt;fmt.Println(&quot;\x1b[34mRed text!\x1b[0m&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;But a library makes this a bit easier. There’s already a bunch of libraries out
there for Go specifically, the most popular being Fatih Arslan’s &lt;a href=&quot;https://github.com/fatih/color&quot;&gt;&lt;code&gt;color&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-go&quot;&gt;&lt;code&gt;color.New(color.FgRed).Add(color.Bold).Add(color.BgCyan).Println(&quot;bold red&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;This is stored as:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-go&quot;&gt;&lt;code&gt;type (
    Attribute int
    Color     struct { params  []Attribute }
)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;I wanted a simple way to add some colouring, which looks a bit nicer than the
method chain in the &lt;code&gt;color&lt;/code&gt; library, and eventually figured out you don’t need a
&lt;code&gt;[]int&lt;/code&gt; to store all the different attributes but that a single &lt;code&gt;uint64&lt;/code&gt; will do
as well:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-go&quot;&gt;&lt;code&gt;zli.Colorf(&quot;bold red&quot;, zli.Red | zli.Bold | zli.Cyan.Bg())

// Or alternatively, use Color.String():
fmt.Printf(&quot;%sbold red%s\n&quot;, zli.Red|zli.Bold|zli.Cyan.Bg(), zli.Reset)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Which in my eyes looks a bit nicer than Fatih’s library, and also makes it
easier to add 256 and true colour support.&lt;/p&gt;

&lt;p&gt;All of the below can be used in any language by the way, and little of this is
specific to Go. You will need Go 1.13 or newer for the binary literals to work.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Here’s how &lt;code&gt;zli&lt;/code&gt; stores all of this in a &lt;code&gt;uint64&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;full&quot;&gt;&lt;code&gt;                                   fg true, 256, 16 color mode ─┬──┐
                                bg true, 256, 16 color mode ─┬─┐│  │
                                                             │ ││  │┌── parsing error
 ┌───── bg color ────────────┐ ┌───── fg color ────────────┐ │ ││  ││┌─ term attr
 v                           v v                           v v vv  vvv         v
 0000_0000 0000_0000 0000_0000 0000_0000 0000_0000 0000_0000 0000_0000 0000_0000
 ^         ^         ^         ^         ^         ^         ^         ^
64        56        48        40        32        24        16         8
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;I’ll go over it in detail later, but in short (from right to left):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;The first 9 bits are flags for the basic terminal attributes such as bold,
italic, etc.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The next bit is to signal a parsing error for true colour codes (e.g. &lt;code&gt;#123123&lt;/code&gt;).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;There are 3 flags for the foreground and background colour each to signal that
a colour should be applied, and how it should be interpreted (there are 3
different ways to set the colour: 16-colour, 256-colour, and 24-bit “true
colour”, which use different escape codes).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The colours for the foreground and background are stored separately, because
you can apply both a foreground and background. These are 24-bit numbers.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;A value of 0 is reset.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this, you can make any combination of the common text attributes, the above
example:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;zli.Colorf(&quot;bold red&quot;, zli.Red | zli.Bold | zli.Cyan.Bg())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Would be the following in binary layout:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;full&quot;&gt;
                                              fg 16 color mode ────┐
                                           bg 16 color mode ───┐   │
                                                               │   │        bold
                bg color ─┬──┐                fg color ─┬──┐   │   │           │
                          v  v                          v  v   v   v           v
 0000_0000 0000_0000 0000_0&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;0 0000_0000 0000_0000 0000_000&lt;b&gt;1&lt;/b&gt; 00&lt;b&gt;1&lt;/b&gt;0_0&lt;b&gt;1&lt;/b&gt;00 0000_000&lt;b&gt;1&lt;/b&gt;
 ^         ^         ^         ^         ^         ^         ^         ^
64        56        48        40        32        24        16         8
&lt;/pre&gt;
&lt;/div&gt;

&lt;!-- Fix Vim syntax: _ --&gt;

&lt;hr /&gt;

&lt;p&gt;We need to go through several steps to actually do something meaningful with
this. First, we want to get all the flag values (the first 9 bits); a “flag” is
a bit being set to true (&lt;code&gt;1&lt;/code&gt;) or false (&lt;code&gt;0&lt;/code&gt;).&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-go&quot;&gt;&lt;code&gt;const (
    Bold         = 0b0_0000_0001
    Faint        = 0b0_0000_0010
    Italic       = 0b0_0000_0100
    Underline    = 0b0_0000_1000
    BlinkSlow    = 0b0_0001_0000
    BlinkRapid   = 0b0_0010_0000
    ReverseVideo = 0b0_0100_0000
    Concealed    = 0b0_1000_0000
    CrossedOut   = 0b1_0000_0000
)

func applyColor(c uint64) {
    if c &amp;amp; Bold != 0 {
        // Write escape code for bold
    }
    if c &amp;amp; Faint != 0 {
        // Write escape code for faint
    }
    // etc.
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&lt;code&gt;&amp;amp;&lt;/code&gt; is the bitwise AND operator. It works just as the more familiar &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; except
that it operates on every individual bit where &lt;code&gt;0&lt;/code&gt; is &lt;code&gt;false&lt;/code&gt; and &lt;code&gt;1&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt;.
The end result will be &lt;code&gt;1&lt;/code&gt; if both bits are “true” (&lt;code&gt;1&lt;/code&gt;). An example with just
four bits:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;
00&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt; &amp;amp; 0&lt;b&gt;1&lt;/b&gt;0&lt;b&gt;1&lt;/b&gt; = 000&lt;b&gt;1&lt;/b&gt;
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;This can be thought of as four separate operations (from left to right):&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;0 AND 0 = 0      both false
0 AND 1 = 0      first value is false, so the end result is false
1 AND 0 = 0      second value is false
1 AND 1 = 1      both true
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;So what &lt;code&gt;if c &amp;amp; Bold != 0&lt;/code&gt; does is check if the “bold bit” is set:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;
Only bold set:
  0 0000 000&lt;b&gt;1&lt;/b&gt;
&amp;amp; 0 0000 000&lt;b&gt;1&lt;/b&gt;
= ―――――――――――
  0 0000 000&lt;b&gt;1&lt;/b&gt;

Underline bit set:
  0 0000 &lt;b&gt;1&lt;/b&gt;000
&amp;amp; 0 0000 000&lt;b&gt;1&lt;/b&gt;
= ―――――――――――
  0 0000 0000                0 since there are no cases of &quot;1 AND 1&quot;

Bold and underline bits set:
  0 0000 &lt;b&gt;1&lt;/b&gt;00&lt;b&gt;1&lt;/b&gt;
&amp;amp; 0 0000 000&lt;b&gt;1&lt;/b&gt;
= ―――――――――――
  0 0000 000&lt;b&gt;1&lt;/b&gt;                Only &quot;bold AND bold&quot; is &quot;1 AND 1&quot;
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;As you can see, &lt;code&gt;c &amp;amp; Bold != 0&lt;/code&gt; could also be written as &lt;code&gt;c &amp;amp; Bold == Bold&lt;/code&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;The colours themselves are stored as a regular number like any other, except
that they’re “offset” a number of bits. To get the actual number value we need
to clear all the bits we don’t care about, and shift it all to the right:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;full ft-go&quot;&gt;&lt;code&gt;const (
    colorOffsetFg   = 16

    colorMode16Fg   = 0b0000_0100_0000_0000
    colorMode256Fg  = 0b0000_1000_0000_0000
    colorModeTrueFg = 0b0001_0000_0000_0000

    maskFg          = 0b00000000_00000000_00000000_11111111_11111111_11111111_00000000_00000000
)

func getColor(c uint64) {
    if c &amp;amp; colorMode16Fg != 0  {
        cc := (c &amp;amp; maskFg) &amp;gt;&amp;gt; colorOffsetFg
        // ..write escape code for this color..
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;First we check if the “16 colour mode” flag is set using the same method as the
terminal attributes, and then we AND it with &lt;code&gt;maskFg&lt;/code&gt; to clear all the bits we
don’t care about:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;full&quot;&gt;
                                   fg true, 256, 16 color mode ─┬──┐
                                bg true, 256, 16 color mode ─┬─┐│  │
                                                             │ ││  │┌── parsing error
 ┌───── bg color ────────────┐ ┌───── fg color ────────────┐ │ ││  ││┌─ term attr
 v                           v v                           v v vv  vvv         v
 0000_0000 0000_0000 0000_0&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;0 0000_0000 0000_0000 0000_000&lt;b&gt;1&lt;/b&gt; 00&lt;b&gt;1&lt;/b&gt;0_0&lt;b&gt;1&lt;/b&gt;00 0000_&lt;b&gt;1&lt;/b&gt;00&lt;b&gt;1&lt;/b&gt;
AND maskFg
 0000_0000_0000_0000_0000_0000_&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;_&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;_&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;_&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;_&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;_&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;_0000_0000_0000_0000
=
 0000_0000 0000_0000 0000_0000 0000_0000 0000_0000 0000_000&lt;b&gt;1&lt;/b&gt; 0000_0000 0000_0000
 ^         ^         ^         ^         ^         ^         ^         ^
64        56        48        40        32        24        16         8
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;After the AND operation we’re left with just the 24 bits we care about, and
everything else is set to &lt;code&gt;0&lt;/code&gt;. To get a normal number from this we need to shift
the bits to the right with &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;
&lt;b&gt;1&lt;/b&gt;0&lt;b&gt;1&lt;/b&gt;0 &amp;gt;&amp;gt; 1 = 0&lt;b&gt;1&lt;/b&gt;0&lt;b&gt;1&lt;/b&gt;    All bits shifted one position to the right.
&lt;b&gt;1&lt;/b&gt;0&lt;b&gt;1&lt;/b&gt;0 &amp;gt;&amp;gt; 2 = 00&lt;b&gt;1&lt;/b&gt;0    Shift two, note that one bit gets discarded.
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Instead of &lt;code&gt;&amp;gt;&amp;gt; 16&lt;/code&gt; you can also subtract &lt;code&gt;65535&lt;/code&gt; (a 16-bit number): &lt;code&gt;(c &amp;amp;
maskFg) - 65535&lt;/code&gt;. The end result is the same, but bit shifts are much easier to
reason about in this context.&lt;/p&gt;

&lt;p&gt;We repeat this for the background colour (except that we shift everything 40
bits to the right). The background is actually a bit easier since we don’t need
to AND anything to clear bits, as all the bits to the right will just be
discarded:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;&lt;code&gt;cc := c &amp;gt;&amp;gt; ColorOffsetBg
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;For 256 and “true” 24-bit colours we do the same, except that we need to send
different escape codes for them, which is a detail that doesn’t really matter
for this explainer about bitmasks.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;To set the background colour we use the &lt;code&gt;Bg()&lt;/code&gt; function to transforms a
foreground colour to a background one. This avoids having to define &lt;code&gt;BgCyan&lt;/code&gt;
constants like Fatih’s library, and makes working with 256 and true colour
easier.&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;full ft-go&quot;&gt;&lt;code&gt;const (
    colorMode16Fg   = 0b00000_0100_0000_0000
    colorMode16Bg   = 0b0010_0000_0000_0000

    maskFg          = 0b00000000_00000000_00000000_11111111_11111111_11111111_00000000_00000000
)

func Bg(c uint64) uint64 {
    if c &amp;amp; colorMode16Fg != 0 {
        c = c ^ colorMode16Fg | colorMode16Bg
    }
    return (c &amp;amp;^ maskFg) | (c &amp;amp; maskFg &amp;lt;&amp;lt; 24)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;First we check if the foreground colour flags is set; if it is then move that
bit to the corresponding background flag.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;|&lt;/code&gt; is the OR operator; this works like &lt;code&gt;||&lt;/code&gt; except on individual bits like in
the above example for &lt;code&gt;&amp;amp;&lt;/code&gt;. Note that unlike &lt;code&gt;||&lt;/code&gt; it won’t stop if the first
condition is false/0: if any of the two values are &lt;code&gt;1&lt;/code&gt; the end result will be
&lt;code&gt;1&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;
0 OR 0 = 0      both false
0 OR 1 = 1      second value is true, so end result is true
1 OR 0 = 1      first value is true
1 OR 1 = 1      both true

  00&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;
| 0&lt;b&gt;1&lt;/b&gt;0&lt;b&gt;1&lt;/b&gt;
= ――――
  0&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&lt;code&gt;^&lt;/code&gt; is the “exclusive or”, or XOR, operator. It’s  similar to OR except that it
only outputs &lt;code&gt;1&lt;/code&gt; if exactly one value is &lt;code&gt;1&lt;/code&gt;, and not if both are:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre&gt;
0 XOR 0 = 0      both false
0 XOR 1 = 1      second value is true, so end result is true
1 XOR 0 = 1      first value is true
1 XOR 1 = 0      both true, so result is 0

  00&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;
^ 0&lt;b&gt;1&lt;/b&gt;0&lt;b&gt;1&lt;/b&gt;
= ――――
  0&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;0
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Putting both together, &lt;code&gt;c ^ colorMode16Fg&lt;/code&gt; clears the foreground flag and &lt;code&gt;|
colorMode16Bg&lt;/code&gt; sets the background flag.&lt;/p&gt;

&lt;p&gt;The last line moves the bits from the foreground colour to the background
colour:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-go&quot;&gt;&lt;code&gt;return (c &amp;amp;^ maskFg) | (c &amp;amp; maskFg &amp;lt;&amp;lt; 24)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&lt;code&gt;&amp;amp;^&lt;/code&gt; is “AND NOT”: these are two operations: first it will inverse the right
side (“NOT”) and then ANDs the result. So in our example the &lt;code&gt;maskFg&lt;/code&gt; value is
inversed:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;full&quot;&gt;
 0000_0000_0000_0000_0000_0000_1111_1111_1111_1111_1111_1111_0000_0000_0000_0000
&lt;b&gt;NOT&lt;/b&gt;
 1111_1111_1111_1111_1111_1111_0000_0000_0000_0000_0000_0000_1111_1111_1111_1111
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;We then used this inversed &lt;code&gt;maskFg&lt;/code&gt; value to clear the foreground colour,
leaving everything else intact:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;full&quot;&gt;
 1111_1111_1111_1111_1111_1111_0000_0000_0000_0000_0000_0000_1111_1111_1111_1111
&lt;b&gt;AND&lt;/b&gt;
 0000_0000 0000_0000 0000_0&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;0 0000_0000 0000_0000 0000_000&lt;b&gt;1&lt;/b&gt; 00&lt;b&gt;1&lt;/b&gt;0_0&lt;b&gt;1&lt;/b&gt;00 0000_&lt;b&gt;1&lt;/b&gt;00&lt;b&gt;1&lt;/b&gt;
&lt;b&gt;=&lt;/b&gt;
 0000_0000 0000_0000 0000_0&lt;b&gt;1&lt;/b&gt;&lt;b&gt;1&lt;/b&gt;0 0000_0000 0000_0000 0000_0000 00&lt;b&gt;1&lt;/b&gt;0_0&lt;b&gt;1&lt;/b&gt;00 0000_&lt;b&gt;1&lt;/b&gt;00&lt;b&gt;1&lt;/b&gt;
 ^         ^         ^         ^         ^         ^         ^         ^
64        56        48        40        32        24        16         8
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;C and most other languages don’t have this operator and have &lt;code&gt;~&lt;/code&gt; for NOT (which
Go doesn’t have), so the above would be &lt;code&gt;(c &amp;amp; ~maskFg)&lt;/code&gt; in most other languages.&lt;/p&gt;

&lt;p&gt;Finally, we set the background colour by clearing all bits that are not part of
the foreground colour, shifting them to the correct place, and ORing this to get
the final result.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;I skipped a number of implementation details in the above example for clarity,
especially for people not familiar with Go. &lt;a href=&quot;https://github.com/arp242/zli/blob/main/color.go&quot;&gt;The full code is of course
available&lt;/a&gt;. Putting all of
this together gives a fairly nice API IMHO in about 200 lines of code which
mostly avoids boilerplateism.&lt;/p&gt;

&lt;p&gt;I only showed the 16-bit colours in the examples, in reality most of this is
duplicated for 256 and true colours as well. It’s all the same logic, just with
different values. I also skipped over the details of terminal colour codes, as
this article isn’t really about that.&lt;/p&gt;

&lt;p&gt;In many of the above examples I used binary literals for the constants, and this
seemed the best way to communicate how it all works for this article. This isn’t
necessarily the best or easiest way to write things in actual code, especially
not for such large numbers. In the actual code it looks like:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-go&quot;&gt;&lt;code&gt;const (
    ColorOffsetFg = 16
    ColorOffsetBg = 40
)

const (
    maskFg Color = (256*256*256 - 1) &amp;lt;&amp;lt; ColorOffsetFg
    maskBg Color = maskFg &amp;lt;&amp;lt; (ColorOffsetBg - ColorOffsetFg)
)

// Basic terminal attributes.
const (
    Reset Color = 0
    Bold  Color = 1 &amp;lt;&amp;lt; (iota - 1)
    Faint
    // ...
)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Figuring out how this works is left as an exercise for the reader :-)&lt;/p&gt;

&lt;p&gt;Another thing that might be useful is a little helper function to print a number
as binary; it helps visualise things if you’re confused:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-go&quot;&gt;&lt;code&gt;func bin(c uint64) {
    reBin := regexp.MustCompile(`([01])([01])([01])([01])([01])([01])([01])([01])`)
    reverse := func(s string) string {
        runes := []rune(s)
        for i, j := 0, len(runes)-1; i &amp;lt; j; i, j = i+1, j-1 {
            runes[i], runes[j] = runes[j], runes[i]
        }
        return string(runes)
    }
    fmt.Printf(&quot;%[2]s → %[1]d\n&quot;, c,
        reverse(reBin.ReplaceAllString(reverse(fmt.Sprintf(&quot;%064b&quot;, c)),
            `$1$2$3${4}_$5$6$7$8 `)))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;I put a slighly more advanced version of this at
&lt;a href=&quot;https://pkg.go.dev/zgo.at/zstd/zfmt#Binary&quot;&gt;zgo.at/zstd/zfmt.Binary&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can also write a little wrapper to make things a bit easier:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-go&quot;&gt;&lt;code&gt;type Bitflag64 uint64 uint64

func (f Bitflag64) Has(flag Bitflag64) bool { return f&amp;amp;flag != 0 }
func (f *Bitflag64) Set(flag Bitflag64)     { *f = *f | flag }
func (f *Bitflag64) Clear(flag Bitflag64)   { *f = *f &amp;amp;^ flag }
func (f *Bitflag64) Toggle(flag Bitflag64)  { *f = *f ^ flag }
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;If you need more than 64 bits then not all is lost; you can use &lt;code&gt;type thingy
[2]uint64&lt;/code&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Here’s an example where I did it wrong:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-go&quot;&gt;&lt;code&gt;type APITokenPermissions struct {
    Count      bool 
    Export     bool 
    SiteRead   bool 
    SiteCreate bool 
    SiteUpdate bool 
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;This records the permissions for an API token the user creates. Looks nice, but
how do you check that &lt;em&gt;only&lt;/em&gt; &lt;code&gt;Count&lt;/code&gt; is set?&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-go&quot;&gt;&lt;code&gt;if p.Count &amp;amp;&amp;amp; !p.Export &amp;amp;&amp;amp; !p.SiteRead &amp;amp;&amp;amp; !p.SiteCreate &amp;amp;&amp;amp; !p.SiteUpdate { .. }
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Ugh; not very nice, and neither is checking if multiple permissions are set:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-go&quot;&gt;&lt;code&gt;if perm.Export &amp;amp;&amp;amp; perm.SiteRead &amp;amp;&amp;amp; perm.SiteCreate &amp;amp;&amp;amp; perm.SiteUpdate { .. }
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Had I stored it as a bitmask instead, it would have been easier:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-go&quot;&gt;&lt;code&gt;// Only Count is set.
if perm &amp;amp;^ Count == 0 { .. }

// Everything in permSomething is set.
const permSomething = perm.Export | perm.SiteRead | perm.SiteCreate | perm.SiteUpdate
if perm &amp;amp; permSomething == 0 { .. }
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;No one likes functions with these kind of signatures either:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-go&quot;&gt;&lt;code&gt;f(false, false, true)
f(true, false, true)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;But with a bitmask things can look a lot nicer:&lt;/p&gt;

&lt;div class=&quot;pre-wrap&quot;&gt;
&lt;pre class=&quot;ft-go&quot;&gt;&lt;code&gt;const (
    AddWarpdrive   = 0b001
    AddTractorBeam = 0b010
    AddPhasers     = 0b100
)

f(AddPhasers)
f(AddWarpdrive | AddPhasers)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;style&gt;b { color: red }&lt;/style&gt;

</description>
    </item>
  </channel>
</rss>
