<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>bomberfish's blog</title>
        <link>https://bomberfish.ca/blog/</link>
        <description>various assorted thoughts and ramblings.</description>
        <lastBuildDate>Thu, 09 Apr 2026 05:18:09 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[Keeping AI slop out of this blog]]></title>
            <link>https://bomberfish.ca/blog/ai-use-in-this-blog</link>
            <guid isPermaLink="false">https://bomberfish.ca/blog/ai-use-in-this-blog</guid>
            <pubDate>Sun, 29 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[My stance on using AI on bomberfish.ca.]]></description>
            <content:encoded><![CDATA[<link rel="preload" as="image" href="https://bomberfish.ca/blog-images/img2181.jpg"/><p><img src="https://bomberfish.ca/blog-images/img2181.jpg" alt=""/></p>
<cap><subt><span class="material-symbols">info</span>Image credit: Me.</subt><br/><subt>Taken on an iPhone 13 Pro Max. No further editing, apart from cropping.</subt></cap>
<p>Okay, that was kind of a lie. But let me explain.</p>
<p><strong>I don&#x27;t have anything against AI tools.</strong> On this blog, I occasionally use tools such as Apple&#x27;s proofread feature, and other tools such as Kagi Doc (which is in a private preview as of the writing of this post), to help polish an already-written post. I&#x27;ve also used AI to help create some of the features and elements on bomberfish.ca. Most recently, I used AI to create the animated interactive background (based on a set of guidelines that I set out), and the Google Photo Sphere viewer component used in the <a href="https://bomberfish.ca/blog/google-glass-retrospective" class="router-link">Google Glass post</a> (although, that actually wraps <a href="https://photo-sphere-viewer.js.org/">an existing library</a>). It&#x27;s also really useful for menial tasks such as <a href="https://github.com/bomberfish/bomberfish.ca/commit/2cdeb7bf22040e5c54f41c7c2f604f1433040869">cleaning up stylesheets</a> and <a href="https://github.com/bomberfish/bomberfish.ca/commit/84520f9b69aca1e624e395498fe85df487dc30a0">migrating library versions</a>.</p>
<p>But, I would like to make some commitments here.</p>
<p>I will <em>never</em> fully AI-generate any text or image on my blog. If I refine a post using AI tools, I will commit both the unedited and edited versions to the git repository. You can see an example of such a diff <a href="https://github.com/bomberfish/bomberfish.ca/commit/31f52ec4b4ac9f5940b48da0bc9fae6bba8f0d1d">here</a>. If an image is enhanced with AI-powered tools (such as Topaz Photo for upscaling), I will disclose it as such in the image caption.</p>
<p>I&#x27;m not going to outsource my creativity to a robot. This blog is a place for me to express myself, and I intend on keeping it that way. It has been, and will remain, a slop-free zone.</p>]]></content:encoded>
            <enclosure url="https://bomberfish.ca/blog-images/img2181.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Google Glass was ahead of it]]></title>
            <link>https://bomberfish.ca/blog/google-glass-retrospective</link>
            <guid isPermaLink="false">https://bomberfish.ca/blog/google-glass-retrospective</guid>
            <pubDate>Fri, 27 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Some thoughts on a device that came out too soon.]]></description>
            <enclosure url="https://bomberfish.ca/blog-images/glass-pano.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[I should post more]]></title>
            <link>https://bomberfish.ca/blog/postmore</link>
            <guid isPermaLink="false">https://bomberfish.ca/blog/postmore</guid>
            <pubDate>Sun, 15 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Thoughts on post frequency.]]></description>
            <content:encoded><![CDATA[<link rel="preload" as="image" href="https://bomberfish.ca/blog-images/lakeview-topaz-upscale-3x-grain.jpg"/><p><img src="https://bomberfish.ca/blog-images/lakeview-topaz-upscale-3x-grain.jpg" alt=""/></p>
<cap><subt><span class="material-symbols">info</span>Image credit: Sun Microsystems/Oracle. Desktop background included with Solaris 10.</subt><br/><subt>Locally AI-upscaled and cleaned up with Topaz Photo.</subt></cap>
<p>I really want to start posting on this blog more often. I have a slightly longer post in the works right now, and a whole pile of discards that I couldn&#x27;t get to be the length I wanted.</p>
<p>But then again, I could just start writing more short posts so I don&#x27;t have to keep to that high standard.</p>]]></content:encoded>
            <enclosure url="https://bomberfish.ca/blog-images/lakeview-topaz-upscale-3x-grain.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Changelog 1: New Year, New Look]]></title>
            <link>https://bomberfish.ca/blog/site-updates-january-2026</link>
            <guid isPermaLink="false">https://bomberfish.ca/blog/site-updates-january-2026</guid>
            <pubDate>Thu, 08 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A look at all the new features introduced in version 9 of bomberfish.ca.]]></description>
            <content:encoded><![CDATA[<link rel="preload" as="image" href="https://bomberfish.ca/blog-images/site-v9.jpg"/><p><img src="https://bomberfish.ca/blog-images/site-v9.jpg" alt="A screenshot of this website."/></p>
<p>It&#x27;s 2026, and it&#x27;s time I redesigned this website for the 4th time since last fall. Let&#x27;s take a look at what&#x27;s new.</p>
<blockquote>
<p>I&#x27;ve decided to start doing update logs on the website now. Stay tuned for future updates!</p>
</blockquote>
<h2>New layout</h2>
<p>In 9.0, I opted for a much cleaner and more compact layout, with a centered content box. On mobile, the box occupies the entire screen for visual neatness. It also adapts well to smaller displays and older browsers.</p>
<h2>Refined visual language</h2>
<p>Since 9.0 is mainly a layout overhaul, the general visual language of 8.x has been mostly left intact, with tweaks to how elevated surface colors are used. More iconography is present throughout the site, and the appearance of things like borders has been consolidated.</p>
<h2>Adjusted colour palette</h2>
<p>The palette from 8.x has been tweaked a considerable amount, being somewhat darkened and with the hue shifted to match that of <a href="https://bomberfish.ca/me.png" target="_blank">my new profile picture</a>. Additionally, I&#x27;ve refractored how certain colors are computed, adding more flexibility to the palette. You can even temporarily hue-rotate the palette using the slider in the &quot;<a href="https://bomberfish.ca/siteinfo" class="router-link">About This Website</a>&quot; page. Note that it does not persist on a full reload.</p>
<h2>Updated background</h2>
<p>Gone is the slow-to-load dithered background from 7.x and 8.x. In 9.1 (which was released the day after 9.0), it&#x27;s been replaced with the grid from late 6.x, which in it of itself it a modified version of the dot grid of 5.x. A couple of the parameters were tweaked to match the vibe of version 9, but it remains largely unchanged from its 6.2 incarnation.</p>
<h2>Overhauled projects page</h2>
<p>Keeping with the theme of bringing back older design decisions, the projects page&#x27;s grid layout from 6.x and 7.x has been brought back in 9.2 (released 2 days after 9.0). Additionally, support has been added for the <a href="https://webkit.org/blog/17660/introducing-css-grid-lanes/">finalized version of CSS masonry</a>, meaning an even more beautiful layout will be coming soon to a modern browser near you!</p>
<h2>Performance and compatibility</h2>
<p>I&#x27;ve been hard at work improving performance. That&#x27;s why I&#x27;m glad to announce that bomberfish.ca has achieved a perfect score across all categories in Lighthouse desktop performance! Font loading was optimized to lazily load web fonts, with the page using the system&#x27;s installed version of the regular Helvetica font and swapping Helvetica Now in when it loads. I&#x27;ve also removed various unused assets, and while the CSS bundle size has risen since last time, I was able to increase browser compatibility. I can now confirm that the site <a href="https://wetdry.world/@fish/115858737045320286">functions with a near-perfect layout</a> on Firefox 10!</p>]]></content:encoded>
            <enclosure url="https://bomberfish.ca/blog-images/site-v9.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[On brainrot, intentionality, and the physical cellphone keyboard]]></title>
            <link>https://bomberfish.ca/blog/on-brainrot-and-blackberries</link>
            <guid isPermaLink="false">https://bomberfish.ca/blog/on-brainrot-and-blackberries</guid>
            <pubDate>Sat, 27 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Or, how physically segmenting your digital life can help un-fry your brain]]></description>
            <content:encoded><![CDATA[<link rel="preload" as="image" href="https://bomberfish.ca/blog-images/brainrot-cover.jpg"/><link rel="preload" as="image" href="https://bomberfish.ca/blog-images/q10.jpg"/>

<p><img src="https://bomberfish.ca/blog-images/brainrot-cover.jpg" alt="A set of devices laid out on a table. In the center is a BlackBerry Curve, with a PSP, a JVC camcorder, a 5th gen iPod, and a Casio watch around it"/></p>
<cap><subt><span class="material-symbols">info</span>Image credit: Me.</subt><br/><subt>Canon EOS 550D + 22mm f/4.5 | 1/60s | ISO 800 | <a href="https://bomberfish.ca/blog-images/brainrot-cover-fullsize.jpg">View full-size image.</a></subt></cap>
<blockquote>
<p>This post originally started as something where I <a href="https://wetdry.world/@fish/115722187976177074">talked about my experience</a> using BlackBerry&#x27;s software keyboard on Android, but soon morphed into a discussion about reclaiming your attention span in a world where everything seems to be hand over fist in trying to take a slice of it.</p>
</blockquote>
<p>I have a pretty weird hobby: collecting old BlackBerry phones. All my friends call me crazy, but on the other hand I&#x27;ve talked to many former RIM employees who&#x27;ve assured me otherwise. Nonetheless, there&#x27;s been one thing that&#x27;s stuck with me: those keyboards were goddamn excellent.</p>
<h2>My experiences</h2>
<p>In the past few months, I&#x27;ve personally been trying to fix my attention span somewhat by segmenting my digital life. I usually carry a mobile phone for communication, a camcorder for taking photos and video, an iPod for listening to music, and a PSP for playing video games. This makes it so moving between digital tasks requires you to take your attention off the current device, physically swap devices, and then finally refocus on the new task. This is unlike the current paradigm, where everything you do is an app on your phone, and you end up doing multiple things at once and keeping multiple tasks on your mind at once (which does get rather fatiguing!)</p>


<p>I did attempt to daily drive a BlackBerry Q10 from my collection for about a week, but dealing with the sheer pile of workarounds needed to make modern messengers usable quickly got tedious. I have since decided to concede that part of my life and switch back to a mainstream vertical phone, but I do have a pre-order placed for the <a href="https://linkapus.com/products/q25-pro-full-device">Zinwa Technologies Q25</a> (a BlackBerry Classic retrofitted with a modern budget Android mainboard) and it should be arriving in late January of 2026. Earlier backers have already received their units, and they seem to be going pretty well quality-wise, so expect a full review on this blog when my unit arrives.</p>
<p>Now, let&#x27;s get to where it all ties in with the physical form factor of the keyboard phone.</p>
<h2>Form factor is everything</h2>

<p class="image-item"><p><img src="https://bomberfish.ca/blog-images/q10.jpg" alt="A BlackBerry Q10"/></p><cap><subt><span class="material-symbols">info</span>Image credit: Me.</subt><br/><subt>Canon EOS 550D + 17mm f/4.5 | 1/60s | ISO 800 | <a href="https://bomberfish.ca/blog-images/q10-fullsize.jpg">View full-size image.</a></subt></cap></p>
<p>This is a BlackBerry Q10, released in 2013. It has a 3.1 inch 720p AMOLED, 2GB of RAM, 16GB of storage, and a 35-key QWERTY keyboard.</p>
<p>The first thing that you&#x27;ll notice is that the aspect ratio is extremely inconvenient for things like short-form vertical video, but very useful for doing things like writing emails. The Q10 specifically has a square screen, but the majority of devices of this form factor used horizontal screens typically at a 4:3 aspect ratio.</p>
<p>Usually on a mainstream, vertically-oriented phone, you get the same usable amount of screen space when composing text as on a keyboard phone. The key part is when you&#x27;re composing text. Due to, well, consumer wants, modern phones are content consumption machines first and foremost. You scroll X, TikTok, and Instagram vertically, and you occasionally rotate the phone sideways to watch traditional horizontal video. The originally-intended purpose of the device, to make phone calls and send messages to people, is demoted to a secondary function.</p>
<p>Apart from the attention span bit, physical keyboards on phones have other benefits. One example I&#x27;ve heard is that it&#x27;s much easier to type while busy doing something like driving <subt>(for legal reasons I don&#x27;t condone this bla bla bla).</subt> Additionally, a hallmark feature of some phones of that era (especially BlackBerry devices) is the ability to navigate the operating system through a set of hardware navigation buttons in addition to a trackball, optical trackpad, or rotary encoder. This enables the use of the device with gloves, an absolute godsend in my home country of Canada, where in the winter your hands <em>will</em> get cold and numb after a minute of typing on a touchscreen phone.</p>
<h2>Conclusion</h2>
<p>Maybe it is time to make a returnal to how we used to do it. Maybe we will be better off doing it. But there&#x27;s only one way to find out.</p>]]></content:encoded>
            <enclosure url="https://bomberfish.ca/blog-images/brainrot-cover.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Dude, Where’s My Touch Bar?]]></title>
            <link>https://bomberfish.ca/blog/touchbar</link>
            <guid isPermaLink="false">https://bomberfish.ca/blog/touchbar</guid>
            <pubDate>Mon, 22 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[In defense of Apple’s most hated hardware feature.]]></description>
            <content:encoded><![CDATA[<link rel="preload" as="image" href="https://bomberfish.ca/blog-images/touchbar.gif"/><p><img src="https://bomberfish.ca/blog-images/touchbar.gif" alt="MacBook Pro with Touch Bar"/></p>
<cap><subt><span class="material-symbols">info</span>Image Credit: <a href="https://www.apple.com/ca/newsroom/2020/05/apple-updates-13-inch-macbook-pro-with-magic-keyboard-double-the-storage-and-faster-performance/">Apple</a></subt></cap>
<p>Before I moved onto my current 16&quot; MacBook Pro with an M4 Pro, I used to daily drive the 4-port variant of the 2020 13&quot; MacBook Pro. Being the last model of Intel Mac ever produced, it obviously became a hot mess (literally! the idle temperature was 80ºC) which is why I made the move. One thing I did miss however, was the Touch Bar.</p>
<p>For the uninitiated, from 2016 to 2020, certain Mac notebooks ditched the standard function row for a small touchscreen OLED, which could change its software buttons and controls based on the context of the current app. This choice quickly became a controversial one with many power users complaining that it ruined muscle memory. This concern was actually addressed in the Touch Bar&#x27;s original implementation, with an always-present software escape key (which was later replaced with a physical one in the 2019 and 2020 models to create a very nice hybrid approach) and the ability to display the classic function keys instead of the app-dependent controls.</p>
<p>Now, I actually used the app-dependent mode, and I actually quite liked it! It was really useful in apps that supported it, and didn&#x27;t really get in the way when I didn&#x27;t need it. There was an option to change the mode on a per-app basis, so when I used an app that didn&#x27;t support the Touch Bar and made heavy use of the function keys, I could force it to display those keys all the time.</p>
<p>In the end, I did end up adjusting to the return to hardware keys on my new MacBook. But something inside me still yearns for that, even if unorthodox, cool little differentiating feature.</p>]]></content:encoded>
            <enclosure url="https://bomberfish.ca/blog-images/touchbar.gif" length="0" type="image/gif"/>
        </item>
        <item>
            <title><![CDATA[Why it might be a good idea to pull the brakes on AI]]></title>
            <link>https://bomberfish.ca/blog/ai</link>
            <guid isPermaLink="false">https://bomberfish.ca/blog/ai</guid>
            <pubDate>Wed, 03 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[My opinions on the current pickle the world finds itself in.]]></description>
            <content:encoded><![CDATA[<link rel="preload" as="image" href="https://bomberfish.ca/blog-images/ai-dep.gif"/><p><img src="https://bomberfish.ca/blog-images/ai-dep.gif" alt="The XKCD &quot;Dependencies&quot; panel, with what appears to be a jack of sorts wedged in the middle, causing half of the stack to tilt precariously to the left."/></p>
<p>I&#x27;d like to preface this by saying that I am by no means qualified or deeply knowledgeable in the topic at hand. If you came here expecting a nuanced and well-researched take on the current pace of AI development, <u>it&#x27;s best you turn back <strong>now</strong></u>. I&#x27;m just a moody teenager with a strong opinion and nowhere to voice it.</p>
<p>And if you were wondering, this post was never touched by clanker hands (tool calls? shoggoth tentacles?) <br/>
What you&#x27;re reading is 100% human-created.</p>
<p>With all of that out of the way, let&#x27;s get started.</p>
<h2>Some background</h2>
<p>The year is (late) 2025. &quot;Attention Is All You Need&quot; was published 8 years ago, OpenAI released <code>gpt-3.5-turbo</code> 3 years ago (despite a year earlier refusing to release GPT-3 due to concerns around misinformation!), and we currently live in an absolute hellscape of an AI race. We&#x27;re continually getting the new &quot;best model ever&quot; on a weekly basis. Billions are being poured into anything with &quot;AI&quot; in the pitch deck, and if it all goes south, we get a second Great Depression. Not to mention, everyone&#x27;s racing to build hyperscale datacenters and GPU farms to train these models and do inference with them, which has definitely had quite a few consequences.</p>
<p>And you&#x27;re telling me we haven&#x27;t stopped to think for just a minute? Let&#x27;s just go over a <em>few</em> of the adverse effects:</p>
<ul>
<li><strong>Layoffs</strong>: No, this doesn&#x27;t mean that LLMs can replace people&#x27;s jobs. It means that managers <em>think</em> that they can.</li>
<li><strong>Environmental Impact</strong>: You&#x27;ve heard this one before, but it&#x27;s still true. Those aforementioned hyperscalers use lots of electricity, and all of that has to come from somewhere. Let&#x27;s just say, renewables aren&#x27;t quite our main power source yet, and the noise made from their cooling systems is an absolute nuisance they are to the towns they&#x27;re built near.</li>
<li><strong>Everyone forgets how to think for themselves</strong>: Studies show that making the machine think for you does in fact make you think less.</li>
<li><strong>Misalignment</strong>: If we don&#x27;t stop and make sure the machine is aligned with human values, we&#x27;ll end up building Skynet.</li>
<li><strong>Misuse out the wazoo</strong>: ...wait. That deserves an entire section of its own!</li>
</ul>
<h2>How other people are using AI to make the world a worse place</h2>
<p>Alright. First of all, let&#x27;s pick off the easiest thing: fabricated information. As I mentioned before, OpenAI themselves were worried about the proliferation of fake news when GPT-3 was created (that didn&#x27;t stop them from releasing it anyways!).</p>
<p>The obvious things are fake news stories, backed up by AI-generated images, video, and audio. But that only scratches the surface. Take, for example, scams. Now, a random fella in Nigeria can fake the voice and face of one of your relatives and demand a ransom. Do you really want that future? Your grandparents– hell, even your parents don&#x27;t stand a chance!</p>
<p>Now, for something a bit more serious. Various private companies (e.g. Palantir, Flock Safety) are using AI-based solutions to enhance their (rather creepy!) surveillance platforms. It&#x27;s only a matter of time before we slip into the Watch Dogs timeline, and it probably won&#x27;t be as cool as the games.</p>
<p>Anyway, now that I&#x27;m getting a bit tired of writing, let&#x27;s go over a few common arguments against the slowing of AI development in rapid-fire succession.</p>
<h2>&quot;If we stopped, China would beat us!&quot;</h2>
<p>Bullshit. If humanity was able to work out nuclear nonproliferation in the 60s, I can guarantee we can agree to put down the H100s for a few years and work out the ethics of this Pandora&#x27;s box we&#x27;ve opened and maybe regulate it a bit.</p>
<p>Also, the Cold War is over, ya damn boomer.</p>
<h2>&quot;You&#x27;re just a luddite!&quot;</h2>
<p>For crying out loud, I&#x27;m not calling for some kind of Butlerian Jihad against all autocorrect. I said I just wanted to take a short break.</p>
<h2>&quot;Regulation stifles innovation!&quot;</h2>
<p>Yeah, apply the same logic to something like cars. Would it really be a great idea to have every road be an Autobahn with no traffic lights or signs? Hopefully <em>then</em> you can understand why putting regulations on AI is a good idea.</p>
<h2>Conclusion</h2>
<p>All in all, I really think we should just take a breather, y&#x27;know? Figure out if it&#x27;s really worthwhile to go all in on the tech, and maybe put some rules and regulations in place to tame the beast we&#x27;ve created.</p>
<p>But of course, that&#x27;ll never happen, because number must go up <em>now</em>.</p>]]></content:encoded>
            <enclosure url="https://bomberfish.ca/blog-images/ai-dep.gif" length="0" type="image/gif"/>
        </item>
        <item>
            <title><![CDATA[What happened to RSS?]]></title>
            <link>https://bomberfish.ca/blog/rss</link>
            <guid isPermaLink="false">https://bomberfish.ca/blog/rss</guid>
            <pubDate>Sun, 30 Nov 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[A look at RSS’s rise, decline, and potential resurgence as people seek a simpler, curated web.]]></description>
            <content:encoded><![CDATA[<link rel="preload" as="image" href="https://bomberfish.ca/blog-images/rss_cover.jpg"/><p><img src="https://bomberfish.ca/blog-images/rss_cover.jpg" alt="A BlackBerry Storm2 displaying this blog&#x27;s RSS feed via its&#x27; built-in reader"/></p>
<cap><subt><span class="material-symbols">info</span>Image credit: Me.</subt><br/><subt>Canon EOS 550D + 50mm f/1.4 | 1/125s | ISO 100 | <a href="https://bomberfish.ca/blog-images/rss-cover-fullsize.jpg">View full-size image.</a></subt></cap>
<blockquote>
<p>Note: I do plan on posting on this blog more often now. I&#x27;m not exactly good at it right now, but it&#x27;ll get better over time.</p>
</blockquote>
<p>I was recently working on adding an <a href="https://bomberfish.ca/feed.xml">RSS feed</a> to this newly-moved blog, and I couldn&#x27;t help but notice that less and less sites had RSS feeds as time went on. Now, I personally grew up mostly in a post-RSS world, but I do remember the tail end of its popularity.</p>
<h2>Origins</h2>
<p>The early internet was very sparse, as search engines were far less advanced as they are today. I mean, think back to the last time you actually used a bookmark instead of either using Google or letting your browser&#x27;s history autocomplete do most of the work. RSS was created so you could keep a tab on your favourite blog, and its ability to attach files even gave birth to podcasting! (Whether that was a good or bad thing is up to you.) Nonetheless, RSS was popular enough that most blogs used it and most blogging software supported it.</p>
<h2>Decline</h2>
<p>Social media has been around for around as long as RSS has been. In fact, many social media platforms offered RSS feeds, so you could check up on what your friends or the people you followed were up to. This all changed with the advent of the algorithmic timeline, shifting the focus from connecting with others to consuming content. Many social media platforms quietly removed their RSS feeds in the early 2010s.</p>
<p>The decline can also be attributed to people starting to get their news exclusively from social media platforms and other algorithmic news feeds such as Flipboard or Google News. This meant that people no longer needed to subscribe to individual blogs or news sites, as they had all their information fed to them by the machine.</p>
<h2>Rebirth?</h2>
<p>From the late 2010s, and especially in the mid 2020s, there&#x27;s been a resurgence in desire for a return to a simpler web. To put it simply, people are tired of the current meta. With the advent of LLMs skewing the share of human to nonhuman content on social media, people yearn for a time when the web was a place for humans to connect with each other, not slop farms. RSS perfectly fits into this vision, because it allows users to curate their own experience on the net without relying on algorithms dictating what they should see.</p>
<p>So, who knows? Maybe we&#x27;ll all be using RSS again in a couple years.</p>]]></content:encoded>
            <enclosure url="https://bomberfish.ca/blog-images/rss_cover.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[How I dumped a rare version of BlackBerry 10]]></title>
            <link>https://bomberfish.ca/blog/z10</link>
            <guid isPermaLink="false">https://bomberfish.ca/blog/z10</guid>
            <pubDate>Fri, 28 Nov 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Dumping the NAND flash from a BlackBerry Z10 not meant for the public’s eyes.]]></description>
            <content:encoded><![CDATA[<link rel="preload" as="image" href="https://bomberfish.ca/blog-images/z10_ebay.jpg"/><link rel="preload" as="image" href="https://bomberfish.ca/blog-images/z10_link.jpeg"/><link rel="preload" as="image" href="https://bomberfish.ca/blog-images/z10_dev.jpeg"/><p><img src="https://bomberfish.ca/blog-images/z10_ebay.jpg" alt="eBay listing"/></p>
<cap><subt><span class="material-symbols">info</span>Image credit: Me.</subt></cap>
<p>I recently came across an interesting listing on eBay. At first I thought it was a Dev Alpha B, a device sent out to developers before the release of the BlackBerry 10 platform. But upon closer inspection, I realized it more closely resembled a retail Z10, albeit with markings denoting its status as a non-consumer unit. So, I ordered it.</p>
<h2>Initial impressions</h2>
<p>The device arrived after a few weeks (as to be expected with international shipping), and the first thing I noticed is that when powering it on, it first showed the initial boot splash, but the screen went dark immediately after with no signs of life afterwards. After a little bit, I decided to press a few buttons, and to my surprise, I accidentally activated the screen reader. This means that the phone booted fine, but some kind of bug prevented it from initializing the display. I then connected it to an older computer and started up BlackBerry Link, and the software recognized the device.</p>
<blockquote>
<p>Update (2025-11-31):
It turns out that the display backlight was off. Oops.</p>
</blockquote>
<blockquote>
<p>Update (2026-01-03):
I managed to figure out that running <code>displayctl power_off; displayctl power_on</code> through the exposed telnet root shell brought the display up manually. I might do a quick follow up post later about all the interesting internal goodies and whatnot I was able to find.</p>
</blockquote>
<p><img src="https://bomberfish.ca/blog-images/z10_link.jpeg" alt="Z10 in BlackBerry Link"/></p>
<cap><subt><span class="material-symbols">info</span>Image credit: Me.</subt></cap>
<p>The software version however didn&#x27;t seem right. Instead of starting with &quot;10.x.x&quot;, the version number was &quot;127.0.1.7295&quot;. I decided to ask around in the <a href="https://discord.gg/s5jnytyvRS">Lunar Project Discord server</a>, and one member suggested running RIM&#x27;s Command-Line Programmer, also known as cfp.exe. I did so, and some of the output was interesting:</p>
<pre><code>Bootrom Version:       5.35.0.33
    Hardware ID:       0x8500240A RIM BlackBerry Device
    HW ID Override:    0x8500240A (OSTypes: 0x00000010)
    Hardware OS ID:    0x051D0001
    BR ID:             0xFF009000
    Supported Bands:   0xFFFF                    ***** WARNING *****
    Metrics Version:   6.43
    Build Date:        Feb  7 2013
    Build Time:        13:29:07
    Build User:        ec_agent
    Security:          Disabled
</code></pre>
<pre><code>OS Version:            127.0.1.7295 DEV
    Hardware ID:       0x051D0001 RIM BlackBerry Device
    Metrics Version:   3.18
    FS Code Version:   0.0.0.0
    Package Version:   &quot;&quot;
    Build Date:        Sep 30 2013
    Build Time:        13:07:49
    Build User:        ec_agent
</code></pre>
<p>Note the &quot;Security: Disabled&quot; and the &quot;DEV&quot; suffix on the OS version. This meant that it was an internal OS version that had many security features disabled. Someone else suggested trying to connect to the device through the RNDIS network device exposed over the USB connection, which worked. There was a shell exposed over telnet with the credentials root/root, and a separate developer console on HTTP port 8080.</p>
<p><img src="https://bomberfish.ca/blog-images/z10_dev.jpeg" alt="Dev console and root shell"/></p>
<cap><subt><span class="material-symbols">info</span>Image credit: Me.</subt></cap>
<p>I then inserted a microSD card and used <code>dd</code> to extract various partitions. Finally, I uploaded the created images to the <a href="https://archive.org/details/z10_nand">Internet Archive</a>, where you can download them right now.</p>]]></content:encoded>
            <enclosure url="https://bomberfish.ca/blog-images/z10_dev.jpeg" length="0" type="image/jpeg"/>
        </item>
        <item>
            <title><![CDATA[Improving the White House Shutdown Clock]]></title>
            <link>https://bomberfish.ca/blog/whitehouse</link>
            <guid isPermaLink="false">https://bomberfish.ca/blog/whitehouse</guid>
            <pubDate>Thu, 02 Oct 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Redesigning and enhancing the White House’s official government shutdown clock.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Note: This article is primarily an embedded frame. This may cause problems with assistive software.</p>
</blockquote>
<iframe src="https://bomberfish.ca/blog/whitehouse-static.html" style="border:none;width:100%;height:100%;min-height:500px"></iframe>]]></content:encoded>
            <enclosure url="https://bomberfish.ca/blog-images/clock.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Notes on reverse-engineering the Apple Music API]]></title>
            <link>https://bomberfish.ca/blog/apple-music-api</link>
            <guid isPermaLink="false">https://bomberfish.ca/blog/apple-music-api</guid>
            <pubDate>Mon, 05 May 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Some notes on the endpoints of Apple Music’s private REST API.]]></description>
            <content:encoded><![CDATA[<link rel="preload" as="image" href="https://bomberfish.ca/blog-images/apple-park-photo-1-building-trees.jpg"/><p><img src="https://bomberfish.ca/blog-images/apple-park-photo-1-building-trees.jpg" alt="Apple Park"/></p>
<cap><subt><span class="material-symbols">info</span>Image credit: Apple.</subt></cap>
<p>I&#x27;ve recently been working on <a href="https://bomberfish.ca/Transcribe">Transcribe</a>, a tool which transfers playlists between music streaming services directly inside your browser (<a href="https://youtu.be/MshSHxytkdQ">Demo video</a>). To add support for Apple Music, I had to reverse-engineer its API. Now, you may be wondering:</p>
<h2>Why not just use MusicKit?</h2>
<p>For anyone who doesn&#x27;t know, MusicKit (JS) is Apple Music&#x27;s <strong>official API</strong>. The issue with MusicKit is that using it requires an active Apple Developer Program membership. This would require me to pay Apple almost $150 every year, which is money I do not have. So, I had to resort to a more legally dubious method.</p>
<p>Now without any further ado, let&#x27;s get into the cool stuff!</p>
<blockquote>
<p>Please note that this is far from complete and should not be considered complete documentation. It might be updated in the future. Also, please hire me Apple :3</p>
</blockquote>
<h2>How I did it</h2>
<p>This is very simple. I simply opened <a href="https://beta.music.apple.com">Apple Music&#x27;s web client</a> in my browser, opened the Network tab in Chrome&#x27;s DevTools, and watched any fetch requests it made.</p>
<p>The base URL for all requests is <code>https://amp-api.music.apple.com</code>.</p>
<h2>Authentication</h2>
<p>The API seems to need two pieces of information to accept requests: a user token (via the <code>Authentication</code> header) and some cookies.</p>
<p>As for the cookies, I haven&#x27;t looked into what parts of the cookie the API actually requires to be there, but pasting everything sent in the <code>Cookie</code> header works fine.</p>
<h2>Common data types</h2>
<p>Here are a selection of TypeScript types from Transcribe. I&#x27;ll refer to them later in example responses.</p>
<pre><code class="language-ts"><span class="pl-c">/**</span>
<span class="pl-c"> * Represents a generic reference to an Apple Music resource.</span>
<span class="pl-c"> */</span>
<span class="pl-k">interface</span> <span class="pl-en">AMResourceReference</span> {
  <span class="pl-v">id</span><span class="pl-k">:</span> <span class="pl-c1">string</span>;
  <span class="pl-v">type</span><span class="pl-k">:</span> <span class="pl-en">AMResourceType</span>;
  <span class="pl-v">href</span><span class="pl-k">:</span> <span class="pl-c1">string</span>;
}

<span class="pl-c">/**</span>
<span class="pl-c"> * Represents a relationship link to other resources.</span>
<span class="pl-c"> */</span>
<span class="pl-k">interface</span> <span class="pl-en">AMRelationship</span>&lt;<span class="pl-en">T</span> <span class="pl-k">extends</span> <span class="pl-en">AMResourceReference</span>&gt; {
  <span class="pl-v">href</span><span class="pl-k">:</span> <span class="pl-c1">string</span>;
  <span class="pl-v">data</span><span class="pl-k">:</span> <span class="pl-en">T</span>[];
  <span class="pl-v">meta</span><span class="pl-k">?:</span> { <span class="pl-c">// Optional meta information, seen for tracks</span>
    <span class="pl-v">total</span><span class="pl-k">:</span> <span class="pl-c1">number</span>;
  };
}

<span class="pl-c">/**</span>
<span class="pl-c"> * Container for included resource objects, keyed by type, then ID.</span>
<span class="pl-c"> */</span>
<span class="pl-k">interface</span> <span class="pl-en">AMResourcesContainer</span> {
  <span class="pl-s"><span class="pl-pds">&#x27;</span>library-playlists<span class="pl-pds">&#x27;</span></span><span class="pl-k">?:</span> { [<span class="pl-v">id</span><span class="pl-k">:</span> <span class="pl-c1">string</span>]<span class="pl-k">:</span> <span class="pl-en">AMLibraryPlaylist</span> };
  <span class="pl-s"><span class="pl-pds">&#x27;</span>playlists<span class="pl-pds">&#x27;</span></span><span class="pl-k">?:</span> { [<span class="pl-v">id</span><span class="pl-k">:</span> <span class="pl-c1">string</span>]<span class="pl-k">:</span> <span class="pl-en">AMCatalogPlaylist</span> };
  <span class="pl-s"><span class="pl-pds">&#x27;</span>library-songs<span class="pl-pds">&#x27;</span></span><span class="pl-k">?:</span> { [<span class="pl-v">id</span><span class="pl-k">:</span> <span class="pl-c1">string</span>]<span class="pl-k">:</span> <span class="pl-en">AMLibrarySong</span> };
  <span class="pl-s"><span class="pl-pds">&#x27;</span>songs<span class="pl-pds">&#x27;</span></span><span class="pl-k">?:</span> { [<span class="pl-v">id</span><span class="pl-k">:</span> <span class="pl-c1">string</span>]<span class="pl-k">:</span> <span class="pl-en">AMCatalogSong</span> };
  <span class="pl-s"><span class="pl-pds">&#x27;</span>apple-curators<span class="pl-pds">&#x27;</span></span><span class="pl-k">?:</span> { [<span class="pl-v">id</span><span class="pl-k">:</span> <span class="pl-c1">string</span>]<span class="pl-k">:</span> <span class="pl-en">AMAppleCurator</span> };
  <span class="pl-s"><span class="pl-pds">&#x27;</span>artists<span class="pl-pds">&#x27;</span></span><span class="pl-k">?:</span> { [<span class="pl-v">id</span><span class="pl-k">:</span> <span class="pl-c1">string</span>]<span class="pl-k">:</span> <span class="pl-en">AMArtist</span> };
  <span class="pl-s"><span class="pl-pds">&#x27;</span>albums<span class="pl-pds">&#x27;</span></span><span class="pl-k">?:</span> { [<span class="pl-v">id</span><span class="pl-k">:</span> <span class="pl-c1">string</span>]<span class="pl-k">:</span> <span class="pl-en">AMAlbum</span> };
  <span class="pl-s"><span class="pl-pds">&#x27;</span>music-videos<span class="pl-pds">&#x27;</span></span><span class="pl-k">?:</span> { [<span class="pl-v">id</span><span class="pl-k">:</span> <span class="pl-c1">string</span>]<span class="pl-k">:</span> <span class="pl-en">AMMusicVideo</span> };
  <span class="pl-s"><span class="pl-pds">&#x27;</span>stations<span class="pl-pds">&#x27;</span></span><span class="pl-k">?:</span> { [<span class="pl-v">id</span><span class="pl-k">:</span> <span class="pl-c1">string</span>]<span class="pl-k">:</span> <span class="pl-en">AMStation</span> };
  <span class="pl-s"><span class="pl-pds">&#x27;</span>uploaded-videos<span class="pl-pds">&#x27;</span></span><span class="pl-k">?:</span> { [<span class="pl-v">id</span><span class="pl-k">:</span> <span class="pl-c1">string</span>]<span class="pl-k">:</span> <span class="pl-en">AMUploadedVideo</span> };\
}
</code></pre>
<h2>Getting stuff from your Library</h2>
<h3>Recently Added</h3>
<p><strong>Endpoint:</strong> <code>GET /v1/me/library/recently-added</code></p>
<h3>All Playlists</h3>
<p><strong>Endpoint:</strong> <code>GET /v1/me/library/playlists</code></p>
<p>You can limit the number of playlists returned with <strong>the <code>limit</code> parameter.</strong>
<strong>What the response would look like:</strong></p>
<pre><code class="language-ts">[
  {
	tracks: <span class="pl-smi">AMRelationship</span><span class="pl-k">&lt;</span><span class="pl-smi">AMResourceReference</span><span class="pl-k">&gt;</span>,
	catalog?: <span class="pl-smi">AMRelationship</span><span class="pl-k">&lt;</span><span class="pl-smi">AMResourceReference</span><span class="pl-k">&gt;</span>,
  }
]
</code></pre>
<h3>Songs in a specific playlist</h3>
<p><strong>Endpoint:</strong> <code>GET /v1/me/library/playlists/:id</code></p>
<p><strong>What the response would look like:</strong></p>
<pre><code class="language-ts">[
  {
	data: <span class="pl-smi">AMResourceReference</span>[],
	resources?: <span class="pl-smi">AMResourcesContainer</span>,
  }
]
</code></pre>
<h2>Managing your Library</h2>
<h3>Creating a Playlist</h3>
<p><strong>Endpoint:</strong> <code>POST /v1/me/playlists</code>
<strong>What your body should look like:</strong></p>
<pre><code class="language-ts">{
  <span class="pl-en">attributes</span>: {
    <span class="pl-en">name</span>: <span class="pl-smi">string</span>;
	<span class="pl-en">description</span>: <span class="pl-smi">string</span>;
	<span class="pl-en">isPublic</span>: <span class="pl-smi">boolean</span>;
  },
  <span class="pl-en">relationships</span>: {
  	<span class="pl-en">tracks</span>: {
	  <span class="pl-en">data</span>: [
	  	<span class="pl-smi">id</span>: <span class="pl-smi">string</span>,
		<span class="pl-smi">type</span>: <span class="pl-s"><span class="pl-pds">&quot;</span>songs<span class="pl-pds">&quot;</span></span><span class="pl-k">|</span><span class="pl-s"><span class="pl-pds">&quot;</span>library-songs<span class="pl-pds">&quot;</span></span><span class="pl-k">|</span><span class="pl-s"><span class="pl-pds">&quot;</span>music-videos<span class="pl-pds">&quot;</span></span>,
	  ]
	}
  }
}
</code></pre>
<p><strong>What the response would look like:</strong></p>
<pre><code class="language-ts">[
  {
  	id: <span class="pl-smi">string</span>;
  	<span class="pl-smi">type</span>: <span class="pl-s"><span class="pl-pds">&quot;</span>library-playlists<span class="pl-pds">&quot;</span></span>,
  	attributes: <span class="pl-smi">any</span>,
  }
]
</code></pre>
<h2>Other</h2>
<h3>Search</h3>
<p><strong>Endpoint:</strong> <code>GET /v1/catalog/ca/search</code>
This one&#x27;s a bit tricky. I haven&#x27;t quite cracked how it handles parameters, but here&#x27;s what parameters I use in Transcribe to search for only songs:</p>
<pre><code class="language-js"><span class="pl-k">let</span> params <span class="pl-k">=</span> {
    <span class="pl-s"><span class="pl-pds">&quot;</span>art[music-videos:url]<span class="pl-pds">&quot;</span></span><span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">&quot;</span>c<span class="pl-pds">&quot;</span></span>,
    <span class="pl-s"><span class="pl-pds">&quot;</span>art[url]<span class="pl-pds">&quot;</span></span><span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">&quot;</span>f<span class="pl-pds">&quot;</span></span>,
    extend<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">&quot;</span>artistUrl<span class="pl-pds">&quot;</span></span>,
    <span class="pl-s"><span class="pl-pds">&quot;</span>fields[albums]<span class="pl-pds">&quot;</span></span><span class="pl-k">:</span>
        <span class="pl-s"><span class="pl-pds">&quot;</span>artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialNotes,name,playParams,releaseDate,url,trackCount<span class="pl-pds">&quot;</span></span>,
    <span class="pl-s"><span class="pl-pds">&quot;</span>fields[artists]<span class="pl-pds">&quot;</span></span><span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">&quot;</span>url,name,artwork<span class="pl-pds">&quot;</span></span>,
    <span class="pl-s"><span class="pl-pds">&quot;</span>format[resources]<span class="pl-pds">&quot;</span></span><span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">&quot;</span>map<span class="pl-pds">&quot;</span></span>,
    <span class="pl-s"><span class="pl-pds">&quot;</span>include[albums]<span class="pl-pds">&quot;</span></span><span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">&quot;</span>artists<span class="pl-pds">&quot;</span></span>,
    <span class="pl-s"><span class="pl-pds">&quot;</span>include[songs]<span class="pl-pds">&quot;</span></span><span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">&quot;</span>artists<span class="pl-pds">&quot;</span></span>,
    l<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">&quot;</span>en-CA<span class="pl-pds">&quot;</span></span>,
    limit<span class="pl-k">:</span> limit,
    <span class="pl-s"><span class="pl-pds">&quot;</span>omit[resource]<span class="pl-pds">&quot;</span></span><span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">&quot;</span>autos<span class="pl-pds">&quot;</span></span>,
    platform<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">&quot;</span>web<span class="pl-pds">&quot;</span></span>,
    <span class="pl-s"><span class="pl-pds">&quot;</span>relate[albums]<span class="pl-pds">&quot;</span></span><span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">&quot;</span>artists<span class="pl-pds">&quot;</span></span>,
    <span class="pl-s"><span class="pl-pds">&quot;</span>relate[songs]<span class="pl-pds">&quot;</span></span><span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">&quot;</span>albums<span class="pl-pds">&quot;</span></span>,
    term<span class="pl-k">:</span> query,
    types<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">&quot;</span>songs<span class="pl-pds">&quot;</span></span>,
    with<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">&quot;</span>lyricHighlights,lyrics,serverBubbles,subtitles<span class="pl-pds">&quot;</span></span>,
};
</code></pre>
<p><strong>Relevant types for the response:</strong></p>
<pre><code class="language-ts"><span class="pl-c">/**</span>
<span class="pl-c"> * The top-level structure of the Apple Music API search response.</span>
<span class="pl-c"> */</span>
<span class="pl-k">interface</span> <span class="pl-en">AMSearchResponse</span> {
    <span class="pl-v">results</span><span class="pl-k">:</span> <span class="pl-en">AMSearchResults</span>;
    <span class="pl-v">resources</span><span class="pl-k">?:</span> <span class="pl-en">AMResourcesContainer</span>; <span class="pl-c">// Included full resources (optional based on request/results)</span>
    <span class="pl-v">meta</span><span class="pl-k">?:</span> <span class="pl-en">AMSearchMeta</span>; <span class="pl-c">// Optional meta information</span>
}

<span class="pl-c">/**</span>
<span class="pl-c"> * Represents the &#x27;results&#x27; part of the search response, containing different groups.</span>
<span class="pl-c"> * Keys are typically the `groupId` values.</span>
<span class="pl-c"> */</span>
<span class="pl-k">interface</span> <span class="pl-en">AMSearchResults</span> {
    <span class="pl-v">top</span><span class="pl-k">?:</span> <span class="pl-en">AMTopSearchResultGroup</span>;
    <span class="pl-v">artist</span><span class="pl-k">?:</span> <span class="pl-en">AMSearchResultGroup</span>&lt;<span class="pl-en">AMResourceReference</span>&gt;;
    <span class="pl-v">album</span><span class="pl-k">?:</span> <span class="pl-en">AMSearchResultGroup</span>&lt;<span class="pl-en">AMResourceReference</span>&gt;;
    <span class="pl-v">song</span><span class="pl-k">?:</span> <span class="pl-en">AMSearchResultGroup</span>&lt;<span class="pl-en">AMResourceReference</span>&gt;;
    <span class="pl-v">playlist</span><span class="pl-k">?:</span> <span class="pl-en">AMSearchResultGroup</span>&lt;<span class="pl-en">AMResourceReference</span>&gt;;
    <span class="pl-s"><span class="pl-pds">&#x27;</span>radio_episode<span class="pl-pds">&#x27;</span></span><span class="pl-k">?:</span> <span class="pl-en">AMSearchResultGroup</span>&lt;<span class="pl-en">AMResourceReference</span>&gt;; <span class="pl-c">// Note: uses station type reference</span>
    <span class="pl-v">station</span><span class="pl-k">?:</span> <span class="pl-en">AMSearchResultGroup</span>&lt;<span class="pl-en">AMResourceReference</span>&gt;;
    <span class="pl-s"><span class="pl-pds">&#x27;</span>music_video<span class="pl-pds">&#x27;</span></span><span class="pl-k">?:</span> <span class="pl-en">AMSearchResultGroup</span>&lt;<span class="pl-en">AMResourceReference</span>&gt;;
    <span class="pl-s"><span class="pl-pds">&#x27;</span>video_extra<span class="pl-pds">&#x27;</span></span><span class="pl-k">?:</span> <span class="pl-en">AMSearchResultGroup</span>&lt;<span class="pl-en">AMResourceReference</span>&gt;; <span class="pl-c">// Note: uses uploaded-video type reference</span>
    [<span class="pl-v">key</span><span class="pl-k">:</span> <span class="pl-c1">string</span>]<span class="pl-k">:</span> <span class="pl-en">AMTopSearchResultGroup</span> <span class="pl-k">|</span> <span class="pl-en">AMSearchResultGroup</span>&lt;<span class="pl-c1">any</span>&gt; <span class="pl-k">|</span> <span class="pl-c1">undefined</span>; <span class="pl-c">// Index signature for flexibility</span>
}

<span class="pl-c">/**</span>
<span class="pl-c"> * Represents the &#x27;top&#x27; results group which contains mixed types.</span>
<span class="pl-c"> */</span>
<span class="pl-k">interface</span> <span class="pl-en">AMTopSearchResultGroup</span> {
    <span class="pl-c">// No href/next at this level</span>
    <span class="pl-v">data</span><span class="pl-k">:</span> <span class="pl-en">AMResourceReference</span>[];
    <span class="pl-v">name</span><span class="pl-k">:</span> <span class="pl-c1">string</span>; <span class="pl-c">// e.g., &quot;Top Results&quot;</span>
    <span class="pl-v">groupId</span><span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">&#x27;</span>top<span class="pl-pds">&#x27;</span></span>;
}

<span class="pl-c">/**</span>
<span class="pl-c"> * Represents a group of search results for a specific type (e.g., artists, albums).</span>
<span class="pl-c"> */</span>
<span class="pl-k">interface</span> <span class="pl-en">AMSearchResultGroup</span>&lt;<span class="pl-en">T</span> <span class="pl-k">extends</span> <span class="pl-en">AMResourceReference</span>&gt; {
    <span class="pl-v">href</span><span class="pl-k">?:</span> <span class="pl-c1">string</span>; <span class="pl-c">// Link to refine search for this type</span>
    <span class="pl-v">next</span><span class="pl-k">?:</span> <span class="pl-c1">string</span>; <span class="pl-c">// Link to next page of results for this type</span>
    <span class="pl-v">data</span><span class="pl-k">:</span> <span class="pl-en">T</span>[]; <span class="pl-c">// Array of references for this type</span>
    <span class="pl-v">name</span><span class="pl-k">:</span> <span class="pl-c1">string</span>; <span class="pl-c">// Display name (e.g., &quot;Artists&quot;)</span>
    <span class="pl-v">groupId</span><span class="pl-k">:</span> <span class="pl-c1">string</span>; <span class="pl-c">// Internal group ID (e.g., &quot;artist&quot;)</span>
}

<span class="pl-c">/**</span>
<span class="pl-c"> * Represents the &#x27;meta&#x27; part of the search response.</span>
<span class="pl-c"> */</span>
<span class="pl-k">interface</span> <span class="pl-en">AMSearchMeta</span> {
    <span class="pl-v">results</span><span class="pl-k">:</span> {
        <span class="pl-v">order</span><span class="pl-k">:</span> <span class="pl-c1">string</span>[]; <span class="pl-c">// Array of groupIds in display order</span>
        <span class="pl-v">rawOrder</span><span class="pl-k">:</span> <span class="pl-c1">string</span>[]; <span class="pl-c">// Array of groupIds potentially including types with no results</span>
    };
    <span class="pl-v">metrics</span><span class="pl-k">?:</span> { <span class="pl-c">// Optional metrics data</span>
        <span class="pl-v">dataSetId</span><span class="pl-k">:</span> <span class="pl-c1">string</span>;
    };
}

</code></pre>]]></content:encoded>
            <enclosure url="https://bomberfish.ca/blog-images/apple-park-photo-1-building-trees.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[My favourite Mac apps]]></title>
            <link>https://bomberfish.ca/blog/mac-apps</link>
            <guid isPermaLink="false">https://bomberfish.ca/blog/mac-apps</guid>
            <pubDate>Tue, 04 Mar 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[A personal list of useful Mac apps for productivity, development, and maintenance.]]></description>
            <content:encoded><![CDATA[<link rel="preload" as="image" href="https://bomberfish.ca/blog-images/raycast.jpg"/><link rel="preload" as="image" href="https://bomberfish.ca/blog-images/yabai.jpg"/><link rel="preload" as="image" href="https://bomberfish.ca/blog-images/bartender.jpg"/><link rel="preload" as="image" href="https://bomberfish.ca/blog-images/warp.jpg"/><link rel="preload" as="image" href="https://bomberfish.ca/blog-images/hexfiend.jpg"/><link rel="preload" as="image" href="https://bomberfish.ca/blog-images/onyx.jpg"/><link rel="preload" as="image" href="https://bomberfish.ca/blog-images/omnidisksweeper.jpg"/><link rel="preload" as="image" href="https://bomberfish.ca/blog-images/appcleaner.jpg"/><p>macOS is a nice operating system, but is very lacking in good functionality out of the box. Thankfully, lots of solutions exist for minor nitpicks and issues.</p>
<p>All apps are free unless indicated otherwise, and I am not affiliated with any of the developers.
Expect this list to be updated over time.</p>
<h2>Productivity</h2>
<h3>Raycast</h3>
<p><img src="https://bomberfish.ca/blog-images/raycast.jpg" alt="Screenshot"/></p>
<p><a href="https://www.raycast.com/">Raycast</a> is a Spotlight Search replacement for macOS.</p>
<h3>yabai</h3>
<p><img src="https://bomberfish.ca/blog-images/yabai.jpg" alt="Screenshot of example layout"/></p>
<p><a href="https://github.com/koekeishiya/yabai">yabai</a> is a tiling window manager for macOS, similar to Hyprland, sway, or i3 on Linux.</p>
<h3>Bartender 5</h3>
<p><img src="https://bomberfish.ca/blog-images/bartender.jpg" alt="Screenshot"/></p>
<p><a href="https://www.macbartender.com/">Bartender</a> ($30, but free trial can be reset by clearing app data) is an app that can hide excess menu bar items until you expand them. As mentioned earlier, if you clear the app data (e.g. using AppCleaner), the free trial can be extended by another 30 days.</p>
<h2>Development</h2>
<h3>Warp</h3>
<p><img src="https://bomberfish.ca/blog-images/warp.jpg" alt="Screenshot"/></p>
<p><a href="https://warp.dev">Warp</a> is an excellent terminal application for macOS, Linux and Windows that has a modern user interface. They also gave me a free shirt once.</p>
<h3>Hex Fiend</h3>
<p><img src="https://bomberfish.ca/blog-images/hexfiend.jpg" alt="Screenshot"/></p>
<p><a href="https://hexfiend.com/">Hex Fiend</a> is a fast, native hex editor for macOS.</p>
<h2>Maintenance</h2>
<h3>OnyX</h3>
<p><img src="https://bomberfish.ca/blog-images/onyx.jpg" alt="Screenshot"/></p>
<p><a href="https://www.titanium-software.fr/en/onyx.html">OnyX</a> is a comprehensive utility for maintaining your Mac. It lets you perform maintainance tasks such as rebuilding indexes and clearing system caches, in addition to other useful features such as various inaccessible system settings.</p>
<h3>OmniDiskSweeper</h3>
<p><img src="https://bomberfish.ca/blog-images/omnidisksweeper.jpg" alt="Screenshot"/></p>
<p><a href="https://www.omnigroup.com/more">OmniDiskSweeper</a> shows you a map of the files on any storage device sorted by size, and lets you easily free up space.</p>
<h3>AppCleaner</h3>
<p><img src="https://bomberfish.ca/blog-images/appcleaner.jpg" alt="Screenshot"/></p>
<p><a href="https://freemacsoft.net/appcleaner/">AppCleaner</a> lets you completely and thoroughly uninstall apps on macOS. It&#x27;s pretty useful for clearing the data of apps that download a lot of miscellaneous files.</p>]]></content:encoded>
            <enclosure url="https://bomberfish.ca/blog-images/yabai.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[How I hacked Arc Search]]></title>
            <link>https://bomberfish.ca/blog/browse-for-me</link>
            <guid isPermaLink="false">https://bomberfish.ca/blog/browse-for-me</guid>
            <pubDate>Tue, 29 Oct 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[How I made an Arc Search Browse For Me shared link display arbitrary content, and why this can be abused.]]></description>
            <content:encoded><![CDATA[<link rel="preload" as="image" href="https://bomberfish.ca/blog-images/bfm-desktop.jpeg"/><link rel="preload" as="image" href="https://bomberfish.ca/blog-images/bfm-mobile.jpg"/><link rel="preload" as="image" href="https://bomberfish.ca/blog-images/bfm-fake-quote.jpg"/><p>Hello! In this article, I’d like to go over how I was able to make an Arc Browse For Me result link which could display whatever content I wanted.</p>
<blockquote>
<p>In this article, I’ll refer to The Browser Company of New York as “BCNY”, and Browse For Me as “BFM”.</p>
</blockquote>
<h2>Update (2025-03-04)</h2>
<p>BCNY seems to have resolved this issue... by disabling the sharing of BFM pages. The API endpoint seems to be down as well. Existing links however, still work.</p>
<h2>The goal</h2>
<p>Make a shared BFM link display whatever content I want.</p>
<h2>What is Arc Search?</h2>
<p>Arc Search is the mobile companion app to BCNY’s popular desktop browser, Arc. Arc Search’s claim to fame is Browse For Me, an AI-powered search engine, similar to Perplexity or SearchGPT. In the Arc Search app, you can press the share button in a BFM page, which generates a link you can share with your friends (i.e. https://search.arc.net/UYwUAKlR1Qm5fWDYuzPO).</p>
<h2>Backstory</h2>
<p>Recently while I was using BFM on Arc Search, I began to wonder why sharing the page took a long time (~5-10 seconds). So, I fired up <a href="https://proxyman.io/ios">Proxyman</a> on my iPhone to intercept the HTTP traffic going to and from my phone, and what I found was surprising.</p>
<h2>How sharing a Browse For Me result works</h2>
<p>When you press the Share button on a BFM page in Arc Search, it fires a <code>POST</code> request to <code>https://search.arc.net/api/share</code>. However, in the body of request, I found that the entire contents of the search page were sent as well. Here’s how a typical request looks:</p>
<pre><code class="language-http"><span class="pl-k">POST</span><span class="pl-c1"> /api/share HTTP/1.1</span>
<span class="pl-s"><span class="pl-v">Content-Type:</span> text/plain; charset=utf-8</span>
<span class="pl-s"><span class="pl-v">Host:</span> search.arc.net</span>
<span class="pl-s"><span class="pl-v">Connection:</span> close</span>
<span class="pl-s"><span class="pl-v">Content-Length:</span> 142321</span>
</code></pre>
<pre><code class="language-json">{
  <span class="pl-ent">&quot;stream&quot;</span>: [],
  <span class="pl-ent">&quot;raw&quot;</span>: <span class="pl-s"><span class="pl-pds">&quot;&quot;</span></span>,
  <span class="pl-ent">&quot;shouldRetain&quot;</span>: <span class="pl-c1">false</span>,
  <span class="pl-ent">&quot;finished&quot;</span>: <span class="pl-c1">true</span>
}
</code></pre>
<p>The <code>raw</code> key can be ignored, as the server also seems to ignore it. The real magic is in the <code>stream</code> key, which is an array of JSON objects. This contains everything that is displayed on the frontend.</p>
<p>All objects in <code>stream</code> have the following keys in common:</p>
<ul>
<li><code>title</code>: string</li>
<li><code>type</code>: string, can be one of the following:
<ul>
<li><code>emojiList</code> - A list with emoji as bullet points</li>
<li><code>sectionTitle</code> - A subheading</li>
<li><code>userFeedback</code> - Asks for user feedback</li>
<li><code>citation</code> - A citation. This will show a “Verified” tag next to the url, regardless if it was fake.</li>
<li><code>pageTitle</code> - The title at the top of the page</li>
</ul>
</li>
<li><code>isFinished</code>: boolean</li>
<li><code>isPlaceholder</code>: boolean
There are also other values in objects which differ depending on the <code>type</code>.</li>
</ul>
<p><strong>Any value in the body can be changed without the server caring</strong>, which means you can easily falsify results.</p>
<p>Once a link is created, the server sends a response like the following:</p>
<pre><code class="language-http"><span class="pl-c1">HTTP/1.1 200 OK</span>
<span class="pl-s"><span class="pl-v">Cache-Control:</span> public, max-age=0, must-revalidate</span>
<span class="pl-s"><span class="pl-v">Content-Type:</span> text/plain;charset=UTF-8</span>
<span class="pl-s"><span class="pl-v">Date:</span> Mon, 28 Oct 2024 20:30:45 GMT</span>
<span class="pl-s"><span class="pl-v">Server:</span> Vercel</span>
<span class="pl-s"><span class="pl-v">Strict-Transport-Security:</span> max-age=63072000</span>
<span class="pl-s"><span class="pl-v">Vary:</span> RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Url</span>
<span class="pl-s"><span class="pl-v">X-Matched-Path:</span> /api/share</span>
<span class="pl-s"><span class="pl-v">X-Vercel-Cache:</span> MISS</span>
<span class="pl-s"><span class="pl-v">X-Vercel-Id:</span> cle1::iad1::wd6z4-1730147445251-0db83fb9f76c</span>
<span class="pl-s"><span class="pl-v">Connection:</span> close</span>
<span class="pl-s"><span class="pl-v">Transfer-Encoding:</span> chunked</span>
</code></pre>
<pre><code class="language-json">{<span class="pl-ent">&quot;shareID&quot;</span>:<span class="pl-s"><span class="pl-pds">&quot;</span>PNmICgV7lW4zJxmVDr71<span class="pl-pds">&quot;</span></span>}
</code></pre>
<p><code>shareID</code> seems to be the link’s ID. I navigated to <code>https://search.arc.net/&lt;id&gt;</code>, and lo and behold, my content was there!</p>
<p><img src="https://bomberfish.ca/blog-images/bfm-desktop.jpeg" alt="BFM showing custom page on desktop"/></p>
<p>It even works on the Arc Search app, when visiting the shared link:</p>
<p><img src="https://bomberfish.ca/blog-images/bfm-mobile.jpg" alt="Arc Search on iOS showing custom page"/></p>
<h2>What are the risks?</h2>
<p>The ability to create custom search results brings with it dire consequences, most alarmingly the ability to falsify search results and pass them off as legitimate. For example, my hijacked page includes a fake quote from https://arc.net, which displays a “Verified” tag next to it.</p>
<p><img src="https://bomberfish.ca/blog-images/bfm-fake-quote.jpg" alt="Fake quote from arc.net"/></p>
<p>This creates a risk of misinformation being spread through Arc Search, while it appears to be a legitimate search result. For example, a search for “Who won the 2020 US election?” could be modified to state the winner was Kermit the Frog. The link could then be shared online, and convince some people of the fabricated result.</p>
<h2>Demo</h2>
<p>You can see a live version of a fake search page <a href="https://search.arc.net/PNmICgV7lW4zJxmVDr71">here</a>, and an archived version is available <a href="https://archive.is/D9MYb">here</a> in case BCNY takes it down or they go out of business (VC money doesn’t last forever). For research purposes, the exact request body I used to generate it can be found <a href="https://rentry.org/sqpu4hwu">here</a>.</p>
<h2>Fix and Conclusion</h2>
<p>BCNY can fix this issue by generating a fixed link when a result is first generated and keep the process completely opaque from the client, similar to what competing services do already. But for now, this has been BomberFish, and I do declare; end broadcast.</p>]]></content:encoded>
            <enclosure url="https://bomberfish.ca/blog-images/bfm-fake-quote.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[How I hacked an obscure set-top box from 2013]]></title>
            <link>https://bomberfish.ca/blog/jadoo3</link>
            <guid isPermaLink="false">https://bomberfish.ca/blog/jadoo3</guid>
            <pubDate>Tue, 06 Aug 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[How I gained root access to a Jadoo3 set-top box and what I discovered during the investigation.]]></description>
            <content:encoded><![CDATA[<link rel="preload" as="image" href="https://bomberfish.ca/blog-images/jadoo3.webp"/><link rel="preload" as="image" href="https://bomberfish.ca/blog-images/jadoo3-pcb-1.jpg"/><link rel="preload" as="image" href="https://bomberfish.ca/blog-images/jadoo3-pcb-2.jpg"/><link rel="preload" as="image" href="https://bomberfish.ca/blog-images/love-in-the-air.jpg"/><p><img src="https://bomberfish.ca/blog-images/jadoo3.webp" alt="Jadoo3"/></p>
<p>Hello, friends! Today, I&#x27;d like to go over how I got root shell access to the Jadoo3, an obscure set-top box from 2013.</p>
<h2>What is the Jadoo3?</h2>
<p>As you can extrapolate from the name, the Jadoo3 is the third set-top box by JadooTV. The device aims to provide on-demand content from South Asia and the Middle East, with the name originating from the Hindi word for &quot;magic&quot;.</p>
<p>I&#x27;ve had the device for a while, but haven&#x27;t found anything to do with it until now. As such, it&#x27;s been collecting dust in a half-disassembled state for some time.</p>
<h2>Internals</h2>
<p>Opening up the device, we can see the heart of the machine.</p>
<p><img src="https://bomberfish.ca/blog-images/jadoo3-pcb-1.jpg" alt="Jadoo3 PCB, top view"/></p>
<p>In the center, we can see some sort of SoC, with a model number of VSI1871A-CBE3. Searching for this online returns very few results, with no datasheet available.</p>
<blockquote>
<p><strong>NOTE:</strong> Just off screen to the bottom left of the image, there seems to be a 10-pin header, which could be a debugging interface such as JTAG. However, this was not needed, as will be discussed later in the article.</p>
</blockquote>
<p><img src="https://bomberfish.ca/blog-images/jadoo3-pcb-2.jpg" alt="Jadoo3 PCB, front-left view"/></p>
<p>However, in the front-left corner of the PCB, we can see some identifying information. The device seems to be based on the Syabas Popbox V8, another set-top box which is powered by the SMP8670, a MIPS CPU created by Sigma Designs.</p>
<h2>Getting in</h2>
<p>Booting the device up and getting it connected to a network, we can run <code>nmap</code> to reveal an open telnet service on port 23. This can&#x27;t be good.</p>
<p><img src="https://bomberfish.ca/blog-images/love-in-the-air.jpg" alt="Love is in the air? Wrong. Exposed root shell on port 23"/></p>
<p>That&#x27;s right, a root shell was exposed on an open, unauthenticated channel. From there, it was as simple as connecting with <code>nc &lt;ip&gt; 23</code>.</p>
<p>What&#x27;s very interesting is that the box seems to run on a flavor of Linux. Running <code>uname -a</code> gives us the following output:</p>
<pre><code>Linux JADOO 2.6.29.6-22-sigma #263 PREEMPT Thu Feb 12 17:19:10 MYT 2015 mips GNU/Linux
</code></pre>
<p>This seems to confirm this is a MIPS processor by Sigma Designs, as discussed earlier. The contents of <code>/proc/cpuinfo</code> gives us more insight:</p>
<pre><code>system type             : Sigma Designs TangoX
processor               : 0
cpu model               : MIPS 24Kc V7.12  FPU V0.0
BogoMIPS                : 466.94
wait instruction        : yes
microsecond timers      : yes
tlb_entries             : 32
extra interrupt vector  : yes
hardware watchpoint     : yes, count: 4, address/irw mask: [0x0ffc, 0x0ffc, 0x0ffb, 0x0ffb]
ASEs implemented        : mips16
shadow register sets    : 1
core                    : 0
VCED exceptions         : not available
VCEI exceptions         : not available

SMP8XXX Chip ID         : 8671
SMP8XXX Rev ID          : 2
System bus frequency    : 351000000 Hz
CPU frequency           : 702000000 Hz
DSP frequency           : 351000000 Hz
</code></pre>
<p>Specifically, it seems to be a revised version of the SMP8670, called the SMP8671.</p>
<p>Checking <code>/proc/meminfo</code> indicates the device comes with ~128MB of RAM.</p>
<h2>What now?</h2>
<p>The device&#x27;s interface is apparently Flash-based, so it might be possible to replace some of the included apps with arbitrary swf files. Additionally, the standard Linux framebuffer is exposed, which could open up the possibility of running non-flash programs (DOOM anyone?).</p>
<p>I will most likely make one or more follow-up posts if I manage to get my own programs running on the device.</p>
<p>For now, this has been BomberFish, and I do declare; end broadcast.</p>]]></content:encoded>
            <enclosure url="https://bomberfish.ca/blog-images/jadoo3.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[iOS Sideloading: The Rundown]]></title>
            <link>https://bomberfish.ca/blog/ios-sideloading</link>
            <guid isPermaLink="false">https://bomberfish.ca/blog/ios-sideloading</guid>
            <pubDate>Fri, 26 Jan 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Explains Apple’s iOS sideloading changes for the EU, how they work, and implications for developers and users.]]></description>
            <content:encoded><![CDATA[<p>Apple <a href="https://www.apple.com/newsroom/2024/01/apple-announces-changes-to-ios-safari-and-the-app-store-in-the-european-union/">just announced</a> sideloading for the EU on iOS 17.4 and above. What does this mean for you?</p>
<blockquote>
<p><strong>Note:</strong> Scroll to the bottom of the article for a TL;DR.</p>
</blockquote>
<h2>Updates</h2>
<p>Recently, Apple has added the capability to install apps directly from a developer&#x27;s website. However, the review requirements discussed below still apply.</p>
<h2>Access control</h2>
<p><strong>NEW:</strong> iOS appears to use a new daemon, <code>countryd</code>, to determine eligibility. It checks various device factors, such as Wi-Fi region, SIM card, and more.</p>
<p>In iOS 17.3, a new MobileGestalt key called <code>DeviceSupportsEUCapabilities</code> was added, which may have something to do with sideloading. If somehow a sandbox escape is released for 17.4, it might be possible to enable this even if you don&#x27;t live in the EU. However, it may not be worth it. Let&#x27;s find out.</p>
<h2>Becoming a Marketplace</h2>
<blockquote>
<p>All quotes in this section come from <a href="https://developer.apple.com/support/alternative-app-marketplace-in-the-eu/">this article.</a></p>
</blockquote>
<p>First of all, the changes aren&#x27;t &quot;sideloading&quot; per se. Instead, Apple is allowing <strong>registered developers</strong> (the ones who pay a $100 fee yearly) to create an &quot;alternative app marketplace&quot;. I&#x27;ll let Apple themselves explain how someone can do that.</p>
<blockquote>
<p>If you’re interested in becoming a marketplace developer in the EU, the Account Holder of your Apple Developer Program membership will first need to agree to the Alternative Terms Addendum for Apps in the EU. Once they’ve agreed, they can submit a request for the entitlement.</p>
</blockquote>
<p>Additionally, you must &quot;provide Apple a stand-by letter of credit from an A-rated financial Institution of <strong>€1,000,000</strong>&quot;. This makes it impossible for smaller companies (let alone individuals) to open their own app stores, and essentially crushes any hopes of true sideloading by the end user.</p>
<h2>Codesigning</h2>
<blockquote>
<p>All quotes in this section come from <a href="https://developer.apple.com/support/dma-and-apps-in-the-eu/#ios-app-eu">this article.</a></p>
</blockquote>
<p>As you may know, all binaries that run on iOS require a valid code signature. Let&#x27;s see how this works for the new sideloading mechanism.</p>
<blockquote>
<p>Developers can submit a single binary and will be able to choose alternative distribution options in App Store Connect.</p>
</blockquote>
<blockquote>
<p>Apple will encrypt and sign all iOS apps intended for alternative distribution to help protect developers’ intellectual property and ensure that users get apps from known parties.</p>
</blockquote>
<p>These two quotes from Apple signify that you will still have to submit apps to Apple for signing if you want them to run on iOS.</p>
<blockquote>
<p><strong>Functionality</strong> — Binaries must be reviewable, free of serious bugs or crashes, and compatible with the current version of iOS. They cannot manipulate software or hardware in ways that negatively impact the user experience.</p>
</blockquote>
<blockquote>
<p><strong>Security</strong> — Apps cannot enable distribution of malware or of suspicious or unwanted software. They cannot download executable code, read outside of the container, or direct users to lower the security on their system or device. Also, apps must provide transparency and allow user consent to enable any party to access the system or device, or reconfigure the system or other software.</p>
</blockquote>
<p>These two points mean that it will be impossible to install jailbreaks (or jailed tools) through this new &quot;sideloading&quot; mechanism.</p>
<h2>Conclusion</h2>
<p>Apple&#x27;s recent announcement of allowing third-party app stores for customers in the EU is not much more than a case of malicious compliance. They did the bare minimum to comply with the law, and it shows. If you want a better explanation, I highly recommend <a href="https://youtu.be/_6dbNzFD0zM">this video</a>.</p>
<h2>TL;DR</h2>
<ul>
<li>The new &quot;sideloading&quot; in iOS 17.4 sucks.</li>
<li>Developers have to pay Apple not only $99 a year for the Dev Program, but also €1,000,000 so Apple can &quot;trust&quot; them.</li>
<li>All apps still need to be reviewed by Apple.</li>
<li>It will be impossible to install jailbreaks/jailed tools with this new sideloading mechanism.</li>
</ul>
<h2>Updates</h2>
<ul>
<li>2024-01-26 (shortly after initial publish)
<ul>
<li>Add conclusion</li>
</ul>
</li>
<li>2024-04-23
<ul>
<li>Fix wording, clarify web installation and <code>countryd</code></li>
</ul>
</li>
</ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Proxying on the Edge: Part 2]]></title>
            <link>https://bomberfish.ca/blog/cf-workers-proxy-part-2</link>
            <guid isPermaLink="false">https://bomberfish.ca/blog/cf-workers-proxy-part-2</guid>
            <pubDate>Sat, 21 Oct 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Notes on developing the Infrared edge proxy and adding TompHTTP V3 support.]]></description>
            <content:encoded><![CDATA[<h2>Recap</h2>
<p>Recently, I made a proof-of-concept edge proxy called Superposition. It was very unpolished, and served only as a proof-of-concept.</p>
<h2>Infrared</h2>
<p>In <a href="https://bomberfish.ca/blog/cf-workers-proxy-part-1.html">my last post</a> on this topic, I mentioned I was working on a new proxy called Infrared. I am proud to announce that I have officially released it, and you can visit it at <a href="https://infrared.bomberfish.ca">infrared.bomberfish.ca</a>. This post will explain the challenges behind creating a proxy like this.</p>
<h2>TompHTTP</h2>
<p>Since my last proxy was so unpolished, I made sure Infrared conformed to a popular proxy standard. Thankfully, the <a href="https://github.com/TompHTTP">TompHTTP</a> standard proved popular enough.</p>
<h2>bare-server-worker</h2>
<p>It turned out that work was already done for porting the TompHTTP bare-server to the Workers runtime, called <code>bare-server-worker</code>. However, this implementation was not maintained, so it only supported the older V2 standard.</p>
<h2>Adding V3</h2>
<p>Adding support for the newer V3 standard was trivial, as the only major change was merging the old URL segment headers into a new header, named <code>x-bare-url</code>.</p>
<h2>Frontend</h2>
<p>I opted to use <a href="https://github.com/titaniumnetwork-dev/Ultraviolet">Ultraviolet</a> as my bare client, as it was well-maintained and had a large userbase. For the webpage, I used a heavily-modified version of <a href="https://github.com/titaniumnetwork-dev/Ultraviolet-Static">Ultraviolet-Static</a>, as it was written in vanilla HTML.</p>
<h2>Hosting</h2>
<p>For hosting the website, I decided to use Cloudflare Pages, as hosting both backend and frontend on Cloudflare&#x27;s edge network would reduce the chance of the frontend being blocked via IP address (versus GitHub Pages, which was my host of choice)</p>
<h2>What next?</h2>
<p>I plan on adding search suggestions and overall improving user experience.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Notes on installing alternative OSes on Chromebooks]]></title>
            <link>https://bomberfish.ca/blog/mrchromebox-notes</link>
            <guid isPermaLink="false">https://bomberfish.ca/blog/mrchromebox-notes</guid>
            <pubDate>Fri, 20 Oct 2023 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Tested on Dell Chromebook 3100, with MrChromebox&#x27;s RW_LEGACY flashed via SH1MMER. Proxying on the edge part 2 eta son, just need to get to writing it.</p>
<h2>Windows 11</h2>
<ul>
<li>Used tiny11 b1 iso</li>
<li>Got to winPE, trackpad did not work. USB mouse did though.</li>
<li>Couldn&#x27;t start installation, complained about missing disk drivers</li>
</ul>
<h2>Windows 10</h2>
<ul>
<li>Worked fine, needed to install EC drivers from CoolStar.</li>
<li>Make sure to use a dedicated Windows USB installer such as WoeUSB on Linux.</li>
</ul>
<h2>Fedora 38 Workstation</h2>
<ul>
<li>Booted and installed fine</li>
<li>All devices seem to work well (trackpad, kb, wifi, bt)</li>
<li>Best to install to stateful partition. Storage size is good enough™, also allows you to go back to crOS via recovery usb.</li>
<li>Stable enough to daily drive</li>
</ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Proxying on the Edge: Part 1]]></title>
            <link>https://bomberfish.ca/blog/cf-workers-proxy-part-1</link>
            <guid isPermaLink="false">https://bomberfish.ca/blog/cf-workers-proxy-part-1</guid>
            <pubDate>Mon, 25 Sep 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Part 1 of a series about running web proxies using Cloudflare Workers (Superposition).]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>This post is a part of an ongoing series on running web proxies using Cloudflare Workers.</p>
</blockquote>
<p>Recently, I made a proxy called <a href="https://superposition.bomberfish.ca">Superposition</a>, running on Cloudflare&#x27;s Workers serverless platform.</p>
<p>You can check out its source code <a href="https://github.com/BomberFish/Superposition">here</a>.</p>
<h2>How?</h2>
<p>It turns out there&#x27;s already a project for proxying requests using CF Workers, called <a href="https://github.com/aD4wn/Workers-Proxy">Workers-Proxy</a>. It works by making a request to a fixed URL, then returning the response to the user. The only issue is that it only works with a single domain, which is hard-coded into the program.</p>
<p>Superposition fixes this issue by checking for a <code>?url=</code> parameter in the requested URL. If the parameter is present, it proxies the provided URL. If not, it returns a landing page for you to input a URL.</p>
<p>Another feature of Superposition is that when it proxies a website, it also injects a button fixed to the top-left of the screen to go back to the landing page.</p>
<h2>Why?</h2>
<p>The idea was simple: Since existing proxy websites rely on a central server which may or may not be close to the user, why not use an established global network such as Cloudflare to proxy the requests?</p>
<h2>What next?</h2>
<p>I am currently working on a new proxy, dubbed <strong>Infrared</strong>, which is completely compliant with the <a href="https://tomp.app">TompHTTP</a> specifications. Infrared aims to be more stable than Superposition, and includes more advanced features such as WebSocket proxying.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[MacDirtyCow app development: From zero to hero.]]></title>
            <link>https://bomberfish.ca/blog/mdc-zero-to-hero</link>
            <guid isPermaLink="false">https://bomberfish.ca/blog/mdc-zero-to-hero</guid>
            <pubDate>Fri, 12 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[A walkthrough for building an app that leverages the MacDirtyCow exploit (MDC).]]></description>
            <content:encoded><![CDATA[<link rel="preload" as="image" href="https://bomberfish.ca/blog-images/xcode-home.jpg"/><link rel="preload" as="image" href="https://bomberfish.ca/blog-images/xc-proj-opts.jpg"/><link rel="preload" as="image" href="https://bomberfish.ca/blog-images/xc-starter.jpg"/><h2>Wait. What exactly is MacDirtyCow?</h2>
<p><a href="https://nvd.nist.gov/vuln/detail/CVE-2022-46689">MacDirtyCow</a>, often abbreviated as MDC, is a security vunerability that exploits a race condition in XNU&#x27;s virtual memory system to overwrite the cached version of any given file. Because of its versatility, it has been used in a number of recent iOS customization apps such as Cowabunga.</p>
<blockquote>
<p>I&#x27;m saying this like I&#x27;m some mega-nerd who knows how everything works, but in reality I don&#x27;t even know how copy-on-write works 😭</p>
</blockquote>
<p>Now with that out of the way, let&#x27;s get into how you can make your own app that leverages the MacDirtyCow exploit.</p>
<h2>Prerequisites</h2>
<ul>
<li>A Mac with Xcode 14+ installed</li>
<li>An iOS device with an iOS version of 16.1.2 or below. I recommend anywhere from 15.0 to 16.1.2.</li>
</ul>
<h2>Getting Started with your Xcode Project</h2>
<p>Open Xcode. You should be presented with a screen similar to this:</p>
<p><img src="https://bomberfish.ca/blog-images/xcode-home.jpg" alt="Xcode Startup Screen"/></p>
<p>Click &quot;Create a new Xcode project&quot; to create a new project. Select &quot;App&quot; under the &quot;iOS&quot; tab. Click Next and you will be prompted to choose options for the project. Set it up similar to the image below, but make sure to change the Product Name to what you want your app to be called, and the Organization Identifier to whatever you want. Make sure Interface is set to SwiftUI and Language is set to Swift.</p>
<p><img src="https://bomberfish.ca/blog-images/xc-proj-opts.jpg" alt="Xcode Project Options"/></p>
<p>Click next and save the directory wherever you want.</p>
<p>Congratulations! You&#x27;ve made your first Xcode project.</p>
<p><img src="https://bomberfish.ca/blog-images/xc-starter.jpg" alt="Xcode Project"/></p>
<h2>Adding MDC</h2>
<p>Now here comes the fun part: adding the MacDirtyCow exploit.</p>
<h3>Option 1: Using DirtyCowKit</h3>
<p>Step 1: In Xcode, under the file menu, click &quot;Add Packages...&quot;</p>
<p>Step 2: In the search bar, enter <code>https://github.com/BomberFish/DirtyCowKit</code>. Click &quot;Add Package&quot; and then &quot;Add Package&quot; (again) and it should add the package to your project.</p>
<p><strong>OPTIONAL:</strong> Add the AbsoluteSolver package from the following link: <code>https://github.com/BomberFish/AbsoluteSolver-iOS</code>. Absolute Solver will automatically toggle between regular file operations using Swift&#x27;s <code>FileManager</code> and an MDC-based operation.</p>
<h3>Option 2 (advanced, not recommended): Manually adding the exploit files</h3>
<p>Step 1: Get the exploit files. You can easily find these from somewhere like the <a href="https://github.com/leminlimez/Cowabunga/tree/main/MacDirtyCowSwift/Exploit">Cowabunga GitHub repository</a>.</p>
<p>Step 2: Add the files to your Xcode project, making sure to include them in your bridging header. If you don&#x27;t know what that is or are confused, you&#x27;re best off using option 1. The steps below are for option 1 only.</p>
<h2>Unsandboxing</h2>
<p>At the top of your <code>&lt;Project name&gt;App.swift</code> file, type <code>import DirtyCowKit</code>. Then, under <code>ContentView()</code>, add the following code:</p>
<pre><code class="language-swift">.<span class="pl-c1">onAppear</span> {
    <span class="pl-k">if</span> <span class="pl-c1">#available</span>(<span class="pl-k">iOS</span> <span class="pl-c1">16.2</span>, <span class="pl-k">*</span>) {
        <span class="pl-c1">print</span>(<span class="pl-s"><span class="pl-pds">&quot;</span>Throwing not supported error (mdc patched)<span class="pl-pds">&quot;</span></span>)
        DispatchQueue.<span class="pl-smi">main</span>.<span class="pl-c1">async</span> {
            <span class="pl-k">let</span> alert <span class="pl-k">=</span> <span class="pl-c1">UIAlertController</span>(<span class="pl-c1">title</span>: <span class="pl-s"><span class="pl-pds">&quot;</span>Not Supported<span class="pl-pds">&quot;</span></span>, <span class="pl-c1">message</span>: <span class="pl-s"><span class="pl-pds">&quot;</span>This version of iOS is not supported.<span class="pl-pds">&quot;</span></span>, <span class="pl-c1">preferredStyle</span>: .<span class="pl-smi">alert</span>)
            alert.<span class="pl-c1">addAction</span>(<span class="pl-c1">UIAlertAction</span>(<span class="pl-c1">title</span>: <span class="pl-s"><span class="pl-pds">&quot;</span>OK<span class="pl-pds">&quot;</span></span>, <span class="pl-c1">style</span>: .<span class="pl-smi">default</span>, <span class="pl-c1">handler</span>: <span class="pl-c1">nil</span>))
            UIApplication.<span class="pl-smi">shared</span>.<span class="pl-smi">windows</span>.<span class="pl-c1">first</span><span class="pl-k">?</span>.<span class="pl-smi">rootViewController</span><span class="pl-k">?</span>.<span class="pl-c1">present</span>(alert, <span class="pl-c1">animated</span>: <span class="pl-c1">true</span>, <span class="pl-c1">completion</span>: <span class="pl-c1">nil</span>)
        }
    } <span class="pl-k">else</span> {
        <span class="pl-c">// grant r/w access</span>
        <span class="pl-k">if</span> <span class="pl-c1">#available</span>(<span class="pl-k">iOS</span> <span class="pl-c1">15</span>, <span class="pl-k">*</span>) {
            <span class="pl-c1">print</span>(<span class="pl-s"><span class="pl-pds">&quot;</span>Escaping Sandbox...<span class="pl-pds">&quot;</span></span>)
            DispatchQueue.<span class="pl-smi">main</span>.<span class="pl-c1">asyncAfter</span>(<span class="pl-c1">deadline</span>: .<span class="pl-c1">now</span>() <span class="pl-k">+</span> <span class="pl-c1">0.15</span>) {
                <span class="pl-k">do</span> {
                    <span class="pl-k">try</span> MacDirtyCow.<span class="pl-c1">unsandbox</span>()
                } <span class="pl-k">catch</span> {
                    <span class="pl-k">let</span> alert <span class="pl-k">=</span> <span class="pl-c1">UIAlertController</span>(<span class="pl-c1">title</span>: <span class="pl-s"><span class="pl-pds">&quot;</span>Unsandboxing Error<span class="pl-pds">&quot;</span></span>, <span class="pl-c1">message</span>: <span class="pl-s"><span class="pl-pds">&quot;</span><span class="pl-pse">\(</span>error.<span class="pl-smi">localizedDescription</span><span class="pl-pse">)</span><span class="pl-cce">\n</span>Please close the app and retry. If the problem persists, reboot your device.<span class="pl-pds">&quot;</span></span>, <span class="pl-c1">preferredStyle</span>: .<span class="pl-smi">alert</span>)
                    UIApplication.<span class="pl-smi">shared</span>.<span class="pl-smi">windows</span>.<span class="pl-c1">first</span><span class="pl-k">?</span>.<span class="pl-smi">rootViewController</span><span class="pl-k">?</span>.<span class="pl-c1">present</span>(alert, <span class="pl-c1">animated</span>: <span class="pl-c1">true</span>, <span class="pl-c1">completion</span>: <span class="pl-c1">nil</span>)
                }
            }
        }
    }
} 
</code></pre>
<p><strong>Your file should look something like this:</strong></p>
<pre><code class="language-swift"><span class="pl-k">import</span> <span class="pl-en">MacDirtyCow</span>
<span class="pl-k">import</span> <span class="pl-en">SwiftUI</span>

<span class="pl-k">@main</span>
<span class="pl-k">struct</span> <span class="pl-en">DirtyCowExampleApp</span>: <span class="pl-e">App </span>{
    <span class="pl-k">var</span> body: <span class="pl-k">some</span> Scene {
        <span class="pl-c1">WindowGroup</span> {
            <span class="pl-c1">ContentView</span>()
                .<span class="pl-c1">onAppear</span> {
                    <span class="pl-k">if</span> <span class="pl-c1">#available</span>(<span class="pl-k">iOS</span> <span class="pl-c1">16.2</span>, <span class="pl-k">*</span>) {
                        <span class="pl-c1">print</span>(<span class="pl-s"><span class="pl-pds">&quot;</span>Throwing not supported error (mdc patched)<span class="pl-pds">&quot;</span></span>)
                        DispatchQueue.<span class="pl-smi">main</span>.<span class="pl-c1">async</span> {
                            <span class="pl-k">let</span> alert <span class="pl-k">=</span> <span class="pl-c1">UIAlertController</span>(<span class="pl-c1">title</span>: <span class="pl-s"><span class="pl-pds">&quot;</span>Not Supported<span class="pl-pds">&quot;</span></span>, <span class="pl-c1">message</span>: <span class="pl-s"><span class="pl-pds">&quot;</span>This version of iOS is not supported.<span class="pl-pds">&quot;</span></span>, <span class="pl-c1">preferredStyle</span>: .<span class="pl-smi">alert</span>)
                            alert.<span class="pl-c1">addAction</span>(<span class="pl-c1">UIAlertAction</span>(<span class="pl-c1">title</span>: <span class="pl-s"><span class="pl-pds">&quot;</span>OK<span class="pl-pds">&quot;</span></span>, <span class="pl-c1">style</span>: .<span class="pl-smi">default</span>, <span class="pl-c1">handler</span>: <span class="pl-c1">nil</span>))
                            UIApplication.<span class="pl-smi">shared</span>.<span class="pl-smi">windows</span>.<span class="pl-c1">first</span><span class="pl-k">?</span>.<span class="pl-smi">rootViewController</span><span class="pl-k">?</span>.<span class="pl-c1">present</span>(alert, <span class="pl-c1">animated</span>: <span class="pl-c1">true</span>, <span class="pl-c1">completion</span>: <span class="pl-c1">nil</span>)
                        }
                    } <span class="pl-k">else</span> {
                        <span class="pl-c">// grant r/w access</span>
                        <span class="pl-k">if</span> <span class="pl-c1">#available</span>(<span class="pl-k">iOS</span> <span class="pl-c1">15</span>, <span class="pl-k">*</span>) {
                            <span class="pl-c1">print</span>(<span class="pl-s"><span class="pl-pds">&quot;</span>Escaping Sandbox...<span class="pl-pds">&quot;</span></span>)
                            DispatchQueue.<span class="pl-smi">main</span>.<span class="pl-c1">asyncAfter</span>(<span class="pl-c1">deadline</span>: .<span class="pl-c1">now</span>() <span class="pl-k">+</span> <span class="pl-c1">0.15</span>) {
                                <span class="pl-k">do</span> {
                                    <span class="pl-k">try</span> MacDirtyCow.<span class="pl-c1">unsandbox</span>()
                                } <span class="pl-k">catch</span> {
                                    <span class="pl-k">let</span> alert <span class="pl-k">=</span> <span class="pl-c1">UIAlertController</span>(<span class="pl-c1">title</span>: <span class="pl-s"><span class="pl-pds">&quot;</span>Unsandboxing Error<span class="pl-pds">&quot;</span></span>, <span class="pl-c1">message</span>: <span class="pl-s"><span class="pl-pds">&quot;</span><span class="pl-pse">\(</span>error.<span class="pl-smi">localizedDescription</span><span class="pl-pse">)</span><span class="pl-cce">\n</span>Please close the app and retry. If the problem persists, reboot your device.<span class="pl-pds">&quot;</span></span>, <span class="pl-c1">preferredStyle</span>: .<span class="pl-smi">alert</span>)
                                    UIApplication.<span class="pl-smi">shared</span>.<span class="pl-smi">windows</span>.<span class="pl-c1">first</span><span class="pl-k">?</span>.<span class="pl-smi">rootViewController</span><span class="pl-k">?</span>.<span class="pl-c1">present</span>(alert, <span class="pl-c1">animated</span>: <span class="pl-c1">true</span>, <span class="pl-c1">completion</span>: <span class="pl-c1">nil</span>)
                                }
                            }
                        }
                    }
                }
        }
    }
}
</code></pre>
<h2>Using MDC</h2>
<p>Here are some examples of using MDC in your app.</p>
<h3>How to replace a file</h3>
<pre><code class="language-swift"><span class="pl-k">import</span> <span class="pl-en">MacDirtyCow</span>
MacDirtyCow.<span class="pl-c1">overwriteFileWithDataImpl</span>(<span class="pl-c1">originPath</span>: <span class="pl-c1">String</span>, <span class="pl-c1">replacementData</span>: Data)
</code></pre>
<h3>If you opted to add AbsoluteSolver earlier</h3>
<pre><code class="language-swift"><span class="pl-k">import</span> <span class="pl-en">AbsoluteSolver</span>
AbsoluteSolver.<span class="pl-c1">replace</span>(<span class="pl-c1">at</span>: URL, <span class="pl-c1">with</span>: NSData)
</code></pre>
<h2>What now?</h2>
<p>I recommend using websites like <a href="https://www.hackingwithswift.com">Hacking With Swift</a> to up your Swift knowledge. Also make sure to look at the source code of other MDC applications like Cowabunga, ControlConfig or AppCommander for more advanced usage.</p>
<p>Otherwise, happy hacking!</p>
<h3>Revisions</h3>
<ul>
<li>2023-05-12: Minor updates</li>
<li>2023-09-26: Fix formatting</li>
</ul>]]></content:encoded>
            <enclosure url="https://bomberfish.ca/blog-images/xc-starter.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Hello world!]]></title>
            <link>https://bomberfish.ca/blog/hello-world</link>
            <guid isPermaLink="false">https://bomberfish.ca/blog/hello-world</guid>
            <pubDate>Thu, 11 May 2023 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>This is my <strong>brand new</strong> blog. I hope to post various articles about different projects I&#x27;m working on.</p>]]></content:encoded>
        </item>
    </channel>
</rss>