<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Blogopolis</title><link href="https://tvc-16.science/" rel="alternate"></link><link href="https://tvc-16.science/feeds/all.atom.xml" rel="self"></link><id>https://tvc-16.science/</id><updated>2026-03-31T00:00:00+02:00</updated><entry><title>A MARCHintosh 2026 summary</title><link href="https://tvc-16.science/globaltalk-2026.html" rel="alternate"></link><published>2026-03-31T00:00:00+02:00</published><updated>2026-03-31T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2026-03-31:/globaltalk-2026.html</id><summary type="html">&lt;p&gt;i am once again jacking in to the dunelab through ethertalk&lt;/p&gt;</summary><content type="html">&lt;p&gt;Wow, we're a quarter through the year already! That means it's &lt;a href="https://marchintosh.com/"&gt;MARCHintosh&lt;/a&gt; time!&lt;br&gt;
It's still March 31st in &lt;em&gt;some parts of the world&lt;/em&gt; as I'm posting this, so it's all kosher and legit and cool.  &lt;/p&gt;
&lt;p&gt;Last year towards the end of the month, the power switch on my &lt;a href="./performa-plus-display.html"&gt;Apple Performa Plus Display&lt;/a&gt; stopped working properly and required the help of tape to remain powered on...&lt;br&gt;
So I spent most of my March 2026 &lt;em&gt;quality Mac time&lt;/em&gt; repairing that CRT instead of actually interacting with &lt;a href="./globaltalk.html"&gt;GlobalTalk&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;It's not my fault though! &lt;sub&gt;ok maybe a little bit&lt;/sub&gt; I didn't think those switches were that bad!  &lt;/p&gt;
&lt;h1&gt;ME5A switches fucking suck holy shit&lt;/h1&gt;
&lt;p&gt;The power switch of the Performa Plus (and many CRTs of that vintage) is a &lt;strong&gt;PREH ME5A&lt;/strong&gt; -- This is a locking switch whose entire "locking" part relies on a &lt;em&gt;small plastic nub that's subject to wear.&lt;/em&gt;  &lt;/p&gt;
&lt;p&gt;So at some point, the plastic is entirely sanded off and the switch doesnt pass power unless you keep it held manually.&lt;br&gt;
I'm aware this is 90s tech and they likely didn't have anything better.. but those switches truly are a disaster to work with.&lt;br&gt;
Being a cheapskate, I did attempt to repair my existing switch once I desoldered it off the board, as you can replace the plastic nub with &lt;a href="https://youtube.com/watch?v=TLYulBG5lG8"&gt;3D-printed parts&lt;/a&gt;.&lt;br&gt;
&lt;img alt="unmitigated teardown disaster" src="./images/globaltalk/me5ahell.jpg"&gt;&lt;br&gt;
Except... putting the switch back together is also a nightmare since the entire design relies on small metal contacts that will fly off at the first chance they get.  &lt;/p&gt;
&lt;p&gt;After two evenings spent trying to put the fucking thing back without a lot of resource online as to how the design works... I gave up and just bought a new old stock switch off the internet.&lt;br&gt;
&lt;img alt="das it mane" src="./images/dasitmane.jpg"&gt;&lt;br&gt;
Those things are expensive too... I might just start hunting for CRT corpses just to harvest the power buttons from them&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt;.  &lt;/p&gt;
&lt;p&gt;While the monitor was torn down though, I also finally replaced the hackjob video cable I made for it 6 years ago.&lt;br&gt;
&lt;img alt="" src="./images/globaltalk/performaredux1.jpg"&gt;&lt;br&gt;
This CRT came with hardwired video out of the factory, so adding a DB15 socket there is an actual upgrade!&lt;br&gt;
There's no space to put it in the monitor chassis itself, so you need a little breakout box that protrudes out.  &lt;/p&gt;
&lt;p&gt;I grabbed a failed 3D printed box I had to house the socket and wires; I could've made a better scaled box for this, but I already had this print laying around that wasn't doing anything 🤷&lt;br&gt;
&lt;img alt="don't look at the tape too closely its just decorative i swear" src="./images/globaltalk/performaredux2.jpg"&gt;&lt;br&gt;
I already spent money on the accursed switch and the DB15 socket, so &lt;strong&gt;you better believe&lt;/strong&gt; I'm going to save on 0.006 cents' worth of PLA to compensate.  &lt;/p&gt;
&lt;p&gt;It's still a hackjob, but less so! The box has some screws to hold the socket in place so it's actually pretty sturdy, and I don't need to wiggle the video cable anymore to get stable color&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt;.      &lt;/p&gt;
&lt;h1&gt;A quick tour of GlobalTalk&lt;/h1&gt;
&lt;p&gt;With the CRT functional again, I booted up the Centris 650 and restored the &lt;a href="https://bsky.app/profile/difegue.tvc-16.science/post/3lk54yua2ww2d"&gt;TVC-16 AppleTalk Zone&lt;/a&gt;.&lt;br&gt;
You can have a browse right now! I'll probably leave it open for a few days after the end of March to compensate for uh, not being here most of the month.  &lt;/p&gt;
&lt;p&gt;There are a bunch of new Zones&lt;sup id="ref-3"&gt;&lt;a href="#note-3"&gt;***&lt;/a&gt;&lt;/sup&gt; to browse through this year as well, which is the most fun part of this to me -- Although having to do it through the Centris' slow network is always a bit annoying, that's part of the charm.&lt;br&gt;
My &lt;a href="./globaltalk.html"&gt;thoughts from last year&lt;/a&gt; on the &lt;em&gt;cool factor&lt;/em&gt; of this network still stand, and there's newer things being done with it every year that makes it even more fun to browse.   &lt;/p&gt;
&lt;p&gt;This year, you could have an &lt;a href="https://bitbang.social/@kalleboo/116267934997465916"&gt;After Dark screensaver&lt;/a&gt; styled after &lt;em&gt;fsn&lt;/em&gt; &lt;sub&gt;the file manager not the visual novel&lt;/sub&gt; that shows you all the Zones in the network, alongside the usual chat programs and online games.  &lt;/p&gt;
&lt;p&gt;Speaking of games, &lt;a href="https://robotspacer.software/transfer-point.html"&gt;transfer point&lt;/a&gt; has fully released, and can be played either on real hardware or in an emulator. I'm def going to give that a deeper look later as a fellow &lt;a href="./funtography.html"&gt;multi-document interface adventure game&lt;/a&gt; enjoyer. &lt;/p&gt;
&lt;p&gt;There's even a little &lt;a href="https://bitbang.social/@kalleboo/116302182454732958"&gt;wplace-like&lt;/a&gt; you can use this year -- It closes in about 12 hours from this blog's posting so you still have time to go make a silly doodle if you want!&lt;br&gt;
&lt;img alt="GlobalTalk Canvas in its nearly final form" src="./images/globaltalk/canvas.png"&gt;  &lt;/p&gt;
&lt;p&gt;Someone also made a new Telnet client for classic Macs for your BBSing needs.  &lt;br&gt;
It's always fun seeing terminal emulators in an OS that absolutely did not have any support for these out of the gate.&lt;br&gt;
&lt;img alt="flynn telnet client running on the centris 650 connected to a BBS" src="./images/globaltalk/flynn.jpg"&gt;&lt;br&gt;
and...oh it's fucking claude code. oh thats gore in my comfort retrocomputing environment i cant escape this bloody thing no matter where i go this is software hell and we're all living in it happily wearing our badges of shame as we grow dependent on a heavily subsidized slot machine &lt;/p&gt;
&lt;p&gt;See you next year! Maybe next time I'll go for more CRT pain and try to repair my &lt;a href="https://www.cultofmac.com/apple-history/macintosh-portrait-display"&gt;Portrait Display&lt;/a&gt;.  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; Realistically it shouldn't be that hard to design a replacement for those switches that uses a modern locking switch under the hood (at the expense of losing that clicky feeling a bit i suppose), so maybe I'll look into that whenever this replacement switch inevitably dies too. &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; 6 years lifespan for the previous tape-fueled cable is a pretty good time though, so never let anyone tell you your temporary solution sucks xoxo &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-3"&gt;&lt;a href="#ref-3"&gt;***&lt;/a&gt; A few cool zone names from this year's selection: Cyberelf Space Zone, MilleniumMacs, EnbyNET, Dunelab. &lt;/sup&gt;   &lt;/p&gt;</content><category term="Blogposting"></category><category term="apple"></category><category term="macintosh"></category><category term="mac"></category><category term="macos"></category><category term="crt"></category><category term="diy"></category><category term="retrocomputing"></category><category term="marchintosh"></category></entry><entry><title>A quick review of the Hobonichi Techo App</title><link href="https://tvc-16.science/techo-app-review.html" rel="alternate"></link><published>2026-03-02T00:00:00+01:00</published><updated>2026-03-02T00:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2026-03-02:/techo-app-review.html</id><summary type="html">&lt;p&gt;tl;dr it's pretty good but what even is their business model&lt;/p&gt;</summary><content type="html">&lt;p&gt;The &lt;a href="https://techoapp.1101.com/en/"&gt;Hobonichi Techo app&lt;/a&gt; is finally available in English on both iOS and Android! Wow!&lt;br&gt;
This app is basically a digital version of their popular &lt;a href="https://www.1101.com/store/techo/en/magazine/2026/y26/"&gt;"Life Book"&lt;/a&gt; planner, although its approach to journaling is more guided than just giving you a blank page every day.  &lt;/p&gt;
&lt;p&gt;This was announced back in August of last year and I was &lt;a href="./2026-techo.html"&gt;pretty curious about it&lt;/a&gt; when I was buying my planner for the year -- And upon receiving said planner Hobonichi  threw in a code to use some premium plan features for free, so I went in and gave it a try!  &lt;/p&gt;
&lt;h1&gt;The Journaling (pretty good)&lt;/h1&gt;
&lt;p&gt;Similarly to Apple's &lt;a href="https://www.apple.com/newsroom/2023/12/apple-launches-journal-app-a-new-app-for-reflecting-on-everyday-moments/"&gt;Journal&lt;/a&gt;, the Techo app can pull data from your phone to help you in your reflection endeavors.&lt;br&gt;
With permission, it can fetch:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the day's weather  &lt;/li&gt;
&lt;li&gt;photos from your library  &lt;/li&gt;
&lt;li&gt;your calendar  &lt;/li&gt;
&lt;li&gt;your GPS location during the day  &lt;/li&gt;
&lt;li&gt;health/sleep data.   &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That makes it so that every day's entry will likely be pretty filled up already if you enable everything!&lt;br&gt;
If you don't fancy enabling automatic sync, you can add any of those elements manually at any time, alongside manual written notes.&lt;/p&gt;
&lt;p&gt;You can add &lt;strong&gt;tags&lt;/strong&gt; to any item in the timeline so you can search for them later, as well as &lt;strong&gt;pin&lt;/strong&gt; one of those items to be "&lt;em&gt;Today's Cover&lt;/em&gt;", AKA the main thing that happened on a given day.&lt;br&gt;
&lt;img alt="Techo app screenshot with a random meme as the cover picture" src="./images/techo/app.jpg"&gt;&lt;br&gt;
I find that this makes for a very &lt;strong&gt;low-friction&lt;/strong&gt; approach to journaling: It's super easy to just pick a random photo you took or a meme you saved, pin it, and write like 5 words about your day.  &lt;/p&gt;
&lt;p&gt;I mostly use my physical Techo as an &lt;a href="https://artreview.com/daydreaming-is-so-important-to-me-how-david-lynch-fishes-for-ideas/"&gt;idea book&lt;/a&gt;, with a few sprinklings of TODO-listing and "&lt;em&gt;paste random physical shit&lt;/em&gt;" whenever I have concert tickets or other stuff.&lt;br&gt;
I don't tend to do much actual journaling with it since... reflecting back on your day is &lt;em&gt;kinda hard&lt;/em&gt; sometimes?  &lt;/p&gt;
&lt;p&gt;It might just be because I have terminal ADHD brain, but it's sometimes surprisingly hard to write about the actual things that happened on a given day, either because too much shit happened... or too little.&lt;br&gt;
I've been finding the phone app surprisingly useful for this, as the pre-filled entries have made me go "&lt;em&gt;oh yeah &lt;strong&gt;that&lt;/strong&gt; happened today&lt;/em&gt;" on a few occasions already.  &lt;/p&gt;
&lt;p&gt;Also... it &lt;strong&gt;is&lt;/strong&gt; easier at times for me to type a quick blurb with a phone keyboard than it is to do some handwriting.&lt;br&gt;
That's a fact I'm not necessarily comfortable having figured out... BUT at least it's eaten into the doomscrolling time a bit.  &lt;/p&gt;
&lt;p&gt;There's some small gamification on top of it all with the "Omake" currency that increments every time you write, and the daily reminder notification is also pretty useful -- It matches the &lt;em&gt;Senpai&lt;/em&gt; you've selected, which makes it kinda fun to look forward to each day.  &lt;/p&gt;
&lt;h1&gt;The Senpai feature (its ok)&lt;/h1&gt;
&lt;p&gt;When you start using the app, you're asked to choose a kind of virtual assistant from a selection of three avatars.&lt;br&gt;
I picked "Froggo" the frog since I liked the cut of his jib, but there's also a Dog and a King available.&lt;br&gt;
&lt;img alt="Froggo doing his best to encourage me to talk about how dogshit my meetings are" src="./images/techo/app2.jpg"&gt;&lt;br&gt;
The Senpai were initially marketed as "Thought Intelligence", which immediately rang the AI slop warning bells in my mind at full volume. But!&lt;br&gt;
As a matter of fact there is &lt;strong&gt;no genAI&lt;/strong&gt; bullshit in this app: The Senpai's comments are all &lt;a href="https://help.1101.com/hc/en-us/articles/5130179892766-How-are-Senpai-s-comments-created"&gt;prewritten stuff&lt;/a&gt;.&lt;br&gt;
They're basically just &lt;em&gt;nice, wholesome journaling Clippy&lt;/em&gt; without the horrors.  &lt;/p&gt;
&lt;p&gt;The app does do some small image recognition on the photos you import into it; I've seen specialized comments on photos of cars, food, and written text.&lt;br&gt;
I initially thought this would &lt;strong&gt;run on your device&lt;/strong&gt;, since basic object detection can be done with small ML&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt; models and modern phones can 100% do this stuff!  It's cheaper and makes sense!&lt;br&gt;
I couldn't find any hints of that in the app's open-source library list&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt; though.  &lt;br&gt;
&lt;img alt="Froggo detecting a kirby food png" src="./images/techo/app3.jpg"&gt;&lt;br&gt;
&lt;sub&gt;(shoutout to &lt;a href="https://sethmlarson.dev/food-jpegs-in-super-smash-bros-and-kirby-air-riders"&gt;this blogpost&lt;/a&gt; for giving me sakurai food pngs to test out - also it's a fun read)&lt;/sub&gt;  &lt;/p&gt;
&lt;p&gt;And well, the app &lt;strong&gt;doesn't actually run without an Internet connection&lt;/strong&gt;, so I guess they're uploading your photos to a server just to run object detection for funny comments, then deleting them.  &lt;/p&gt;
&lt;p&gt;I didn't have the motivation to go in and crack open the app to see what data it sends so this is just conjecture 🤷&lt;br&gt;
The app does offer &lt;strong&gt;free cloud storage&lt;/strong&gt; for your journal, but is capped to one photo per day.  &lt;/p&gt;
&lt;h2&gt;Wait, cloud storage? Does this app just copy all the data on my phone or something?&lt;/h2&gt;
&lt;p&gt;Well...&lt;em&gt;yes&lt;/em&gt;! Anything you add to the app (either manually or through autosync) gets &lt;strong&gt;saved to the cloud&lt;/strong&gt; and tied to your Hobonichi ID account in case you switch devices.&lt;br&gt;
The terms of service do mention that your data is &lt;a href="https://help.1101.com/hc/en-us/articles/4848987432094-About-Data-Security-Measures"&gt;encrypted and isn't shared with third-parties&lt;/a&gt;, but I think you should still be aware that you're technically putting something as &lt;em&gt;deeply personal&lt;/em&gt; as a journal on a remote server.  &lt;/p&gt;
&lt;p&gt;I kinda wish you could disable the cloud mirroring, but that'd imply being able to run the app offline to begin with...&lt;br&gt;
What you &lt;strong&gt;can&lt;/strong&gt; do is have the app backup all your photos instead of one per day if you pay for the Premium plan, which is the &lt;em&gt;opposite&lt;/em&gt; of what I want!  &lt;/p&gt;
&lt;h1&gt;The Premium plan (i would never pay for this)&lt;/h1&gt;
&lt;p&gt;The &lt;a href="https://techoapp.1101.com/en/premium/"&gt;Premium plan&lt;/a&gt; is a &lt;strong&gt;55€/year&lt;/strong&gt; subscription which is a terrible value for money as the app currently stands -- Even if you don't &lt;a href="./clz-to-gameye.html"&gt;hate subscriptions&lt;/a&gt; like I do.  &lt;/p&gt;
&lt;p&gt;As mentioned above, the sub allows you to upload all your photos (and videos) to Hobonichi's cloud, which is something Google can do for you at half the price&lt;sup id="ref-3"&gt;&lt;a href="#note-3"&gt;***&lt;/a&gt;&lt;/sup&gt;.&lt;br&gt;
The app will also downscale your photos when uploading them even on the paid plan, so it's not even really good as a backup..  &lt;/p&gt;
&lt;p&gt;Other features include deeper image recognition by allowing you to search for keywords in your photos on top of your manually-added tags, a "&lt;em&gt;Memory Print&lt;/em&gt;" feature that works with the "real" physical Hobonichi Techo, and... &lt;a href="https://help.1101.com/hc/en-us/articles/4842400268574-Exporting-Data"&gt;JSON export&lt;/a&gt;.  &lt;/p&gt;
&lt;h2&gt;what the fuck, exporting your data is paid?&lt;/h2&gt;
&lt;p&gt;I think it's nice that the app actually &lt;strong&gt;allows&lt;/strong&gt; you to exfiltrate your data, and the functionality seems solid (JSON + a zip for your photos/videos), but it kinda feels like you're being held at ransom if you ever want to put your data somewhere else.  &lt;/p&gt;
&lt;p&gt;Then again, subscriptions always have a trial period now, so nothing will really stop you from getting the free month, doing your export and then cancelling. But still, this stuff should be basic functionality!  &lt;/p&gt;
&lt;h2&gt;Memory Print&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://help.1101.com/hc/en-us/articles/4842459006750-Printing-Memories-Memory-Print"&gt;remaining premium feature&lt;/a&gt; feels like a modern version of the &lt;a href="https://www.youtube.com/watch?v=7_cfpJPDXSQ"&gt;Wii Digicam Print Channel&lt;/a&gt; -- It essentially allows you to print your Hobonichi App timeline's items into little vignettes suitable for pasting into your physical Hobonichi Techo.&lt;br&gt;
&lt;img alt="" src="./images/techo/appprint.jpg"&gt;&lt;br&gt;
In a way, it's also data export... except way more analog.  &lt;/p&gt;
&lt;p&gt;This feature is "&lt;em&gt;better in Japan&lt;/em&gt;"™️as they've implemented direct compatibility with the print shop systems that are available in convenience stores, and it sounds kinda cute to get your stuff printed that way on sticker paper -- But realistically this is only generating a PDF.  &lt;/p&gt;
&lt;p&gt;If you want to print stuff from the app to paste, this is barely more convenient than just taking screen captures on your phone and printing those? It certainly doesn't feel worth the subscription price as it is.  &lt;/p&gt;
&lt;h1&gt;tl;dr&lt;/h1&gt;
&lt;p&gt;I really like the mood of the app!&lt;br&gt;
A lot of effort was put in to make it fun to use, between the Senpai assistants and the little gamification bits.&lt;br&gt;
&lt;sub&gt;The SFX that plays when you earn &lt;em&gt;Omake&lt;/em&gt; is pretty satisfying&lt;/sub&gt;&lt;br&gt;
The Senpai unfortunately do seem to have a limited set of lines so you'll see repeats relatively quickly, but in 2026 it's a breath of fresh air not having it be AI slop.  &lt;/p&gt;
&lt;p&gt;Now, whether or not the app's approach to journaling works? I think that depends on you!&lt;br&gt;
I can totally see the value in automatically aggregating all the data on your phone to help with self-reflection, and even if you feel like you can't commit to writing every day, having all those prefilled bits you can pin makes it very easy to build a habit of always having &lt;em&gt;something&lt;/em&gt; in there.  &lt;/p&gt;
&lt;p&gt;And as a result, when you look back on a month or a year, even if it was "low-effort", you'll still have a bird's eye view of all the... &lt;em&gt;life things&lt;/em&gt; that happened, and I think that has a lot of value.&lt;br&gt;
Especially nowadays when it feels easier than ever to only remember the awful shit that's going on seemingly at all times.  &lt;/p&gt;
&lt;p&gt;So even if you think journaling is too difficult, I'd recommend trying it out!&lt;br&gt;
&lt;img alt="crt engaging in self-reflection" src="./images/crt_beach.jpg"&gt;&lt;br&gt;
Now from a tech perspective, I do have qualms about how the app effectively locks you in unless you pay to export your data, and the mandatory online connection and cloud synchronization doesn't really sit well with me considering this app &lt;strong&gt;by nature&lt;/strong&gt; processes a lot of your personal information&lt;sup id="ref-4"&gt;&lt;a href="#note-4"&gt;#&lt;/a&gt;&lt;/sup&gt;.  &lt;/p&gt;
&lt;p&gt;There's also no telling whether the app will remain like this; The business model seems to be praying that enough people pay for the premium plan to subsidize the free users, but considering how bad the value is on the subscription I have my doubts this will prove viable.  &lt;/p&gt;
&lt;p&gt;Nonetheless... I'm going to stick with the Techo app for a bit longer as I &lt;strong&gt;do&lt;/strong&gt; think it works quite well as a companion to my physical planner.&lt;br&gt;
I'll keep the more detailed/personal journaling to said physical though, the app is good at data collecting and that's what it shall do! I'm keen to use it to store sleep data from my Pebble watch once that does Health sync again...  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; i'm not fucking calling machine learning AI even if everyone does so -- I wouldve expected to see something like &lt;a href="https://huggingface.co/microsoft/resnet-50"&gt;https://huggingface.co/microsoft/resnet-50&lt;/a&gt;, which is Apache2 and weighs like 50 megs. I'm not a ML expert tho so there's likely some better state of the art at this point&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; And boy was it a longass list.. I didn't see anything really interesting in there, the app uses Flutter for cross-platform UI on iOS and Android, with a SQLite database on top. &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-3"&gt;&lt;a href="#ref-3"&gt;***&lt;/a&gt; Not that I'm suggesting you put your stuff on Google Photos instead -- Cloud hosting is all about trusting the company with your data, so I wouldn't blame you if you trusted a random Japanese company more than "&lt;em&gt;do no evil&lt;/em&gt;" silicon valley behemoth Google.&lt;/sup&gt;  &lt;br&gt;
&lt;sup id="note-4"&gt;&lt;a href="#ref-4"&gt;#&lt;/a&gt; I got curious and looked at FOSS alternatives -- &lt;a href="https://storypad.me/"&gt;StoryPad&lt;/a&gt; looks pretty good, although it doesn't do any of the phone data collection stuff that makes the Techo app's journaling special. &lt;/sup&gt;  &lt;/p&gt;</content><category term="Techoposting"></category><category term="techo"></category><category term="hobonichi"></category><category term="stationery"></category><category term="phone"></category><category term="app"></category></entry><entry><title>Hacktoberfest is dying, and that's a good thing™️</title><link href="https://tvc-16.science/rip-hacktoberfest.html" rel="alternate"></link><published>2025-12-06T00:00:00+01:00</published><updated>2025-12-06T00:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2025-12-06:/rip-hacktoberfest.html</id><summary type="html">&lt;p&gt;i made an excel spreadsheet and everything so you know this has been researched&lt;/p&gt;</summary><content type="html">&lt;p&gt;For the uninitiated, &lt;a href="https://hacktoberfest.com/"&gt;Hacktoberfest&lt;/a&gt; is a yearly event ran by DigitalOcean, where they encourage new and seasoned software developers alike to &lt;s&gt;work for free&lt;/s&gt; contribute to open-source projects throughout the month of October through various incentives.  &lt;/p&gt;
&lt;p&gt;The most well-known of those incentives is the &lt;a href="https://dev.to/fernandezbaptiste/last-10-years-of-hacktoberfest-merch-a-journey-through-time-8od"&gt;free T-shirt&lt;/a&gt; you can get if you make enough contributions/PRs during the month.&lt;br&gt;
&lt;img alt="2020 Hacktoberfest shirt" src="/images/holopin/shirt.jpg"&gt;&lt;br&gt;
Initially there was no required quality level for contributions, as the idea was obviously to bait as many people as possible into making PRs, with &lt;a href="https://www.freecodecamp.org/news/i-just-got-my-free-hacktoberfest-shirt-heres-a-quick-way-you-can-get-yours-fa78d6e24307/"&gt;multiple tutorials&lt;/a&gt; showing how frictionless the process could be.&lt;br&gt;
(&lt;em&gt;"Look how easy this is! You can just help us edit our docs!"&lt;/em&gt;)   &lt;/p&gt;
&lt;p&gt;This led to people gaming the system with minimal effort/irrelevant PRs, which culminated in a &lt;a href="https://www.theregister.com/2020/10/01/digitalocean_hacktoberfest_pull_request_spam/"&gt;big spam wave in 2020&lt;/a&gt;.&lt;br&gt;
As the maintainer of a small-scale open-source project, I've interacted with Hacktoberfest a fair amount over the past &lt;a href="./hacktoberfest-lrr.html"&gt;8 years&lt;/a&gt;, and 2020 was... &lt;strong&gt;pretty bad&lt;/strong&gt;! I'm relatively niche but got hit with a bit of spam regardless.   &lt;/p&gt;
&lt;p&gt;So you can imagine that for larger projects like uhh, the fucking &lt;em&gt;HTML standard&lt;/em&gt;?? It was &lt;a href="https://domenic.me/hacktoberfest/"&gt;way worse&lt;/a&gt;!&lt;br&gt;
A dedicated &lt;a href="https://twitter.com/shitoberfest"&gt;@shitoberfest&lt;/a&gt; Twitter account was made, which used to be a good indicator of when an issue was reaching collective consciousness.  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“In reality, Hacktoberfest is a corporate-sponsored distributed denial of service attack against the open source maintainer community.
So far today, on a single repository(&lt;code&gt;whatwg/html&lt;/code&gt;), myself and fellow maintainers have closed 11 spam pull requests.”&lt;br&gt;
&lt;sub&gt;(&lt;a href="https://domenic.me/hacktoberfest/"&gt;"DigitalOcean's Hacktoberfest is Hurting Open Source"&lt;/a&gt;)&lt;/sub&gt; &lt;/p&gt;
&lt;p&gt;“It was creating a lot of negative press for us. The very people that we were trying to help were really upset with us. We needed a quick solution. It was an emergency crisis situation.”&lt;br&gt;
&lt;sub&gt;(&lt;a href="https://developerrelations.com/case-studies/overcoming-hacktoberfest-spam/"&gt;"Overcoming spam during Hacktoberfest"&lt;/a&gt;)&lt;/sub&gt;  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;DigitalOcean reined in the spam by making the entire event opt-in, and the shirt reward was eventually &lt;a href="https://www.digitalocean.com/blog/ten-years-hacktoberfest#new-in-2023-moving-away-from-t-shirt-rewards"&gt;removed in 2023&lt;/a&gt;.&lt;br&gt;
Hacktoberfest's participation numbers cratered afterwards -- DO posts &lt;a href="https://www.digitalocean.com/blog/hacktoberfest-2025-wrapup"&gt;a recap blog&lt;/a&gt; each year with a bunch of stats, and they didn't even post one for 2024.  &lt;/p&gt;
&lt;p&gt;So for the big 2025... &lt;a href="https://www.digitalocean.com/blog/hacktoberfest-2025"&gt;they brought the shirts back???&lt;/a&gt; And boy was I &lt;strong&gt;worried&lt;/strong&gt;!&lt;br&gt;
With AI/vibe coding tools being more prevalent than ever in the developer ecosystem this year, you can see it's already &lt;a href="https://social.coop/@jsbarretto/115624517760498357"&gt;straining OSS maintainers&lt;/a&gt;:&lt;br&gt;
&lt;img alt="Mastodon post from joshua barretto about an AI slop issue+PR" src="images/holopin/mastopost.jpg"&gt;&lt;br&gt;
So despite the more stringent limitations this year &lt;sub&gt;(6 PRs instead of 4, capped to 10,000 shirts)&lt;/sub&gt;, how can there &lt;strong&gt;not&lt;/strong&gt; be a new wave of spam? Especially as Hacktoberfest itself seemed to be incentivizing AI, by retweeting posts &lt;a href="https://twitter.com/awakenjake/status/1976759095588749691?s=20"&gt;like this&lt;/a&gt;:&lt;br&gt;
&lt;img alt="I still have this CSS extension that replaces twitter blue with the nerd emoji and i'm never taking it off" src="images/holopin/nerdemoji.png"&gt; &lt;br&gt;
Even the guy that made the &lt;code&gt;@shitoberfest&lt;/code&gt; account 5 years ago is now saying &lt;a href="https://twitter.com/GeoffreyHuntley/status/1985860953905492188"&gt;code is commoditized&lt;/a&gt; and that you should get on AI agents &lt;strong&gt;NOW&lt;/strong&gt; before the bubble pops and you can't learn them for free anymore.  &lt;/p&gt;
&lt;p&gt;Surely everyone is going to take advantage of poor old DigitalOcean once again and swarm maintainers with endless slop PRs! 
So I steeled myself and my little GitHub repo during October and....  &lt;/p&gt;
&lt;p&gt;&lt;img alt="live crust reaction" src="/images/live-crust-reaction.jpg"&gt;  &lt;/p&gt;
&lt;h1&gt;Nothing.&lt;/h1&gt;
&lt;p&gt;Wait what the fuck this isn't right, where &lt;strong&gt;is&lt;/strong&gt; the AI? Why isn't the Copilot/Claude/Cursor triumvirat bombing my email box?  &lt;/p&gt;
&lt;p&gt;Looking at one of the first repositories that pops up when you search for the &lt;code&gt;hacktoberfest&lt;/code&gt; label, the Windows Terminal, they only got &lt;a href="https://github.com/microsoft/terminal/pulls?q=is%3Apr+is%3Aclosed+is%3Aunmerged+created%3A%3E2025-10-01+updated%3A%3C2025-11-05+"&gt;~7 spam PRs&lt;/a&gt; during the month, which is less than what &lt;code&gt;whatwg/html&lt;/code&gt; got in a single day back in 2020.  &lt;/p&gt;
&lt;p&gt;I also looked at &lt;a href="https://github.com/home-assistant/core/pulls?q=is%3Apr+is%3Aclosed+is%3Aunmerged+created%3A%3E2025-10-01+updated%3A%3C2025-11-20+-label%3Aby-code-owner"&gt;Home Assistant&lt;/a&gt;, and that did get a &lt;strong&gt;lot&lt;/strong&gt; more activity; But most of the PRs seem to be from folks &lt;em&gt;actually&lt;/em&gt; working with specific smart device platforms, and not the kind of generic &lt;code&gt;refactored everything to make it more beautiful&lt;/code&gt; changes you'd see from someone spamming PRs with the vibe coding printer.&lt;br&gt;
There certainly is &lt;a href="https://github.com/home-assistant/core/pull/154403"&gt;some&lt;/a&gt; &lt;a href="https://github.com/home-assistant/core/pull/154298"&gt;slop&lt;/a&gt; &lt;a href="https://github.com/home-assistant/core/pulls?q=is%3Apr+is%3Aclosed+is%3Aunmerged+created%3A%3E2025-10-01+updated%3A%3C2025-11-20+-label%3Aby-code-owner+label%3Aspam"&gt;in there&lt;/a&gt;, though.  &lt;/p&gt;
&lt;p&gt;So that all got me curious, &lt;em&gt;how many people actually participated this year, and how does it compare to 2020&lt;/em&gt;?&lt;br&gt;
I based myself on the DO recap blogposts, so I don't have a number for the amount of PRs in 2024&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt;, and the participants are an estimate from the &lt;a href="https://www.digitalocean.com/blog/hacktoberfest-2025"&gt;2025 announcement&lt;/a&gt;... which is literally just a copypaste of &lt;a href="https://www.digitalocean.com/blog/hacktoberfest-2024"&gt;the 2024 one&lt;/a&gt; with a few word changes btw:&lt;br&gt;
&lt;img alt="2024 v 2025 hacktoberfest blogposts. That's a lot of love for open source!" src="/images/holopin/hackposts.png"&gt;&lt;br&gt;
What is this, a Team Fortress 2 update? Anyway, the numbers:   &lt;/p&gt;
&lt;blockquote&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;2019&lt;/th&gt;
&lt;th&gt;2020&lt;/th&gt;
&lt;th&gt;2021&lt;/th&gt;
&lt;th&gt;2022&lt;/th&gt;
&lt;th&gt;2023&lt;/th&gt;
&lt;th&gt;2024&lt;/th&gt;
&lt;th&gt;2025&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Participants&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;131841&lt;/td&gt;
&lt;td&gt;169886&lt;/td&gt;
&lt;td&gt;141000&lt;/td&gt;
&lt;td&gt;146981&lt;/td&gt;
&lt;td&gt;98855&lt;/td&gt;
&lt;td&gt;90000(?)&lt;/td&gt;
&lt;td&gt;56768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Shirts awarded&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;61871&lt;/td&gt;
&lt;td&gt;66798&lt;/td&gt;
&lt;td&gt;46676&lt;/td&gt;
&lt;td&gt;40000&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;&amp;lt; 10000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Total eligible PRs&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;483127&lt;/td&gt;
&lt;td&gt;387052&lt;/td&gt;
&lt;td&gt;294451&lt;/td&gt;
&lt;td&gt;335000&lt;/td&gt;
&lt;td&gt;118469&lt;/td&gt;
&lt;td&gt;Unknown&lt;/td&gt;
&lt;td&gt;87929&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;PRs / participant&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;3,66446705&lt;/td&gt;
&lt;td&gt;2,27830427&lt;/td&gt;
&lt;td&gt;2,08830496&lt;/td&gt;
&lt;td&gt;2,27920616&lt;/td&gt;
&lt;td&gt;1,19841182&lt;/td&gt;
&lt;td&gt;Unknown&lt;/td&gt;
&lt;td&gt;1,5489184&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/blockquote&gt;
&lt;p&gt;Wow, it's worse than I thought! You might be wondering where I'm pulling that &lt;code&gt;&amp;lt; 10000&lt;/code&gt; number for the 2025 shirts.  &lt;/p&gt;
&lt;p&gt;It's pretty easy for me to clear Hacktoberfest each year&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt;, so this year I timed it so that my final PRs would be completed in November, after the official end of the event.&lt;br&gt;
I thought that surely by that point, the 10,000 shirt cap would have been passed and I wouldn't be eligible for one, right? &lt;br&gt;
&lt;img alt="Screenshot of being awarded the &amp;quot;Supercontibutor&amp;quot; Hacktoberfest badge on November 7th, alongside a t-shirt code" src="/images/holopin/hackshirt.png"&gt;&lt;br&gt;
&lt;code&gt;ayy lmao&lt;/code&gt;  &lt;/p&gt;
&lt;p&gt;To me this confirms that &lt;strong&gt;less than 10,000 people even bothered&lt;/strong&gt; going for the shirt grind this year.&lt;br&gt;
It's a far cry from 2020-2022, and the total PR count is still less than the no-shirt era of 2023 because the participant count just... &lt;em&gt;fell off a fucking cliff&lt;/em&gt;??  &lt;/p&gt;
&lt;p&gt;DigitalOcean counts everyone who registers as a participant, even if they don't make any contributions, so the number is slightly inflated... And yet it's barely over half of 2023's?&lt;br&gt;
Even with the return of &lt;strong&gt;the&lt;/strong&gt; T-shirt&lt;sup id="ref-3"&gt;&lt;a href="#note-3"&gt;***&lt;/a&gt;&lt;/sup&gt; that caused all this spam 5 years ago?  &lt;/p&gt;
&lt;p&gt;This leads me to believe that the shirt was a silly facade all along and that the spam wave of 2020 was pretty much coincidental -- So it brings the question, &lt;strong&gt;what&lt;/strong&gt; actually got people to register for Hacktoberfest, and &lt;strong&gt;why&lt;/strong&gt; is that dying?  &lt;/p&gt;
&lt;p&gt;As Domenic mentioned, Hacktoberfest is &lt;strong&gt;corporate-sponsored&lt;/strong&gt;, so that means that it's primarily about &lt;u&gt;&lt;strong&gt;benefiting tech companies&lt;/strong&gt;&lt;/u&gt;. Some of that is obviously covered by getting free labor on projects that have been open-sourced, like the &lt;a href="https://github.com/microsoft/terminal"&gt;Windows Terminal&lt;/a&gt;... But in case you can't get free labor... Surely you can get &lt;em&gt;very cheap&lt;/em&gt; labor instead?  &lt;/p&gt;
&lt;p&gt;That's right! I'm going to be talking about:   &lt;/p&gt;
&lt;h1&gt;The junior Software Engineering rat-race&lt;/h1&gt;
&lt;p&gt;If you've ever touched GitHub in any shape you've probably seen shit like this:&lt;br&gt;
&lt;img alt="&amp;quot;This is what a self-taught junior engineer github profile looks like&amp;quot;: *awful kitted out badge fuckfest that makes it look like a resume*" src="images/holopin/nerd2.jpg"&gt;&lt;br&gt;
Open source has gradually gotten &lt;em&gt;LinkedIn-ified&lt;/em&gt; over years, as the expectation came in that a good software engineer should have a portfolio of open-source contributions to match.  &lt;/p&gt;
&lt;p&gt;It's a ghoulish transmogrification of the evergreen &lt;a href="https://news.ycombinator.com/item?id=2664243"&gt;"does working on personal projects make me a better developer?"&lt;/a&gt; debate - Good developers tend to work on personal/OSS on the side, so &lt;strong&gt;obviously&lt;/strong&gt; it became a requirement to HR firms.  &lt;/p&gt;
&lt;p&gt;And both Hacktoberfest and their corporate sponsors absolutely play into this whole concept! They're not even fucking hiding it, there are &lt;a href="https://youtu.be/_5EN8hHVRvQ?t=2326"&gt;entire slideshows&lt;/a&gt; about how "OSS contributions increase chances of getting a job in tech"! &lt;br&gt;
&lt;img alt="&amp;quot;How open source can help you build your career&amp;quot; presentation at Hacktoberfest 2025 wrap up" src="images/holopin/wrapup.jpg"&gt;&lt;br&gt;
(Isn't it &lt;em&gt;slightly tonedeaf&lt;/em&gt; to do this even as big tech is &lt;a href="https://arstechnica.com/information-technology/2025/11/hp-plans-to-save-millions-by-laying-off-thousands-ramping-up-ai-use"&gt;firing thousands of workers&lt;/a&gt; in the hope that all their AI somehow picks up and starts making money?)  &lt;/p&gt;
&lt;p&gt;DigitalOcean is effectively selling the "American dream of software", which is a win-win-win for tech companies:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Younger devs are trained by OSS maintainers, &lt;em&gt;for free&lt;/em&gt;  &lt;/li&gt;
&lt;li&gt;With a bit of luck, they'll contribute to your own open-sourced projects, &lt;em&gt;for free&lt;/em&gt;  &lt;/li&gt;
&lt;li&gt;You'll have access to a wide pool of developers continuously comparing and optimizing their portfolios on a Git repo-turned-social-media... &lt;strong&gt;&lt;em&gt;for free&lt;/em&gt;&lt;/strong&gt;  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It's also the reason why most of the recorded Hacktoberfest contributors don't come from first-world countries.&lt;br&gt;
&lt;img alt="Hacktoberfest 2021 top countries: India, USA, Indonesia, Brazil, Germany, Sri Lanka" src="images/holopin/top2021.png"&gt;&lt;br&gt;
&lt;sub&gt;(Sourced from the &lt;a href="https://www.digitalocean.com/blog/hacktoberfest-2021-recap#participants"&gt;2021 recap&lt;/a&gt; - Also see the &lt;a href="https://www.digitalocean.com/blog/hacktoberfest-recap2020#participant-stats"&gt;2020 top countries&lt;/a&gt;. They stopped reporting those afterwards...)&lt;/sub&gt;   &lt;/p&gt;
&lt;p&gt;Except that much like the real American dream, this has essentially &lt;strong&gt;dried up&lt;/strong&gt;.&lt;br&gt;
Now that the job market is in the toilet (&lt;a href="https://www.forbes.com/sites/hessiejones/2025/11/24/ai-is-not-killing-entry-level-jobs/"&gt;not even because of AI&lt;/a&gt; outside of big tech, although it's being used as an excuse), the facade doesn't hold up anymore! People aren't going to bother if they &lt;strong&gt;know&lt;/strong&gt; they can't get a job in tech in 2025!  &lt;/p&gt;
&lt;p&gt;I believe that's the core reason as to why Hacktoberfest is dying -- As with every economic shock, junior hiring has crashed down, so there's no incentive to feed the machine anymore.  &lt;/p&gt;
&lt;h1&gt;why is it a good thing tho&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://youtu.be/7TQGAVfHmNk?t=1721"&gt;🎵 BGM - You Must Learn All Night Long, Fantastic Plastic Machine&lt;/a&gt;   &lt;/p&gt;
&lt;p&gt;There's a reoccuring theme with software developers that one must always stay on top of the latest trends in software stacks&lt;sup id="ref-4"&gt;&lt;a href="#note-4"&gt;#&lt;/a&gt;&lt;/sup&gt; and tooling lest they become &lt;sub&gt;(gasp)&lt;/sub&gt; "obsolete".  &lt;/p&gt;
&lt;p&gt;Whether or not that's true is a different debate&lt;sub&gt;(although I think it kinda ties with the whole "personal project" debate that's led to the social mediafication of GitHub)&lt;/sub&gt;, but to me it's pretty obvious that this concept is being abused by big tech trying &lt;strong&gt;very hard&lt;/strong&gt; to make AI as indispensable as all the marketing is making it up to be.  &lt;/p&gt;
&lt;p&gt;I'm gonna quote the &lt;code&gt;@shitoberfest&lt;/code&gt; guy &lt;a href="https://twitter.com/GeoffreyHuntley/status/1992067558951047442"&gt;one last time&lt;/a&gt;:  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;the ethics, climate and energy topics should not be on your mind as a swe - your job is to get good with the tools.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Isn't that kinda fucked up?&lt;br&gt;
&lt;em&gt;"Don't worry about how ghoulish this all is bro just get good at prompting you can't fall behind"&lt;/em&gt;   &lt;/p&gt;
&lt;p&gt;The argument being that no matter what, companies will cheap out on trying to make good software if they can use AI for it, so you &lt;strong&gt;must&lt;/strong&gt; know/use AI to remain relevant.&lt;br&gt;
But if you're a junior developer, the whole industry is seemingly telling you to go fuck yourself already -- So &lt;em&gt;why even try&lt;/em&gt; at this point, right?&lt;br&gt;
&lt;img alt="Japanese man saying never give up" src="images/nevergiveup.jpg"&gt;&lt;br&gt;
&lt;strong&gt;I reject this viewpoint.&lt;/strong&gt;  &lt;/p&gt;
&lt;p&gt;I don't believe that companies will be able to keep using coding assistants&lt;sup id="ref-5"&gt;&lt;a href="#note-5"&gt;##&lt;/a&gt;&lt;/sup&gt; once their &lt;a href="https://www.wheresyoured.at/costs/"&gt;very unsustainable funding runs out&lt;/a&gt;, so the threats just ring hollow to me.  &lt;/p&gt;
&lt;p&gt;It &lt;strong&gt;would&lt;/strong&gt; be a threat if big tech's push for AI adoption actually succeeded - But as this Hacktoberfest is showing, I'm &lt;strong&gt;not&lt;/strong&gt; seeing a tidal wave of AI-generated open source contributions.  &lt;/p&gt;
&lt;p&gt;You're trying to sell me this shit as a world-changing productivity improvement, and yet people who would be most likely to interact with it (would-be junior devs and OSS contributors)  &lt;em&gt;aren't jumping on it&lt;/em&gt;? How can that even work?  &lt;/p&gt;
&lt;p&gt;Once junior developers (inevitably) become desirable to hire again, I don't think whether you're &lt;code&gt;le epic 10x prompt engineer&lt;/code&gt; or not will be a key differentiator. &lt;a href="https://youtu.be/uGtUuciQHRA?t=106"&gt;Hold out! Programmer!&lt;/a&gt;  &lt;/p&gt;
&lt;p&gt;I do think that you can use your Github profile as a resume, but &lt;sub&gt;(hot take)&lt;/sub&gt; I don't think the tools matter &lt;em&gt;that much&lt;/em&gt; if you've managed to make cool things and share them with the wider world as free software!&lt;br&gt;
&lt;a href="https://github.com/Difegue/LANraragi"&gt;LANraragi&lt;/a&gt; started life as a collection of Perl CGI scripts and uses Redis for data persistence to this very day even though using a relational database would be "the proper way of doing it"...  &lt;/p&gt;
&lt;h1&gt;Closing words&lt;/h1&gt;
&lt;p&gt;This is probably the largest blogpost I ever wrote (and I cut a bunch out!) and I'm hoping I got all the AI rambling out of my system for the foreseeable future.&lt;br&gt;
Overall the best reponse to this entire big tech shit ouroboros is to not engage with any of it, and that includes Hacktoberfest.  &lt;/p&gt;
&lt;p&gt;But instead you can still engage with free software -- If we look back at Home Assistant, that is positively &lt;strong&gt;thriving&lt;/strong&gt;. I think it's notable that one of the most popular projects on Github is the one that's specifically designed to free control of your smart home from capital.  &lt;/p&gt;
&lt;p&gt;In a fairer world, Microsoft, Google and co. should be held accountable for how they've cannibalized free software time and time again, from outsourcing R&amp;amp;D&lt;sup id="ref-6"&gt;&lt;a href="#note-5"&gt;###&lt;/a&gt;&lt;/sup&gt; and &lt;a href="https://thenewstack.io/ffmpeg-to-google-fund-us-or-stop-sending-bugs/"&gt;bugfixes&lt;/a&gt;, to outright pilfering decades of code to train their AIs.  &lt;/p&gt;
&lt;p&gt;Once the bubble pops and they come back begging, don't forget how hard they tried to burn it all down!&lt;br&gt;
Keep making cool shit and don't let yourself be exploited by companies trying to profit off your desire to learn.  &lt;/p&gt;
&lt;p&gt;I wonder what the WHATWG guy is thinking about all thi--oh, &lt;a href="https://domenic.me/retirement/"&gt;he retired&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="yea" src="images/yea.png"&gt;  &lt;/p&gt;
&lt;p&gt;wow i wish i had google money too!  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; You could potentially scrape GH to see all the PRs made in October 2024 on Hacktoberfest-enabled repos, but the number would potentially still be imprecise, as I don't think there's a way to know if a repo would have the hacktoberfest label back in 2024 instead of just now. &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; Since I'm just working on my OSS projects as usual -- There's been a new &lt;a href="https://github.com/Difegue/LANraragi/releases/tag/v.0.9.60"&gt;LANraragi release&lt;/a&gt; and I spent some time on a custom version of my McDonald's LCD simulator for &lt;a href="https://bsky.app/profile/difegue.tvc-16.science/post/3m3ps45mr6s2q"&gt;Burger Bard&lt;/a&gt;. There's also a Stylophone update pending but I haven't wrapped up Liquid Glass for iOS yet...&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-3"&gt;&lt;a href="#ref-3"&gt;***&lt;/a&gt; Back then the shirt wasn't the only incentive too; DO also used to offer free credits for their VPS hosting service as part of the event, and it was fairly common back in 2019 for other large OSS entities to offer their own swag packs for PRs outside of Hacktoberfest -- &lt;a href="./hacktoberfest-lrr-2.html"&gt;I did it too!&lt;/a&gt;&lt;br&gt;
Nowadays, all you really get are &lt;a href="https://blog.holopin.io/posts/hacktoberfest-2025"&gt;Holopin badges&lt;/a&gt; which are just the software equivalent of Xbox 360 achievements.&lt;br&gt;
(And who also play somewhat into the whole gamification/resume building thing, although it seems quite inconsequential in comparison) &lt;/sup&gt;  &lt;br&gt;
&lt;sup id="note-4"&gt;&lt;a href="#ref-4"&gt;#&lt;/a&gt; Have you learned your &lt;a href="https://github.com/Gaurav-Gosain/tuios"&gt;Charm stack with Bubble Tea v2 and Lipgloss in Go&lt;/a&gt; yet bro? (No shade being thrown, I just find the names very funny)&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-5"&gt;&lt;a href="#ref-5"&gt;##&lt;/a&gt; Lower-cost models that just do code autocomplete and &lt;em&gt;maybe&lt;/em&gt; the chat ones will probably stick around and run on-device, but those aren't in same ballpark as the whole agent shit that's being sold now. And those will get outdated in time if there's no more funding to train new ones so.... &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-6"&gt;&lt;a href="#ref-6"&gt;###&lt;/a&gt; Now that WinUI 3 is &lt;a href="https://github.com/microsoft/microsoft-ui-xaml/discussions/10700#discussioncomment-15154004"&gt;fully open-source&lt;/a&gt;, everyone's hoping people will fix (for free) what Microsoft has failed to fix ever since Windows 8 and I think that's hilarious&lt;/sup&gt;
&lt;/sup&gt;&lt;/p&gt;</content><category term="Software"></category><category term="hacktoberfest"></category><category term="open-source"></category><category term="llms"></category><category term="github"></category><category term="copilot"></category></entry><entry><title>Migrating a game collection database from CLZ Games to Gameye</title><link href="https://tvc-16.science/clz-to-gameye.html" rel="alternate"></link><published>2025-09-27T00:00:00+02:00</published><updated>2025-09-27T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2025-09-27:/clz-to-gameye.html</id><summary type="html">&lt;p&gt;ONE MORE SUBSCRIPTION REJECTED&lt;/p&gt;</summary><content type="html">&lt;p&gt;I own a relatively mid-sized &lt;a href="/kallax-crt.html"&gt;game collection&lt;/a&gt;, and it has recently gotten to the point I unfortunately can't always precisely remember everything I own when I'm hunting through garage sales.&lt;br&gt;
&lt;sub&gt;Mostly because I've gotten more into 360/ps3 where big AAA series became commonplace and no I can't fucking remember if I already bought uncharted 2 or 3 or asscreed 2 or 4&lt;/sub&gt;  &lt;/p&gt;
&lt;p&gt;So I got into logging all the stuff I own...&lt;br&gt;
And I would've run out of motivation &lt;em&gt;really fast&lt;/em&gt; if I had to manually type in every game name!&lt;br&gt;
I searched a bit and ended up using &lt;a href="https://clz.com/games"&gt;CLZ Games&lt;/a&gt;, which has a &lt;strong&gt;fantastic&lt;/strong&gt; barcode scanner I threw my entire collection at like it was clearance on &lt;a href="https://www.youtube.com/watch?v=wzM0t96x84A"&gt;Aisle 10&lt;/a&gt;.&lt;br&gt;
&lt;img alt="most of my collection logged onto CLZ" src="./images/games/clz.png"&gt;&lt;br&gt;
I have to give a lot of props to the app overall - Their database is great and differentiates between collectors, platinum/players choice editions, and the barcode scanner found every single game I scanned through it, regardless of region.  &lt;/p&gt;
&lt;p&gt;The UI is also quite nice and there's a web version... I'd happily keep using this!&lt;br&gt;
&lt;strong&gt;Except it's subscription-based.&lt;/strong&gt;&lt;br&gt;
&lt;img alt="its a fucking subscription again god damn it" src="./images/1-2-hurts-just-a-little-bit.jpg"&gt;&lt;br&gt;
Luckily the app has an &lt;strong&gt;export function&lt;/strong&gt;&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt;, which means with a little bit of scripting, I can get my scanned collection out and into something else that won't cost me 20€ a year&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt; to look at.  &lt;/p&gt;
&lt;h1&gt;The competition&lt;/h1&gt;
&lt;p&gt;Since the point is to have the collection on the go for cross-checking, I wanted to have it on &lt;strong&gt;Android&lt;/strong&gt;.&lt;br&gt;
I could technically just have everything in a spreadsheet and use Excel on the go or something, but being able to filter by console and easily keep track of whether I own the box/manual for a given game are very nice to have.  &lt;/p&gt;
&lt;p&gt;It also needs to have an &lt;strong&gt;import function&lt;/strong&gt; of its own so I can pipe the data into it.&lt;br&gt;
So! As the title of the blogpost spoils, I ended up choosing &lt;a href="https://www.gameye.app/"&gt;GAMEYE&lt;/a&gt; as the host for the export.  &lt;/p&gt;
&lt;p&gt;I originally looked at Gameye for initially scanning my collection since it also has a barcode scanner... But their database is much worse than CLZ on that front and I was having a pretty low success rate&lt;sup id="ref-3"&gt;&lt;a href="#note-3"&gt;***&lt;/a&gt;&lt;/sup&gt;.  &lt;/p&gt;
&lt;h1&gt;The export/import formats&lt;/h1&gt;
&lt;p&gt;CLZ Cloud's exports are pretty straightforward: You can either get an XML with all the info, or a custom CSV/TXT with fields of your choosing.  &lt;/p&gt;
&lt;p&gt;For the rest of this blogpost, I went with a CSV that has the following columns:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;Title,Platform,Region,Box,Manual&lt;/span&gt;
&lt;span class="err"&gt;&amp;quot;ChuChu Rocket!&amp;quot;,Dreamcast,Europe,No,No&lt;/span&gt;
&lt;span class="err"&gt;&amp;quot;Crazy Taxi&amp;quot;,Dreamcast,Europe,Yes,No&lt;/span&gt;
&lt;span class="err"&gt;&amp;quot;Crazy Taxi&amp;quot;,Dreamcast,Japan,Yes,Yes&lt;/span&gt;
&lt;span class="err"&gt;&amp;quot;Crazy Taxi 2&amp;quot;,Dreamcast,Japan,Yes,Yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Gameye's import/export functions are a bit less easy to work with, but relatively straightforward.&lt;br&gt;
Creating a "Restore Point" from the app gives you a &lt;strong&gt;.ged file&lt;/strong&gt;, which is just a zipped SQLite database.&lt;br&gt;
&lt;img alt="good ol' DB explorer for SQLite showing a Gameye database" src="./images/games/gameye/gameye_db.png"&gt;&lt;br&gt;
I'll focus on the &lt;code&gt;ownership&lt;/code&gt; table here, since that's the one that contains all the collection data.  &lt;/p&gt;
&lt;h1&gt;The script&lt;/h1&gt;
&lt;p&gt;I hate SQL about as much as I hate subscriptions, so I decided to just do csv-to-csv conversion, using &lt;a href="https://sqlitebrowser.org/"&gt;DB Browser&lt;/a&gt; after the fact to insert the data in the Gameye DB.  &lt;/p&gt;
&lt;p&gt;The meat of the complexity here is to find the &lt;strong&gt;Gameye IDs&lt;/strong&gt; for each game -- Everything else (manual/box ownership, region and console IDs) was easy to figure out by just diffing a few exports.&lt;/p&gt;
&lt;p&gt;Luckily, Gameye has a freely available online &lt;a href="https://www.gameye.app/encyclopedia"&gt;search tool&lt;/a&gt;! And all of the IDs match what the mobile app uses, which means we will perform the illegal act of... &lt;strong&gt;web automation.&lt;/strong&gt;&lt;br&gt;
&lt;img alt="" src="./images/anything.jpg"&gt;&lt;br&gt;
So to recap, we need to do all the following:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CSV parsing to read the CLZ export  &lt;/li&gt;
&lt;li&gt;Web requests and JSON parsing to search for each game and grab the matching Gameye ID  &lt;/li&gt;
&lt;li&gt;Some way to generate GUIDs while rewriting the data so it matches what the Gameye DB expects  &lt;/li&gt;
&lt;li&gt;More CSV processing to export the final result  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;PowerShell&lt;/strong&gt; has built-in tools for all of this stuff, so that's what I rolled with.  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;Parameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;Mandatory&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$true&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="no"&gt;[string]&lt;/span&gt;&lt;span class="nv"&gt;$inputCsv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;Parameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;Mandatory&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$true&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="no"&gt;[string]&lt;/span&gt;&lt;span class="nv"&gt;$outputCsv&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Import the input CSV&lt;/span&gt;
&lt;span class="c"&gt;# CLZ export delimiter is , but if you preprocessed it thru Excel you&amp;#39;ll want ;&lt;/span&gt;
&lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Import-Csv&lt;/span&gt; &lt;span class="n"&gt;-Path&lt;/span&gt; &lt;span class="nv"&gt;$inputCsv&lt;/span&gt; &lt;span class="n"&gt;-Delimiter&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;,&amp;#39;&lt;/span&gt;

&lt;span class="c"&gt;# Initialize an array to store the results&lt;/span&gt;
&lt;span class="nv"&gt;$results&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;@()&lt;/span&gt;
&lt;span class="nv"&gt;$notfound&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;@()&lt;/span&gt;
&lt;span class="nv"&gt;$id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;1&lt;/span&gt;


&lt;span class="c"&gt;# Loop through each row in the CSV&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$row&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c"&gt;# Assume CLZ CSV has the following headers: Title,Platform,Region,Box,Manual&lt;/span&gt;
    &lt;span class="nv"&gt;$title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;
    &lt;span class="nv"&gt;$platform&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Platform&lt;/span&gt;
    &lt;span class="nv"&gt;$region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Region&lt;/span&gt;
    &lt;span class="nv"&gt;$box&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Box&lt;/span&gt;
    &lt;span class="nv"&gt;$manual&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Manual&lt;/span&gt;

    &lt;span class="c"&gt;# Calculate gameeye ownership mask&lt;/span&gt;
    &lt;span class="nv"&gt;$ownershipMask&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;1&lt;/span&gt; &lt;span class="c"&gt;# loose&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$box&lt;/span&gt; &lt;span class="o"&gt;-eq&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Yes&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;-And&lt;/span&gt; &lt;span class="nv"&gt;$manual&lt;/span&gt; &lt;span class="o"&gt;-eq&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Yes&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$ownershipMask&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;7&lt;/span&gt; &lt;span class="c"&gt;# complete&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;elseif&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$box&lt;/span&gt; &lt;span class="o"&gt;-eq&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Yes&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;-And&lt;/span&gt; &lt;span class="nv"&gt;$manual&lt;/span&gt; &lt;span class="o"&gt;-eq&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;No&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$ownershipMask&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;5&lt;/span&gt; &lt;span class="c"&gt;# box no manual&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;elseif&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$box&lt;/span&gt; &lt;span class="o"&gt;-eq&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;No&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;-And&lt;/span&gt; &lt;span class="nv"&gt;$manual&lt;/span&gt; &lt;span class="o"&gt;-eq&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Yes&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$ownershipMask&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;3&lt;/span&gt; &lt;span class="c"&gt;# manual no box&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;# Convert region/platform to gameeye IDs&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$region&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;USA&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$regionId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Europe&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$regionId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;15&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c"&gt;# UK&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Japan&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$regionId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;3&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$regionId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;34&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c"&gt;# World&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$platform&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Dreamcast&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;16&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Family Computer / Famicom&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;7&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c"&gt;# Same as NES&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Game &amp;amp; Watch&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;76&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Game Boy Advance&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;5&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;GameCube&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;2&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Genesis / Mega Drive&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;18&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;NES&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;7&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Nintendo 64&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;3&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Nintendo 3DS&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;41&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Nintendo DS&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;8&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Nintendo Switch&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;97&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;PC&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;PlayStation&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;10&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;PlayStation 2&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;11&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;PlayStation 3&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;12&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;PlayStation 4&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;46&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;PlayStation 5&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;105&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;PSP&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;13&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;PlayStation Vita&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;37&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Saturn&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;17&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Sega 32X&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;32&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Sega CD&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;20&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Sega Master System&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;34&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;SNES&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;6&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Super Famicom&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;6&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c"&gt;# Same as SNES&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Wii&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;9&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Wii U&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;36&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Xbox&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;14&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Xbox 360&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;15&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Xbox One&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;47&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Xbox Series X&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;106&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c"&gt;# Unknown&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nb"&gt;Write-Output&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Processing: $title on $platform for $region (Platform ID: $platformId, Region ID: $regionId)&amp;quot;&lt;/span&gt;

    &lt;span class="c"&gt;# Perform the API request to find the gameye ID for the title&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$apiUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://www.gameye.app/api/deep_search?offset=0&amp;amp;limit=25&amp;amp;title=$title&amp;amp;platforms=$platformId&amp;amp;country=$regionId&amp;amp;order=0&amp;amp;asc=1&amp;amp;cat=0&amp;quot;&lt;/span&gt;
        &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Invoke-RestMethod&lt;/span&gt; &lt;span class="n"&gt;-Uri&lt;/span&gt; &lt;span class="nv"&gt;$apiUrl&lt;/span&gt; &lt;span class="n"&gt;-Method&lt;/span&gt; &lt;span class="n"&gt;Get&lt;/span&gt; &lt;span class="n"&gt;-Body&lt;/span&gt; &lt;span class="nv"&gt;$body&lt;/span&gt; &lt;span class="n"&gt;-ContentType&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;application/json&amp;quot;&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;-and&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="o"&gt;-gt&lt;/span&gt; &lt;span class="n"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c"&gt;# If there are any results, take the first one&lt;/span&gt;
            &lt;span class="nv"&gt;$gameID&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
            &lt;span class="nv"&gt;$gametitle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;
            &lt;span class="nb"&gt;Write-Output&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;   Found Gameye equivalent: $gameID - $gametitle&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nb"&gt;Write-Output&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;   Nope!&amp;quot;&lt;/span&gt;
            &lt;span class="nv"&gt;$notfound&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;$title ($platform)&amp;quot;&lt;/span&gt; 
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;# Craft gameye DB entry&lt;/span&gt;
        &lt;span class="c"&gt;# id,item_id,platform_id,country_id,ownership_mask,item_quality,manual_quality,box_quality,paid,sold,note,created_at,updated_at,category_id,user_record_type,title,uuid,generation_id,collection_id&lt;/span&gt;
        &lt;span class="nv"&gt;$results&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="no"&gt;[PSCustomObject]&lt;/span&gt;&lt;span class="p"&gt;@{&lt;/span&gt;
            &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;
            &lt;span class="n"&gt;item_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$gameID&lt;/span&gt;
            &lt;span class="n"&gt;platform_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$platformId&lt;/span&gt;
            &lt;span class="n"&gt;country_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$regionId&lt;/span&gt;
            &lt;span class="n"&gt;ownership_mask&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$ownershipMask&lt;/span&gt;
            &lt;span class="n"&gt;item_quality&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
            &lt;span class="n"&gt;manual_quality&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
            &lt;span class="n"&gt;box_quality&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
            &lt;span class="n"&gt;paid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
            &lt;span class="n"&gt;sold&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
            &lt;span class="n"&gt;note&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
            &lt;span class="c"&gt;# unix timestamp&lt;/span&gt;
            &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="no"&gt;[int][double]&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nb"&gt;Get-Date&lt;/span&gt; &lt;span class="n"&gt;-UFormat&lt;/span&gt; &lt;span class="k"&gt;%&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="no"&gt;[int][double]&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nb"&gt;Get-Date&lt;/span&gt; &lt;span class="n"&gt;-UFormat&lt;/span&gt; &lt;span class="k"&gt;%&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;category_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;0&lt;/span&gt;
            &lt;span class="n"&gt;user_record_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;0&lt;/span&gt;
            &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$title&lt;/span&gt;
            &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="no"&gt;[guid]&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;generation_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;0&lt;/span&gt;
            &lt;span class="n"&gt;collection_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;1&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;++&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;Write-Output&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Error processing row: &lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Export the results to the output CSV&lt;/span&gt;
&lt;span class="nv"&gt;$results&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;Export-Csv&lt;/span&gt; &lt;span class="n"&gt;-Path&lt;/span&gt; &lt;span class="nv"&gt;$outputCsv&lt;/span&gt; &lt;span class="n"&gt;-NoTypeInformation&lt;/span&gt;

&lt;span class="nb"&gt;Write-Output&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Processing complete. Results saved to $outputCsv&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$notfound&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="o"&gt;-gt&lt;/span&gt; &lt;span class="n"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;Write-Output&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;The following titles were not found in Gameye:&amp;quot;&lt;/span&gt;
    &lt;span class="nv"&gt;$notfound&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;Sort-Object&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;Get-Unique&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="k"&gt;ForEach&lt;/span&gt;&lt;span class="n"&gt;-Object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;Write-Output&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot; - $_&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The script keeps track of all the games it can't find and prints them at the end, so you know what you need to add manually after reimporting.&lt;br&gt;
&lt;img alt="the script do be working" src="./images/games/gameye/gameye_script.jpg"&gt;&lt;br&gt;
All that's left is to copy the output csv into the DB, zip it up again, import into the Android app... And presto!  &lt;/p&gt;
&lt;h1&gt;The results&lt;/h1&gt;
&lt;p&gt;&lt;img alt="The collection, side-by-side in both CLZ and Gameye. Shoutout Surface Duo my beloved" src="./images/games/gameye/collection.jpg"&gt;&lt;br&gt;
This is very much a quickly hacked thing and the search could certainly be improved...&lt;br&gt;
I got &lt;em&gt;Barbie Mystery Detective&lt;/em&gt; out of a search for &lt;em&gt;Myst&lt;/em&gt; on PS1 so I could have at least added some quotes.  &lt;/p&gt;
&lt;p&gt;But out of 450 items, I ended up with like 30 false positives and 30 games I had to re-add manually?&lt;br&gt;
It certainly was worth it timewise.  &lt;/p&gt;
&lt;p&gt;Gameye's database is subpar in comparison to CLZ &lt;sub&gt;(There's no proper PAL region, Famicom/NES and SFC/SNES are mixed together)&lt;/sub&gt;, but for a free offering I consider it more than good enough.  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; The export is through the web/cloud instead of the app so it could theoretically be turned off at any point, but I gotta give props to the devs for having the option available! &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; I have to mention the app has a free 7-day trial, so I effectively paid &lt;strong&gt;nothing&lt;/strong&gt; to scan my entire collection, export the data and fuck off somewhere else. The CLZ app really doesn't feel predatory and the subscription is probably there to fund server infrastructure, commercial barcode databases or something... I still don't want to pay it tho 😤 &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-3"&gt;&lt;a href="#ref-3"&gt;***&lt;/a&gt; Gameye however also has a &lt;strong&gt;cartridge scanner&lt;/strong&gt;, which I used after all this database fudging to catalog my handheld games. It's...not very good either, but I got maybe a fourth of my Game Boy collection in through it? Better than nothing. &lt;/sup&gt;    &lt;/p&gt;</content><category term="Physicality of Gaming"></category><category term="video games"></category><category term="collection"></category><category term="migration"></category><category term="clz"></category><category term="gameye"></category><category term="sqlite"></category><category term="powershell"></category><category term="scraping"></category><category term="subscription hell"></category></entry><entry><title>My 2026 Hobonichi Techo lineup picks</title><link href="https://tvc-16.science/2026-techo.html" rel="alternate"></link><published>2025-08-28T00:00:00+02:00</published><updated>2025-08-28T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2025-08-28:/2026-techo.html</id><summary type="html">&lt;p&gt;Stationery suggestions and Cover considerations.&lt;/p&gt;</summary><content type="html">&lt;p&gt;It's &lt;a href="https://www.1101.com/store/techo/en/magazine/2026/y26/"&gt;Hobonichi Techo preview season&lt;/a&gt; again!&lt;br&gt;
Here's a list of designs from this year's lineup I find neat™️.  &lt;/p&gt;
&lt;p&gt;I usually make those Techoposts at the end of the year when I &lt;a href="./2025-techo.html"&gt;switch my planner&lt;/a&gt;, but most of the time what I bought is long sold out by this point...  &lt;/p&gt;
&lt;p&gt;So I thought this year I'd actually make the post &lt;em&gt;right before sales actually begin&lt;/em&gt; - It also serves as my shopping list as a result instead of having to keep 20 tabs on my desktop! I'm a genius.  &lt;/p&gt;
&lt;p&gt;I'm not rehosting any images this time around, so make sure you have third-party images allowed in your browser.&lt;/p&gt;
&lt;h1&gt;An aside about the Techo phone app&lt;/h1&gt;
&lt;p&gt;While last year was a pretty good time Yen-wise to buy a planner, this year feels noticeably worse, &lt;strong&gt;especially&lt;/strong&gt; if you're in the US... I guess you could try using their &lt;a href="https://techoapp.1101.com/en/"&gt;new phone app&lt;/a&gt; instead?&lt;br&gt;
&lt;img alt="Techo phone app screenshot, Global Release in December 2025" src="https://www.1101.com/store/techo/2026/images/tx/topen/1755513296640_mag_y26_techoapp_sq_en.jpg"&gt;&lt;br&gt;
I don't know how I feel about this, it looks a bit more fun than existing offerings like Apple's &lt;a href="https://www.apple.com/newsroom/2023/12/apple-launches-journal-app-a-new-app-for-reflecting-on-everyday-moments/"&gt;Journal&lt;/a&gt; app, but the reason I personally buy those planners is to, yknow, &lt;strong&gt;avoid using a computer&lt;/strong&gt;.  &lt;/p&gt;
&lt;p&gt;You can't really paste or draw random crap with a phone app and it's one of the reasons I've never really been able to keep up with digital note-taking&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt;... I'll still give it a spin just out of curiosity and because &lt;a href="https://www.youtube.com/watch?v=D3xDw-gBoXM"&gt;Shogo Sakai&lt;/a&gt; is doing the BGM.  &lt;/p&gt;
&lt;p&gt;The assistants look cute, but it's unclear if they're actually AI/LLM shit or not?&lt;br&gt;
If it's just some basic image recognition with prewritten lines, that'd be a breath of fresh air at this point.  &lt;/p&gt;
&lt;p&gt;Anyway, on with the recs!  &lt;/p&gt;
&lt;h1&gt;Techo/Planner Covers&lt;/h1&gt;
&lt;h3&gt;&lt;a href="https://www.1101.com/store/techo/en/2026/pc/detail_cover/oc26_inoue/"&gt;Yuichi Inoue - Hana (Flower)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img alt="Yuichi Inoue - Hana (Flower)" src="https://www.1101.com/store/techo/2026/detail_images/oc26_inoue/prod_02.webp"&gt;&lt;br&gt;
This one is pretty nice and I really dig the yellow accent as you might have realized by how this blog looks.&lt;br&gt;
It's similar to 2020's &lt;a href="https://www.1101.com/store/techo/en/2020/pc/detail_cover/oc20_ochitsuke/"&gt;Ochitsuke cover&lt;/a&gt;, but with a bit more flair. The lavender bookmarks are nice too!  &lt;/p&gt;
&lt;h3&gt;&lt;a href="https://www.1101.com/store/techo/en/2026/pc/detail_cover/oc26_mothersaturn/"&gt;MOTHER - Mr. Saturn (It’s Great)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img alt="Mr. Saturn lenticular cover" src="https://www.1101.com/store/techo/2026/detail_images/oc26_mothersaturn/prod_02.webp"&gt;&lt;br&gt;
There have been a lot of Mr.Saturn themed covers over the years and I somehow haven't bought one yet... But this one is really tempting with the lenticular effect. Forget great, it's &lt;strong&gt;cool&lt;/strong&gt;!&lt;br&gt;
It is a bit more expensive than the other covers though; Not to the outrageous level of the &lt;a href="https://www.1101.com/store/techo/en/2026/pc/detail_cover/oc25_mothernbike/"&gt;leather one from last year&lt;/a&gt;, but still a solid 10€ extra from the flower one, for example.   &lt;/p&gt;
&lt;p&gt;Also note the &lt;a href="https://www.1101.com/store/techo/en/2026/pc/detail_cover/cc26_mothersaturn/"&gt;larger A5 variant&lt;/a&gt; with even more lenticular Mr.Saturns!&lt;br&gt;
I always buy the A6 planners, since I don't really write enough (or draw) to fill those huge A5 ones...  &lt;/p&gt;
&lt;h3&gt;&lt;a href="https://www.1101.com/store/techo/en/2026/pc/detail_cover/oc26_jito/"&gt;Junji Ito - Tomie - Knowing Smile&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img alt="" src="https://www.1101.com/store/techo/2026/detail_images/oc26_jito/prod_02.webp"&gt;&lt;br&gt;
What's up with the material choices this year? I'm not complaining though.&lt;br&gt;
The iridescent/shiny material peeking through works really well with Junji's older artwork on this one, it's giving old mecha anime freezeframe vibes. &lt;sub&gt;Does that make sense? I wish I had an example screenshot on hand but I don't&lt;/sub&gt;  &lt;/p&gt;
&lt;h3&gt;&lt;a href="https://www.1101.com/store/techo/en/2026/pc/detail_cover/oc25_ishikawa/"&gt;Naoki Ishikawa - Denali in the Midnight Sun #1&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img alt="Denali in the Midnight Sun cyanotype cover" src="https://www.1101.com/store/techo/2026/detail_images/oc25_ishikawa/prod_02.webp"&gt;&lt;br&gt;
This one is technically from last year, but it's still available and was in my bucket list back then.&lt;br&gt;
I just think cyanotype is really neat! It's also cheaper than the other covers due to being an older design.  &lt;/p&gt;
&lt;p&gt;The inside of the cover is a bit boring, but that doesn't really matter when you're actually using it.  &lt;/p&gt;
&lt;h3&gt;&lt;a href="https://www.1101.com/store/techo/en/2026/pc/detail_toolstoys/tt_coco_tamagotchi/"&gt;Tamagotchi - Cover on Cover for A6 Size (Growth Chart!)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img alt="Tamagotchi - Cover on Cover" src="https://www.1101.com/store/techo/2026/detail_images/tt_coco_tamagotchi/prod_01.webp"&gt;&lt;br&gt;
The Tamagotchi collab for this year is pretty cool, but I'm not a fan of the &lt;a href="https://www.1101.com/store/techo/en/2026/pc/detail_cover/oc26_tamagotchi/"&gt;cover design&lt;/a&gt; they put out for it -- It's far too busy and heavy on the pixel art overall... This translucent cover-on-cover though? Pretty good!  &lt;/p&gt;
&lt;p&gt;Those are nice alternatives to buying "full" covers, since you can pair them with one of the (comparatively cheap) &lt;a href="https://www.1101.com/store/techo/en/2026/pc/detail_cover/oc25_colorswcandle/"&gt;unicolor covers&lt;/a&gt; and mix-and-match however you want.&lt;br&gt;
If you're more money-savvy than I am and don't buy a brand new cover every year, cover-on-covers are nice alternatives to still switch up the design of your Techo every year.  &lt;/p&gt;
&lt;p&gt;I might do something stupid this year and pair this with the Denali cover... It'd be fun, OK? &lt;em&gt;Do you hate fun or something?&lt;/em&gt;  &lt;/p&gt;
&lt;h1&gt;Stationery&lt;/h1&gt;
&lt;h3&gt;&lt;a href="https://www.1101.com/store/techo/en/2026/pc/detail_toolstoys/tt_template_moomin/"&gt;Hobonichi Stencil - Moomin&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img alt="Moomin stencil" src="https://www.1101.com/store/techo/2026/detail_images/tt_template_moomin/prod_02.webp"&gt;&lt;br&gt;
hell yeah I love me some Moomin  &lt;/p&gt;
&lt;p&gt;Similarly to Tamagotchi, I'm not a fan of the &lt;a href="https://www.1101.com/store/techo/en/2026/pc/detail_cover/oc26_moomin/"&gt;cover design&lt;/a&gt; for this collab, but I'm probably going to grab this stencil so I can mindlessly doodle some Finn creatures in my pages from time to time.  &lt;/p&gt;
&lt;h3&gt;&lt;a href="https://www.1101.com/store/techo/en/2026/pc/detail_toolstoys/tt_addonfusen_tsuki/"&gt;Add-on Sticky Notes&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img alt="Add-on Sticky Notes" src="https://www.1101.com/store/techo/2026/detail_images/tt_addonfusen_tsuki/loc_02.webp"&gt;&lt;br&gt;
This is one of those "holy shit &lt;em&gt;technology&lt;/em&gt;" type of stationery, similar to the &lt;a href="https://www.1101.com/store/techo/en/2026/pc/detail_toolstoys/tt_pocket/"&gt;Pocket Cards&lt;/a&gt; which I'm a huge fan of.&lt;br&gt;
Usually when I write too much I just overflow a bit on the pages of the prior/next day, but being able to just paste an additional sheet like that...what power!  &lt;/p&gt;
&lt;p&gt;Of course you could do this by just...pasting a bit of paper from wherever into your page and folding it, but it's nice to have a version that uses the exact same paper and grid size.  &lt;/p&gt;
&lt;h3&gt;&lt;a href="https://www.1101.com/store/techo/en/2026/pc/detail_toolstoys/tt_cardset_tsuki/"&gt;TSUKI no IRO - Mini Card Set&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img alt="Mini Card Set w/ envelopes" src="https://www.1101.com/store/techo/2026/detail_images/tt_cardset_tsuki/loc_01.webp"&gt;&lt;br&gt;
Those are just some small cards and matching stickers, more for putting on top of presents than in a planner - I quite like the 70s-ness of the designs though, which is why it's here.&lt;/p&gt;
&lt;p&gt;Like everything in the Tsuki no Iro series though, they're a bit expensive...&lt;br&gt;
Not as bad as "&lt;em&gt;&lt;a href="https://www.1101.com/store/techo/en/2026/pc/detail_toolstoys/tt_uballset_tsuki/"&gt;64€ for 12 ballpoint pens&lt;/a&gt; what do you MEAN&lt;/em&gt;", but still.  &lt;/p&gt;
&lt;h3&gt;&lt;a href="https://www.1101.com/store/techo/en/2026/pc/detail_toolstoys/tt_decorush_2026/"&gt;Deco Rush - Tamagotchi&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img alt="holy shit tama roll" src="https://www.1101.com/store/techo/2026/detail_images/tt_decorush_2026/prod_03.webp"&gt;&lt;br&gt;
Do I even have to say anything?&lt;br&gt;
...Well, I will say that &lt;a href="https://www.1101.com/store/techo/en/2026/pc/detail_toolstoys/tt_decorush/"&gt;Deco Rush&lt;/a&gt; stickers are surprisingly more annoying to apply than you might think looking at the promo pictures -- You have to press down pretty hard or else they'll usually tear upon application.  &lt;/p&gt;
&lt;h3&gt;&lt;a href="https://www.1101.com/store/techo/en/2026/pc/detail_toolstoys/tt_stappo_moomin/"&gt;Stappo - Moomin&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img alt="" src="https://www.1101.com/store/techo/2026/detail_images/tt_stappo_moomin/prod_01.webp"&gt;&lt;br&gt;
I have now been shilling the Stappo stationery pouch for three years and will continue to do so as long as they release new banger designs for them.&lt;br&gt;
I almost wish I'd waited before getting mine now that they're putting out those nice versions with necktie fabric and embroidery... alas.&lt;br&gt;
&lt;img alt="Stappo stationery pouch and pretzel-powered bag of holding" src="images/techo/stappo.jpg"&gt;  &lt;/p&gt;
&lt;h3&gt;&lt;a href="https://www.1101.com/store/techo/en/2026/pc/detail_toolstoys/s_maste/"&gt;Just some washi tape&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img alt="" src="https://www.1101.com/store/techo/2026/detail_images/s_maste/prod_01.webp"&gt;&lt;br&gt;
Is this a boring one to end with? I dig the fruit/food sticker designs and they're about the price of a TF2 key each so...  &lt;/p&gt;
&lt;p&gt;Also consider the &lt;a href="https://www.1101.com/store/techo/en/2026/pc/detail_toolstoys/tt_fusen_tamagotchi/"&gt;Tamagotchi sticky notes&lt;/a&gt;.  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; It's hard enough thinking of something to write every day at times 😤&lt;/sup&gt;  &lt;/p&gt;</content><category term="Techoposting"></category><category term="techo"></category><category term="hobonichi"></category><category term="stationery"></category><category term="journaling"></category></entry><entry><title>Announcing Yonderu! DoujinSoft for the Playdate</title><link href="https://tvc-16.science/yonderu.html" rel="alternate"></link><published>2025-07-21T00:00:00+02:00</published><updated>2025-07-21T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2025-07-21:/yonderu.html</id><summary type="html">&lt;p&gt;it's officially the summer of wario&lt;/p&gt;</summary><content type="html">&lt;p&gt;Happy "end of season 2" to all &lt;a href="https://play.date/"&gt;Playdate&lt;/a&gt; owners! Did you enjoy the games?&lt;br&gt;
I actually haven't played most of them yet... since as soon as I heard the SDK now officially had &lt;em&gt;networking support&lt;/em&gt;, I saw an opportunity to get some &lt;a href="https://bsky.app/profile/difegue.tvc-16.science/post/3ljlqtygsek27"&gt;brainworms&lt;/a&gt;&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt; out of my skull.  &lt;/p&gt;
&lt;p&gt;And now, two months later, I'm proud to present my &lt;a href="/sonic-drift-mania.html"&gt;second ever&lt;/a&gt; Playdate release.  &lt;/p&gt;
&lt;video autoplay loop src="./images/doujinsoft/yd_intro.mp4" muted=true title="Title screen for Yonderu! DoujinSoft"&gt;&lt;/video&gt;

&lt;p&gt;&lt;strong&gt;Yonderu! DoujinSoft&lt;/strong&gt; &lt;sub&gt;(approximately translates to "Let's read! DoujinSoft"&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt;)&lt;/sub&gt; is a &lt;em&gt;comic reader&lt;/em&gt; for the Playdate which is directly hooked up to the &lt;a href="https://diy.tvc-16.science/"&gt;DoujinSoft Store&lt;/a&gt;, the largest content archive for 2010's&lt;a href="https://en.wikipedia.org/wiki/WarioWare_DIY"&gt; WarioWare DIY&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;Comics in WWDIY are black-and-white 193 x 126 x 4-strip images, which makes them a nearly perfect fit for the Playdate screen! Someone did &lt;a href="https://paulstraw.itch.io/xkpd"&gt;the same thing with XKCD&lt;/a&gt; comics, so you know this is a winner. 😎  &lt;/p&gt;
&lt;p&gt;Ala &lt;em&gt;Garfield-in-the-newspaper&lt;/em&gt;, the game will download a &lt;strong&gt;new comic&lt;/strong&gt; every day from a curated selection for users to read, share, and rate.&lt;br&gt;
And if you want more, you can also download all the latest comics uploaded to the store by the DIY community... which is still pretty active! Here's &lt;a href="https://diy.tvc-16.science/comics?id=4c527559b4f1bf1bded41e00ec456fb3"&gt;a comic with a Deltarune ch3 joke&lt;/a&gt;, for example.  &lt;/p&gt;
&lt;p&gt;...And of course, you can use the crank to scroll between pages.
&lt;img alt="the tutorial turtle is undying" src="/images/doujinsoft/yd_scroll.gif"&gt;  &lt;/p&gt;
&lt;h3&gt;You can download the game on itch with the link below.&lt;/h3&gt;
&lt;iframe src="https://itch.io/embed/3705003" width="552" height="167" frameborder="0"&gt;&lt;a href="https://difegue.itch.io/yonderu-doujinsoft"&gt;Yonderu! DoujinSoft by dfug&lt;/a&gt;&lt;/iframe&gt;

&lt;p&gt;I always felt that comics are kind of the black sheep of DIY's offerings - The microgames are obviously the main draw, and the music maker gets a fair amount of love from the same folks who like to make absurdly complex compositions in Mario Paint...&lt;br&gt;
There's some real funny stuff in the &lt;em&gt;40000+&lt;/em&gt; comics uploaded to the archive though, and I'm hoping this Playdate outing will help shine a bit more light on them.  &lt;/p&gt;
&lt;p&gt;This game is actually the &lt;strong&gt;first time&lt;/strong&gt; you can submit &lt;a href="https://diy.tvc-16.science/surveys"&gt;ratings&lt;/a&gt; for WWDIY content without using a Wii with DIY Showcase! It's pretty fun being able to stretch the lifetime of a game like this utterly beyond what it was designed to do.&lt;br&gt;
&lt;img alt="A DSi and a Playdate both reading the tutorial comic -- i actually used mspaint's new remove background feature for the blur on this and was pleasantly surprised" src="images/doujinsoft/yonderu.jpg"&gt;&lt;br&gt;
While the Playdate is a great fit for this kind of pick-up-and-play &lt;sub&gt;(or is it pick-up-and-read?)&lt;/sub&gt; content, I did have to rework a few bits of the experience for it to work on Panic's 1-bit console;  &lt;/p&gt;
&lt;p&gt;The comic iconography has mostly been redrawn, and I used some MacPaint patterns to replace the colors that comic creators can use for their covers in the original game. It gives quite a different vibe from the original! &lt;br&gt;
&lt;img alt="A comic cover in the OG NDS game, and how it looks in Yonderu." src="images/doujinsoft/yd_covers.png"&gt;  &lt;/p&gt;
&lt;p&gt;The sound bites that play when you start/finish a comic have also been changed compared to the originals; I had fun digging through samples to make 10 new intro/outros, but that was at the point where I realized I might have spent way more time building this than I had anticipated...  &lt;/p&gt;
&lt;p&gt;That time included picking up the &lt;em&gt;actual DIY comic creator&lt;/em&gt; again to make an in-game tutorial too.&lt;br&gt;
The text tools on the original NDS are still painful, and my drawing skills haven't really evolved since 2014&lt;sup id="ref-3"&gt;&lt;a href="#note-3"&gt;***&lt;/a&gt;&lt;/sup&gt;...&lt;br&gt;
&lt;img alt="" src="images/doujinsoft/yd_tutorial.png" title="" style="width: 40%;"&gt;&lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; This technically doesn't follow the original vision™️ of putting DIY comics on the Pebble watch, but now that I made all the server tooling for it? Maybe for the next Rebble hackathon.&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; Real connaisseurs will of course understand this is a reference to the Japanese name for WarioWare DIY Showcase, &lt;i&gt;"Asobu! Made in Ore"&lt;/i&gt;... my naming sense is truly immaculate. Hire me Microsoft, i'll fix your dogshit names before you can even put me in your sixth round of layoffs &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-3"&gt;&lt;a href="#ref-3"&gt;***&lt;/a&gt; before you ask yes the comic reading guy is literally just a knockoff servbot&lt;/sup&gt;    &lt;/p&gt;</content><category term="Gamedev"></category><category term="nintendo"></category><category term="nintendo ds"></category><category term="warioware"></category><category term="doujinsoft"></category><category term="playdate"></category><category term="comics"></category><category term="gamedev"></category></entry><entry><title>Funtography is part of Indiepocalypse #65!</title><link href="https://tvc-16.science/indiepocalypse.html" rel="alternate"></link><published>2025-06-10T00:00:00+02:00</published><updated>2025-06-10T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2025-06-10:/indiepocalypse.html</id><summary type="html">&lt;p&gt;Patch notes: The Grape assistant has turned into a Medjed&lt;/p&gt;</summary><content type="html">&lt;p&gt;Woops, haven't written a blogpost in a few months..&lt;br&gt;
I've been busy playing video games&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt; and &lt;a href="https://bsky.app/profile/difegue.tvc-16.science/post/3lqo3v7c3cc2k"&gt;memeing something up with the Playdate&lt;/a&gt;.&lt;br&gt;
But in the meantime, my &lt;a href="./funtography.html"&gt;silly gameboy camera VN&lt;/a&gt; has made it into Indiepocalypse Issue 65!  &lt;/p&gt;
&lt;p&gt;&lt;a href="https://pizzapranks.com/"&gt;Indiepocalypse&lt;/a&gt; is a curated monthly collection looking to highlight the very best of the alternative indie game scene.&lt;br&gt;
Please &lt;a href="https://pizzapranks.itch.io/indiepocalypse-65"&gt;give it a look&lt;/a&gt;, it's not even the only game in there with an operating system interface!  &lt;/p&gt;
&lt;p&gt;I've remade a build of Funtography that includes a few QoL changes from the (slowly) ongoing larger fake OS project, so if you haven't played it yet, dare I say... there's never been a better time??  &lt;/p&gt;
&lt;p&gt;&lt;img alt="I actually finally made a proper icon for the clock but it's not in this screenshot smh" src="./images/funtography/ft_indiepocalypse.png"&gt;  &lt;/p&gt;
&lt;p&gt;I wrote a few extra words about the game for the included zine, feel free to read them below if you want some more Funtolore™️:  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hey! I hope you're enjoying the zine.
People often wonder what the hell the fake OS has to do with Funtography as a game and the reality of it is.... nothing! &lt;/p&gt;
&lt;p&gt;I've been building this fake OS with super shaky foundations in Unity 2019 for 5 years by now to try and make a 80's BBS simulator game ala Digital, and Funtography is "just" a full playable game that I've built within that framework.&lt;/p&gt;
&lt;p&gt;The concept of "VN adventure game with Gameboy Camera pictures" was stuck in my head for nearly 15 years, so at some point I just thought fuck it, let's do it in here! It was quite cathartic to actually get this out in 2023.&lt;/p&gt;
&lt;p&gt;The story is pretty fun to look back at; It's silly and the ending is very rushed, but I enjoy writing alt-history stuff like this that grounds itself in real events and kinda just runs with it. &lt;/p&gt;
&lt;p&gt;I'm actually still proud of the soundtrack for this - It's a hodgepodge of slowed-down chiptune horrors, but for someone who has negative music skills, I still think it's pretty good... 
I hope you'll be interested enough to read up to when "special stage" plays as the BGM. &lt;/p&gt;
&lt;p&gt;In case the full fake OS game never comes out, at least this VN is something that actually shipped I can look back on. It's been hard to make time for gamedev recently, but I'm keeping at it! 
This dumb idea will probably kill me at some point if I don't actually get it out.&lt;/p&gt;
&lt;p&gt;This Indiepocalypse build has a few QOL updates from the old Itch version I've made since 2023, namely actualy functional UI scaling, a very dodgy scanline filter, and a 2X zoom option for the Camera view.&lt;/p&gt;
&lt;p&gt;If you clear the game, you'll unlock the sound test in the CD Player program! &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; I haven't made a mini-review post in a hot minute but I enjoyed the hell out of "Get in the Car, Loser!" after finally playing it a month ago. also yeah deltarune ch3/4 i guess &lt;/sup&gt;  &lt;/p&gt;</content><category term="Gamedev"></category><category term="video games"></category><category term="gamedev"></category><category term="unity"></category><category term="c#"></category><category term=".net"></category><category term="gameboy"></category><category term="gameboy camera"></category><category term="faux-OS"></category><category term="indiepocalypse"></category></entry><entry><title>The cozy alternate Internet of GlobalTalk</title><link href="https://tvc-16.science/globaltalk.html" rel="alternate"></link><published>2025-03-22T00:00:00+01:00</published><updated>2025-03-22T00:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2025-03-22:/globaltalk.html</id><summary type="html">&lt;p&gt;the lengths you have to go through to have fun on the computer these days, i tell you what&lt;/p&gt;</summary><content type="html">&lt;p&gt;It's March! That means Spring is coming in, but also &lt;a href="https://www.marchintosh.com/"&gt;MARCHintosh&lt;/a&gt;, 
the month-long event dedicated to vintage Macs.&lt;br&gt;
And alongside it, so is the &lt;strong&gt;GlobalTalk&lt;/strong&gt; network!  &lt;/p&gt;
&lt;p&gt;GlobalTalk basically consists of using pre-Internet, vintage Apple networking &lt;strong&gt;grossly beyond its initial spec&lt;/strong&gt; 
to connect dozens of old Macintosh computers&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt; together, 
&lt;a href="https://biosrhythm.com/?p=2767"&gt;using the Internet as a bridge&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;I missed out on the fun last year, but this time I got a few of my &lt;a href="./adbuino-ps2.html"&gt;trashpicked&lt;/a&gt; &lt;a href="./emac-lcd-mod.html"&gt;machines&lt;/a&gt; online.&lt;br&gt;
If we're still in March as you're reading this, you can just go and &lt;a href="https://bsky.app/profile/difegue.tvc-16.science/post/3lk54yua2ww2d"&gt;connect to them right now!&lt;/a&gt;  &lt;/p&gt;
&lt;p&gt;&lt;img alt="Mac OS 9 screenshot showing a shared folder from a Centris 650 with the classic naviGator icon" src="images/globaltalk/globaltalk.png"&gt;&lt;br&gt;
Now, what can you actually do when you're on GlobalTalk?&lt;br&gt;
Well... the networking standard in use, &lt;a href="https://en.wikipedia.org/wiki/AppleTalk"&gt;AppleTalk&lt;/a&gt;, doesn't really allow you to do much!&lt;br&gt;
From your &lt;em&gt;Zone&lt;/em&gt;, you can share local folders from Macs on your network, alongside printers&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt;.  &lt;/p&gt;
&lt;p&gt;So GlobalTalk is &lt;em&gt;mostly&lt;/em&gt; just an interconnected net of shared folders, with printers you can print to remotely.&lt;br&gt;
It's as close to &lt;strong&gt;"primordial Internet"&lt;/strong&gt; as you can get and feels a bit &lt;a href="https://www.youtube.com/watch?v=afFOMVO3unM"&gt;Battle Network-esque&lt;/a&gt; in a way?  &lt;/p&gt;
&lt;p&gt;You &lt;em&gt;jack in&lt;/em&gt; to a user's AppleTalk Zone, and get to look at the files and programs he's left there for you to look at.&lt;br&gt;
Maybe fuck around and send some memes to his printer, or drop a file into a folder he left write access on.  &lt;/p&gt;
&lt;p&gt;The very &lt;a href="https://www.fastcompany.com/91044923/watching-susan-kare-explain-the-mac-ux-in-1984-is-the-most-relaxing-thing-ever"&gt;user-oriented interface&lt;/a&gt; of the classic Mac OS accentuates this feeling a &lt;strong&gt;lot&lt;/strong&gt; -- It's incredibly easy to customize the names, icons and positions of every single file in your Zone, so every user's tiny network ends up looking very personal.  &lt;/p&gt;
&lt;p&gt;I mean, do you know of &lt;strong&gt;any other network&lt;/strong&gt; where you can just fullscreen Big Challenges smoking the boof&lt;sup id="ref-3"&gt;&lt;a href="#note-3"&gt;***&lt;/a&gt;&lt;/sup&gt; in the File Explorer as a set of 34 custom icons glued to one another??&lt;br&gt;
&lt;img alt="Another Mac OS 9 screenshot, showing TalkCrawler indexing a bunch of AppleTalk zones and a shared folder with... Big Challenges smoking the boof! hell yeah!" src="images/globaltalk/talkcrawler.png"&gt;&lt;br&gt;
You can just spend &lt;strong&gt;hours&lt;/strong&gt; looking through the Zone list, connecting to different oldass computers across the world named "&lt;em&gt;Glorbo's death star Performa 600&lt;/em&gt;", where they're sharing a pack of Simpsons icons and a text file saying  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;hello! thank you for visiting computer  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;that took you 30 seconds to open at 64k speed.  &lt;/p&gt;
&lt;p&gt;It's...fun! There's a real sense of discovery here caused by the limitations and customizability of it all.  &lt;br&gt;
You can &lt;strong&gt;have fun &lt;em&gt;browsing through things on the computer&lt;/em&gt;&lt;/strong&gt;, which is essentially impossible nowadays on real web browsers.  &lt;/p&gt;
&lt;p&gt;And despite this being a relatively small network all things considered, the variety is still pretty impressive to me.&lt;br&gt;
There are lots of vintage programs, obviously, but also &lt;a href="https://anticapitalist.party/@ahihi/114206693858413006"&gt;music&lt;/a&gt;! &lt;a href="https://social.europlus.zone/@europlus/114125705720284399"&gt;art pieces&lt;/a&gt;!&lt;br&gt;
A bloody &lt;em&gt;scavenger hunt&lt;/em&gt; done through printers and files hidden in various zones?&lt;br&gt;
&lt;img alt="I now have a moral obligation to kept my sharezone online as it's being used as part of a puzzle how did this even come to be" src="images/globaltalk/ScavengerHunt.jpg"&gt; &lt;br&gt;
In the current age of the &lt;a href="./total-internet-hyperdeath.html"&gt;Total Internet Hyperdeath&lt;/a&gt; caused by the slow coalescing of users onto a handful of massive corporately-owned social platforms that are all turning fascist in one way or another...  &lt;/p&gt;
&lt;p&gt;GlobalTalk is &lt;strong&gt;punk&lt;/strong&gt; as hell in comparison.&lt;br&gt;
Despite the Internet basically crumbling under its own rot, it's still possible to use it to build people-focused and people-driven communities, and I think that's pretty cool.  &lt;/p&gt;
&lt;p&gt;Now, should we actually &lt;code&gt;RETVRN&lt;/code&gt; to AppleTalk? No.... it's &lt;em&gt;really slow&lt;/em&gt;.&lt;br&gt;
But I'd say it's worth &lt;a href="https://docs.google.com/document/d/1pXMjrAF5vC08TamkdSt2oFCrDFMP47i5dYcjshn9JzU/edit?usp=sharing"&gt;checking out&lt;/a&gt; if you can. You don't necessarily have to use a vintage Mac either!&lt;br&gt;
Lots of people are using VMs and/or purpose-written software instead.&lt;br&gt;
&lt;img alt="*duke nukem voice* those alien bastards have transed their gender! good for them i say" src="images/globaltalk/visited.jpg"&gt;&lt;br&gt;
&lt;sub&gt;&lt;sup&gt;     (visited.jpg, received in the Dropbox of the TVC-16 Zone on GlobalTalk)&lt;/sup&gt;&lt;/sub&gt;  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; Or things that pretend to be Macintosh computers! There are QEMU emulated macs, Linux machines through &lt;code&gt;netatalk&lt;/code&gt;, Windows 95 machines, &lt;a href="https://mastodon.social/@oevl/114196246218250463"&gt;NeXTStep&lt;/a&gt;... even brand new routers written in Go. &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; which might or might not be an actual physical 1990s printer they're keeping alive through sheer force of will? I have no idea how they do it, printers are hell... I should mention there are also some programs that actually support AppleTalk networking, so you can use a few games and &lt;a href="https://bitbang.social/@kalleboo/114193676799421822"&gt;chat programs&lt;/a&gt;. &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-3"&gt;&lt;a href="#ref-3"&gt;***&lt;/a&gt; The OG big challenges boof artwork can be found &lt;a href="https://bsky.app/profile/squeakitties.bsky.social/post/3ljx7teytms2i"&gt;here&lt;/a&gt;. It brings me unbridled joy to look at, and &lt;a href="https://innerspiral.lol/Blog/comfort/comfort"&gt;that's great&lt;/a&gt;. &lt;/sup&gt;   &lt;/p&gt;</content><category term="Blogposting"></category><category term="apple"></category><category term="macintosh"></category><category term="mac"></category><category term="macos"></category><category term="crt"></category><category term="diy"></category><category term="retrocomputing"></category><category term="marchintosh"></category></entry><entry><title>Making new microgames in WarioWare DIY, 14 years later</title><link href="https://tvc-16.science/diy-fi.html" rel="alternate"></link><published>2025-01-28T00:00:00+01:00</published><updated>2025-01-28T00:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2025-01-28:/diy-fi.html</id><summary type="html">&lt;p&gt;hahaha!!! i made a fake OS on the nintendo ds!!!&lt;/p&gt;</summary><content type="html">&lt;p&gt;Despite running the largest &lt;a href="https://diy.tvc-16.science/"&gt;WarioWare DIY archive&lt;/a&gt; for about eight years now&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt;, I hadn't made any games with it since... &lt;a href="https://diy.tvc-16.science/games?id=66ff7257b3d7ea3361c3745ed36ed05e"&gt;wow, 2011!&lt;/a&gt;&lt;br&gt;
DIY is of course very limiting as a game creation tool, but I'm quite grateful to it for being the one of the ways I released "proper" games online, after multiple attempts with Game Maker 7/8 in the late 2000s&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt;.   &lt;/p&gt;
&lt;p&gt;While running those old GM games on modern hardware is a pain, the various efforts around DIY have made it surpisingly easy to pick up and use even nowadays. &lt;sub&gt;(I don't think you'd be getting &lt;a href="https://diy.tvc-16.science/games?id=2d92ffcc9d1ec20a024d37210a35f51d"&gt;skibidi games&lt;/a&gt; otherwise..)&lt;/sub&gt;&lt;br&gt;
There's the &lt;a href="./doujinsoft-3.html"&gt;built-in&lt;/a&gt; players on DoujinSoft, of course, but also working online with &lt;a href="https://www.wiilink24.com/"&gt;WiiLink&lt;/a&gt; and Wimmfi for the original games.  &lt;/p&gt;
&lt;p&gt;..And the new kid on the block, &lt;a href="https://diy-fi.net/"&gt;DIY-Fi&lt;/a&gt;!&lt;br&gt;
This new service fully reimplements DIY's online store for the DS version of the game, bringing back weekly games&lt;sup id="ref-3"&gt;&lt;a href="#note-3"&gt;***&lt;/a&gt;&lt;/sup&gt; to download, alongside design contests.&lt;br&gt;
&lt;img alt="DIY-Fi showing 2025 Halloween contest winners on real hardware" src="./images/doujinsoft/diy-fi.jpg"&gt;&lt;br&gt;
A design contest popped up for Halloween, so I thought, why not test myself a bit and see if I &lt;em&gt;could make a game without redoing any of the tutorials 14 years later&lt;/em&gt;?&lt;br&gt;
&lt;sub&gt;(DoujinSoft now supports &lt;code&gt;iframe&lt;/code&gt; embeds! Feel free to embed all your DIY microgames and comics on your own websites.)&lt;/sub&gt;  &lt;/p&gt;
&lt;iframe src="https://diy.tvc-16.science/games?id=a7f667db4362842bee783123cd235699#iframe" width="536" height="490"&gt;&lt;/iframe&gt;

&lt;p&gt;...OK, it was still pretty easy. The &lt;em&gt;Game MakerMatic&lt;/em&gt; creation tool within WarioWare DIY isn't massively complex!  &lt;/p&gt;
&lt;p&gt;Due to having maintained DoujinSoft for all those years, I never really forgot all the concepts and terminology, so I guess I had a bit of an advantage.&lt;br&gt;
I think the only thing I had to look back up after all this time was how to do randomness?  &lt;/p&gt;
&lt;h1&gt;A review of the MakerMatic in 2025&lt;/h1&gt;
&lt;p&gt;How does it stack up as a game-making tool though? I think it's alright!&lt;br&gt;
You're very constrained, obviously, but that also makes scope creep essentially &lt;strong&gt;impossible&lt;/strong&gt;, which is very nice if all you want to do is bang out a thing in a few days hunched over your Nintendo DS and call it a day.  &lt;/p&gt;
&lt;p&gt;The palette is a bit low around 32 or so colors, so you end up relying on dithering pretty fast.&lt;br&gt;
While you might think the most limiting think in the MakerMatic is the "point" system that dictates how much pixel art you can cram into the 64 kilobytes of an individual microgame, I think the true thing that kneecaps you is the maximum of &lt;strong&gt;6 AI statements&lt;/strong&gt; per object.  &lt;/p&gt;
&lt;p&gt;In DIY, AI is not a capitalist buzzword for poorly functioning chatbots, but is essentially the equivalent of a &lt;code&gt;if/else&lt;/code&gt; function in normal programming. So broadly speaking, you only have 6 &lt;code&gt;ifs&lt;/code&gt; per object.  &lt;/p&gt;
&lt;p&gt;You can imagine this gets limiting very fast! Combined with the fact each object can only hold &lt;strong&gt;one&lt;/strong&gt; boolean variable, any form of complex logic usually ends up eating through your entire &lt;em&gt;15 object&lt;/em&gt; limit.&lt;br&gt;
&lt;img alt="A bunch of objects in Doujin-x68k, entirely filling out the point limit!" src="./images/doujinsoft/diy-objects.jpg"&gt;&lt;br&gt;
Now there are some tricks you can employ to maximize your object use -- Offscreen positions can be used as makeshift additional integer variables, you can also rely on the animation system, etc.. But it kinda all comes back to the fact &lt;strong&gt;logic is the biggest limiter&lt;/strong&gt;.  &lt;/p&gt;
&lt;p&gt;Not allowing the use of the NDS buttons or microphone is also a bit of a missed opportunity - I can understand the mic if you wanted to keep interoperability with the Wii version&lt;sup id="ref-4"&gt;&lt;a href="#note-4"&gt;#&lt;/a&gt;&lt;/sup&gt;, but the D-pad and A/B buttons would've been a great way to mix things up without adding much complexity. &lt;/p&gt;
&lt;p&gt;There's also no real random function in DIY - The game itself teaches you to make use of one of its built-in "random placement" functions to act as your RNG by then doing a position check. It's clever! But also kind of hacky!!   &lt;/p&gt;
&lt;h1&gt;What about... second game???&lt;/h1&gt;
&lt;p&gt;After &lt;em&gt;SpookOS&lt;/em&gt;, I wanted to actually test those limits and see how much functionality you can cram in a WWDIY microgame.  &lt;/p&gt;
&lt;p&gt;There are some pretty impressive DIY creations out there that do &lt;a href="https://diy.tvc-16.science/games?id=e3c8d428afe0c193710bb0258874e7f2"&gt;Anaglyph 3D&lt;/a&gt; or &lt;a href="https://diy.tvc-16.science/games?id=e44be2dc1cc27af1de4096c28189290c"&gt;multi-room puzzles&lt;/a&gt;, but I wanted to try my hand at making a game that just crams a bunch of various stuff in.&lt;br&gt;
We're going &lt;strong&gt;maximalist&lt;/strong&gt; baby! It's the &lt;a href="https://www.kartkrew.org/"&gt;Ring Racers&lt;/a&gt; of WarioWare DIY!  &lt;/p&gt;
&lt;p&gt;There &lt;a href="https://diy.tvc-16.science/games?id=88ba7dd23152109b8712f4a209dd26ef"&gt;are&lt;/a&gt; &lt;a href="https://diy.tvc-16.science/games?id=0f14d0ddc0e6def0b45adbdbb16ccc6f"&gt;a&lt;/a&gt; &lt;a href="https://diy.tvc-16.science/games?id=3d7c9d2797d3ce65c3a2dd84ea70d278"&gt;lot&lt;/a&gt; &lt;a href="https://diy.tvc-16.science/games?id=7a1adf1d1e31185bddf1904d59186e00"&gt;of&lt;/a&gt; &lt;a href="https://diy.tvc-16.science/games?id=f31e0eb5beefc9336826f92d6a77287b"&gt;DIY microgames&lt;/a&gt; that replicate Windows or computer interfaces already... But most of these are pretty simple, and I love my toy operating systems!  &lt;/p&gt;
&lt;p&gt;So I grabbed the boss template and made a tiny fake OS using graphics from &lt;a href="./funtography.html"&gt;Funtography&lt;/a&gt;, with multiple windows and embedded games/messages.&lt;br&gt;
I'd just traced the graphics as usual for SpookOS, but here I caved in and used &lt;a href="https://www.romhacking.net/utilities/1011/"&gt;mioedit&lt;/a&gt; to import some bitmaps from the computer. This approach isn't perfect due to the palette limitations though, so I still had to do some redrawing.  &lt;/p&gt;
&lt;iframe src="https://diy.tvc-16.science/games?id=2124d89e60265d2535101bc58f7a22fb#iframe" width="536" height="490"&gt;&lt;/iframe&gt;

&lt;p&gt;Of course there's realistically not &lt;em&gt;that&lt;/em&gt; much you can do in DIY...&lt;br&gt;
But I had fun figuring out the balancing act between the various point, AI, and object limits.  &lt;/p&gt;
&lt;p&gt;To make an object blink, you &lt;em&gt;can&lt;/em&gt; use a sprite animation... Or you could just rely on two variables to teleport it out of bounds and back in within the same AI statement. Stuff like that. &lt;/p&gt;
&lt;p&gt;I was pretty happy with the microgame, so I forcefully sent it to 150 Wii consoles through WiiLink with the yearly DoujinSoft newsletter.&lt;br&gt;
That's the harsh truth of marketing, baby...&lt;br&gt;
&lt;img alt="And I get to make a Songs of Innocence joke! phenomenal" src="./images/doujinsoft/wiimb-u2.jpg"&gt;&lt;br&gt;
I do think there's a bit of a framework here for "zine-like" DIY games, that contain just tiny little bits of text, graphics and maybe showcasing some tricks you can do with the MakerMatic.&lt;br&gt;
Putting together text in the editor on the NDS is &lt;strong&gt;maximum pain&lt;/strong&gt; though, so I don't think that'll happen!  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; DoujinSoft has now been running for a longer time that the official Nintendo WFC service did for DIY on Nintendo DS... Also I was rewatching some older Game Center CX episodes and Arino was only 32 at the start of the show?? holy shit&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; &lt;strong&gt;Before&lt;/strong&gt; it got bought over by yoyo games! can you imagine? I was too young to get into Klik &amp;amp; Play/Multimedia Fusion when it was the &lt;a href="https://jtholen.bandcamp.com/album/new-active-object"&gt;hype gamemaking tool for kids&lt;/a&gt;. &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-3"&gt;&lt;a href="#ref-3"&gt;***&lt;/a&gt; The weekly games are powered by snapshots of the latest additions to the DoujinSoft dataset! It's really cool to see the archive being used in that way.&lt;/sup&gt; &lt;br&gt;
&lt;sup id="note-4"&gt;&lt;a href="#ref-4"&gt;#&lt;/a&gt; Although they &lt;strong&gt;could&lt;/strong&gt; have used waggle/motion controls on the Wii in place of the mic! It'd have been silly but pretty fun if you ask me.&lt;/sup&gt;  &lt;/p&gt;</content><category term="Gamedev"></category><category term="nintendo"></category><category term="wii"></category><category term="nintendo ds"></category><category term="warioware"></category><category term="doujinsoft"></category><category term="mio"></category><category term="diy"></category><category term="gamedev"></category></entry><entry><title>My 2025 Hobonichi Techo picks</title><link href="https://tvc-16.science/2025-techo.html" rel="alternate"></link><published>2024-12-31T00:00:00+01:00</published><updated>2024-12-31T00:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2024-12-31:/2025-techo.html</id><summary type="html">&lt;p&gt;time and cringe are social constructs and don't matter&lt;/p&gt;</summary><content type="html">&lt;p&gt;2024, huh.  &lt;/p&gt;
&lt;p&gt;The usual thought is "&lt;em&gt;wow this year went by in a flash!&lt;/em&gt;" but then I looked at a bunch of photos and went "&lt;em&gt;wait this was in 2024? I thought that was ages ago..&lt;/em&gt;"&lt;br&gt;
All I'm saying is that time is bullshit and shouldn't really matter as much as it does.  &lt;/p&gt;
&lt;p&gt;Since computers are getting &lt;a href="./dialogueforest-msstore.html"&gt;increasingly worse&lt;/a&gt;, I've been continuing to handwrite in my &lt;a href="https://www.1101.com/store/techo/en/"&gt;Hobonichi Techo&lt;/a&gt; planner.&lt;br&gt;
So as usual, here's what I bought to keep doing that in 2025!&lt;br&gt;
With the current state of the Yen, I think it's pretty accessible at the moment.&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt;  &lt;/p&gt;
&lt;p&gt;My cover pick for this year is the new &lt;a href="https://www.1101.com/store/techo/en/2025/pc/detail_cover/oc25_pamm/"&gt;PAMM one&lt;/a&gt; - I missed out on the green one last year, but this new blue variant is a fine alternative. I like squiggly lines! That's &lt;a href="./stylophone-27.html"&gt;just the way it is&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;Although I have to say, it really doesn't fit with the color scheme they picked this year for the Jetstream pen -- What is this, &lt;a href="https://blog.codinghorror.com/a-tribute-to-the-windows-31-hot-dog-stand-color-scheme/"&gt;Hot Dog Stand&lt;/a&gt;? I might just keep using my blue 2022 one and swap the ink cartridges or something.   &lt;br&gt;
&lt;img alt="2024 v 2025 techos" src="images/techo/2025.jpg"&gt;&lt;br&gt;
I picked the Original again instead of the English version - I didn't have any problem with it &lt;a href="./053-2024-techo.html"&gt;last year&lt;/a&gt; and appreciated the different layout, so might as well keep saving those 500 yen.&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt;  &lt;/p&gt;
&lt;p&gt;Other designs I considered this year are the really cool &lt;em&gt;&lt;a href="https://www.1101.com/store/techo/en/2025/pc/detail_cover/oc25_ishikawa/"&gt;Cyanotype mountain photo&lt;/a&gt;&lt;/em&gt; and this Indian &lt;a href="https://www.1101.com/store/techo/en/2025/pc/detail_cover/oc25_phoolon/"&gt;fabric&lt;/a&gt; cover.&lt;br&gt;
And wow, they finally ran out of &lt;a href="https://www.1101.com/store/techo/en/2025/pc/detail_cover/oc20_mothertile/"&gt;Mr. Saturn tiles&lt;/a&gt;! I never bought this one in the end.&lt;br&gt;
&lt;img alt="Stappo stationery pouch and pretzel-powered bag of holding" src="images/techo/stappo.jpg"&gt;&lt;br&gt;
I didn't really buy any extra stationery this year since I still have a batch I'm going through;&lt;br&gt;
I just bought some new &lt;a href="https://www.1101.com/store/techo/en/2025/pc/detail_toolstoys/tt_pocket/?recommend"&gt;stick-on pockets&lt;/a&gt; for cards and a few &lt;a href="https://www.1101.com/store/techo/en/2025/pc/detail_toolstoys/tt_stampset/"&gt;new stamps&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;They have a &lt;a href="https://www.1101.com/store/techo/en/2025/pc/detail_toolstoys/tt_stappom_camib"&gt;mini version&lt;/a&gt; of their stationery pouch now though!&lt;br&gt;
I still like my regular-sized one a year in, so you might be interested.  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;"&lt;em&gt;What do I even write in those planners&lt;/em&gt;" you might ask? Usually it's basic food stuff, pasting in cards and tickets, and the occasional gamedev/other idea I have to put down to paper before it &lt;a href="https://artreview.com/daydreaming-is-so-important-to-me-how-david-lynch-fishes-for-ideas/"&gt;vanishes away&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;Sometimes I find it hard to put to paper things you daydream about because... &lt;strong&gt;it's cringe&lt;/strong&gt;!&lt;br&gt;
Some of this stuff is dumb feel-good crap, has zero value and will probably never be read again, right?&lt;br&gt;
Even being aware that cringe is made up and doesn't matter, there's always that slight deterrent in the back of your mind.  &lt;/p&gt;
&lt;p&gt;I was reading the &lt;a href="https://www.1101.com/store/techo/en/magazine/contents/y25_itoi/mnpkrp9nr.html"&gt;yearly Itoi interview&lt;/a&gt; when making my purchases this year, and I found a pretty good answer to that:  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It’s a little embarrassing, but then again, I can only feel that way because I wrote it down in the first place.&lt;br&gt;
It’s a feeling I encourage everyone to experience.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yeah, I think I'll strive to write even more cringe in 2025.  &lt;/p&gt;
&lt;p&gt;I'm always slightly worried about all the semi-blank pages I'm leaving in each year since I'm anything but diligent..&lt;br&gt;
But when you compare the bulgy planner of the last year with the fresh one, I always end up thinking "&lt;em&gt;oh yeah this was a pretty packed year! I think I did good here&lt;/em&gt;"&lt;br&gt;
&lt;img alt="thick boy on the left" src="images/techo/2025-2.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;Life goes on and it'll probably be alright.&lt;br&gt;
&lt;br/&gt;
&lt;br/&gt;
&lt;br/&gt;
&lt;br/&gt;
&lt;br/&gt;
...Am I going to manage to work on my goddamn game this year???  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; unless you live in Europe and have atrocious customs fees I guess.. There are also always those outrageously priced luxury covers. I'm shocked they made a &lt;a href="https://www.1101.com/store/techo/en/2025/pc/detail_cover/oc25_mothernbike/"&gt;leather MOTHER 2&lt;/a&gt; one this year, they think Earthbound fans are all megarich socialites or something? I'm trying to envision the final collapse of Capitalism over here!&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt;I'm painfully aware the endgame is probably to raise the price of the original once the other english version gets killed off because low sales or some shit, but while it lasts? I'll take the savings ig&lt;/sup&gt;  &lt;/p&gt;</content><category term="Techoposting"></category><category term="techo"></category><category term="hobonichi"></category><category term="stationery"></category></entry><entry><title>DialogueForest is now on the Microsoft Store!</title><link href="https://tvc-16.science/dialogueforest-msstore.html" rel="alternate"></link><published>2024-12-21T00:00:00+01:00</published><updated>2024-12-21T00:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2024-12-21:/dialogueforest-msstore.html</id><summary type="html">&lt;p&gt;This blogpost contains unsafe amounts of ranting against AI.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="./dialogueforest.html"&gt;A bit more than a year ago&lt;/a&gt;, I shipped DialogueForest, which had itself been in development for a year+...&lt;br&gt;
So the app is actually almost three years old now! Wow!&lt;br&gt;
&lt;img alt="DialogueForest screenshot" src="images/dialogueforest/screenshot.png"&gt;&lt;br&gt;
I don't update it that much as it's really mostly for my personal use&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt;, but as the Windows App SDK runtime keeps getting updates, it was getting a bit difficult to run the executable I shipped on &lt;a href="https://difegue.itch.io/dialogueforest"&gt;itch.io&lt;/a&gt; back then.  &lt;/p&gt;
&lt;p&gt;So I solved that and added a &lt;a href="https://github.com/Difegue/DialogueForest/releases/tag/v.1.0.4"&gt;few more features&lt;/a&gt;:&lt;br&gt;
DialogueForest is now on the &lt;strong&gt;Microsoft Store&lt;/strong&gt; so you can 1-click install it, &lt;em&gt;entirely free&lt;/em&gt;.  &lt;/p&gt;
&lt;p&gt;&lt;a href="https://apps.microsoft.com/detail/9P7MWMG1V6M6?cid=tvc-16&amp;mode=full"&gt;
    &lt;img src="https://get.microsoft.com/images/en-us%20dark.svg" width="200"/&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Merry Christmas! Go write some dialogue for your games.  &lt;/p&gt;
&lt;p&gt;&lt;img width="400px" alt="i hadnt opened spline in more than a year it's still pretty fun" src="images/dialogueforest/ForestXmas.png" title="i hadnt opened spline in more than a year it's still pretty fun"&gt;&lt;/p&gt;
&lt;p&gt;... You're still here? I guess I did promise some ranting.  &lt;/p&gt;
&lt;p&gt;So one of the reasons I'd held back MS Store publishing for this app back then was...&lt;br&gt;
that I was planning to add &lt;strong&gt;AI&lt;/strong&gt; to it! 👻👻👻&lt;br&gt;
Not really because I believed in the technology &lt;sub&gt;(especially not for creative writing, miss me with that shit)&lt;/sub&gt;, but I wanted to 🧪experiment🧪 with something.  &lt;/p&gt;
&lt;p&gt;Microsoft was promising those &lt;a href="https://learn.microsoft.com/en-us/windows/ai/apis/phi-silica"&gt;shiny new WinAppSDK APIs&lt;/a&gt; in May to interact with language models that'd be bundled with Windows -- With those, it'd cost essentially &lt;em&gt;no effort or money&lt;/em&gt; to integrate text/dialogue generation in the app.  &lt;/p&gt;
&lt;p&gt;So I would put the app on the store...&lt;br&gt;
Add in the AI integration as a &lt;strong&gt;5$ paid in-app-purchase&lt;/strong&gt;...&lt;br&gt;
&lt;strong&gt;Market the app&lt;/strong&gt; as being "&lt;em&gt;enhanced by the incredible power of AI!!1!1!&lt;/em&gt;"...  &lt;br&gt;
Get &lt;a href="https://blogs.windows.com/windowsdeveloper/2023/05/23/welcoming-ai-to-the-microsoft-store-on-windows/"&gt;featured on the store&lt;/a&gt; by Microsoft who's desperate to sell that shit to everyone who has eyes...  &lt;br&gt;
And &lt;strong&gt;rack in the money&lt;/strong&gt; from the room-temperature-IQ Elon Musk fans who think a glorified autocomplete is the future of technology.  &lt;/p&gt;
&lt;p&gt;The plan was, dare I say it, perfect; I just needed to wait for the APIs to become available... 
&lt;img alt="yes this meme format is outdated and i'm immediately exposing myself as a poser for misusing it with technical bullshit terms like &amp;quot;API&amp;quot; like I'm trying to write an out of touch marketing blogpost for a big tech company IT'S PART OF THE JOKE I SWEAR" src="images/dialogueforest/gru_df.jpg"&gt;&lt;br&gt;
I honestly thought that since this was yet another of Microsoft's famous &lt;em&gt;"refocus the entire OS on a single bit of tech&lt;/em&gt;" moments, this stuff would come out relatively early despite the fact Windows UI technology/WinAppSDK moves at a glacial pace...&lt;br&gt;
But nope, &lt;code&gt;Microsoft.Windows.AI.Generative&lt;/code&gt; is still unavailable more than 6 months later.  &lt;/p&gt;
&lt;p&gt;As a result, what's the current guidance from Microsoft if I want, as a Windows developer, to join the &lt;br&gt;
""""AI Revolution""""? Well, they made a whole app about it with examples, &lt;a href="https://aka.ms/ai-dev-gallery-blog"&gt;look how nice it is&lt;/a&gt;!&lt;br&gt;
&lt;img alt="huggingface is also a terrible platform btw" src="images/dialogueforest/aidevgallery.png"&gt;&lt;br&gt;
...And it basically boils down to "&lt;em&gt;Download some shit from huggingface, slap it into your app and run the model yourself on GPU -- want language processing? That'll be an extra 2 gigabytes thank you&lt;/em&gt;"&lt;br&gt;
It's laughable, nobody's realistically going to do this??&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt;    &lt;/p&gt;
&lt;p&gt;Even &lt;strong&gt;Apple&lt;/strong&gt;, who did the &lt;em&gt;least&lt;/em&gt; possible amount of compliance so that their shareholders wouldn't crash the stock by fear of missing the AI hype train, added &lt;a href="https://developer.apple.com/documentation/imageplayground"&gt;some tools&lt;/a&gt; for developers to use their built-in generative stuff.  &lt;/p&gt;
&lt;p&gt;It's shit, &lt;em&gt;but at least you can use it&lt;/em&gt;!&lt;br&gt;
Microsoft is one of the companies who went the hardest on dooming themselves with AI garbage,&lt;br&gt;
&lt;strong&gt;and EVEN THEN THEY CAN'T FUCKING DO IT RIGHT!&lt;/strong&gt;  &lt;/p&gt;
&lt;p&gt;&lt;br/&gt;
&lt;br/&gt;&lt;br&gt;
&lt;br/&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;... aight i got that out of my system we good now&lt;br&gt;
&lt;img alt="" src="images/dasitmane.jpg"&gt;&lt;br&gt;
I imagine most of the preinstalled-AI-on-Windows stuff was severely delayed by the Recall &lt;a href="https://doublepulsar.com/recall-stealing-everything-youve-ever-typed-or-viewed-on-your-own-windows-pc-is-now-possible-da3e12e9465e"&gt;trash fire&lt;/a&gt;, which is how we got to this silly situation.  &lt;/p&gt;
&lt;p&gt;I don't really blame&lt;sup id="ref-3"&gt;&lt;a href="#note-3"&gt;***&lt;/a&gt;&lt;/sup&gt; the Windows devs, this shit comes down from management and they were absolutely right to redirect their limited resources into &lt;a href="https://blogs.windows.com/windowsdeveloper/2024/09/04/whats-new-in-windows-app-sdk-1-6/"&gt;actually useful stuff&lt;/a&gt; instead of those APIs.  &lt;/p&gt;
&lt;p&gt;But well, as for my &lt;em&gt;devilish AI plan&lt;/em&gt;? The hype cycle is essentially dead by now. &lt;sub&gt;(if it was ever somewhat alive)&lt;/sub&gt;&lt;br&gt;
When you look at OpenAI trying to stir hype with their &lt;a href="https://www.theverge.com/24314146/openai-12-days-ship-mas-chatgpt-sora-o1-update"&gt;"12 days of shipmas"&lt;/a&gt; &lt;sub&gt;(terrible techbro ass name btw)&lt;/sub&gt; and delivering &lt;strong&gt;nothing of value&lt;/strong&gt;&lt;sup id="ref-4"&gt;&lt;a href="#note-4"&gt;#&lt;/a&gt;&lt;/sup&gt;, I don't see it picking back up.  &lt;/p&gt;
&lt;p&gt;There's absolutely zero incentive from me to bundle any form of AI to DialogueForest now, so I thought I might as well just &lt;a href="https://apps.microsoft.com/detail/9P7MWMG1V6M6?cid=tvc-16&amp;amp;mode=full"&gt;put it up on the Store&lt;/a&gt;. And with AOT, it's now faster, so it was worth the wait? &lt;em&gt;Kinda?&lt;/em&gt;  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; I actually haven't been using it that much recently... game writing takes time and I don't have it because I'm here ranting about how capitalistic boom/bust cycles are ruining the computer experience for everyone&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; I do think there's a lot of use for small bundled ML models (remember when we just called it ML? good times), but nobody is going to cram a bloody 2GB+ stable diffusion or llama model in a consumer app and call that good user experience, be realistic &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-3"&gt;&lt;a href="#ref-3"&gt;***&lt;/a&gt; You know what I &lt;strong&gt;do&lt;/strong&gt; blame them for? &lt;a href="https://github.com/microsoft/WindowsAppSDK/issues/1808"&gt;You still can't make an x86/ARM msixbundle in one click using Visual Studio with WinAppSDK/WinUI3&lt;/a&gt;, it's been fuckbusted for ages and there's no indication as to why it fails and you have to make separate per-architecture packages instead. &lt;a href="https://www.youtube.com/watch?v=IBelvYeF440"&gt;Phenomenal&lt;/a&gt; &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-4"&gt;&lt;a href="#ref-4"&gt;#&lt;/a&gt; Sora does not count fuck you, they closed signups 10 minutes after launching it because their servers blew up, textbook definition of a paper launch &lt;/sup&gt;  &lt;/p&gt;</content><category term="Software"></category><category term="dotnet"></category><category term="c#"></category><category term="gamedev"></category><category term="winui"></category><category term="windows"></category><category term="windows 11"></category><category term="winappsdk"></category><category term="llms"></category><category term="outliner"></category><category term="dialogueforest"></category></entry><entry><title>Controlling an IKEA LED strip with video capture from game consoles</title><link href="https://tvc-16.science/ha-led-dazzle.html" rel="alternate"></link><published>2024-09-21T00:00:00+02:00</published><updated>2024-09-21T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2024-09-21:/ha-led-dazzle.html</id><summary type="html">&lt;p&gt;Finally... Home Assistant gaming.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I have a nice LED strip lighting up my &lt;a href="./kallax-crt.html"&gt;gaming cabinet&lt;/a&gt;, but it's a bit clunky turning it on manually every time I'm gaming on the ole Wii.&lt;br&gt;
The strip I bought is an &lt;a href="https://www.ikea.com/gb/en/p/ormanaes-led-lighting-strip-smart-wireless-dimmable-colour-and-white-spectrum-90541329/"&gt;IKEA Ormanas&lt;/a&gt;, which is Zigbee compatible and can plug in just fine to a Home Assistant installation. It's even multicolor! &lt;br&gt;
&lt;img alt="Please don't look at my terrible led band routing i'm begging you" src="./images/kallax_crt.jpg"&gt;&lt;br&gt;
So surely, we can make this strip light up on its own when the consoles are in use, right?  &lt;/p&gt;
&lt;p&gt;The easiest way would obviously be a &lt;a href="https://community.home-assistant.io/t/what-are-the-recommended-energy-monitoring-smart-plugs-for-ha/589681"&gt;smart plug&lt;/a&gt; hooked up to the CRT that detects when it's drawing power...&lt;br&gt;
But I had an old &lt;a href="https://en.wikipedia.org/wiki/Dazzle_(video_recorder)"&gt;Dazzle DVC100&lt;/a&gt; capture card lying around, so why not try to use that instead?  &lt;/p&gt;
&lt;h1&gt;TV/Dazzle connection setup&lt;/h1&gt;
&lt;p&gt;The Dazzle itself just takes standard composite video and stereo audio and feeds it to a computer over USB 2.0, so it's easy to just plug it to a splitter with the other bit going to your TV.  &lt;/p&gt;
&lt;p&gt;I have to note here than plugging any sort of additional stuff to composite video can introduce significant &lt;strong&gt;interference and video noise&lt;/strong&gt; -- Personally I recently splurged on an &lt;a href="https://aliexpress.com/item/1005004428449908.html"&gt;automatic SCART switcher&lt;/a&gt;&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt; that isolates TV and capture outputs perfectly, but your mileage may vary.  &lt;/p&gt;
&lt;p&gt;As for the computer doing the capture - I have a Raspberry Pi close to the TV that I already use as a &lt;a href="https://github.com/badaix/snapcast/"&gt;snapcast&lt;/a&gt; client for music playback, so I just hooked the Dazzle to it. &lt;br&gt;
&lt;img alt="Perfectly cromulant capture from the dazzle" src="./images/hass/dazzle_capture.jpg"&gt;&lt;br&gt;
Configuring the DVC100 is substantially easier on Linux than it is on modern Windows - I just followed &lt;a href="https://github.com/danyfernandes/vhs-capture-pinnacle-linux"&gt;this guide&lt;/a&gt; to get perfectly cromulent captures out of the card.  &lt;/p&gt;
&lt;p&gt;The DVC100 just acts as a webcam once configured, so its video feed can be exposed to your network as a MJPEG stream by something like &lt;a href="https://github.com/pikvm/ustreamer"&gt;&lt;code&gt;ustreamer&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
I've slapped together this service file for that:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ustreamer Dazzle&lt;/span&gt;
&lt;span class="na"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;

&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;simple&lt;/span&gt;
&lt;span class="na"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;
&lt;span class="na"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/local/bin&lt;/span&gt;
&lt;span class="na"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/local/bin/ustreamer --host 0.0.0.0 -d /dev/video1 -r 720x576 -q 100 -a PAL -m RGB565 -p 8081&lt;/span&gt;

&lt;span class="k"&gt;[Install]&lt;/span&gt;
&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I use &lt;code&gt;/dev/video1&lt;/code&gt; here for the DVC100 since my Pi also has a normal webcam plugged in.&lt;br&gt;
Depending on what you capture, you might want to mention NTSC/480i instead of PAL/576i for the video standard&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt;.   &lt;/p&gt;
&lt;p&gt;Since &lt;code&gt;ustreamer&lt;/code&gt; outputs as a MJPEG stream, you can add your video feed to HA directly. It's technically not necessary... but it's fun! And I now have an easy way to take screenshots when I'm playing on the CRT.&lt;br&gt;
&lt;img alt="Did you know that DoujinSoft can send mail directly to your Wii in the current year? It's good shit" src="./images/hass/hass_gaming.png"&gt;  &lt;/p&gt;
&lt;h1&gt;The Home Assistant sauce&lt;/h1&gt;
&lt;p&gt;So now that we have a video feed in HA, surely it's easy to look at it and control the LED strip? Not quite 🫠  &lt;/p&gt;
&lt;p&gt;We can use the &lt;a href="https://www.home-assistant.io/integrations/color_extractor/"&gt;Color Extractor integration&lt;/a&gt; to pick the predominant color from the captured image and apply it to your lights - Except that this integration will always &lt;strong&gt;turn on&lt;/strong&gt; the light, even if the current feed from the capture card is a black image.  &lt;/p&gt;
&lt;p&gt;So we need an additional &lt;strong&gt;sensor&lt;/strong&gt; in HA to detect whether there's actual video coming from the capture card or not -- I struggled a bit for this before ending up with the perfectly dumb solution of &lt;em&gt;looking at the size of a screenshot from the video feed every second&lt;/em&gt;.  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nl"&gt;command_line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;sensor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;Size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CRT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Stream&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nl"&gt;scan_interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nl"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;curl -so /dev/null http://[ustreamer_server]:8081/snapshot -w &amp;#39;&amp;#39;%{size_download}&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Horribly inefficient? Yes! But it works.&lt;br&gt;
&lt;img alt="" src="./images/anything.jpg"&gt;  &lt;br&gt;
The output capture from the Dazzle when nothing is outputting to it is about 100KBs&lt;sup id="ref-3"&gt;&lt;a href="#note-3"&gt;***&lt;/a&gt;&lt;/sup&gt;, so I check for 150 to have some margin.  &lt;/p&gt;
&lt;p&gt;And that gives us our final script:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;alias&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Kallax&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LED&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Colorizer&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="k"&gt;sequence&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;condition&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;numeric_state&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nl"&gt;entity_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sensor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size_of_crt_stream_snapshot&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nl"&gt;above&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;150000&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;color_extractor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;turn_on&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nl"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nl"&gt;color_extract_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//[&lt;/span&gt;&lt;span class="n"&gt;ustreamer_server&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8081&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nl"&gt;brightness&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nl"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nl"&gt;entity_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ikea_of_sweden_ormanas_led_strip_light&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;turn_off&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nl"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nl"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nl"&gt;entity_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ikea_of_sweden_ormanas_led_strip_light&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="nl"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="nl"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;mdi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Notice that as mentioned previously, this doesn't use the video feed registered in HA at all and just hits the &lt;code&gt;ustreamer&lt;/code&gt; server directly. Which means you'll have to add its URL to &lt;code&gt;allowlist_external_urls&lt;/code&gt; in your Home Assistant &lt;a href="https://developers.home-assistant.io/docs/dev_101_config/"&gt;configuration&lt;/a&gt;.&lt;/p&gt;
&lt;video autoplay loop src="./images/hass/castlehue.mp4" title=""&gt;&lt;/video&gt;

&lt;p&gt;One could argue that it'd use less electricity to just turn on the LED strip all the time than to do all of this...&lt;br&gt;
But would you get the Philips Hue™️ experience that way? Certainly not.  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; I'm aware of the irony of &lt;em&gt;"doesn't want to spend money on a smart plug but will dunk 100€ in a switcher so he doesn't have to push buttons manually to swap outputs"&lt;/em&gt;&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; My SEGA Saturn is a Japanese model, so capturing it on PAL mode gives me a squished grayscale image - which is more than enough just to drive LED lights. &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-3"&gt;&lt;a href="#ref-3"&gt;***&lt;/a&gt; If you have an image on screen that is pure white it actually can be lower than that due to how jpg compression works - So sometimes when using the Wii Menu which is predominantly white, my LEDs will turn off...&lt;br&gt;Writing a customized version of ColorExtractor would probably be a bit better than this. I found &lt;a href="https://github.com/xplus2/homeassistant-ambient-extractor"&gt;one&lt;/a&gt;, but it didn't work for me.&lt;/sup&gt;  &lt;/p&gt;</content><category term="Cool Tricks"></category><category term="home assistant"></category><category term="zigbee"></category><category term="ikea"></category><category term="led"></category><category term="dazzle"></category><category term="dvc100"></category><category term="video capture"></category><category term="gaming"></category><category term="jankfest philips hue facsimile"></category></entry><entry><title>The Stylophone 2.7 Update is here!</title><link href="https://tvc-16.science/stylophone-27.html" rel="alternate"></link><published>2024-09-09T00:00:00+02:00</published><updated>2024-09-09T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2024-09-09:/stylophone-27.html</id><summary type="html">&lt;p&gt;isn't it kinda stupid to make the free version the one on the platform that users tend to pay for the most?&lt;/p&gt;</summary><content type="html">&lt;p&gt;Another &lt;a href="./stylophone-26.html"&gt;year&lt;/a&gt;, another Stylophone update!&lt;br&gt;
This is a smaller update this year, but I still have a few niceties in store -- Most notably, &lt;/p&gt;
&lt;h1&gt;Mac support!&lt;/h1&gt;
&lt;p&gt;&lt;img alt="Stylophone 2.7 on Sonoma" src="https://tvc-16.science/images/stylophone/v27-catalyst.jpg"&gt;&lt;br&gt;
As mentioned last time, the Mac Catalyst version finally works, so I took some time to "polish" it up and make it shippable.  &lt;/p&gt;
&lt;p&gt;...I say "polish" because at its heart, it's really just the iOS version thrown in Catalyst?&lt;br&gt;
There are no large Mac-only interface changes here, I'm afraid. &lt;/p&gt;
&lt;h3&gt;It's just too much gat dang work!&lt;/h3&gt;
&lt;p&gt;To make up for that though, I've decided to make the macOS version entirely 🫰&lt;strong&gt;free&lt;/strong&gt;🫰 - No need to compile it yourself or to get it from an App Store, it's just there for the taking in the &lt;a href="https://github.com/Difegue/Stylophone/releases/tag/2.7.0"&gt;GitHub releases&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;I hope you have fun with it! It's signed and notarized so it should just work™️.    &lt;/p&gt;
&lt;h1&gt;The other things&lt;/h1&gt;
&lt;p&gt;For all platforms, I've mostly gone through some user asks in this release, so:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can now enable/disable specific MPD outputs through Settings&lt;/li&gt;
&lt;li&gt;You can now specify the port of the httpd stream for Local Playback  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That's about it. I didn't really find anything interesting&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt; in iOS17 all things considered, so the app version still just requires iOS16.  &lt;/p&gt;
&lt;p&gt;But for Windows, there's a &lt;em&gt;small bonus&lt;/em&gt;!&lt;br&gt;
You can now use the &lt;code&gt;stylophone://&lt;/code&gt; protocol on your PC to launch and control the app. See &lt;a href="https://github.com/Difegue/Stylophone#protocol-usage-windows-only"&gt;here&lt;/a&gt; for details.  &lt;/p&gt;
&lt;p&gt;This actually comes from another feature I was experimenting on; &lt;strong&gt;Widget support&lt;/strong&gt;.&lt;br&gt;
Widgets are normally a WinUI thing and &lt;a href="https://nicksnettravels.builttoroam.com/uwp-widget/"&gt;kinda suck to support on UWP&lt;/a&gt;, so I started architecting a two-app solution that could potentially become its own product...  &lt;/p&gt;
&lt;p&gt;And it worked! Except the Windows Widget pane actually &lt;a href="https://kolektiva.social/@Difegue/113076064804645110"&gt;still kinda sucks&lt;/a&gt; even with the DMA changes.&lt;br&gt;
&lt;img alt="dankpods voice: can you believe no one uses this" src="https://tvc-16.science/images/stylophone/v27-widget.jpg"&gt;&lt;br&gt;
It's all nice and good to base your widgets on &lt;a href="https://learn.microsoft.com/en-us/windows/apps/design/widgets/widgets-create-a-template"&gt;Adaptive Cards&lt;/a&gt; - except as a result you have no support for font icons, light/dark theming sucks, and all your resources need to be URLs&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt;.  &lt;/p&gt;
&lt;p&gt;I don't think it's really worth doing anything with the Windows Widget board in its current state unless you're on a Microsoft payroll - Which is probably why no devs outside of Spotify have done so.  &lt;/p&gt;
&lt;p&gt;Maybe in a few years when the AI hype weans off and we start getting actual OS features again? I think Widgets could be powerful if they could be perma-anchored to the desktop like macOS does.  &lt;/p&gt;
&lt;p&gt;The well might be poisoned already though, since now everyone associates Widgets with the dogshit News features the MSN team crammed into the OS before fucking off to do Bing chat and Bing chat accessories.   &lt;/p&gt;
&lt;p&gt;In the meantime, I've made the now playing indicator a squiggly line. everyone loves squiggly lines cmon&lt;br&gt;
&lt;img alt="hell yeah" src="https://tvc-16.science/images/stylophone/v27-squiggle.png"&gt;  &lt;/p&gt;
&lt;h3&gt;As usual, the apps can be downloaded from both the &lt;a href="https://www.microsoft.com/store/apps/9NCB693428T8?cid=storebadge&amp;amp;ocid=badge"&gt;Microsoft Store&lt;/a&gt; and the &lt;a href="https://apps.apple.com/us/app/stylophone/id1644672889?itsct=apps_box_link&amp;amp;itscg=30200"&gt;App Store.&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I hope you enjoy the updates! Free for existing users as always. Here's the full changelog:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;##&lt;/span&gt; &lt;span class="k"&gt;All&lt;/span&gt; &lt;span class="n"&gt;platforms&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;support&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;listing&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;enabling&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;disabling&lt;/span&gt; &lt;span class="n"&gt;MPD&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="n"&gt;outputs&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;httpd&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;Local&lt;/span&gt; &lt;span class="n"&gt;Playback&lt;/span&gt; &lt;span class="n"&gt;can&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="n"&gt;configured&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Fix&lt;/span&gt; &lt;span class="k"&gt;some&lt;/span&gt; &lt;span class="n"&gt;crashes&lt;/span&gt; &lt;span class="n"&gt;that&lt;/span&gt; &lt;span class="n"&gt;could&lt;/span&gt; &lt;span class="n"&gt;happen&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;selecting&lt;/span&gt; &lt;span class="n"&gt;an&lt;/span&gt; &lt;span class="n"&gt;album&lt;/span&gt; &lt;span class="n"&gt;very&lt;/span&gt; &lt;span class="n"&gt;quickly&lt;/span&gt; &lt;span class="k"&gt;after&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="k"&gt;search&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;98&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;##&lt;/span&gt; &lt;span class="n"&gt;macOS&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;I&lt;/span&gt; &lt;span class="n"&gt;am&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="n"&gt;offering&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;Mac&lt;/span&gt; &lt;span class="n"&gt;Catalyst&lt;/span&gt; &lt;span class="k"&gt;version&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;Stylophone&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;freebie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Feel&lt;/span&gt; &lt;span class="k"&gt;free&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;download&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="n"&gt;below&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;This&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ss"&gt;&amp;quot;bare minimum&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;iOS&lt;/span&gt; &lt;span class="k"&gt;version&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;Mac&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Don&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;t expect much, but it&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;notarized&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;works&lt;/span&gt; &lt;span class="n"&gt;just&lt;/span&gt; &lt;span class="n"&gt;fine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;  

&lt;span class="o"&gt;##&lt;/span&gt; &lt;span class="n"&gt;iOS&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;Local&lt;/span&gt; &lt;span class="n"&gt;Playback&lt;/span&gt; &lt;span class="n"&gt;should&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="n"&gt;resume&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;going&lt;/span&gt; &lt;span class="n"&gt;back&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="k"&gt;after&lt;/span&gt; &lt;span class="n"&gt;an&lt;/span&gt; &lt;span class="n"&gt;audio&lt;/span&gt; &lt;span class="n"&gt;interruption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;96&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;##&lt;/span&gt; &lt;span class="n"&gt;UWP&lt;/span&gt; 
&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Stylophone&lt;/span&gt; &lt;span class="n"&gt;can&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="n"&gt;launched&lt;/span&gt; &lt;span class="n"&gt;through&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;stylophone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;This&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt; &lt;span class="n"&gt;also&lt;/span&gt; &lt;span class="n"&gt;makes&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="n"&gt;so&lt;/span&gt; &lt;span class="n"&gt;you&lt;/span&gt; &lt;span class="n"&gt;can&lt;/span&gt; &lt;span class="n"&gt;control&lt;/span&gt; &lt;span class="k"&gt;some&lt;/span&gt; &lt;span class="n"&gt;features&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="n"&gt;through&lt;/span&gt; &lt;span class="n"&gt;protocol&lt;/span&gt; &lt;span class="n"&gt;invocations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;See&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Difegue&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Stylophone&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="n"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;usage&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;windows&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;only&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;more&lt;/span&gt; &lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;  
&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Updated&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;playing&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Queue&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="n"&gt;marquee&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;clearer&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="n"&gt;playing&lt;/span&gt; &lt;span class="n"&gt;indicator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; 

&lt;span class="n"&gt;See&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="k"&gt;full&lt;/span&gt; &lt;span class="n"&gt;changelog&lt;/span&gt; &lt;span class="n"&gt;here&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Difegue&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Stylophone&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;compare&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h1&gt;Closing thoughts&lt;/h1&gt;
&lt;p&gt;&lt;img alt="good ol' vsmac.." src="https://tvc-16.science/images/stylophone/vsmac.png"&gt;&lt;br&gt;
Visual Studio for Mac is dead and buried as of a few days ago, but I still used it to get the iOS update out...&lt;br&gt;
Since it mostly relies on &lt;code&gt;dotnet&lt;/code&gt; these days, there's no problem using it with the latest &lt;a href="https://dotnet.microsoft.com/en-us/download/dotnet/8.0"&gt;.NET SDK&lt;/a&gt; which works just fine for buildin', debuggin', and gettin' your &lt;code&gt;.ipa&lt;/code&gt;s out.  &lt;/p&gt;
&lt;p&gt;The bitrot will probably get to it next year, but for now? I guess I can enjoy the last Microsoft IDE that doesn't have a bloody chatbot&lt;sup id="ref-3"&gt;&lt;a href="#note-3"&gt;***&lt;/a&gt;&lt;/sup&gt; built into it.  &lt;/p&gt;
&lt;p&gt;With the new action button™️ on iPhones now, I should add Shortcuts to the iOS app at some point...  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; I mentioned &lt;a href="https://bendodson.com/weblog/2023/07/26/tipkit-tutorial/"&gt;TipKit&lt;/a&gt; last time, but turns out that doesn't have any ObjC bindings so it can't be used from Xamarin.. NET9 (finally) has Swift interop support now though, so maybe next year?&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; Taking apart the built-in widgets shows there is &lt;strong&gt;some&lt;/strong&gt; support for reading image resources from the full app package? But it's completely undocumented so I had no idea where to start. &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-3"&gt;&lt;a href="#ref-3"&gt;***&lt;/a&gt; I actually like base copilot for code completion not gonna lie, but the chatbot thing? meh. Where the fuck is VS2024 already? The redesign has been in preview for years at this point &lt;/sup&gt;  &lt;/p&gt;</content><category term="Software"></category><category term="mpd"></category><category term="xamarin"></category><category term="dotnet"></category><category term="c#"></category><category term="uwp"></category><category term="windows"></category><category term="xbox"></category><category term="ios"></category><category term="macos"></category><category term="catalyst"></category><category term="app store"></category><category term="music"></category><category term="client"></category><category term="widgets"></category><category term="stylophone"></category></entry><entry><title>Summer videogame clears and mini-reviews</title><link href="https://tvc-16.science/games-summer-2024.html" rel="alternate"></link><published>2024-08-11T00:00:00+02:00</published><updated>2024-08-11T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2024-08-11:/games-summer-2024.html</id><summary type="html">&lt;p&gt;brat summer? more like yakuza bird character action summer&lt;/p&gt;</summary><content type="html">&lt;p&gt;It's Summer! &lt;em&gt;I hate Summer!&lt;/em&gt; But it's always good to get some hits off the videogame backlog.&lt;br&gt;
I played some other games like &lt;a href="https://kokoscript.itch.io/wordhopper"&gt;Wordhopper&lt;/a&gt;, &lt;a href="https://warrrkus.itch.io/chalicebound"&gt;Chalicebound&lt;/a&gt; and &lt;a href="https://www.youtube.com/watch?v=fRikL6vpeto"&gt;Cleopatra Fortune&lt;/a&gt; as well, but this post is long enough already and I don't have as much to say about those - Do check them out though!&lt;/p&gt;
&lt;h1&gt;Yakuza 3&lt;/h1&gt;
&lt;p&gt;Going back to "old" Yakuza after Kiwami 2 actually changes gameplay so much, things become fresh again!&lt;br&gt;
The Okinawa map is also a treat to run through, and this is probably the &lt;a href="https://www.youtube.com/watch?v=vl6HrS_zc4M"&gt;best fishing minigame&lt;/a&gt; in a Yakuza thus far.&lt;br&gt;
&lt;img alt="man" src="./images/games/yak3.jpg"&gt;&lt;br&gt;
People who say this game's combat is a bore have probably never played a fighting game before since the solution to enemies blocking is the same here - Just grab them. It's that easy.  &lt;/p&gt;
&lt;p&gt;I want to finish playing through the series before my Game pass sub lapses at the end of the year, but it's hard to avoid franchise fatigue with those games so I haven't started 4 yet...  &lt;/p&gt;
&lt;h1&gt;Psychonauts 2&lt;/h1&gt;
&lt;p&gt;&lt;img alt="" src="./images/games/psy2.jpg"&gt;&lt;br&gt;
This game has a fairly slow start, but I found it very enjoyable when it actually opens up and lets you explore around.&lt;br&gt;
Very solid setpieces all around, it's rare these days to get a 3D platformer I would consider "AAA" that isn't Mario&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt;.&lt;br&gt;
Schafer's still fucking got it when it comes to writing too!&lt;/p&gt;
&lt;p&gt;I hope Double Fine doesn't get swallowed by the Microsoft behemoth and can keep making games like this. Speaking of...  &lt;/p&gt;
&lt;h1&gt;Hi-Fi Rush&lt;/h1&gt;
&lt;p&gt;I can't believe you would close a game studio to get your accounting books green right after they'd release this &lt;strong&gt;banger&lt;/strong&gt; of a game.&lt;br&gt;
This is a crazy good character action game for what was basically Tango Gameworks' first shot at it - Microsoft had their Bayonetta and they just..threw it away like that&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt;.&lt;br&gt;
&lt;img alt="accurate recreation of a microsoft executive meeting" src="./images/games/hifi.jpg"&gt;&lt;br&gt;
The emphasis on the beat system makes the game stand out while not veering into full-on rhythm game territory -- Although I certainly did enjoy the occasional Space Channel 5-esque QTE battles.  &lt;/p&gt;
&lt;p&gt;I'd recommend pirating this game, the studio's dead anyway and Microsoft doesn't deserve your money.  &lt;/p&gt;
&lt;p&gt;It's surprising how close the &lt;a href="https://www.youtube.com/watch?v=zYJg_CCkYq8"&gt;"streamer soundtrack"&lt;/a&gt; gets to the licensed music used for some of the bosses - It's a good move considering the license rights will probably expire in 8 or so years and the original tracks will have to be pulled out.  &lt;/p&gt;
&lt;h1&gt;Burning Rangers&lt;/h1&gt;
&lt;p&gt;The lesser-known Sonic Team Saturn game. I'd played this before but couldn't get past the final level -- Not anymore!  &lt;/p&gt;
&lt;p&gt;&lt;img alt="Please don't look at my terrible led band routing i'm begging you" src="./images/kallax_crt.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;I like the arcade gameplay in this better than NiGHTS, but the game really stretches the Saturn to its limits and has insanely aggressive culling as a result - Essential chunks of geometry will disappear as soon as they're about to leave the camera frame and it can make navigation difficult!  &lt;/p&gt;
&lt;p&gt;The setting of the game is super cool and it's a shame Sega never did anything else with it -- I can't shake the feeling that Sonic Team watched &lt;a href="https://en.wikipedia.org/wiki/Memories_(1995_film)"&gt;Magnetic Rose&lt;/a&gt; and thought "&lt;em&gt;ayo what if we mixed it with Sentai?&lt;/em&gt;".  &lt;/p&gt;
&lt;p&gt;This game's soundtrack has the killer combo of Naofumi Hataya(Sonic CD) and Fumie Kumatani(&lt;sub&gt;all the best sonic adventure songs don't @ me&lt;/sub&gt;) like with NiGHTS and it's &lt;a href="https://www.youtube.com/watch?v=1pSk2EVCxiQ&amp;amp;t=1346s"&gt;really cool!&lt;/a&gt; Kinda like NiGHTS' dynamic music, the boss themes have multiple phases depending on their health and they flow in a really neat way. &lt;/p&gt;
&lt;p&gt;You should check out this ongoing &lt;a href="https://sonicfangameshq.com/forums/showcase/burning-rangers-tribute.1048/"&gt;Unity remake fanproject&lt;/a&gt;.  &lt;/p&gt;
&lt;h1&gt;BLiNX - The Time Sweeper&lt;/h1&gt;
&lt;p&gt;Another notch on my target list of playing all the Sonic Team-adjacent video games.  &lt;/p&gt;
&lt;p&gt;I was aware this game gets surprisingly punishing for what was xbox's own mascot platformer at the time, but holy &lt;strong&gt;fuck&lt;/strong&gt; was I not ready - BLiNX gets &lt;strong&gt;merciless&lt;/strong&gt; by the time you're at the end.&lt;br&gt;
&lt;img alt="not even close to one of the worst parts not sure why i screenshotted this" src="./images/games/blinx2.jpg"&gt;&lt;br&gt;
&lt;em&gt;"why yes I would like a strict time limit, resource and ammo management in my cartoon 3D platformer"&lt;/em&gt; - words spoken by the utterly deranged  &lt;/p&gt;
&lt;p&gt;I will say that all the difficulty does make the game engaging despite the bullshit - It's quite satisying when you finally manage to nail everything correctly in a level.&lt;br&gt;
&lt;em&gt;Massive protip if you get to the end&lt;/em&gt; -- The boss rush is poorly programmed, so you can just hit &lt;strong&gt;start-&amp;gt;retry&lt;/strong&gt; on any individual boss without redoing the entire rush. Thank u naoto bigisland ohshima and god bless  &lt;/p&gt;
&lt;p&gt;ex-ST composers Mariko Nanba(Chaotix, Sonic 06's Kingdom Valley) and Hataya worked on this game's soundtrack, &lt;a href="https://www.youtube.com/watch?v=wxVpeE55y2g"&gt;and it shows.&lt;/a&gt;&lt;br&gt;
Similarly to BR, the final boss theme has health-dependent phases, except unlike the 3 or so phases of a BR boss, this one has like &lt;a href="https://www.youtube.com/watch?v=DF_7eSCR5lQ"&gt;11??&lt;/a&gt; it's insane&lt;/p&gt;
&lt;h1&gt;Perfect Dark Zero&lt;/h1&gt;
&lt;p&gt;I was excited to play this because hell, it's an early 2000s cyberpunk Xbox shooter!&lt;br&gt;
Give me those &lt;em&gt;glossy textures&lt;/em&gt; and &lt;em&gt;moody environments&lt;/em&gt;, look at this shit this is straight up Deus Ex Infinite War-HD-texture-pack-core:&lt;br&gt;
&lt;img alt="hell yes, hell fucking yes" src="./images/games/pdz.jpg"&gt;&lt;br&gt;
The UI is sponsored by Samsung! The second level is in a fucking nightclub! The &lt;a href="https://www.youtube.com/watch?v=G-H0J-D7lOY"&gt;vibes are immaculate!&lt;/a&gt;  &lt;/p&gt;
&lt;p&gt;Then it falls apart because the locales become boring, the level design kinda sucks, the gunplay is barebones and the stealth aspects barely work, on top of Rare's mission-based FPS design which i've never been a huge fan of.&lt;br&gt;
The Xbox Series' auto-HDR for 360 backwards compatible titles does make the game pretty though.  &lt;/p&gt;
&lt;h1&gt;Thirsty Suitors&lt;/h1&gt;
&lt;p&gt;"This is just Scott Pilgrim but with more gay in it" is a pretty apt description of this game... but I wouldn't consider that a negative.&lt;br&gt;
The characters and story in this are more endearing than I was expecting, and it's short enough that I actually wouldn't have minded a bit more. Jala is a &lt;strong&gt;massive girlfailure&lt;/strong&gt; and it's really fun to roll along for the self-improvement ride.  &lt;br&gt;
&lt;img alt="we have reached a point in time where eva references are boomer clapbait and i am the boomer" src="./images/games/thirst.jpg"&gt;&lt;br&gt;
The RPG gameplay does the SMT thing a bit where hitting enemy weaknesses gives you press turn advantage, but the game is nowhere near difficult enough for this to really come into play&lt;sup id="ref-3"&gt;&lt;a href="#note-3"&gt;***&lt;/a&gt;&lt;/sup&gt;.  &lt;/p&gt;
&lt;p&gt;I wish the skateboarding was expanded upon more - It's no Jet Set Radio but it couldve gotten pretty close with more, longer levels. As it is there's no real point to it except screwing around for an hour or so. Budget issues probably..  &lt;/p&gt;
&lt;p&gt;Soundtrack is &lt;a href="https://www.youtube.com/watch?v=xA2X8vba8qc"&gt;quite good&lt;/a&gt;, although &lt;a href="https://youtu.be/WGRuIIxCqOE?t=160"&gt;Diya's theme&lt;/a&gt; pretty much clears all the other songs imo.  &lt;/p&gt;
&lt;p&gt;Tyler is literally trans girl Shadow the Hedgehog and I will not elaborate.&lt;sup id="ref-4"&gt;&lt;a href="#note-4"&gt;#&lt;/a&gt;&lt;/sup&gt;  &lt;/p&gt;
&lt;h1&gt;A Short Hike&lt;/h1&gt;
&lt;p&gt;This is just an isometric 3D version of &lt;a href="./games-spring.html"&gt;Lil' Gator Game&lt;/a&gt; I played recently - I can see why people would complain about "comfy" games flooding the market, but I had a nice bit of fun with that one still.&lt;br&gt;
&lt;img alt="sounds rad" src="./images/games/hike3.png"&gt;&lt;br&gt;
Since you're a bird in this you can glide out of the box, and the main means of progression is stamina powerups that also allow you to multi-jump.&lt;br&gt;
Aerial mobility gets quite fun towards the end as a result and I got some NiGHTS vibes out of the ending, which is always nice. (I feel like I'm mentioning NiGHTS a lot this time around for some reason...)  &lt;/p&gt;
&lt;p&gt;I like that the internal resolution is a setting, allowing you to go from N64 chunky ass jaggies™️ to smooth modern indie game lowpoly.  &lt;/p&gt;
&lt;h1&gt;An Outcry&lt;/h1&gt;
&lt;p&gt;Wrapping up with some &lt;code&gt;certified RPG Maker 2003 horrorkino&lt;/code&gt; -- This game doesn't go for any cheap tricks when it comes to scaring you, opting to rely on the atmosphere and (pretty damn good) writing instead.  &lt;/p&gt;
&lt;p&gt;The gameplay toys a bit with genre conventions(which you might or might not expect considering this is RPG Maker), but there's nothing obtuse in getting most of the endings here; You should probably save often though.&lt;br&gt;
&lt;img alt="Vienna citybelt screenshot from an outcry" src="./images/games/outo.png"&gt;&lt;br&gt;
The Vienna citybelt is a mostly optional spot in the game but contains a ton of storytelling I really recommend clicking through - The election poster is one of my favorites despite how simple it is.  &lt;/p&gt;
&lt;p&gt;The game is about a flock of magic birds in Austria which kills everyone who isn't bird enough - It's (very) obvious what the birds are a metaphor for, and despite the game being two years old it still feels very relevant to &lt;em&gt;current world events.&lt;/em&gt;  &lt;/p&gt;
&lt;p&gt;Very recommended, even if you're not too attuned to LGBT/queer terms - The birds come for everyone anyway.  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; And even Mario is fairly rare since he only gets a full 3D platformer once per gen..&lt;/sup&gt; &lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; I'm aware DMC-style games are never going to sell as well as a CoD, but I don't think you can keep surviving as a console manufacturer if all you want are AAA million-sellers.&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-3"&gt;&lt;a href="#ref-3"&gt;***&lt;/a&gt; Also you don't really have any weaknesses on your own and can deflect most status ailments through QTEs smh&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-4"&gt;&lt;a href="#ref-4"&gt;#&lt;/a&gt; In this blogpost anyway. &lt;/sup&gt;  &lt;/p&gt;</content><category term="Blogposting"></category><category term="video games"></category><category term="game pass"></category><category term="yakuza"></category><category term="psychonauts 2"></category><category term="burning rangers"></category><category term="sonic team"></category><category term="blinx"></category><category term="perfect dark zero"></category><category term="thirsty suitors"></category><category term="an outcry"></category><category term="soundtracks"></category></entry><entry><title>Using UWP Update Tasks in a MSIX-packaged Win32 app</title><link href="https://tvc-16.science/netfx-updatetask.html" rel="alternate"></link><published>2024-06-07T00:00:00+02:00</published><updated>2024-06-07T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2024-06-07:/netfx-updatetask.html</id><summary type="html">&lt;p&gt;it's friday night and i'm deep in msix hell again&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Update Tasks&lt;/strong&gt; are a feature of MSIX that allows Windows to &lt;a href="https://learn.microsoft.com/en-us/windows/uwp/launch-resume/run-a-background-task-during-updatetask"&gt;run a piece of code when your package is updated&lt;/a&gt;, without needing to launch the entire app.&lt;br&gt;
This is a feature that heralds from the UWP era, where it's fairly easy to use since you're fully in WinRT land already.. But if you're packaging a regular Win32 app instead, or the newer &lt;a href="https://github.com/microsoft/WindowsAppSDK/discussions/2314"&gt;Windows App SDK&lt;/a&gt;, it gets &lt;em&gt;tricky&lt;/em&gt;.  &lt;/p&gt;
&lt;p&gt;And by tricky I mean &lt;em&gt;bullshit and barely documented&lt;/em&gt;, just like &lt;a href="./netfx-islands.html"&gt;last time&lt;/a&gt;!&lt;br&gt;
Here's a walkthrough of how you can add an Update Task to any kind of MSIX package.  &lt;/p&gt;
&lt;h1&gt;1. Create the background task class&lt;/h1&gt;
&lt;p&gt;Since this feature relies on UWP's Background Tasks, your code needs to run in a &lt;a href="https://learn.microsoft.com/en-us/uwp/winrt-cref/winmd-files"&gt;Windows Runtime component&lt;/a&gt;, aka a &lt;code&gt;.winmd&lt;/code&gt;.&lt;br&gt;
There are actually no differences here compared to the &lt;a href="https://learn.microsoft.com/en-us/windows/uwp/launch-resume/run-a-background-task-during-updatetask#step-1-create-the-background-task-class"&gt;UWP implementation&lt;/a&gt;; You can just create a Windows Runtime component project in VS and host your code in it, implementing &lt;code&gt;IBackgroundTask&lt;/code&gt;.  &lt;/p&gt;
&lt;p&gt;You &lt;em&gt;can&lt;/em&gt; technically create a C# Class Library instead and &lt;a href="https://learn.microsoft.com/en-us/windows/apps/develop/platform/csharp-winrt/authoring"&gt;use CsWinRT to author the component&lt;/a&gt; if you really need NET6 instead of the old UWP-flavored NET Framework&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt;.  &lt;/p&gt;
&lt;p&gt;Once you've built your component, include your &lt;code&gt;.winmd&lt;/code&gt; in your packaged files.  &lt;/p&gt;
&lt;p&gt;🛑 Make sure it has the same name as the &lt;strong&gt;namespace&lt;/strong&gt; of your UpdateTask! WinRT relies on naming to associate the winmd with the classes it contains. (&lt;code&gt;BackgroundTasks.winmd&lt;/code&gt; in this example.)  &lt;/p&gt;
&lt;h1&gt;2. Declare the UpdateTask in the MSIX manifest&lt;/h1&gt;
&lt;p&gt;This is where things get confusing -- With UWP, you just need to reference your freshly-authored component in the main app and add one line to the &lt;code&gt;appxmanifest&lt;/code&gt;:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Application&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;YourWin32App&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  [...]
  &lt;span class="nt"&gt;&amp;lt;Extensions&amp;gt;&lt;/span&gt;  
    &lt;span class="nt"&gt;&amp;lt;Extension&lt;/span&gt; &lt;span class="na"&gt;Category=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;windows.updateTask&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;EntryPoint=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;BackgroundTasks.UpdateTask&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;  
    &lt;span class="nt"&gt;&amp;lt;/Extension&amp;gt;&lt;/span&gt;  
  &lt;span class="nt"&gt;&amp;lt;/Extensions&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Application&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;But if you're packaging a Win32 app instead, said app has no knowledge of WinRT and won't be able to expose your &lt;code&gt;.winmd&lt;/code&gt;'s entrypoint on its own.  &lt;/p&gt;
&lt;p&gt;Most of the documentation&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt; about using UpdateTasks with Win32 apps comes from 2017 back when MSIX was still being called &lt;a href="https://stefanwick.com/2017/06/06/updatetask-for-desktop-bridge-apps/"&gt;Desktop Bridge&lt;/a&gt;, but basically the solution is to declare an &lt;strong&gt;in-process WinRT server&lt;/strong&gt; that'll get started by Windows and run your winmd.  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Windows Runtime (WinRT) supports the concept of In Process Servers, which allows for using objects that are in a different dll/winmd with super-fast performance and easy-to-use ABI.&lt;br&gt;
&lt;sub&gt;(https://github.com/hez2010/WinRTServer)&lt;/sub&gt;  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Package&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Extensions&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;Extension&lt;/span&gt; &lt;span class="na"&gt;Category=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;windows.activatableClass.inProcessServer&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;InProcessServer&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Path&amp;gt;&lt;/span&gt;Your_Inproc_Server.exe&lt;span class="nt"&gt;&amp;lt;/Path&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ActivatableClass&lt;/span&gt; &lt;span class="na"&gt;ActivatableClassId=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;BackgroundTasks.UpdateTask&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;ThreadingModel=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;both&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/InProcessServer&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Extension&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Extensions&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Package&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;sub&gt;notice how this is under Package-&amp;gt;Extensions, not Package-&amp;gt;Applications-&amp;gt;Application-&amp;gt;Extensions like the previous one&lt;/sub&gt;  &lt;/p&gt;
&lt;p&gt;And that's it! Install an update to your MSIX and the component will run -- You don't even need to modify your Win32 app.  &lt;/p&gt;
&lt;h2&gt;🥳🥳🥳&lt;/h2&gt;
&lt;p&gt;But I hear you ask, "&lt;em&gt;how do I build an inprocess WinRT server&lt;/em&gt;"?&lt;br&gt;
&lt;img alt="fuck" src="./images/windows.jpg"&gt;  &lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;I don't know!&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Thankfully Microsoft provides those to you, if you know where to look.  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If your &lt;code&gt;.winmd&lt;/code&gt; contains &lt;strong&gt;managed code&lt;/strong&gt; (compiled .NET/MSIL), you can either use CsWinRT's &lt;a href="https://github.com/microsoft/CsWinRT/blob/master/docs/hosting.md"&gt;WinRT.Host.dll&lt;/a&gt;, or the built-in &lt;a href="https://strontic.github.io/xcyclopedia/library/clrhost.dll-5E23559AAC2A0FE3E5E35FC1124CC73D.html"&gt;CLRHost.dll&lt;/a&gt;&lt;sup id="ref-3"&gt;&lt;a href="#note-3"&gt;***&lt;/a&gt;&lt;/sup&gt;.  &lt;ul&gt;
&lt;li&gt;I think &lt;code&gt;CLRHost.dll&lt;/code&gt; is more practical to use since it's in &lt;code&gt;System32&lt;/code&gt; and &lt;strong&gt;just werks™️&lt;/strong&gt;, whereas you'll have to embed &lt;code&gt;WinRT.Host.dll&lt;/code&gt; in your package.    &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;InProcessServer&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Path&amp;gt;&lt;/span&gt;[WinRT.Host.dll or CLRHost.dll]&lt;span class="nt"&gt;&amp;lt;/Path&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ActivatableClass&lt;/span&gt; &lt;span class="na"&gt;ActivatableClassId=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;BackgroundTasks.UpdateTask&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;ThreadingModel=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;both&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/InProcessServer&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;If your &lt;code&gt;.winmd&lt;/code&gt; contains &lt;strong&gt;native code&lt;/strong&gt; because you &lt;a href="https://stackoverflow.com/questions/38183146/universal-windows-net-native-and-winmd-component-libraries"&gt;passed it through NET Native&lt;/a&gt;? &lt;strong&gt;&lt;em&gt;I have no fucking idea.&lt;/em&gt;&lt;/strong&gt;  &lt;ul&gt;
&lt;li&gt;The logic seems to be that you need to reference the native code in your app dll &lt;a href="https://blogs.windows.com/windowsdeveloper/2017/07/06/calling-winrt-components-win32-process-via-desktop-bridge/"&gt;and use that&lt;/a&gt; as the inProcServer.  &lt;/li&gt;
&lt;li&gt;I'd recommend just not using .NET Native for the WinRT component project tbh it's going to have like 5 lines of code who cares  &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;3. Bonus round: Escape the AppContainer sandbox in your UpdateTask&lt;/h1&gt;
&lt;p&gt;Since your UpdateTask is a WinRT component, it'll run under UWP rules/AppContainer, even if you've wrapped your MSIX package with &lt;code&gt;runFullTrust&lt;/code&gt;.  &lt;/p&gt;
&lt;p&gt;This can be annoying if you need to run code that's not available under the Universal Windows APIs, or if you need to read/write files outside of the sandbox.  &lt;/p&gt;
&lt;p&gt;The only solution available to you is to make use of &lt;a href="https://learn.microsoft.com/en-us/uwp/api/windows.applicationmodel.fulltrustprocesslauncher?view=winrt-22621"&gt;FullTrustProcessLauncher&lt;/a&gt;, so you can invoke your Win32 app (&lt;a href="https://stefanwick.com/2018/04/06/uwp-with-desktop-extension-part-2/"&gt;or any other exe&lt;/a&gt;) from the WinRT component.  &lt;/p&gt;
&lt;p&gt;This can look slightly schizophrenic if you're using your main app as the FullTrustProcess, since it'll look like it's registering itself:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Applications&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Application&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;App&amp;quot;&lt;/span&gt;
    &lt;span class="na"&gt;Executable=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;MyWin32App.exe&amp;quot;&lt;/span&gt;
    &lt;span class="na"&gt;EntryPoint=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Windows.FullTrustApplication&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Extensions&amp;gt;&lt;/span&gt;
      &lt;span class="c"&gt;&amp;lt;!-- Register ourselves as fullTrustProcess so it can be invoked from the updateTask component --&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;desktop:Extension&lt;/span&gt; &lt;span class="na"&gt;Category=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;windows.fullTrustProcess&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;Executable=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;MyWin32App.exe&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;desktop:FullTrustProcess&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/desktop:FullTrustProcess&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/desktop:Extension&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Extension&lt;/span&gt; &lt;span class="na"&gt;Category=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;windows.updateTask&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;EntryPoint=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;BackgroundTasks.UpdateTask&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; 
      &lt;span class="nt"&gt;&amp;lt;/Extension&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Extensions&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Application&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Applications&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;Extensions&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Extension&lt;/span&gt; &lt;span class="na"&gt;Category=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;windows.activatableClass.inProcessServer&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;InProcessServer&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Path&amp;gt;&lt;/span&gt;CLRHost.dll&lt;span class="nt"&gt;&amp;lt;/Path&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;ActivatableClass&lt;/span&gt; &lt;span class="na"&gt;ActivatableClassId=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;BackgroundTasks.UpdateTask&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;ThreadingModel=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;both&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/InProcessServer&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Extension&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Extensions&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And the code in the WinRT component:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;quickLog&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Update Task log you can write wherever&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 

&lt;span class="c1"&gt;// Being able to launch the fullTrustApp directly with a command line parameter requires API contract v2 (W11/22000+ only)&lt;/span&gt;
&lt;span class="c1"&gt;// (Otherwise, the parameter needs to be in the MSIX manifest and you don&amp;#39;t get any feedback to log here)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ApiInformation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsApiContractPresent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Windows.ApplicationModel.FullTrustAppContract&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;fullTrustLaunchOperation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FullTrustProcessLauncher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LaunchFullTrustProcessForCurrentAppWithArgumentsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;--commandLineArgumentForYourExe&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;fullTrustLaunchOperation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AsTask&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;fullTrustResult&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fullTrustLaunchOperation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetResults&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;quickLog&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;FullTrustLaunch result -- &amp;quot;&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fullTrustResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LaunchResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fullTrustResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LaunchResult&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;FullTrustLaunchResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;quickLog&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;&amp;quot; &amp;quot;&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fullTrustResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExtendedError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; 
Keep in mind you can just use &lt;code&gt;LangVersion&lt;/code&gt; to get most of C#11's benefits, even on this old CLR..&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; calling it documentation is a gross euphemism as it's basically just &lt;a href="https://github.com/microsoft/DesktopBridgeToUWP-Samples/tree/master/Samples/JourneyAcrossTheBridge_Build2017Edition/Step3"&gt;sample code&lt;/a&gt;, they didn't even have packaging projects back then so they did it all in javascript and guess what? They &lt;a href="https://github.com/apache/cordova-windows/issues/327"&gt;removed UWP javascript support&lt;/a&gt; in VS2019+ and downloading older visual studio versions requires a microsoft account (or just grab it off &lt;a href="https://community.chocolatey.org/packages/visualstudio2017community#files"&gt;chocolatey&lt;/a&gt;) just so I can see what this bloody sample from 2017 generates in the appxmanifest, fuck you &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-3"&gt;&lt;a href="#ref-3"&gt;***&lt;/a&gt; There's also &lt;a href="https://stackoverflow.com/questions/55643010/what-is-uwphost-dll"&gt;UWPHost.dll/UWPShim.exe&lt;/a&gt;, which comes with the &lt;code&gt;NETCore.UniversalWindowsPlatform&lt;/code&gt; package. It's mostly used for in-development UWP apps when running under debug/without NET Native. Not sure why they weren't just using CLRHost.. &lt;/sup&gt;  &lt;/p&gt;</content><category term="Cool Tricks"></category><category term="wpf"></category><category term="uwp"></category><category term="C#"></category><category term="windows"></category><category term="winui"></category><category term="windows 11"></category><category term="net framework"></category><category term="msix"></category><category term="dotnet"></category><category term="very dangerous windows hack age 18 and up content"></category></entry><entry><title>Using the IKEA Kallax to support a CRT</title><link href="https://tvc-16.science/kallax-crt.html" rel="alternate"></link><published>2024-05-27T00:00:00+02:00</published><updated>2024-05-27T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2024-05-27:/kallax-crt.html</id><summary type="html">&lt;p&gt;reddit users screaming into the ether "but you can't do this! it's just cardboard!!!"&lt;/p&gt;</summary><content type="html">&lt;p&gt;If you're a &lt;em&gt;deranged weirdo&lt;/em&gt; still using a CRT in 2024 for your old games, it's difficult finding proper furniture to accomodate those behemoth displays, not to mention game consoles alongside it.&lt;br&gt;
&lt;img alt="gatoslip enjoyers rise up" src="./images/games/sage/gato.jpg"&gt;&lt;br&gt;
Old entertainment centers can be found in the wild for free, but even those aren't always made out of solid wood, and can hold like 4 game systems maximum -- Surely there's got to be a common, cheap modern unit with a lot of compartments you can just go out and buy/assemble yourself..  &lt;/p&gt;
&lt;p&gt;Well, there is! The IKEA &lt;a href="https://www.ikea.com/us/en/p/kallax-shelf-unit-white-stained-oak-effect-00324518/"&gt;Kallax&lt;/a&gt; system pretty much ticks all of those boxes;&lt;br&gt;
The 4x2 gives you 8 compartments to stuff game systems in&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt;, and you can buy additional glass doors to keep them safe from dust.  &lt;/p&gt;
&lt;p&gt;It's a great unit! And I'm &lt;a href="https://mcmansionhell.com/post/710534397376561152/here-are-some-things-i-like"&gt;not the only one thinking that&lt;/a&gt;:  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;the diversity of items it’s able to accommodate makes it the most financially accessible large storage system out there and it’s not even remotely close.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But if you do a bit of research online, you might be &lt;a href="https://www.reddit.com/r/vinyl/comments/bjkrdl/nightmare_my_ikea_shelves_collapsed_after_10/"&gt;uh&lt;/a&gt;, &lt;a href="https://www.reddit.com/r/retrogaming/comments/8q1bca/36_inch_trinitron_crt_stand_suggestions/"&gt;worried&lt;/a&gt; as to whether the shelf can actually &lt;a href="https://www.reddit.com/r/IKEA/comments/hbbzjq/kallax_2x4_weight_limit_for_tv/"&gt;withstand&lt;/a&gt; the &lt;a href="https://www.reddit.com/r/IKEA/comments/7px66n/total_weight_limit_on_kallax_shelf/"&gt;weight&lt;/a&gt; of a CRT.&lt;br&gt;
&lt;img alt="Please don't look at my terrible led band routing i'm begging you" src="./images/kallax_crt.jpg"&gt;&lt;br&gt;
This is a &lt;code&gt;Panasonic TX-32PS11F&lt;/code&gt; - It weighs a lofty &lt;strong&gt;66 kilograms&lt;/strong&gt; &lt;sub&gt;(145 freedom units)&lt;/sub&gt;, and has been sitting on top of this shelf for about &lt;strong&gt;6 months&lt;/strong&gt; without any sign of bending, skewing, or damage.   &lt;/p&gt;
&lt;p&gt;As another "&lt;em&gt;let's write some advice that'll get consumed by all the dogshit AI search engines and hopefully regurgitate correct information to outsiders&lt;/em&gt;" piece, here are a few pointers in case you want to do the same thing.&lt;br&gt;
&lt;sub&gt;Now of course, I am absolved of all blame if you do the same and somehow destroy your 80kg Trinitron and consoles...&lt;/sub&gt; &lt;/p&gt;
&lt;h2&gt;📺 Orient the dividers properly&lt;/h2&gt;
&lt;p&gt;There are good &lt;a href="https://www.reddit.com/r/vinyl/comments/2qlzbe/psa_please_setup_your_expeditskallaxs_correctly/"&gt;PSAs about this online&lt;/a&gt; already but basically: The long dividers need to be positioned &lt;strong&gt;horizontally&lt;/strong&gt; when assembling the Kallax, in order to maximize support strength on the shelves.  &lt;/p&gt;
&lt;p&gt;Now the thing is, if you buy the 2x4 unit, it's technically designed to be mounted vertically, so the instructions will tell you to assemble it like this&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt;:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;┌────────────┬────────────┬────────────┬────────────┐&lt;/span&gt;
&lt;span class="err"&gt;│            │            │            │            │&lt;/span&gt;
&lt;span class="err"&gt;│            │            │            │            │&lt;/span&gt;
&lt;span class="err"&gt;│ ────────── │ ────────── │ ────────── │ ────────── │&lt;/span&gt;
&lt;span class="err"&gt;│            │            │            │            │&lt;/span&gt;
&lt;span class="err"&gt;│            │            │            │            │&lt;/span&gt;
&lt;span class="err"&gt;└────────────┴────────────┴────────────┴────────────┘&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;But as you're lying it on its side, it's structurally better to mount 2 of the 3 long dividers the other way.    &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;┌────────────┬────────────┬────────────┬────────────┐&lt;/span&gt;
&lt;span class="err"&gt;│            │            │            │            │&lt;/span&gt;
&lt;span class="err"&gt;│            │            │            │            │&lt;/span&gt;
&lt;span class="err"&gt;│ ─────────────────────── │ ─────────────────────── │&lt;/span&gt;
&lt;span class="err"&gt;│            │            │            │            │&lt;/span&gt;
&lt;span class="err"&gt;│            │            │            │            │&lt;/span&gt;
&lt;span class="err"&gt;└────────────┴────────────┴────────────┴────────────┘&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h2&gt;📺 Just glue it™️&lt;/h2&gt;
&lt;p&gt;Yeah you're never going to disassemble the thing ever again, but that's a &lt;em&gt;you-in-ten-years problem&lt;/em&gt;!&lt;br&gt;
Adding wood glue to the wooden &lt;a href="http://placebrandingofpublicspace.files.wordpress.com/2013/02/dowel.jpg"&gt;dowels&lt;/a&gt; before hammering them in the boards will &lt;strong&gt;significantly&lt;/strong&gt; strengthen them.  &lt;/p&gt;
&lt;p&gt;Wood glue sets in quite fast however, so be quick about assembly once you apply it! &lt;sub&gt;Ask me how I know&lt;/sub&gt;  &lt;/p&gt;
&lt;h2&gt;📺 If possible, anchor it to the wall&lt;/h2&gt;
&lt;p&gt;The most common point of failure for Kallaxes that you see in all those horror show vinyl photos is that the unit starts &lt;em&gt;skewing to one side&lt;/em&gt;, eventually breaking down under the weight of what's in the shelves.  &lt;/p&gt;
&lt;p&gt;If you can prevent this skewing by &lt;strong&gt;bolting the unit to the wall&lt;/strong&gt;, you should! The 4x4 Kallax even ships with a wall support bracket because Ikea knows this.  &lt;/p&gt;
&lt;p&gt;The 2x4 doesn't come with one, but if like me you were on an Ikea buying spree, you might have a few steel corner pieces left you can use.&lt;br&gt;
&lt;img alt="poorly oriented steel anchor" src="./images/kallax_anchor.jpg"&gt;&lt;br&gt;
Now tbh for maximum strength those should be flipped vertically, but I only thought of adding those after having placed the unit and it was too much hassle to move it 🫠  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; 
The 35x35cm shelves of the Kallax will fit almost any game system(even the OG xbox - barely), but my Megadrive/32X/Sega CD combo doesn't fit.. gee i wonder why&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; Since LLMs can now read images, the only foolproof way left to make diagrams is to &lt;strong&gt;RETVRN&lt;/strong&gt; to the gamefaqs ASCII diagrams of old &lt;/sup&gt;  &lt;/p&gt;</content><category term="Physicality of Gaming"></category><category term="ikea"></category><category term="crt"></category><category term="kallax"></category><category term="cabinet"></category><category term="video games"></category><category term="furnitureposting"></category></entry><entry><title>Early Spring videogame grab bag</title><link href="https://tvc-16.science/games-spring.html" rel="alternate"></link><published>2024-05-01T00:00:00+02:00</published><updated>2024-05-01T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2024-05-01:/games-spring.html</id><summary type="html">&lt;p&gt;featuring coolgator 2000 and the noise&lt;/p&gt;</summary><content type="html">&lt;p&gt;It's &lt;a href="https://en.wikipedia.org/wiki/International_Workers%27_Day"&gt;International Workers' Day&lt;/a&gt;! What better time to uhh, look back at all the videogames I've been playing.&lt;br&gt;
Thinking of all the man-hours that went into making those is quite humbling.  &lt;/p&gt;
&lt;p&gt;I actually played too many games since &lt;a href="./games-july.html"&gt;the last time&lt;/a&gt; I wrote up mini-reviews like this... And I'm forgetting some that I don't really have much to write about.    &lt;/p&gt;
&lt;h1&gt;Mahou Tsukai no Yoru&lt;/h1&gt;
&lt;p&gt;&lt;img alt="you can't actually take screenshots past chapter 4 or so because that would RUIN the experience" src="./images/games/mahoyo.jpg"&gt;&lt;br&gt;
I actually played through this back in December, but since the FGO crossover event is happening &lt;a href="https://www.youtube.com/watch?v=AFaSQ1fIPOo"&gt;right now&lt;/a&gt;, I'm thinking about Mahoyo again.&lt;br&gt;
It'd been &lt;em&gt;years&lt;/em&gt; since I last read a visual novel and even more since reading a Type-Moon one.. FGO doesn't count, it's all first-person and really doesn't flow as well.  &lt;/p&gt;
&lt;p&gt;The production values on this are wild and it's especially better now that the latest releases are actually voiced; I give it a big rec if you're into magic in modern&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt; times and all that jazz.  &lt;/p&gt;
&lt;p&gt;It's a great entrypoint into Type-Moon as well, no previous knowledge required!&lt;br&gt;
The story takes a small dip at the beginning because of boring school life stuff Nasu can't help himself write, but it only goes up after that.  &lt;/p&gt;
&lt;h1&gt;Coffee Talk 2&lt;/h1&gt;
&lt;p&gt;&lt;img alt="" src="./images/games/coffee.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;It's more Coffee Talk; Basically picks up right after the first game with a new batch of character stories.&lt;br&gt;
Great stuff if you like &lt;em&gt;bartender-type&lt;/em&gt; games. Yeah that's what I'm gonna call those now and there aren't enough of 'em  &lt;/p&gt;
&lt;p&gt;The soundtrack is a lot of chill jams and I basically dropped all of it into my playlist, but I really like &lt;a href="https://www.youtube.com/watch?v=jaqOgKrYihY"&gt;this track&lt;/a&gt; in particular.  &lt;/p&gt;
&lt;h1&gt;Atomic Heart&lt;/h1&gt;
&lt;p&gt;The pacing of this game is &lt;strong&gt;thoroughly&lt;/strong&gt; fucked; The gunplay is nice and I had a lot of fun with it once you hit the overworld, but that's only after 5 hours..&lt;br&gt;
The initial prologue+tutorialized dungeon at the beginning is entirely too long and not very interesting, unfortunately.&lt;br&gt;
&lt;img alt="" src="./images/games/atom-1.jpg"&gt;&lt;br&gt;
The visual design of the game is great, love me some soviet futurism -- There's a lot of heart put into stuff like the fake cartoons in the save rooms where the characters look like they could sell you a &lt;a href="https://en.wikipedia.org/wiki/Dendy_(console)"&gt;Dendy&lt;/a&gt; or something similar.&lt;br&gt;
You can tell they kinda ran out of budget for the environmental detail though, a lot of optional parts of the game end up looking samey.  &lt;/p&gt;
&lt;p&gt;Story is actually fairly engaging when you get into it; They're still making DLCs so I might get back into the game at some point.
The soundtrack is &lt;a href="https://www.youtube.com/watch?v=R1eyjhTmErw&amp;amp;t=998s"&gt;pretty dope&lt;/a&gt;, although I didn't like the Mick Gordon tracks as much as I was hoping.&lt;br&gt;
&lt;img alt="I CLAPPED WHEN I SAW THE BIOSHOCK REFERENCE" src="./images/games/atom-2.jpg"&gt;  &lt;/p&gt;
&lt;h1&gt;Splatoon 1&lt;/h1&gt;
&lt;p&gt;I picked Splat 1 at the local thrift store and thought I'd play it before the Wii U online servers went down for good&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt;.&lt;br&gt;
&lt;img alt="How tf did we even win that one" src="./images/games/splatoon.jpg"&gt;&lt;br&gt;
I'd never gotten into Splatoon before despite the fact team arena FPSes are right in my strike zone; Really loved it! Turf war is an easy game mode to pick up and the game is designed so that playing the objective comes naturally even if you just want to frag nerds.    &lt;/p&gt;
&lt;p&gt;The online was also still active despite the fact Splatoon 1 was massively outdated by that point -- maybe because it's the only game of the three that didn't require you to pay a subscription?&lt;br&gt;
Xbox Live and its copycats have had terrible consequences for the human race...     &lt;/p&gt;
&lt;p&gt;It's also the only game that has the &lt;a href="https://splatoonwiki.org/wiki/The_SQUID_GIRL"&gt;Ika Musume&lt;/a&gt; collab clothes! man I miss ika musume  &lt;/p&gt;
&lt;h1&gt;Yakuza Kiwami 2&lt;/h1&gt;
&lt;p&gt;I took a year-long break between playing 0/Kiwami 1 and this -- And I'm glad I did, because this game just feels like Kiwami 1 again with less content.&lt;br&gt;
It's still Yakuza so it's still fun, but I see why people would consider this to be one of the weakest entries.&lt;br&gt;
Most of the side content is ripped straight from Yakuza 6 too, so it's going to feel real weird when I get to that game..  &lt;/p&gt;
&lt;p&gt;The OG &lt;a href="https://www.youtube.com/watch?v=BHWsYeVts0k"&gt;Yakuza 2 soundtrack&lt;/a&gt; they use here is still pretty good though.  &lt;/p&gt;
&lt;p&gt;There's one karaoke song where Kiryu puts on sunglasses and plays out a 2000s Evanescence music clip in the nolan batcave though and that goes so fucking hard look at this shit&lt;br&gt;
&lt;img alt="" src="./images/games/kiwami2.jpg"&gt;  &lt;/p&gt;
&lt;h1&gt;Pizza Tower's Noise update&lt;/h1&gt;
&lt;p&gt;&lt;img alt="its the noise" src="./images/games/noise.jpg"&gt; &lt;br&gt;
&lt;a href="https://www.youtube.com/watch?v=eMYTSESIsu8"&gt;woag&lt;/a&gt;  &lt;/p&gt;
&lt;p&gt;Noise feels massively unwieldy to play at first compared to the italian pizza man due to no wallrun, but at some point it just clicks and you start absolutely &lt;strong&gt;wrecking&lt;/strong&gt; the game -- Kinda like how it feels when you play Pizza Tower for the first time to begin with!&lt;br&gt;
Great NG+, you know you're doing something right when I'm just 100%ing it again.  &lt;/p&gt;
&lt;h1&gt;Lil' Gator Game&lt;/h1&gt;
&lt;p&gt;&lt;img alt="cool" src="./images/games/gator.jpg"&gt;&lt;br&gt;
A short Breath of the Wild pastiche with lowpoly animals. It plays great and doesn't overstay its welcome, I'd recommend giving it a try through Game Pass or similar.  &lt;/p&gt;
&lt;p&gt;Shield surfing is way more fun in this game than it ever was in BOTW!   &lt;/p&gt;
&lt;h1&gt;Miscellaneous others&lt;/h1&gt;
&lt;h2&gt;Sonic Superstars&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Atrocious&lt;/strong&gt; bosses, level design itself is fine if a bit uninspired except for some standouts (&lt;em&gt;Cyber Station&lt;/em&gt;), Trip is pretty fun to play and I hope they bring her back in future 2D Sonic installments.  &lt;br&gt;
People &lt;a href="https://www.youtube.com/watch?v=-z84TNg_POw"&gt;shit on the soundtrack&lt;/a&gt; but I actually like it despite how &lt;a href="https://www.youtube.com/watch?v=vRcOI4DbbDE"&gt;schizophrenic&lt;/a&gt; it feels.  &lt;/p&gt;
&lt;h2&gt;Super Mario Wonder&lt;/h2&gt;
&lt;p&gt;The wonder effects were fun, but this isn't a 2D Mario I'd replay. The badges are sold as "&lt;em&gt;make your own difficulty&lt;/em&gt;" but the base game is already easier than previous NSMBs..&lt;br&gt;
The final super expert level is straight up bad design and I never thought I'd say this of a Mario game, Nintendo officially went &lt;strong&gt;too far&lt;/strong&gt;  &lt;/p&gt;
&lt;h2&gt;Fortune Street Wii&lt;/h2&gt;
&lt;p&gt;I've been on an Itadaki-binge lately and unlocked all the maps/characters in this; The game actually has a lot of depth and I'm glad this version is localized at least so it's easy to play with friends!  &lt;/p&gt;
&lt;p&gt;Also there's a &lt;a href="https://www.youtube.com/watch?v=FBLjEDI3qWQ"&gt;modpack&lt;/a&gt; and they put facingworlds in it with custom 3D models and everything it's awesome  &lt;/p&gt;
&lt;h2&gt;Entropy : Zero 2&lt;/h2&gt;
&lt;p&gt;I was hunkering for some Half-Life a few months back and since I don't want to buy a VR headset for Alyx, I just played this &lt;a href="https://store.steampowered.com/app/1583720/Entropy__Zero_2/"&gt;off Steam&lt;/a&gt;; Great modded campaign, hard to not recommend it considering its unbeatable low price of &lt;strong&gt;Free&lt;/strong&gt;.  &lt;/p&gt;
&lt;h2&gt;Fatum Betula&lt;/h2&gt;
&lt;p&gt;This game is currently included in the &lt;a href="https://itch.io/b/2321/palestinian-relief-bundle"&gt;Palestinian relief bundle&lt;/a&gt; on itch alongside a bunch of other stuff I've been wanting to play, but I got it on Switch a few months back for free with gold Nintendo coins.&lt;br&gt;
&lt;img alt="" src="https://img.itch.zone/aW1hZ2UvNjEwMzg1LzMyNDUwNjIucG5n/original/0otHaw.png"&gt;&lt;br&gt;
I really liked the main mechanic of "whatever you water the birch with determines your ending", you end up spending the entire game finding more and more convoluted water-based substances to keep in your bottle.&lt;br&gt;
Aesthetics are on point too if PS1 is your thing.  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; Well it takes place in the 80s but that's still urban fantasy it counts i swear&lt;/sup&gt; &lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; Sure you can still play it with Pretendo but let's be honest it's never going to be as active as on the official servers&lt;/sup&gt;  &lt;/p&gt;</content><category term="Blogposting"></category><category term="video games"></category><category term="game pass"></category><category term="mahoyo"></category><category term="splatoon"></category><category term="atomic heart"></category><category term="pizza tower"></category><category term="yakuza"></category><category term="soundtracks"></category></entry><entry><title>An approach to data binding with .NET in an iOS app</title><link href="https://tvc-16.science/xamarin-binding.html" rel="alternate"></link><published>2024-03-31T00:00:00+01:00</published><updated>2024-03-31T00:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2024-03-31:/xamarin-binding.html</id><summary type="html">&lt;p&gt;Key-Value Observer on Timeless Temple&lt;/p&gt;</summary><content type="html">&lt;p&gt;It's been a while since my last &lt;a href="/xamarin-resx.html"&gt;Xamarinpost&lt;/a&gt;, but since I just released a &lt;a href="https://github.com/Difegue/Stylophone/releases/tag/2.6.2"&gt;Stylophone update&lt;/a&gt;, I'm in the mood to wax .NET again!  &lt;/p&gt;
&lt;p&gt;Stylophone, as a Windows-first app, was built upon the principle of &lt;a href="https://learn.microsoft.com/en-us/dotnet/architecture/maui/mvvm"&gt;MVVM&lt;/a&gt;, which as a quick refresher:&lt;br&gt;
- Isolates model and view code entirely&lt;br&gt;
- Uses "&lt;em&gt;ViewModel&lt;/em&gt;" classes as glue between the two, exposing the model's data as properties the view picks up through &lt;strong&gt;data binding&lt;/strong&gt;.  &lt;/p&gt;
&lt;p&gt;One of the key advantages of this pattern is, as the MS documentation says:  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The app UI can be redesigned without touching the view model and model code [...]. Therefore, a new version of the view should work with the existing view model.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So if you can easily have multiple sets of UI with the same base... You should be able to have the same code for multiple platforms that run .NET, and only &lt;strong&gt;remake the view&lt;/strong&gt; for each platform! That's what &lt;a href="/stylophone-25"&gt;Stylophone does&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;&lt;img width="420" style="margin:0" src="https://tvc-16.science/images/stylophone/v25-ipad.jpg"/&gt; &lt;img width="420" style="margin:0" src="https://tvc-16.science/images/stylophone/v25-win.jpg"/&gt;  &lt;/p&gt;
&lt;p&gt;Data-binding comes for free on Windows thanks to XAML, but what about iOS?&lt;br&gt;
Can we bind our .NET code to native UIKit views so they just update automagically?  &lt;/p&gt;
&lt;p&gt;As it turns out, yes! There &lt;strong&gt;is&lt;/strong&gt; a native data-binding mechanism for Apple platforms!&lt;br&gt;
Well, kinda.  &lt;/p&gt;
&lt;h3&gt;Cocoa Bindings&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaBindings/Concepts/WhatAreBindings.html"&gt;Cocoa Bindings&lt;/a&gt; is a piece of AppKit tech that allows you to do data-binding on Mac apps out of the box. It just works!™️&lt;br&gt;
&lt;img alt="Cocoa Bindings" src="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaBindings/art/sliderbindings_2x.png"&gt;&lt;br&gt;
Apple's flavor of data-binding relies on two elements:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Key-Value Coding&lt;/em&gt; (KVC), which gives access to an object’s property with a specified name (a &lt;strong&gt;Keypath&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Key-Value Observing&lt;/em&gt; (KVO), which allows an object to receive notifications of changes to values in other objects  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For dotnetheads, this is basically &lt;em&gt;C# properties&lt;/em&gt; and &lt;code&gt;INotifyPropertyChanged&lt;/code&gt;.  &lt;/p&gt;
&lt;p&gt;That all sounds nice and easy to plug into..except Cocoa Bindings aren't available on UIKit/iOS.&lt;br&gt;
The components are, however! &lt;/p&gt;
&lt;h3&gt;Reimplementing Cocoa Bindings&lt;/h3&gt;
&lt;p&gt;Since UIKit still supports key-value coding, we can just create our own Bindings.&lt;br&gt;
Creating a .NET databinder for iOS basically means doing two things:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keep track of &lt;code&gt;PropertyChanged&lt;/code&gt; events on the C#/ViewModel side and update your views through &lt;strong&gt;KVC&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Keep track of view changes through &lt;strong&gt;KVO&lt;/strong&gt; and update your properties on the ViewModel  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;First, let's create our own &lt;code&gt;Binding&lt;/code&gt; class that would act as universal glue code between C# and native objects.  &lt;br&gt;
Xamarin's automatic type conversion is very useful here, so we don't need to write a lot of code at all:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Binding&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Native UIKit object, and path to its KVC property&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;NSObject&lt;/span&gt; &lt;span class="n"&gt;Object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;NSString&lt;/span&gt; &lt;span class="n"&gt;Keypath&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// C# property, this uses reflection&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;PropertyInfo&lt;/span&gt; &lt;span class="n"&gt;Property&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;/// Update our C# property with information from Key-Value Observing&lt;/span&gt;
        &lt;span class="c1"&gt;/// Since KVO cannot give you type information, you must provide the type yourself.&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;UpdateProperty&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;INotifyPropertyChanged&lt;/span&gt; &lt;span class="n"&gt;targeTViewModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NSObservedChange&lt;/span&gt; &lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;nativeValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Cast to .NET types &lt;/span&gt;
                &lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;NSNumber&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;nativeValue&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Int32Value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;NSNumber&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;nativeValue&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Int64Value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;NSNumber&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;nativeValue&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;DoubleValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;NSNumber&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;nativeValue&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;BoolValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;NSString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;nativeValue&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// A wrapper class should be used in that case &lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;

            &lt;span class="c1"&gt;// Set the value on our viewmodel&lt;/span&gt;
            &lt;span class="n"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targeTViewModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;/// Update the NSObject&amp;#39;s value at the specified keypath with the property.&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;UpdateNSObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Box the value into a native UIKit type&lt;/span&gt;
            &lt;span class="c1"&gt;// (If binding to more complex types than int/bool/strings, this will fail! A wrapper class should be used in that case)&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;nativeValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NSObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FromObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// KVC operations need to run on the main thread&lt;/span&gt;
            &lt;span class="n"&gt;UIApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SharedApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BeginInvokeOnMainThread&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// https://developer.apple.com/documentation/objectivec/nsobject/1418139-setvalue?language=objc&lt;/span&gt;
                &lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetValueForKeyPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nativeValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Keypath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;


    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Then, our &lt;code&gt;PropertyBinder&lt;/code&gt; can just create instances of this object for each property you want to bind, and listen to notifications from both sides:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PropertyBinder&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TViewModel&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;:&lt;/span&gt; &lt;span class="n"&gt;IDisposable&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;TViewModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;INotifyPropertyChanged&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Per-property name bindings&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Binding&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_bindings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="c1"&gt;// Per-property name &lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IDisposable&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_observers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;TViewModel&lt;/span&gt; &lt;span class="n"&gt;_observableObject&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;PropertyBinder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TViewModel&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_observableObject&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="c1"&gt;// Listen to C# property changes&lt;/span&gt;
            &lt;span class="n"&gt;_observableObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PropertyChanged&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;OnObservablePropertyChanged&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_observableObject&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;_observableObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PropertyChanged&lt;/span&gt; &lt;span class="p"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;OnObservablePropertyChanged&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="c1"&gt;// Dispose our observers&lt;/span&gt;
            &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IDisposable&lt;/span&gt; &lt;span class="n"&gt;observer&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_observers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Bind a NSObject&amp;#39;s keypath to a property of our observableObject&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;NSObject&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;keypath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isTwoWay&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;NSString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keypath&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isTwoWay&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnObservablePropertyChanged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PropertyChangedEventArgs&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;properties&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

            &lt;span class="c1"&gt;// The PropertyChanged event can indicate all properties on the object have changed by using either null or String.Empty as&lt;/span&gt;
            &lt;span class="c1"&gt;// the property name in the PropertyChangedEventArgs.&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PropertyName&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_bindings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Keys&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_bindings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContainsKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PropertyName&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// Only one property has changed&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PropertyName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;property&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;bindings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_bindings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetValueOrDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;binding&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;bindings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Update all bindings for this property&lt;/span&gt;
                    &lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UpdateNSObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_observableObject&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;NSObject&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NSString&lt;/span&gt; &lt;span class="n"&gt;keypath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isTwoWay&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;propertyInfo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;propertyValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;propertyInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_observableObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// Create C#/UIKit binding and add it to the list to keep track of it&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;binding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Binding&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Object&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Keypath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;keypath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Property&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;propertyInfo&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_bindings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContainsKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="n"&gt;_bindings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetValueOrDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;
                &lt;span class="n"&gt;_bindings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Binding&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;binding&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;

            &lt;span class="c1"&gt;// Set the initial value &lt;/span&gt;
            &lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UpdateNSObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;propertyValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isTwoWay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Create an Observer with KVO to keep track of UIKit-side changes&lt;/span&gt;
                &lt;span class="c1"&gt;// https://developer.apple.com/documentation/objectivec/nsobject/1412787-addobserver  &lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;observer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keypath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NSKeyValueObservingOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OldNew&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UpdateProperty&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;_observableObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
                &lt;span class="c1"&gt;//      ^ Xamarin conveniently provides a version of this API call that  &lt;/span&gt;
                &lt;span class="c1"&gt;// takes a .NET event, so we can directly invoke our Binding object&amp;#39;s Update.&lt;/span&gt;
                &lt;span class="n"&gt;_observers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;-&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;keypath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;PropertyInfo&lt;/span&gt; &lt;span class="nf"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;propertyName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Small optimization to avoid calling reflection every time&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_bindings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContainsKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;propertyName&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_bindings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;propertyName&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;First&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;propertyInfo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TViewModel&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;GetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;propertyName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;propertyInfo&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ArgumentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Property {propertyName} not found on observable object {typeof(TViewModel).Name}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And that's it!&lt;br&gt;
Usage is then as simple as creating a PropertyBinder from your view code:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;RandomViewModel&lt;/span&gt; &lt;span class="n"&gt;ViewModel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt; 
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;UILabel&lt;/span&gt; &lt;span class="n"&gt;NameLabel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;PropertyBinder&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PlaylistViewModel&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Binder&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ViewModel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;AwakeFromNib&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AwakeFromNib&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// Bind keypath &amp;quot;text&amp;quot; of UILabel to the Name property of our viewmodel&lt;/span&gt;
    &lt;span class="n"&gt;Binder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;NameLabel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ViewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;wait fuck this is iOS&lt;/h3&gt;
&lt;p&gt;One final gotcha is that for KVO to work, aka for &lt;a href="https://developer.apple.com/documentation/objectivec/nsobject/1412787-addobserver?language=objc"&gt;AddObserver&lt;/a&gt; to correctly create Observers and send notifications, your native UI controls must be &lt;strong&gt;KVO-compliant&lt;/strong&gt;.  &lt;/p&gt;
&lt;p&gt;This is a funny Apple word to mean that the controls have to send notifications when their properties change - You can kind of think of it like DependencyProperties in XAML? It's a bit different though.&lt;br&gt;
According to &lt;a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueObserving/Articles/KVOBasics.html"&gt;Apple documentation&lt;/a&gt;:  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Not all classes are KVO-compliant for all properties. [...] Typically properties in Apple-supplied frameworks are only KVO-compliant if they are documented as such.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Basically, this means that since iOS didn't have Cocoa Bindings, they didn't give a single shit and most UIKit controls &lt;a href="https://stackoverflow.com/questions/6114261/how-reliable-is-kvo-with-uikit"&gt;will &lt;strong&gt;not&lt;/strong&gt; work with KVO&lt;/a&gt;.&lt;br&gt;
So for Two-Way bindings, controls like &lt;code&gt;UISwitch&lt;/code&gt; won't notify our &lt;code&gt;Binding&lt;/code&gt; class when the user clicks on them! &lt;em&gt;Doushio?&lt;/em&gt;  &lt;/p&gt;
&lt;p&gt;Well, the easiest solution is basically to do Apple's job in their place, and subclass the UIKit controls you need to &lt;em&gt;make&lt;/em&gt; them comply.&lt;br&gt;
Which is &lt;a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueObserving/Articles/KVOCompliance.html#//apple_ref/doc/uid/20002178-BAJEAIEE"&gt;very easy&lt;/a&gt; as long as you only need a few properties:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="na"&gt;[Register(nameof(KvoUISwitch))]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;KvoUISwitch&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UISwitch&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;KvoUISwitch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ReleaseDesignerOutlets&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;AwakeFromNib&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AwakeFromNib&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// This will trigger when the switch value changes&lt;/span&gt;
        &lt;span class="n"&gt;AddTarget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NotifyChange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UIControlEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValueChanged&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;NotifyChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EventArgs&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// low-budget kvo compliance&lt;/span&gt;
        &lt;span class="n"&gt;WillChangeValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;on&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;DidChangeValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;on&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;There would be more complex things to add here (&lt;code&gt;ICommand&lt;/code&gt; support, Managed object wrapping in bindings, Converters aka &lt;code&gt;NSValueTransformers&lt;/code&gt; on the UIKit side), but I invite you to just peek at the &lt;a href="https://github.com/Difegue/Stylophone/blob/dev/Sources/Stylophone.iOS/Helpers/PropertyBinder.cs"&gt;Stylophone source&lt;/a&gt; if you're interested.&lt;br&gt;
Have a nice day!  &lt;/p&gt;</content><category term="Cool Tricks"></category><category term="xamarin"></category><category term="dotnet"></category><category term="c#"></category><category term="macos"></category><category term="ios"></category><category term="mvvm"></category><category term="data binding"></category><category term="cocoa bindings"></category><category term="monkey trick"></category><category term="key-value coding"></category></entry><entry><title>I made a Playdate game in two days and it's ok™️</title><link href="https://tvc-16.science/sonic-drift-mania.html" rel="alternate"></link><published>2024-02-17T00:00:00+01:00</published><updated>2024-02-17T00:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2024-02-17:/sonic-drift-mania.html</id><summary type="html">&lt;p&gt;gotta crank it with sonic&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've used this year's &lt;a href="https://www.youtube.com/watch?v=noOeYcKlTsQ"&gt;Really Amateur Games Expo&lt;/a&gt; as an excuse to finally look at the &lt;a href="https://sdk.play.date"&gt;Playdate SDK&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;Despite having two weeks to work on it, I only really spent two days... The advantage of a competition aimed towards making the &lt;strong&gt;worst&lt;/strong&gt; fangame possible is that even if it's rushed and bad, that's the entire point so 👌👌🔥&lt;br&gt;
&lt;img alt="can you believe the game.com version of sonic jam also has blue spheres except due to the screen being grayscale they called it get black spheres and that's where i ripped all of that HUD from" src="./images/playdate/playdate-20240216-163945.png"&gt;&lt;br&gt;
Yes it's blue spheres again the world will never be freed of blue spheres&lt;/p&gt;
&lt;h3&gt;You can download the result .pdx file on itch at the link below. Works on real Playdates!&lt;/h3&gt;
&lt;p&gt;&lt;iframe frameborder="0" src="https://itch.io/embed/2532206" width="552" height="167"&gt;&lt;a href="https://difegue.itch.io/sonic-drift-mania"&gt;Sonic Drift Mania by dfug&lt;/a&gt;&lt;/iframe&gt;  &lt;/p&gt;
&lt;p&gt;The only real design idea I had going into this was "you can use the crank to accelerate to &lt;em&gt;ridiculous&lt;/em&gt; speeds":&lt;br&gt;
Making a real platformer with the (self-)alloted time wouldve been difficult, so I settled for a Mode-7 racing game instead, as that's basically just using the builtin &lt;a href="https://sdk.play.date/inside-playdate/#m-graphics.image.drawSampled"&gt;&lt;code&gt;drawSampled&lt;/code&gt;&lt;/a&gt; function.  &lt;/p&gt;
&lt;p&gt;Which is nice and all, but the Playdate core library only gives you this, and no other tools to uh, draw other sprites positioned within the mode-7 world...  &lt;/p&gt;
&lt;p&gt;So the spheres are just painted on the track.&lt;br&gt;
&lt;img alt="i did this all for u blue spheres guy" src="./images/playdate/playdate-20240216-164054.png"&gt;&lt;br&gt;
...I actually &lt;strong&gt;tried&lt;/strong&gt; to make them proper objects, okay?!&lt;br&gt;
It's &lt;a href="https://www.coranac.com/tonc/text/mode7.htm"&gt;too much math&lt;/a&gt; for my dumb brain and there was &lt;a href="https://devforum.play.date/t/mode7-screen-coordinate-in-perspective/8751"&gt;nothing&lt;/a&gt; to &lt;a href="https://devforum.play.date/t/f-time-trial-demo/6989/11"&gt;copypaste&lt;/a&gt;...&lt;br&gt;
There's technically a &lt;a href="https://github.com/risolvipro/playdate-mode7"&gt;library&lt;/a&gt; you can import to get full mode7 functionality on Playdate now, but I wanted to just play with the stock SDK here; We ain't making a &lt;strong&gt;real&lt;/strong&gt; game or anything preposterous like that....  &lt;/p&gt;
&lt;p&gt;Speaking of the SDK, very fun to use! It's quite intuitive and gives you a bunch of tools I wouldn't necessarily expect from a low-spec console, like built-in &lt;a href="https://sdk.play.date/2.3.1/#M-json"&gt;JSON deserialization&lt;/a&gt; and &lt;a href="https://sdk.play.date/inside-playdate/#M-sound"&gt;MP3 decoding&lt;/a&gt;...&lt;br&gt;
Which you should &lt;strong&gt;not&lt;/strong&gt; use! It takes like 50% of the CPU on real hardware so you're better off converting to .wav.&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt;  &lt;/p&gt;
&lt;p&gt;The 1-bit nature of the playdate screen makes it pretty easy to just grab a &lt;a href="https://www.spriters-resource.com/game_gear/sonicdrift/sheet/79145/"&gt;bunch&lt;/a&gt; of &lt;a href="https://www.spriters-resource.com/game_com/sonicjam/sheet/79534/"&gt;different&lt;/a&gt; &lt;a href="https://www.textures-resource.com/pc_computer/sonicr/texture/11828/"&gt;assets&lt;/a&gt;, slam them all into aseprite with default dithering&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt; and boom! &lt;strong&gt;graphics&lt;/strong&gt;! As shrimple as that...&lt;br&gt;
&lt;img alt="COOL!" src="images/playdate/playdate-20240216-164047.png"&gt;  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; wav files are actually converted to .pda by the playdate compiler, so when using the fileplayer you need to load filenames with the .pda extension for them to play... the sampleplayer doesn't have this problem so you can just throw .wav filenames at it for some reason&lt;/sup&gt; &lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; Or &lt;a href="https://gazs.github.io/canvas-atkinson-dither/"&gt;Atkinson dithering&lt;/a&gt; if you're a nerd (I forgot that was a possibility until writing this..)&lt;/sup&gt;  &lt;/p&gt;</content><category term="Gamedev"></category><category term="video games"></category><category term="gamedev"></category><category term="playdate"></category><category term="lua"></category><category term="rage"></category><category term="sage"></category><category term="sonic"></category></entry><entry><title>Compiling snapcast on macOS High Sierra</title><link href="https://tvc-16.science/snapcast-high-sierra.html" rel="alternate"></link><published>2024-01-11T00:00:00+01:00</published><updated>2024-01-11T00:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2024-01-11:/snapcast-high-sierra.html</id><summary type="html">&lt;p&gt;feats of compiling only dreamt of by the utterly deranged&lt;/p&gt;</summary><content type="html">&lt;h3&gt;Happy New Year!&lt;/h3&gt;
&lt;p&gt;Here's a blogpost about fighting off bitrot so you can keep using those nice speakers your iMac ships with.  &lt;/p&gt;
&lt;p&gt;I recently got into setting up &lt;a href="https://community.home-assistant.io/t/perfect-and-free-synchronous-multiroom-audio-with-snapcast/386871"&gt;snapcast&lt;/a&gt; to play music from my NAS/MPD server across multiple rooms.&lt;br&gt;
While a part of the deranged setup is finding a way to put MPD clients&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt; in multiple rooms, I also need speakers that run snapclient to play the synchronized audio from.&lt;br&gt;
&lt;img alt="snapclient running on High Sierra hell yeah" src="images/sierra-snapcast.png"&gt;&lt;br&gt;
I have an old Intel iMac running &lt;a href="https://en.wikipedia.org/wiki/MacOS_High_Sierra"&gt;macOS High Sierra&lt;/a&gt;&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt; , which is both not so old that compiling modern software on it is impossible... and old enough that it becomes kind of a pain. Let's suffer together!  &lt;/p&gt;
&lt;p&gt;...Or don't, you can just grab precompiled binaries for snapcast 0.27 &lt;a href="https://github.com/Difegue/Chaotic-Realm/blob/master/snapcast-highsierra/snapcast_highsierra.zip"&gt;here.&lt;/a&gt; It's on the house!  &lt;/p&gt;
&lt;h1&gt;#include &amp;lt;filesystem&gt; aka the clangening&lt;/h1&gt;
&lt;p&gt;Just running &lt;code&gt;brew install snapcast&lt;/code&gt; on High Sierra will (currently) get you pretty far... but compiling the actual program will fail due to High Sierra's last official Apple C++ compiler &lt;a href="https://stackoverflow.com/questions/49577343/filesystem-with-c17-doesnt-work-on-my-mac-os-x-high-sierra"&gt;not supporting C++17's std::filesystem.&lt;/a&gt;  &lt;/p&gt;
&lt;p&gt;You could &lt;em&gt;seemingly&lt;/em&gt; make it work with &lt;code&gt;&amp;lt;experimental/filesystem&amp;gt;&lt;/code&gt;, but I wasn't able to do it...&lt;br&gt;
So let's just &lt;strong&gt;install a new compiler&lt;/strong&gt;! I went with clang/llvm, like what's used on base macOS;&lt;br&gt;
Just installing a newer version than the stock Apple one.  &lt;/p&gt;
&lt;p&gt;&lt;code&gt;brew install llvm&lt;/code&gt; will &lt;strong&gt;fail&lt;/strong&gt;, but I've found that installing the previous version works, so do &lt;code&gt;brew install llvm@15&lt;/code&gt;.&lt;br&gt;
Now, how do you tell Homebrew to use this compiler instead? &lt;em&gt;I actually don't fucking know!&lt;/em&gt;&lt;br&gt;
&lt;img alt="I CAN DO ANYTHING" src="images/anything.jpg"&gt;&lt;br&gt;
&lt;a href="https://stackoverflow.com/questions/9186033/using-homebrew-with-alternate-gcc"&gt;Apparently&lt;/a&gt; you can use &lt;code&gt;HOMEBREW_CC&lt;/code&gt; and &lt;code&gt;HOMEBREW_CXX&lt;/code&gt; to set C/C++ compilers...&lt;br&gt;
But setting those variables to a path doesn't work, and setting them to &lt;code&gt;clang&lt;/code&gt;/&lt;code&gt;clang++&lt;/code&gt; just makes Homebrew keep using the default Apple compiler. (&lt;code&gt;AppleClang 10.0.0.10001044&lt;/code&gt;)    &lt;/p&gt;
&lt;p&gt;I couldn't install &lt;code&gt;gcc&lt;/code&gt; on High Sierra, so.. let's just start hacking at the &lt;a href="https://raw.githubusercontent.com/Homebrew/homebrew-core/2e5a1cad52074ef53f1ae1b73c47dafa5e1f93ad/Formula/s/snapcast.rb"&gt;snapcast formula&lt;/a&gt; instead&lt;sup id="ref-3"&gt;&lt;a href="#note-2"&gt;***&lt;/a&gt;&lt;/sup&gt; !&lt;br&gt;
You can &lt;a href="https://stackoverflow.com/questions/60082066/how-do-i-get-cmake-to-use-my-homebrew-installation-of-llvm-exclusively"&gt;override compilers&lt;/a&gt; at the &lt;code&gt;cmake&lt;/code&gt; level by using the properties &lt;code&gt;-DCMAKE_C_COMPILER&lt;/code&gt; and &lt;code&gt;-DCMAKE_CXX_COMPILER&lt;/code&gt;.  &lt;/p&gt;
&lt;h1&gt;Atomic bullshit&lt;/h1&gt;
&lt;p&gt;Modifying the formula's cmake call to be  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="n"&gt;system&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;cmake&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;-S&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;-B&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;build&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;std_cmake_args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;-DCMAKE_C_COMPILER=/usr/local/opt/llvm@15/bin/clang&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;-DCMAKE_CXX_COMPILER=/usr/local/opt/llvm@15/bin/clang++&amp;quot;&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;uses our new compiler correctly, but then a new error rises...  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Host compiler appears to require libatomic, but cannot find it.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That one is actually pretty easy and seems to just stem from llvm &lt;a href="https://stackoverflow.com/a/64112384/1418981"&gt;&lt;em&gt;really&lt;/em&gt; wanting libatomic&lt;/a&gt;, even though it's not needed for our &lt;em&gt;spectacular snapcast shenanigans&lt;/em&gt; at all.&lt;br&gt;
Soo, what's one more argument to the cmake line, right? &lt;code&gt;"-DHAVE_CXX_ATOMICS_WITHOUT_LIB=TRUE"&lt;/code&gt; sidesteps this problem.  &lt;/p&gt;
&lt;p&gt;Are we done? No!&lt;br&gt;
&lt;img alt="" src="images/bullshit.jpg"&gt;&lt;br&gt;
After all that, I was encountering some final missing headers for basic C++ libraries, like &lt;code&gt;&amp;lt;string.h&amp;gt;&lt;/code&gt;.&lt;br&gt;
But I'd followed the brew instructions to set my lib/include paths properly...  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;To use the bundled libc++ please add the following LDFLAGS:&lt;br&gt;
  LDFLAGS="-L/usr/local/opt/llvm@15/lib/c++ -Wl,-rpath,/usr/local/opt/llvm@15/lib/c++"&lt;br&gt;
  For compilers to find llvm@15 you may need to set:&lt;br&gt;
  export CPPFLAGS="-I/usr/local/opt/llvm@15/include"  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Well, they're actually &lt;strong&gt;incorrect&lt;/strong&gt;! Your include should be &lt;code&gt;"-I/usr/local/opt/llvm@15/include/c++/v1"&lt;/code&gt; instead. Thanks for nothing. &lt;/p&gt;
&lt;p&gt;So with just a compiler swap and those three lines in your &lt;a href="https://github.com/Difegue/Chaotic-Realm/blob/master/snapcast-highsierra/snapcast.rb"&gt;snapcast formula&lt;/a&gt;:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt;
    &lt;span class="cp"&gt;# Use brew llvm&lt;/span&gt;
    &lt;span class="n"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;CXXFLAGS&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;-I/usr/local/opt/llvm@15/include/c++/v1&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;LDFLAGS&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;-L/usr/local/opt/llvm@15/lib/c++ -Wl,-rpath,/usr/local/opt/llvm@15/lib/c++&amp;quot;&lt;/span&gt;

    &lt;span class="cp"&gt;# Hijack compiler path directly in cmake call &lt;/span&gt;
    &lt;span class="n"&gt;system&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;cmake&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;-S&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;-B&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;build&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;std_cmake_args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;-DHAVE_CXX_ATOMICS_WITHOUT_LIB=TRUE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;-DCMAKE_C_COMPILER=/usr/local/opt/llvm@15/bin/clang&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;-DCMAKE_CXX_COMPILER=/usr/local/opt/llvm@15/bin/clang++&amp;quot;&lt;/span&gt;
    &lt;span class="cp"&gt;# Rest of the formula proceeds as usual&lt;/span&gt;
    &lt;span class="n"&gt;system&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;cmake&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;--build&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;build&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;You should be able to build snapcast on High Sierra.&lt;br&gt;
&lt;img alt="" src="images/over.jpg"&gt;&lt;br&gt;
Nothing like some brew bullshit to start off the year.  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; I'm of course using &lt;a href="https://github.com/Difegue/Stylophone"&gt;Stylophone&lt;/a&gt; by yours truly, but also &lt;a href="https://persephone.fm/"&gt;Persephone&lt;/a&gt; for macOS, &lt;a href="https://gitlab.com/gateship-one/malp"&gt;MALP&lt;/a&gt; for Android, and even a bit of &lt;a href="https://github.com/ncmpcpp/ncmpcpp"&gt;ncmpcpp&lt;/a&gt;!&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; I can already hear the wails of &lt;em&gt;"whyyy aren't you putting linux on it it's just x86"&lt;/em&gt;: I actually use the machine occasionally for 32bit mac software! Also, the &lt;a href="https://github.com/pedrommcarrasco/Brooklyn"&gt;Brooklyn screensaver&lt;/a&gt; is really neat and I like having it in my living room.&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-3"&gt;&lt;a href="#ref-3"&gt;***&lt;/a&gt; I initially tried compiling snapcast 0.26, but that &lt;a href="https://github.com/badaix/snapcast/issues/1082"&gt;doesn't work&lt;/a&gt; with the boost lib provided by brew, so make sure to use 0.27. &lt;/sup&gt;  &lt;/p&gt;</content><category term="Software"></category><category term="macos"></category><category term="snapcast"></category><category term="home automation"></category><category term="homebrew"></category><category term="reuse"></category></entry><entry><title>My 2024 Hobonichi Techo picks</title><link href="https://tvc-16.science/2024-techo.html" rel="alternate"></link><published>2023-12-20T00:00:00+01:00</published><updated>2023-12-20T00:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2023-12-20:/2024-techo.html</id><summary type="html">&lt;p&gt;Happy nothing special day!&lt;/p&gt;</summary><content type="html">&lt;p&gt;This wretched year has almost come to an end and I've done my yearly shopping spree on the &lt;a href="https://www.1101.com/store/techo/en/"&gt;Hobonichi Store&lt;/a&gt;.&lt;br&gt;
I grab a new planner and cover &lt;a href="./2023-techo.html"&gt;each year&lt;/a&gt;, alongside some goodies when the budget allows 🫠  &lt;/p&gt;
&lt;p&gt;My cover pick for this year is... the Tower of the Sun-inspired &lt;em&gt;&lt;a href="https://www.1101.com/store/techo/en/2024/pc/detail_cover/oc24_okamoto/"&gt;Golden Mask&lt;/a&gt;&lt;/em&gt; design!&lt;br&gt;
I wanted to get the &lt;a href="https://www.1101.com/store/techo/en/2024/pc/detail_cover/oc24_pamm/"&gt;PAMM collab&lt;/a&gt; design first, but it was out of stock by the time I placed my order.  &lt;/p&gt;
&lt;p&gt;I think I like &lt;em&gt;Golden Mask&lt;/em&gt; a bit more in the end though, it's rare to have a cover that flashy.&lt;br&gt;
Other designs of note this year are the mint-accented &lt;em&gt;&lt;a href="https://www.1101.com/store/techo/en/2024/pc/detail_cover/oc24_checkbk"&gt;Gingham&lt;/a&gt;&lt;/em&gt; and the stickerbombed &lt;a href="https://www.1101.com/store/techo/en/2024/pc/detail_cover/oc24_motherat/"&gt;yearly &lt;em&gt;MOTHER 2&lt;/em&gt;&lt;/a&gt; cover.  &lt;/p&gt;
&lt;p&gt;I grabbed a random FGO strap I got as a gift a while back to attach to the pen holder this year -- Mostly because the gold border matches the cover.&lt;br&gt;
&lt;img alt="2023 v 2024 techos" src="images/techo/2024.jpg"&gt;&lt;br&gt;
One of the major differences this year is that there are &lt;strong&gt;two&lt;/strong&gt; versions of the English Techo now; The usual one I've been using for a few years already now (&lt;a href="https://www.1101.com/store/techo/en/2024/all_about/planner/"&gt;Planner&lt;/a&gt;), and a 1-to-1 translation of the classic Japanese planner (&lt;a href="https://www.1101.com/store/techo/en/2024/all_about/original/"&gt;Original&lt;/a&gt;).&lt;br&gt;
It's a bit confusing saying it like that, but basically the only major difference is in the daily page layout...  &lt;/p&gt;
&lt;p&gt;And a &lt;em&gt;550 Yen price difference&lt;/em&gt;. Needless to say, I picked the Original this year.&lt;br&gt;
I actually quite like that its page layout has &lt;a href="https://www.1101.com/store/techo/2024/images/all_about/original_about04/zoom_04_2_of_2.jpg"&gt;premade checkmarks&lt;/a&gt; for to-do lists; That's a thing I often do with my daily pages to try and keep my brain organized, so it's a welcome extra.&lt;/p&gt;
&lt;p&gt;Stationery-wise, I wanted to try out the &lt;a href="https://www.1101.com/store/techo/en/2024/pc/detail_toolstoys/tt_stappo_dc/"&gt;stationery pouch&lt;/a&gt; they released last year... I like it a lot! Sure beats the old plastic box I was using to store my washi tapes and stamps.&lt;br&gt;
I also grabbed those &lt;a href="https://www.1101.com/store/techo/en/2024/pc/detail_toolstoys/s_peta/"&gt;transparent speech bubble stickers&lt;/a&gt;. The adhesive feels super weak when you peel them off but it sticks surprisingly well to paper. Used a few of 'em for christmas cards!&lt;br&gt;
&lt;img alt="Stappo stationery pouch and pretzel-powered bag of holding" src="images/techo/stappo.jpg"&gt;&lt;br&gt;
This year's &lt;a href="https://www.1101.com/store/techo/en/2024/benefit/"&gt;bonus item&lt;/a&gt; is a mini-tote bag you can fit the techo in -- Design is randomly picked out of four and I'm very glad to have gotten the objectively best design out of all of them. 🥨🥨🥨  &lt;/p&gt;
&lt;p&gt;I was tempted to get the bigass &lt;a href="https://www.1101.com/store/techo/en/2024/pc/detail_toolstoys/tt_stamp_opmdon/"&gt;One Piece onomatopeia stamp&lt;/a&gt; since it's just fun to use huge stamps..&lt;br&gt;
But I've never actually watched or read OP. ¯\_(ツ)_/¯  &lt;/p&gt;</content><category term="Techoposting"></category><category term="techo"></category><category term="hobonichi"></category><category term="stationery"></category></entry><entry><title>A most fast-fooded Christmas</title><link href="https://tvc-16.science/mcorigins-xmas.html" rel="alternate"></link><published>2023-12-08T00:01:00+01:00</published><updated>2023-12-08T00:01:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2023-12-08:/mcorigins-xmas.html</id><summary type="html">&lt;p&gt;Happy Holidays from me, the McDonald's Corporation, Yuji Naka and Tony Hawk.&lt;/p&gt;</summary><content type="html">&lt;p&gt;We're back with more McDonald's games at Christmas SAGE!&lt;br&gt;
It might not be the ones you wanted though...&lt;br&gt;
&lt;img alt="Tony Hawk's Sonic McOrigins Plus Christmas" src="./images/lcdonald/mcorigins_xmas_hero.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;&lt;a href="./mcorigins-plus.html"&gt;As promised&lt;/a&gt;, I used up all my &lt;em&gt;"I got one more in me"&lt;/em&gt; energy to give McOrigins the true shitpost sendoff it deserved.  &lt;/p&gt;
&lt;h3&gt;Tony Hawk's Sonic McOrigins Plus Christmas is now &lt;a href="https://sonicfangameshq.com/forums/showcase/tony-hawks-sonic-mcorigins-plus-christmas.1953/"&gt;available for download&lt;/a&gt; at Christmas SAGE! Merry Skatemas!&lt;/h3&gt;
&lt;p&gt;&lt;sup&gt;i'll update the itch.io builds later i swear...&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Changes include:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;ESPN Reskins&lt;/strong&gt; of the 2004 games, featuring everyone's favorite Sonic characters: &lt;em&gt;Tony Hawk, Vince Carter&lt;/em&gt;, and more!  &lt;ul&gt;
&lt;li&gt;The reskins are located as additional versions of the Sonic games they're based on.  &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Selecting a game will now randomly pick one of the variants when they're available  &lt;/li&gt;
&lt;li&gt;Small QoL improvements to the UI  &lt;/li&gt;
&lt;li&gt;A few more new manual scans  &lt;/li&gt;
&lt;li&gt;32bit Android support in case you are stuck in 2010 &lt;sub&gt;my condolences&lt;/sub&gt;  &lt;/li&gt;
&lt;li&gt;Added &lt;strong&gt;Endless Mode&lt;/strong&gt; -- How long can you withstand the difficulty (or absolute boredom) of &lt;strong&gt;&lt;em&gt;maximum speed McDonalds gameplay?&lt;/em&gt;&lt;/strong&gt;  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Please enjoy the trailer below as well.&lt;br&gt;
&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/Um-btPpXhfU?si=6Er9rzi9Z4OULhO1" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;This is not a very large update, but I keep forgetting how time-consuming scanning those LCD games are...&lt;br&gt;
I had a lot of fun making the advertising assets though!&lt;br&gt;
&lt;img alt="Dissecting an ESPN game" src="./images/lcdonald/voltagefun.jpg"&gt;&lt;br&gt;
I have a few ideas for one last "ultimate" update, but I don't think I'll get to it before 2025.  &lt;/p&gt;
&lt;p&gt;Also entirely related but by looking at the #mcdonalds hashtag on social media when writing my promo posts, I found out they're making a &lt;a href="https://www.bbc.co.uk/news/business-67644926"&gt;spinoff coffee chain&lt;/a&gt; with 80's branding?&lt;br&gt;
I never thought I'd see the day where McD's corporate design would stop being soulless...&lt;br&gt;
&lt;img alt="The name for the new brand comes from a McDonaldland mascot, an alien from outer space that craves its food, which appeared in adverts in the late 1980s and early 1990s." src="./images/lcdonald/cosmc.jpg"&gt;&lt;br&gt;
That, or 80s revival is so trendy now it has &lt;em&gt;become&lt;/em&gt; the soulless thing... But &lt;strong&gt;look at those sparkles god damn&lt;/strong&gt;  &lt;/p&gt;</content><category term="Software"></category><category term="sonic"></category><category term="mcdonalds"></category><category term="c#"></category><category term="avalonia"></category><category term=".net"></category><category term="lcd"></category><category term="game&amp;watch"></category><category term="video games"></category><category term="simulator"></category><category term="sage"></category><category term="christmas"></category></entry><entry><title>Using System XAML Islands in a MSIX-packaged .NET Framework app</title><link href="https://tvc-16.science/netfx-islands.html" rel="alternate"></link><published>2023-12-04T00:00:00+01:00</published><updated>2023-12-04T00:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2023-12-04:/netfx-islands.html</id><summary type="html">&lt;p&gt;MUX, WUX... more like FUX this shit eyy gotem&lt;/p&gt;</summary><content type="html">&lt;p&gt;Here's a &lt;em&gt;"I want this to pop up in SEO results despite all the AI garbage flooding search results"&lt;/em&gt; article I wish I had on hand.  &lt;/p&gt;
&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/xaml-islands"&gt;XAML Islands&lt;/a&gt; is the catch-all term for the technologies that allow you to host WinRT/UWP user interface elements in apps built with other desktop frameworks, like WPF and al.  &lt;/p&gt;
&lt;p&gt;Since nothing about modern Windows UI is simple, there are &lt;strong&gt;two&lt;/strong&gt; versions of XAML Islands:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;one embedded into the OS/platform XAML (&lt;a href="https://learn.microsoft.com/en-us/uwp/api/windows.ui.xaml.hosting?view=winrt-22621"&gt;&lt;code&gt;Windows.UI.Xaml.Hosting&lt;/code&gt;&lt;/a&gt;),  &lt;/li&gt;
&lt;li&gt;one included as part of the &lt;em&gt;Windows App SDK&lt;/em&gt;. (&lt;a href="https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.hosting"&gt;&lt;code&gt;Microsoft.UI.Xaml.Hosting&lt;/code&gt;&lt;/a&gt;)  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This post will only talk about &lt;code&gt;Windows.UI.Xaml.Hosting&lt;/code&gt;.  &lt;/p&gt;
&lt;p&gt;The easiest way to use XAML Islands in a WPF (or Winforms) app remains the &lt;a href="https://github.com/CommunityToolkit/Microsoft.Toolkit.Win32"&gt;Microsoft.Toolkit.Win32 package&lt;/a&gt; -- It's been archived as Microsoft wants you to use &lt;code&gt;Microsoft.UI.Xaml.Hosting&lt;/code&gt;, but there's no convenient WPF integration for that just yet.  &lt;/p&gt;
&lt;p&gt;Now, if like me you tend to read Microsoft documentation as gospel, you might think that this package won't work on &lt;a href="https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/xaml-islands#not-supported"&gt;.NET Framework codebases&lt;/a&gt;...  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;XAML Islands are supported only in apps that target .NET Core 3.x  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But that's wrong! It is merely &lt;em&gt;unsupported&lt;/em&gt;. The &lt;a href="https://www.nuget.org/packages/Microsoft.Toolkit.Wpf.UI.Controls"&gt;package itself&lt;/a&gt; works perfectly fine on both &lt;code&gt;netcore3&lt;/code&gt; and &lt;code&gt;netfx&lt;/code&gt;&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt;.&lt;br&gt;
Well, not quite perfectly fine -- there are a number of pitfalls on .NET Framework I'll try to cover here.  &lt;/p&gt;
&lt;h2&gt;Application Manifest&lt;/h2&gt;
&lt;p&gt;The XAML islands toolchain in the Toolkit &lt;strong&gt;needs&lt;/strong&gt; your app to have a &lt;a href="https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests"&gt;manifest&lt;/a&gt;, as it will &lt;a href="https://github.com/CommunityToolkit/Microsoft.Toolkit.Win32/issues/258#issuecomment-721421236"&gt;inject&lt;/a&gt; a &lt;code&gt;maxversiontested Id='10.0.18362.0'&lt;/code&gt; attribute into it to ensure it works with XAML Islands&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;You really don't need much in said manifest (and you can add the attribute yourself), here's a sample:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;assembly&lt;/span&gt; &lt;span class="na"&gt;manifestVersion=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;1.0&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;urn:schemas-microsoft-com:asm.v1&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;assemblyIdentity&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;1.0.0.0&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;MyApplication.app&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;trustInfo&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;urn:schemas-microsoft-com:asm.v2&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;security&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;requestedPrivileges&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;urn:schemas-microsoft-com:asm.v3&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!-- UAC Manifest Options&lt;/span&gt;
&lt;span class="c"&gt;             If you want to change the Windows User Account Control level replace the &lt;/span&gt;
&lt;span class="c"&gt;             requestedExecutionLevel node with one of the following.&lt;/span&gt;

&lt;span class="c"&gt;        &amp;lt;requestedExecutionLevel  level=&amp;quot;asInvoker&amp;quot; uiAccess=&amp;quot;false&amp;quot; /&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;        &amp;lt;requestedExecutionLevel  level=&amp;quot;requireAdministrator&amp;quot; uiAccess=&amp;quot;false&amp;quot; /&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;        &amp;lt;requestedExecutionLevel  level=&amp;quot;highestAvailable&amp;quot; uiAccess=&amp;quot;false&amp;quot; /&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;            Specifying requestedExecutionLevel element will disable file and registry virtualization. &lt;/span&gt;
&lt;span class="c"&gt;            Remove this element if your application requires this virtualization for backwards&lt;/span&gt;
&lt;span class="c"&gt;            compatibility.&lt;/span&gt;
&lt;span class="c"&gt;        --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;requestedExecutionLevel&lt;/span&gt; &lt;span class="na"&gt;level=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;asInvoker&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;uiAccess=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;false&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/requestedPrivileges&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/security&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/trustInfo&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;compatibility&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;urn:schemas-microsoft-com:compatibility.v1&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;application&amp;gt;&lt;/span&gt;
      &lt;span class="c"&gt;&amp;lt;!-- A list of the Windows versions that this application has been tested on&lt;/span&gt;
&lt;span class="c"&gt;           and is designed to work with. Uncomment the appropriate elements&lt;/span&gt;
&lt;span class="c"&gt;           and Windows will automatically select the most compatible environment. --&amp;gt;&lt;/span&gt;

      &lt;span class="c"&gt;&amp;lt;!-- Windows 10 --&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;supportedOS&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;maxversiontested&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;10.0.18362&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;/application&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/compatibility&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;application&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;urn:schemas-microsoft-com:asm.v3&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;windowsSettings&amp;gt;&lt;/span&gt;
      &lt;span class="c"&gt;&amp;lt;!-- Use PerMonitorV2, default to PerMonitor on systems where V2 isn&amp;#39;t available. This order is required to use System XAML Islands correctly. --&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;dpiAwareness&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://schemas.microsoft.com/SMI/2016/WindowsSettings&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;PerMonitorV2, PerMonitor&lt;span class="nt"&gt;&amp;lt;/dpiAwareness&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/windowsSettings&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/application&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/assembly&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h2&gt;MSIX Packaging&lt;/h2&gt;
&lt;p&gt;If you're shipping your app in a MSIX/Appx package for Microsoft Store usage, you need to include the following files in your package:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;Microsoft.Toolkit.Wpf.UI.Controls.dll&lt;/span&gt;
&lt;span class="err"&gt;Microsoft.Toolkit.Wpf.UI.XamlHost.dll&lt;/span&gt;
&lt;span class="err"&gt;Microsoft.Toolkit.Win32.UI.XamlHost.dll&lt;/span&gt;
&lt;span class="err"&gt;Microsoft.Toolkit.Win32.UI.XamlHost.winmd&lt;/span&gt;
&lt;span class="err"&gt;Microsoft.Toolkit.Win32.UI.XamlHost.Managed.dll&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Well, so far nothing out of the ordinary -- But the app might still &lt;strong&gt;crash&lt;/strong&gt; once packaged, complaining about a Windows Runtime type missing. &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Could not find Windows Runtime type Microsoft.Toolkit.Win32.UI.XamlHost.IXamlMetadataContainer  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Why would that be?  &lt;/p&gt;
&lt;p&gt;There's an &lt;a href="https://github.com/dotnet/wpf/issues/1290#issuecomment-512944811"&gt;obscure bug&lt;/a&gt; with MSIX packages where if you're including WinMDs (Windows Runtime metadata), they &lt;strong&gt;must be at the root of the package&lt;/strong&gt;. Otherwise, they just.. won't be made available to your app.&lt;br&gt;
&lt;img alt="wow" src="images/wow.jpg"&gt;&lt;br&gt;
I personally just use raw filemaps with &lt;code&gt;MakeAppx&lt;/code&gt; instead of relying on Windows Application Packaging projects as it's frankly easier, but the github link above has csproj steps you can use:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;  &lt;span class="c"&gt;&amp;lt;!-- Stomp the path to application executable.&lt;/span&gt;
&lt;span class="c"&gt;    This task will copy the main exe to the appx root folder.&lt;/span&gt;
&lt;span class="c"&gt;   --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_StompSourceProjectForWapProject&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;BeforeTargets=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_ConvertItems&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
      &lt;span class="c"&gt;&amp;lt;!-- Stomp all &amp;quot;SourceProject&amp;quot; values for all incoming dependencies to flatten the package. --&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;_TemporaryFilteredWapProjOutput&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;@(_FilteredNonWapProjProjectOutput)&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;_FilteredNonWapProjProjectOutput&lt;/span&gt; &lt;span class="na"&gt;Remove=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;@(_TemporaryFilteredWapProjOutput)&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;_FilteredNonWapProjProjectOutput&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;@(_TemporaryFilteredWapProjOutput)&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="c"&gt;&amp;lt;!-- Blank the SourceProject here to vend all files into the root of the package. --&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;SourceProject&amp;gt;&amp;lt;/SourceProject&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/_FilteredNonWapProjProjectOutput&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And after all that mess... You should finally have your UWP controls!  &lt;/p&gt;
&lt;p&gt;Was it worth it? Probably not, you can't even use Windows 11 styling/WinUI 2 unless you start dabbling in &lt;a href="https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/framework-packages/use-the-dynamic-dependency-api"&gt;Dynamic Dependencies.&lt;/a&gt;&lt;sup id="ref-3"&gt;&lt;a href="#note-3"&gt;***&lt;/a&gt;&lt;/sup&gt;  &lt;/p&gt;
&lt;h2&gt;Bonus round: MediaPlayerElement + AdaptiveMediaSource&lt;/h2&gt;
&lt;p&gt;This is a small extra thing that doesn't really warrant a separate post... One of the major reasons you'd want to use XAML Islands is for the UWP &lt;a href="https://learn.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.mediaplayerelement?view=winrt-22621"&gt;MediaPlayerElement&lt;/a&gt; (the others being the Map or InkCanvas stuff).  &lt;/p&gt;
&lt;p&gt;&lt;img alt="MediaPlayerElement in its natural habitat" src="https://github.com/MicrosoftDocs/windows-dev-docs/raw/docs/hub/apps/design/controls/images/controls/mtc_double_video_inprod.png"&gt;  &lt;/p&gt;
&lt;p&gt;The Toolkit package comes with a wrapper for MediaPlayerElement that allows you to simply set an URL as a &lt;code&gt;MediaSource&lt;/code&gt;; But if you want to use an &lt;a href="https://learn.microsoft.com/en-us/samples/microsoft/windows-universal-samples/adaptivestreaming/"&gt;AdaptiveMediaSource&lt;/a&gt; instead to customize bitrate selection or something else, it won't let you.  &lt;/p&gt;
&lt;p&gt;In their infinite wisdom, the Toolkit devs thought they'd be helpful and add a &lt;a href="https://github.com/CommunityToolkit/Microsoft.Toolkit.Win32/blob/9c1463e328a33168d0b0e7c7bea975838f35128f/Microsoft.Toolkit.Wpf.UI.Controls/MediaPlayerElement/MediaPlayerElement.cs#L61"&gt;custom converter&lt;/a&gt; to automatically map URLs to &lt;code&gt;MediaSource&lt;/code&gt;s in XAML... Except that converter will &lt;a href="https://github.com/CommunityToolkit/Microsoft.Toolkit.Win32/blob/9c1463e328a33168d0b0e7c7bea975838f35128f/Microsoft.Toolkit.Wpf.UI.Controls/MediaPlayerElement/MediaSourceConverter.cs#L25"&gt;crash the app&lt;/a&gt; if you use &lt;code&gt;AdaptiveMediaSource&lt;/code&gt;, which doesn't have a &lt;code&gt;Uri&lt;/code&gt; property.  &lt;/p&gt;
&lt;p&gt;As it stands, the best solution I found is to duplicate the MediaPlayerElement control and just remove this binding at line 61. ¯\_(ツ)_/¯  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; 
Aand it's broken in .NET 5 and above as those CLRs &lt;a href="https://github.com/dotnet/runtime/issues/35318"&gt;don't support .winmds&lt;/a&gt; natively anymore... You can still hack it with cswinrt but it's probably not worth it tbh&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; which is frankly speaking a terrible idea?? why wouldn't you just document that developers need to add the attribute instead of bothering to do it yourself? Almost makes you think there's some conspiracy on the Microsoft side to document &lt;code&gt;app.manifest&lt;/code&gt; files as little as humanly possible &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-3"&gt;&lt;a href="#ref-3"&gt;***&lt;/a&gt; which also has two different implementations i cant take this anymore &lt;/sup&gt;  &lt;/p&gt;</content><category term="Cool Tricks"></category><category term="xaml islands"></category><category term="wpf"></category><category term="uwp"></category><category term="C#"></category><category term="windows"></category><category term="windows 11"></category><category term="net framework"></category><category term="msix"></category><category term="very dangerous windows hack age 18 and up content"></category></entry><entry><title>SAGE videogame grab bag</title><link href="https://tvc-16.science/sage-roundup.html" rel="alternate"></link><published>2023-10-01T00:00:00+02:00</published><updated>2023-10-01T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2023-10-01:/sage-roundup.html</id><summary type="html">&lt;p&gt;local man compiles all his reviews into one megapost&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://sagexpo.org/index.html"&gt;SAGE 2023&lt;/a&gt; has ended, and I did good on my previous &lt;a href="./mcorigins-plus"&gt;promise&lt;/a&gt; by playing a &lt;strong&gt;heckuva lot&lt;/strong&gt; of games.  &lt;/p&gt;
&lt;p&gt;Here are some notable ones I've played, in case you might be interested!  &lt;/p&gt;
&lt;h2&gt;&lt;a href="https://sonicfangameshq.com/forums/showcase/gatoslip-chapter-0.1703/"&gt;Gatoslip&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img alt="" src="./images/games/sage/gato.jpg"&gt;&lt;br&gt;
I liked this a lot, topdown RPGs on megadrive always make me think about &lt;a href="https://en.wikipedia.org/wiki/Crusader_of_Centy"&gt;Soleil/Centy&lt;/a&gt;, although the Deltarune inspirations are much more obvious -- Having sonicy platformer sequences instead of shmup is really fun.  &lt;/p&gt;
&lt;h2&gt;&lt;a href="https://sonicfangameshq.com/forums/showcase/b-u-d-d-sage-23-demo.1740/"&gt;B.U.D.D&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img alt="" src="./images/games/sage/budd.png"&gt;&lt;br&gt;
This game truly wears its influences on its sleeve and reminds me I should finish that playthrough of Popful Mail&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt; I started all those years ago.&lt;/p&gt;
&lt;p&gt;Combat system is fun without being too overbearing as things stand, I think it'll be in a pretty good spot once the few missing moves are in the full game.&lt;br&gt;
The SA2-inspired combo meter made me smile but I usually don't get more than a 5 or 6x combo outside of the training room 🥲&lt;/p&gt;
&lt;p&gt;Really great pixel art too! Look at this &lt;em&gt;blessed fake email client.&lt;/em&gt; 🙏 The image loading effect is simple but effective.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://sonicfangameshq.com/forums/showcase/susan-taxpayer-orientation-build.1887/"&gt;Susan Taxpayer&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img alt="" src="./images/games/sage/susan.jpg"&gt;&lt;br&gt;
I &lt;strong&gt;really&lt;/strong&gt; dig the theming of this, 90's americana office settings are becoming a lost artform and I love how the soundtrack/SFX plays along. (&lt;a href="https://www.youtube.com/watch?v=NVLAPYx0dc8"&gt;tada.wav&lt;/a&gt; playing when you get every golden paperclip is chefs kiss tier shit)&lt;/p&gt;
&lt;p&gt;The &lt;a href="./games-february"&gt;Pizza Tower&lt;/a&gt; comparisons are inevitable considering the artstyle and the WL4 inspiration, but this game is much closer to Wario Land speedwise.  &lt;/p&gt;
&lt;p&gt;Input windows are a bit tight for some moves right now and the demo doesn't show all the planned powerups, so I'm interested to see what comes next.  &lt;/p&gt;
&lt;h2&gt;&lt;a href="https://sonicfangameshq.com/forums/showcase/sondro-gomez-sage-23.1676/"&gt;Sondro Gomez&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img alt="" src="./images/games/sage/gomez.png"&gt;&lt;br&gt;
Art direction is A+, both with the ingame sprites and the cutscenes/outside art.&lt;br&gt;
This has 3 playable characters, each going through variants of the same levels with (heavily) tweaked music&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt;/level design.  &lt;/p&gt;
&lt;p&gt;Despite looking like Castlevania it's nowhere near as stiff, the game ends up reminding me more of Ducktales due to the variety of moves available.  &lt;/p&gt;
&lt;h2&gt;&lt;a href="https://sonicfangameshq.com/forums/showcase/sonic-usb-online.1771/"&gt;Sonic USB Online&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img alt="" src="./images/games/sage/usb.png"&gt;&lt;br&gt;
The Sonic CD design philosophy cranked up to the absolute maximum, how the hell does this have so much content packed in I'm never getting through all of this &lt;em&gt;WHY IS ECCO GETTING IN THE SPECIAL STAGE RING&lt;/em&gt;  &lt;/p&gt;
&lt;p&gt;I like the emphasis on badnik bounce combos to get extra XP, even though I'm not sure what the XP actually does...
Game is all &lt;a href="https://www.youtube.com/watch?v=_sv2g0eOzW0"&gt;vibes&lt;/a&gt; though, I really dig the &lt;a href="https://www.youtube.com/watch?v=Sk-yGZu_Yy4"&gt;soundtrack&lt;/a&gt; and aesthetics overall.&lt;/p&gt;
&lt;p&gt;(the online features are ok? I guess? it's mostly people trying to figure out how to unlock characters)&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://sonicfangameshq.com/forums/showcase/gunburst.1894/"&gt;Gunburst&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img alt="" src="./images/games/sage/gunburst.jpeg"&gt;&lt;br&gt;
Small 1-boss demo, played it on a real MegaDrive for kicks.&lt;br&gt;
Game looks great! I could &lt;strong&gt;not&lt;/strong&gt; beat that boss.  &lt;/p&gt;
&lt;p&gt;The bounce attacks are way too unpredictable for a game with 1-hit kills.. I hope this gets completed though!  &lt;/p&gt;
&lt;h2&gt;&lt;a href="https://sonicfangameshq.com/forums/showcase/sonic-and-the-gunslinger.1629/"&gt;Sonic and the Gunslinger&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img alt="absolutely not a saturn game i swear" src="./images/games/sage/gunslinger.jpg"&gt;&lt;br&gt;
&lt;a href="https://www.youtube.com/watch?v=4wzSw2iuk_8"&gt;&lt;strong&gt;2023 YEAR OF FANG THE SNIPER YEAAAAAAAAAAAH&lt;/strong&gt;  &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I haven't played &lt;a href="https://www.srb2.org/"&gt;SRB2&lt;/a&gt; in close to 20 years at this point and it's crazy to see the kind of stuff that can be built with it now...&lt;/p&gt;
&lt;p&gt;I really like the attempt at making this feel like a storybook game with multiple characters, it worked really well imo.
Levels are fun&lt;sup id="ref-3"&gt;&lt;a href="#note-3"&gt;***&lt;/a&gt;&lt;/sup&gt; to go through, and I appreciate the attempt at giving each sonic level its own small gimmick to keep things fresh.&lt;/p&gt;
&lt;h2&gt;&lt;a href="https://sonicfangameshq.com/forums/showcase/bun-n-gun-23-demo.1734/"&gt;Bun 'N Gun&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img alt="bun" src="https://sonicfangameshq.com/forums/attachments/screenshots3-png.22754/"&gt;&lt;br&gt;
Large levels to speed in while scavenger hunting for objectives.&lt;br&gt;
It's incredibly fun to just zip around the physics-based playground knowing the grappling hook can always save you even if you dash straight into a pit.  &lt;/p&gt;
&lt;p&gt;The objective-based gameplay? Not as good sadly, targets are too small and having to capture them is a bore -- I think making them a bit larger and just having the player shoot them would help a lot in keeping the pace going.  &lt;/p&gt;
&lt;h2&gt;&lt;a href="https://sonicfangameshq.com/forums/showcase/project-32x.1785/"&gt;Project 32X&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img alt="" src="./images/games/sage/32x.jpg"&gt;&lt;br&gt;
An attempt at remaking &lt;a href="http://info.sonicretro.org/Sonic_Mars"&gt;Sonic Mars.&lt;/a&gt;&lt;br&gt;
I appreciate the attempt in trying to make it look like a 32x game with the reduced draw distance and all, instead of reproducing chris' &lt;a href="http://info.sonicretro.org/File%3ASxc_sonicdemo1.gif"&gt;mockup&lt;/a&gt; &lt;a href="http://info.sonicretro.org/File%3ASxc_sonicdemo2.gif"&gt;movies&lt;/a&gt; 1:1.&lt;br&gt;
Not much going on for the moment, but might be cool if it gets finished.  &lt;/p&gt;
&lt;h2&gt;&lt;a href="https://sonicfangameshq.com/forums/showcase/swolemochao-1-2-gym-of-the-year-edition.1660"&gt;Swolemochao 1&amp;amp;2&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img alt="art is nice tho" src="https://sonicfangameshq.com/forums/attachments/07-04-at-14-00-13-uxir22tinc-png.21839/"&gt;&lt;br&gt;
i have a mechanical keyboard and the constant clacking while playing this made my wife leave me&lt;br&gt;
9/10  &lt;/p&gt;
&lt;h2&gt;&lt;a href="https://sonicfangameshq.com/forums/showcase/game-cycle.1649"&gt;Game Cycle&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img alt="I'm aware I'm being pedantic but wow the pixel scaling on this is super off" src="https://sonicfangameshq.com/forums/attachments/newscreen1-png.21757/"&gt;&lt;br&gt;
This has a fake operating system, so of course i'm checking it out! This is a selection of faux-shareware games to play.   &lt;/p&gt;
&lt;p&gt;I really like the concept of playtesting/having different challenges for each game - It reminds me of the Game Center CX NDS games&lt;sup id="ref-4"&gt;&lt;a href="#note-4"&gt;#&lt;/a&gt;&lt;/sup&gt; which had similar mini-challenges to keep you playing those faux-retro games.  &lt;/p&gt;
&lt;p&gt;&lt;sup&gt;Except the games here are not very engaging...&lt;/sup&gt;&lt;br&gt;
I feel like there should be at least a few standout titles, while I can totally understand throwing a few intentional simple games for that authentic &lt;em&gt;90s shareware CD compilation&lt;/em&gt; experience, none of the three games in the demo really made me want to keep playing.  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; And actually try Monster World, watch Dirty Pair, etc etc you get the gist&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; The music theme got a bit boring hearing 3 variants of it in a row... I don't think this would be a problem in the final version where you'd play other levels before coming back to level 1&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-3"&gt;&lt;a href="#ref-3"&gt;***&lt;/a&gt; except those water wheel jumps, incredibly painful even with the bubble shield :') &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-4"&gt;&lt;a href="#ref-4"&gt;#&lt;/a&gt; Which are getting a &lt;a href="https://www.youtube.com/watch?v=gBbEI8Pzan0"&gt;rerelease compilation&lt;/a&gt; on Switch with updated 3D graphics and &lt;strong&gt;new games&lt;/strong&gt;??? I don't think this'll get an english release sadly &lt;/sup&gt;  &lt;/p&gt;</content><category term="Blogposting"></category><category term="video games"></category><category term="gamedev"></category><category term="sage"></category><category term="sonic"></category><category term="fangames"></category><category term="demo"></category><category term="faux-OS"></category></entry><entry><title>The Stylophone 2.6 Update is here!</title><link href="https://tvc-16.science/stylophone-26.html" rel="alternate"></link><published>2023-09-20T00:00:00+02:00</published><updated>2023-09-20T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2023-09-20:/stylophone-26.html</id><summary type="html">&lt;p&gt;Thank you, Ryuichi Sakamoto.&lt;/p&gt;</summary><content type="html">&lt;p&gt;It's been... &lt;a href="./stylophone-25.html"&gt;a year&lt;/a&gt; once again! Wow! &lt;br&gt;
Now that the &lt;a href="./funtography"&gt;gamedev&lt;/a&gt; side of the hustle has quieted down a bit, it's time to get back into the app business.  &lt;/p&gt;
&lt;p&gt;In this hectic period of iOS17 app updates, I'm happy to deliver... the &lt;strong&gt;&lt;em&gt;iOS16&lt;/em&gt; update&lt;/strong&gt; for Stylophone&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt;. 🤲&lt;br&gt;
&lt;code&gt;v2.6&lt;/code&gt; is a fairly large update, fixing some longstanding bugs for both iOS and Windows/Xbox.  &lt;/p&gt;
&lt;p&gt;The iOS version has freed itself from the shackles of &lt;em&gt;old&lt;/em&gt; Xamarin and is now... on &lt;a href="https://github.com/xamarin/xamarin-macios/wiki/.NET-release-notes-Xcode-13.3"&gt;&lt;em&gt;new&lt;/em&gt; Xamarin&lt;/a&gt;, AKA .NET 7.  &lt;/p&gt;
&lt;p&gt;The Windows version hasn't budged at all since I still want to support Xboxes&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt;.&lt;br&gt;
It still got some nice new UI improvements thanks to the recent &lt;a href="https://devblogs.microsoft.com/ifdef-windows/announcing-windows-community-toolkit-v8-0/"&gt;Windows Community Toolkit&lt;/a&gt; update, however!  &lt;/p&gt;
&lt;p&gt;&lt;img alt="Stylophone 2.6 on iPhone" src="https://tvc-16.science/images/stylophone/v26-iphone.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;&lt;em&gt;iOS/iPadOS&lt;/em&gt; received the blunt of the UI work this time - iPhone users should enjoy much more usable table views now, with edit mode enabled for &lt;strong&gt;quick reorder/deletion&lt;/strong&gt;.&lt;br&gt;
I added a bunch of missing Narrator/VoiceOver hints following reports by a visually impaired user to both versions. Kinda wish I'd gone over that earlier considering how easy it was..&lt;/p&gt;
&lt;p&gt;There's some new MPD feature support as well, most notably the new &lt;code&gt;playlistdelete&lt;/code&gt; range functionality!&lt;br&gt;
I have made a matching &lt;a href="https://www.nuget.org/packages/MpcNET/"&gt;MpcNET&lt;/a&gt; nuget release, in case you want to use that.  &lt;/p&gt;
&lt;h3&gt;As usual, the apps can be downloaded from both the &lt;a href="https://www.microsoft.com/store/apps/9NCB693428T8?cid=storebadge&amp;amp;ocid=badge"&gt;Microsoft Store&lt;/a&gt; and the &lt;a href="https://apps.apple.com/us/app/stylophone/id1644672889?itsct=apps_box_link&amp;amp;itscg=30200"&gt;App Store.&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I hope you enjoy the updates! Free for existing users as always. Here's the full changelog:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Shared&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Fix&lt;/span&gt; &lt;span class="n"&gt;potential&lt;/span&gt; &lt;span class="n"&gt;failure&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;GetColor&lt;/span&gt; &lt;span class="n"&gt;crashing&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;album&lt;/span&gt; &lt;span class="n"&gt;display&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;39&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;hostname&lt;/span&gt; &lt;span class="n"&gt;support&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;MPD&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Use&lt;/span&gt; &lt;span class="ss"&gt;&amp;quot;albumsort&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;instead&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;album&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;listing&lt;/span&gt; &lt;span class="n"&gt;albums&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;Library&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Use&lt;/span&gt; &lt;span class="ss"&gt;&amp;quot;albumartist&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;album&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt;
    &lt;span class="n"&gt;Use&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;MPD&lt;/span&gt; &lt;span class="n"&gt;playlistdelete&lt;/span&gt; &lt;span class="n"&gt;range&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;removing&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;playlists&lt;/span&gt;

&lt;span class="n"&gt;UWP&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="n"&gt;Migrate&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;Windows&lt;/span&gt; &lt;span class="n"&gt;Community&lt;/span&gt; &lt;span class="n"&gt;Toolkit&lt;/span&gt; &lt;span class="n"&gt;v8&lt;/span&gt; &lt;span class="err"&gt;🎊&lt;/span&gt;
    &lt;span class="n"&gt;Fix&lt;/span&gt; &lt;span class="n"&gt;alternate&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="n"&gt;colors&lt;/span&gt; &lt;span class="n"&gt;being&lt;/span&gt; &lt;span class="n"&gt;broken&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;Windows&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt; &lt;span class="n"&gt;machines&lt;/span&gt;
    &lt;span class="n"&gt;Shadows&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="n"&gt;been&lt;/span&gt; &lt;span class="n"&gt;revamped&lt;/span&gt; &lt;span class="n"&gt;across&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;nicer&lt;/span&gt; &lt;span class="n"&gt;segmented&lt;/span&gt; &lt;span class="n"&gt;control&lt;/span&gt; &lt;span class="n"&gt;has&lt;/span&gt; &lt;span class="n"&gt;been&lt;/span&gt; &lt;span class="n"&gt;added&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="k"&gt;Search&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Settings&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;small&lt;/span&gt; &lt;span class="n"&gt;facelift&lt;/span&gt;
    &lt;span class="n"&gt;Fix&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;loading&lt;/span&gt; &lt;span class="n"&gt;bar&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="n"&gt;showing&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;cover&lt;/span&gt; &lt;span class="n"&gt;art&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;downloading&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;an&lt;/span&gt; &lt;span class="n"&gt;album&lt;/span&gt;
    &lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;playback&lt;/span&gt; &lt;span class="n"&gt;slider&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="n"&gt;properly&lt;/span&gt; &lt;span class="n"&gt;controllable&lt;/span&gt; &lt;span class="n"&gt;via&lt;/span&gt; &lt;span class="n"&gt;keyboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="n"&gt;should&lt;/span&gt; &lt;span class="k"&gt;no&lt;/span&gt; &lt;span class="n"&gt;longer&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="n"&gt;suspended&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;Windows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;This&lt;/span&gt; &lt;span class="n"&gt;fixes&lt;/span&gt; &lt;span class="n"&gt;various&lt;/span&gt; &lt;span class="n"&gt;issues&lt;/span&gt; &lt;span class="n"&gt;regarding&lt;/span&gt; &lt;span class="k"&gt;connection&lt;/span&gt; &lt;span class="n"&gt;stability&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;Xbox&lt;/span&gt; &lt;span class="n"&gt;background&lt;/span&gt; &lt;span class="n"&gt;functionality&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Try&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;catch&lt;/span&gt; &lt;span class="n"&gt;potential&lt;/span&gt; &lt;span class="k"&gt;exception&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;double&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;tap&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;play&lt;/span&gt;
    &lt;span class="n"&gt;Added&lt;/span&gt; &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="n"&gt;Narrator&lt;/span&gt; &lt;span class="n"&gt;hints&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;playback&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;iOS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="n"&gt;has&lt;/span&gt; &lt;span class="n"&gt;migrated&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NET7&lt;/span&gt; &lt;span class="err"&gt;🎊&lt;/span&gt; &lt;span class="n"&gt;Now&lt;/span&gt; &lt;span class="n"&gt;requires&lt;/span&gt; &lt;span class="n"&gt;iOS16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="k"&gt;Table&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="n"&gt;been&lt;/span&gt; &lt;span class="n"&gt;reworked&lt;/span&gt; &lt;span class="n"&gt;across&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="k"&gt;show&lt;/span&gt; &lt;span class="k"&gt;more&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;compact&lt;/span&gt; &lt;span class="k"&gt;mode&lt;/span&gt;
    &lt;span class="n"&gt;Fixed&lt;/span&gt; &lt;span class="k"&gt;Table&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt; &lt;span class="n"&gt;getting&lt;/span&gt; &lt;span class="n"&gt;resized&lt;/span&gt; &lt;span class="n"&gt;incorrectly&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;phones&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;switching&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;portrait&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;landscape&lt;/span&gt; &lt;span class="n"&gt;multiple&lt;/span&gt; &lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;You&lt;/span&gt; &lt;span class="n"&gt;can&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="n"&gt;directly&lt;/span&gt; &lt;span class="n"&gt;tap&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;play&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;add&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;all&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="k"&gt;old&lt;/span&gt; &lt;span class="n"&gt;multiselect&lt;/span&gt; &lt;span class="n"&gt;behavior&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;album&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;playlist&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt; &lt;span class="n"&gt;has&lt;/span&gt; &lt;span class="n"&gt;been&lt;/span&gt; &lt;span class="n"&gt;retired&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="k"&gt;time&lt;/span&gt; &lt;span class="n"&gt;being&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Queue&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;Playlists&lt;/span&gt; &lt;span class="n"&gt;can&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="n"&gt;edited&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;reordered&lt;/span&gt;
    &lt;span class="n"&gt;Fix&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;bug&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;visiting&lt;/span&gt; &lt;span class="n"&gt;multiple&lt;/span&gt; &lt;span class="n"&gt;album&lt;/span&gt; &lt;span class="n"&gt;pages&lt;/span&gt; &lt;span class="n"&gt;would&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;background&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;clicking&lt;/span&gt; &lt;span class="ss"&gt;&amp;quot;Add to Queue&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;Fixed&lt;/span&gt; &lt;span class="n"&gt;navigation&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;phones&lt;/span&gt; &lt;span class="n"&gt;always&lt;/span&gt; &lt;span class="n"&gt;taking&lt;/span&gt; &lt;span class="n"&gt;you&lt;/span&gt; &lt;span class="n"&gt;back&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;Queue&lt;/span&gt; &lt;span class="k"&gt;before&lt;/span&gt; &lt;span class="n"&gt;showing&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;Sidebar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Rework&lt;/span&gt; &lt;span class="n"&gt;NowPlaying&lt;/span&gt; &lt;span class="k"&gt;View&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;take&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="n"&gt;safe&lt;/span&gt; &lt;span class="n"&gt;insets&lt;/span&gt; &lt;span class="n"&gt;properly&lt;/span&gt;
    &lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="k"&gt;Add&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;Playlist&lt;/span&gt; &lt;span class="n"&gt;dialog&lt;/span&gt; &lt;span class="n"&gt;has&lt;/span&gt; &lt;span class="n"&gt;been&lt;/span&gt; &lt;span class="n"&gt;retooled&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;take&lt;/span&gt; &lt;span class="k"&gt;less&lt;/span&gt; &lt;span class="n"&gt;empty&lt;/span&gt; &lt;span class="k"&gt;space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Non&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="n"&gt;notifications&lt;/span&gt; &lt;span class="k"&gt;are&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="k"&gt;less&lt;/span&gt; &lt;span class="n"&gt;intrusive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;73&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Added&lt;/span&gt; &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="n"&gt;VoiceOver&lt;/span&gt; &lt;span class="n"&gt;hints&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;playback&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h1&gt;Closing thoughts&lt;/h1&gt;
&lt;p&gt;&lt;img alt="good ol' vsmac.." src="https://tvc-16.science/images/stylophone/vsmac.png"&gt;&lt;br&gt;
Working on the iOS version of Stylophone means I get to pull out &lt;strong&gt;Visual Studio for Mac&lt;/strong&gt;... Which is &lt;a href="https://devblogs.microsoft.com/visualstudio/visual-studio-for-mac-retirement-announcement/"&gt;dying soon.&lt;/a&gt;&lt;br&gt;
I'm gonna miss the thing!  &lt;/p&gt;
&lt;p&gt;Sure, it's the red-headed stepchild of regular Visual Studio, built off the back of &lt;a href="https://github.com/mono/monodevelop/"&gt;MonoDevelop&lt;/a&gt; without contributing sources back... But I got into macOS/iOS development with it and for all its weird faults and bugs, it was &lt;em&gt;fine&lt;/em&gt; and had a decent workflow for Xamarin-based projects.  &lt;/p&gt;
&lt;p&gt;It feels especially wasteful considering they &lt;a href="https://devblogs.microsoft.com/visualstudio/visual-studio-2022-for-mac-is-now-available/"&gt;revamped large swaths&lt;/a&gt; of it &lt;strong&gt;last year&lt;/strong&gt;!&lt;br&gt;
🤨 What the fuck is Microsoft even &lt;em&gt;doing&lt;/em&gt;? Did they just fire most of the devs in their godawful layoffs and couldn't keep it going?  &lt;/p&gt;
&lt;p&gt;&lt;img alt="Please do not dunk on my music tastes too hard" src="https://tvc-16.science/images/stylophone/v25-catalyst.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;On the flipside, having moved to NET7 gives me access to Mac Catalyst builds of Stylophone again...&lt;br&gt;
There are &lt;a href="https://github.com/danbee/persephone"&gt;great native&lt;/a&gt; MPD clients for macOS already, but I might release that as a freebie version later.  &lt;/p&gt;
&lt;p&gt;Maybe even with iOS17 goodies thrown in!  &lt;/p&gt;
&lt;p&gt;...If the xamarin platform doesn't keel over and die in the meantime&lt;sup id="ref-3"&gt;&lt;a href="#note-3"&gt;***&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; I really wanted to use &lt;a href="https://bendodson.com/weblog/2023/07/26/tipkit-tutorial/"&gt;TipKit&lt;/a&gt;, but the xamarin bindings probably won't be ready for another few months... Them be the woes of cross-platform development.&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; which hopefully will be more usable now that I'm using the ol' silent .wav technique to prevent the app from being background killed 😎😎&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-3"&gt;&lt;a href="#ref-3"&gt;***&lt;/a&gt; Also my mac mini doesn't support Ventura and I already had to hack the minversion to get Xcode 14.3 to run sooo &lt;/sup&gt;  &lt;/p&gt;</content><category term="Software"></category><category term="mpd"></category><category term="xamarin"></category><category term="dotnet"></category><category term="c#"></category><category term="uwp"></category><category term="windows"></category><category term="xbox"></category><category term="ios"></category><category term="app store"></category><category term="music"></category><category term="client"></category><category term="stylophone"></category></entry><entry><title>Sonic McOrigins Plus at SAGE 2023!</title><link href="https://tvc-16.science/mcorigins-plus.html" rel="alternate"></link><published>2023-09-01T00:01:00+02:00</published><updated>2023-09-01T00:01:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2023-09-01:/mcorigins-plus.html</id><summary type="html">&lt;p&gt;to be this good takes a double big mac and some mcnuggets on the side&lt;/p&gt;</summary><content type="html">&lt;h1&gt;The Sonic McOrigins timeline&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="./lcdonald.html"&gt;Original announcement&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;&lt;a href="./mcorigins.html"&gt;2022 SAGE release&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;2023 "Plus" SAGE release (You are here)  &lt;/li&gt;
&lt;li&gt;&lt;a href="./mcorigins-xmas.html"&gt;Tony Hawk's McOrigins Plus Christmas&lt;/a&gt;  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Check &lt;a href="./mcorigins-xmas.html"&gt;here&lt;/a&gt; for the latest McOrigins-related post (and download links 🍟)  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;Is 2023 the year of the LCD game revival? If &lt;a href="https://dukope.com/devlogs/papers-please/lcdplease/"&gt;Lucas Pope&lt;/a&gt; is any indication, it might just be.&lt;br&gt;
Last year's &lt;a href="./mcorigins.html"&gt;Sonic McOrigins&lt;/a&gt; release was quite loved and people keep &lt;a href="https://www.youtube.com/watch?v=v6Ox9-IVB6I"&gt;covering it&lt;/a&gt;, but it was incomplete in a few ways.  &lt;/p&gt;
&lt;p&gt;Most notably, it was missing a few games and manuals...&lt;br&gt;
The internet's hunger&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt; for &lt;em&gt;Shadow Basketball&lt;/em&gt; could not be contained any longer.&lt;br&gt;
&lt;img alt="Hero image for &amp;quot;Sonic McOrigins Plus&amp;quot;, the v2 release of my LCD game simulator. The photo shows a Sonic-themed Happy Meal Box lying on its side with a lot of LCD games pouring out from it, including an Android phone running the simulator. The tagline at the bottom is styled like 2000s Happy Meal adverts and says &amp;quot;to be this good takes ages&amp;quot;, alongside some era-appropriate OS logos for Win/Mac/Linux/Android. The sticker on the top left of the photo is the Sonic Boom &amp;quot;Inspired by the video games!&amp;quot; meme, which I totally didn't put there to hide my dodgy photoshop skills hahaha...." src="images/lcdonald/mcorigins_plus_hero.jpg"&gt;  &lt;/p&gt;
&lt;h3&gt;Sonic McOrigins Plus&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt; is now &lt;a href="https://sonicfangameshq.com/forums/showcase/sonic-mcorigins-plus.1602/"&gt;available for download&lt;/a&gt; at SAGE 2023!&lt;/h3&gt;
&lt;p&gt;And itch.io this time around as well.&lt;br&gt;
&lt;iframe frameborder="0" src="https://itch.io/embed/2243408?link_color=ed8a35" width="552" height="167"&gt;&lt;a href="https://difegue.itch.io/sonic-mcorigins-plus"&gt;Sonic McOrigins Plus by dfug&lt;/a&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p&gt;Changes since the original release include:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mouse/touch support for handling the games&lt;/li&gt;
&lt;li&gt;Added missing games for a total of &lt;strong&gt;20&lt;/strong&gt;: Shadow Hockey, Amy/Rouge Volleyball and Shadow Basketball&lt;/li&gt;
&lt;li&gt;Layout improvements with some more OG McDonald's assets&lt;sup id="ref-3"&gt;&lt;a href="#note-3"&gt;***&lt;/a&gt;&lt;/sup&gt; &lt;/li&gt;
&lt;li&gt;Updated manual scans for Knuckles' Treasure Hunt and AiAi Banana Catch&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;Android&lt;/strong&gt; port&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Please enjoy the trailer below as well.  &lt;/p&gt;
&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/HDJdoqBUhGM?si=qb-_0qy7dgpcD3dF" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen&gt;&lt;/iframe&gt;

&lt;h1&gt;What's next?&lt;/h1&gt;
&lt;p&gt;On the short term? &lt;/p&gt;
&lt;h3&gt;&lt;em&gt;Nothing&lt;/em&gt;! I'm gonna go play some vidya james at &lt;a href="https://sagexpo.org/"&gt;SAGE&lt;/a&gt; and so should you.&lt;/h3&gt;
&lt;p&gt;If you'd be interested in something not-Sonic related from me, maybe you'd fancy looking at &lt;a href="./funtography.html"&gt;Funtography&lt;/a&gt;, my &lt;em&gt;GB Cameradventure game&lt;/em&gt;? It's also at SAGE!  &lt;/p&gt;
&lt;p&gt;Regarding the McOrigins project itself...  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;I'm &lt;strong&gt;not&lt;/strong&gt; doing any other Sonic LCD games - Leave that to the &lt;a href="https://archive.org/details/hh_tsonic"&gt;pros!&lt;/a&gt;  &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;An iOS port is &lt;strong&gt;unlikely&lt;/strong&gt; since Avalonia doesn't support iOS  &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I'm &lt;strong&gt;not&lt;/strong&gt; doing any other McDonald's LCD games since only Sonic could've motivated me to put 2 years of work into this  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://github.com/Difegue/LCDonald"&gt;code&lt;/a&gt; is all there and free for the taking though, so if you want to simulate the Crash or Spyro games for example, it'd be possible to reuse all of it with your own photos/scans/logic.  &lt;/li&gt;
&lt;li&gt;I've documented the process &lt;a href="./lcdonald.html"&gt;rather&lt;/a&gt; &lt;a href="./mcorigins.html"&gt;extensively&lt;/a&gt; at this point and wouldn't mind answering any questions. ✌️&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;One area I &lt;em&gt;might&lt;/em&gt; explore would be the &lt;a href="https://www.ebay.com/itm/313956178072"&gt;ESPN reskins&lt;/a&gt; of the 2004 series. &lt;sub&gt;It's funnier to me than it has any right to be tbh&lt;/sub&gt;  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There should be no logic to rewrite, it's just some more photo/scan work.  &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;And of course, work finding the missing &lt;a href="https://github.com/Difegue/LCDonald/issues"&gt;instruction manual scans&lt;/a&gt; continues.  &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All the hubbub around Apple's &lt;a href="https://developer.apple.com/visionos/"&gt;visionOS&lt;/a&gt; kinda makes me want to look at making a VR/3D version?&lt;br&gt;
I've talked about &lt;a href="https://www.emuvr.net/"&gt;emuVR&lt;/a&gt; before as an example, it'd be funny to have the games available as VRChat props or some stuff like that.  &lt;/p&gt;
&lt;p&gt;...But I don't have any form of VR rig atm so it's probably not happening. 🤷  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; "One thesis of the &lt;a href="https://maxread.substack.com/p/the-internet-is-for-12-year-olds?utm_source=TVC-16"&gt;Read Max newsletter&lt;/a&gt; is that a huge portion-- much more than you might imagine--of content produced on the internet [...] is consumed by 12-year-olds."&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; I was originally considering &lt;em&gt;Sonic McOrigins Ultimate&lt;/em&gt;, &lt;em&gt;Sonic McOrigins DX&lt;/em&gt;, or &lt;em&gt;Sonic McOrigins &amp;amp; Knuckles&lt;/em&gt;, but Sega went ahead and gave the disappointing DLC treatment to Origins so I didn't really have a choice&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-3"&gt;&lt;a href="#ref-3"&gt;***&lt;/a&gt; I considered using the &lt;a href="https://www.itsnicethat.com/articles/turner-duckworth-redesign-mcdonalds-branding-visual-identity-graphic-design-250719"&gt;Speedee typeface&lt;/a&gt; for the UI but ultimately decided against it -- It's not like that font was in use back when the games released anyway.&lt;/sup&gt;  &lt;/p&gt;</content><category term="Software"></category><category term="sonic"></category><category term="mcdonalds"></category><category term="c#"></category><category term="avalonia"></category><category term=".net"></category><category term="lcd"></category><category term="game&amp;watch"></category><category term="video games"></category><category term="simulator"></category><category term="sage"></category></entry><entry><title>Original Game Drop: Funtography</title><link href="https://tvc-16.science/funtography.html" rel="alternate"></link><published>2023-09-01T00:00:00+02:00</published><updated>2023-09-01T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2023-09-01:/funtography.html</id><summary type="html">&lt;p&gt;Who are you running from?&lt;/p&gt;</summary><content type="html">&lt;p&gt;As mentioned in that &lt;a href="./total-internet-hyperdeath.html"&gt;previous post&lt;/a&gt;, the SAGE Double Donk is &lt;strong&gt;real&lt;/strong&gt;!  &lt;/p&gt;
&lt;p&gt;I'm anxiously excited to release &lt;em&gt;Funtography: A Gameboy Cameradventure&lt;/em&gt;, an actual *gulp* &lt;em&gt;original&lt;/em&gt; video game.&lt;br&gt;
&lt;img alt="The SAGE trailer is a pretty cool wallpaper ngl - but you can change it if you want!" src="./images/funtography/ft2.png"&gt;  &lt;/p&gt;
&lt;h3&gt;Check it out at &lt;a href="https://sonicfangameshq.com/forums/showcase/funtography-a-gameboy-cameradventure.1861/"&gt;SAGE 2023&lt;/a&gt;! ... Or itch.io if that's more your speed.&lt;/h3&gt;
&lt;iframe frameborder="0" src="https://itch.io/embed/2243430" width="552" height="167"&gt;&lt;a href="https://difegue.itch.io/funtography-a-gameboy-cameradventure"&gt;Funtography: A Gameboy Cameradventure by dfug&lt;/a&gt;&lt;/iframe&gt;

&lt;p&gt;In this 2h-long &lt;sub&gt;&lt;em&gt;(1h if ur quick...)&lt;/em&gt;&lt;/sub&gt; adventure game, enjoy looking at &lt;strong&gt;100+ Gameboy Camera pictures&lt;/strong&gt; as you explore various urban vistas to solve the mystery of a &lt;em&gt;missing video game prototype&lt;/em&gt;.&lt;br&gt;
&lt;img alt="No vidcon at tubcon." src="./images/funtography/ft_final2.png"&gt;&lt;br&gt;
- Four endings!&lt;br&gt;
- Built-in &lt;strong&gt;developer commentary&lt;/strong&gt;!&lt;br&gt;
- Unlockable soundtrack!&lt;br&gt;
- &lt;strong&gt;Bonus fake operating system!&lt;/strong&gt;  &lt;/p&gt;
&lt;h3&gt;It's got it all!&lt;/h3&gt;
&lt;p&gt;&lt;img alt="i touch on this in the commentary but it's crazy how fast everyone forgot about this olive oil coffee fiasco. hi /agdg/!" src="./images/funtography/ft_final3.png"&gt;&lt;br&gt;
I hope you will consider taking a look at it in the midst of all the good-ass games releasing at SAGE.  &lt;/p&gt;
&lt;h1&gt;Backstory&lt;/h1&gt;
&lt;p&gt;The concept behind this game, &lt;code&gt;Visual novel that uses Gameboy Camera photos with varying palette and borders for extra context&lt;/code&gt; isn't really new&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt; - I tried building this thing close to &lt;a href="https://github.com/Difegue/shaftboy"&gt;10 years ago&lt;/a&gt; already.&lt;br&gt;
&lt;img alt="&amp;quot;Catastrophic project which will likely never get finished&amp;quot;, dated 2014 -- past me been real quiet since this dropped" src="./images/funtography/ft_old.png"&gt;&lt;br&gt;
I had a nice homegrown C++ engine with a barebones scripting system and all, ready to support the story... Then the &lt;em&gt;major blocker&lt;/em&gt; happened:  &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;"what kind of story should I even write, what the shit?"&lt;/strong&gt;  &lt;/p&gt;
&lt;p&gt;&lt;img alt="old shaftboy engine prototype screenshot - you can see that TV photo in the final game nowadays at last.." src="./images/funtography/ft_proto.png"&gt;  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Humans are natural story-tellers. Our brains are wired for storytelling, both in our enjoyment of experiencing stories, and our ability to create them. This fools us into thinking that storytelling is easy, which it’s not. &lt;br&gt;
&lt;sub&gt;&lt;a href="https://dreamertalin.medium.com/four-failures-1996-2002-5611b955f14"&gt;(Four Failures: 1996-2002)&lt;/a&gt;&lt;/sub&gt;  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So I shelved the whole thing, with a bunch of art assets, Gameboy photos and """music""" ready to go.&lt;br&gt;
&lt;img alt="this screen is from january 2022 god where does the time go" src="images/gamedev/jan_22_screen.jpg"&gt;&lt;br&gt;
As I was building out this fake operating system that could run self-contained small apps and games, the idea of reviving &lt;em&gt;Funtography&lt;/em&gt; kinda came up on its own.  &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;"If a goal for this fake OS game is to have unlockable smaller games within it... Why shouldn't I just reuse all those old assets I already had?"&lt;/strong&gt;  &lt;/p&gt;
&lt;p&gt;Then of course it ballooned out of control as I started writing -- The extra engine work and plot outline were made during the lockdowns after bingewatching &lt;em&gt;Twin Peaks&lt;/em&gt;&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt;, but most of the writing happened this year after work on &lt;a href="./dialogueforest.html"&gt;DialogueForest&lt;/a&gt;&lt;sup id="ref-3"&gt;&lt;a href="#note-3"&gt;***&lt;/a&gt;&lt;/sup&gt;  wrapped up.  &lt;/p&gt;
&lt;p&gt;Considering the result actually stands out on its own length-wise, I wanted to ship it early, as both a &lt;strong&gt;demo&lt;/strong&gt; of the existing fake OS&lt;sup id="ref-4"&gt;&lt;a href="#note-4"&gt;#&lt;/a&gt;&lt;/sup&gt;...and hopefully something compelling enough on its own.&lt;br&gt;
&lt;img alt="" src="./images/funtography/ft_final4.png"&gt;&lt;br&gt;
Since I'm not really a pro writer, I thought the best I could do would be to make it as earnest/genuine as I could -- Even if it ends up being a bit corny, I hope it at least makes for something more engaging than a ebin ironic story would be.  &lt;/p&gt;
&lt;p&gt;At least, I can pretty confidently say it's unique...Which is something I did &lt;a href="./2022-recap.html"&gt;mention wanting to do&lt;/a&gt;.&lt;br&gt;
And it does feel nice to have realized an old concept from years past! &lt;em&gt;I am cringe but I am free.&lt;/em&gt;  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; Or original for that matter -- Check out &lt;a href="https://pierrec.itch.io/pocket-puppet"&gt;Pocket Puppet&lt;/a&gt; if you want more Cameradventure! It even works on real hardware.&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; You can certainly see the influences in the final product - Not as much as I was expecting tho&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-3"&gt;&lt;a href="#ref-3"&gt;***&lt;/a&gt; Which of course, I did &lt;strong&gt;not&lt;/strong&gt; use while writing Funtography... I relied on &lt;a href="https://videdialogues.wordpress.com"&gt;VIDE Dialogues&lt;/a&gt; for Unity instead as that allowed me to integrate the SFX/palette/border changes better. Still ended up a mess&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-4"&gt;&lt;a href="#ref-4"&gt;#&lt;/a&gt; Slated for release 2035.. I want to keep working on the larger game but I feel some respite knowing that even if I give up on it now, at least some portion of it will have been used. &lt;/sup&gt;  &lt;/p&gt;</content><category term="Gamedev"></category><category term="video games"></category><category term="gamedev"></category><category term="unity"></category><category term="c#"></category><category term=".net"></category><category term="gameboy"></category><category term="gameboy camera"></category><category term="faux-OS"></category><category term="sage"></category></entry><entry><title>Quake 2 RTX comparison screenshots</title><link href="https://tvc-16.science/quake-rtx.html" rel="alternate"></link><published>2023-07-31T00:01:00+02:00</published><updated>2023-07-31T00:01:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2023-07-31:/quake-rtx.html</id><summary type="html">&lt;p&gt;Design is law.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="Quake 2 RTX screenshot" src="images/games/q2-1-off.jpg"&gt;&lt;img alt="Quake 2 RTX screenshot" src="images/games/q2-1-on.jpg"&gt;&lt;br&gt;
&lt;img alt="Quake 2 RTX screenshot" src="images/games/q2-2-off.jpg"&gt;&lt;img alt="Quake 2 RTX screenshot" src="images/games/q2-2-on.jpg"&gt;&lt;br&gt;
&lt;img alt="Quake 2 RTX screenshot" src="images/games/q2-3-off.jpg"&gt;&lt;img alt="Quake 2 RTX screenshot" src="images/games/q2-3-on.jpg"&gt;&lt;br&gt;
&lt;img alt="Quake 2 RTX screenshot" src="images/games/q2-4-off.jpg"&gt;&lt;img alt="Quake 2 RTX screenshot" src="images/games/q2-4-on.jpg"&gt;&lt;br&gt;
&lt;img alt="Quake 2 RTX screenshot" src="images/games/q2-5-off.jpg"&gt;&lt;img alt="Quake 2 RTX screenshot" src="images/games/q2-5-on.jpg"&gt;&lt;br&gt;
&lt;img alt="Quake 2 RTX screenshot" src="images/games/q2-6-off.jpg"&gt;&lt;img alt="Quake 2 RTX screenshot" src="images/games/q2-6-on.jpg"&gt;&lt;br&gt;
&lt;img alt="Quake 2 RTX screenshot" src="images/games/q2-7-off.jpg"&gt;&lt;img alt="Quake 2 RTX screenshot" src="images/games/q2-7-on.jpg"&gt;  &lt;/p&gt;</content><category term="Blogposting"></category><category term="video games"></category><category term="quake"></category><category term="rtx"></category><category term="quake 2"></category></entry><entry><title>March-July videogame grab bag</title><link href="https://tvc-16.science/games-july.html" rel="alternate"></link><published>2023-07-31T00:00:00+02:00</published><updated>2023-07-31T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2023-07-31:/games-july.html</id><summary type="html">&lt;p&gt;you could say I haven't &lt;em&gt;cranked&lt;/em&gt; one of those out in a while.&lt;/p&gt;</summary><content type="html">&lt;p&gt;...wow I totally meant to make more than &lt;a href="./games-february.html"&gt;one of those&lt;/a&gt; in the last 5 months but didn't get to it&lt;br&gt;
I'm writing this mostly because I want to use vscode's new &lt;a href="https://code.visualstudio.com/updates/v1_80#_markdown-format-pasted-urls-as-markdown-links"&gt;paste-as-markdown feature&lt;/a&gt;, which makes writing those blogposts a cinch!  &lt;/p&gt;
&lt;p&gt;Speaking of links, I've had the honor of Pixel &lt;a href="https://twitter.com/pigadev/status/1685636317982650369?s=20"&gt;linking to DoujinSoft&lt;/a&gt; recently to showcase his old 2010 WarioWare DIY game&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt;. As a cranky old nerd who played Cave Story back in its freeware days, it's great to see those small games keep living through the internet.  &lt;/p&gt;
&lt;p&gt;And speaking of small games and cranks..... &lt;sup&gt;&lt;sub&gt;damn, double transition? showoff&lt;/sup&gt;&lt;/sub&gt;  &lt;/p&gt;
&lt;h1&gt;Panic Playdate&lt;/h1&gt;
&lt;p&gt;&lt;img alt="A screenshot of Recommendation Dog, for the Panic Playdate" src="images/games/playdate-1.gif"&gt;  &lt;/p&gt;
&lt;p&gt;Group 4 representing -- I've finally received my &lt;a href="https://play.date/"&gt;Playdate&lt;/a&gt; console in the mail recently.&lt;br&gt;
It &lt;strong&gt;is&lt;/strong&gt; a lovely device, as expected buttons are nice, you can crank the crank and all that -- But what I've been liking most has to be the &lt;strong&gt;interface&lt;/strong&gt;.  &lt;/p&gt;
&lt;p&gt;It's all 1-bit dither&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt; patterns like you're playing &lt;em&gt;&lt;a href="https://hypercard.org/"&gt;Hypercard&lt;/a&gt;: The Console&lt;/em&gt;, but at a much smoother frame rate, and what the UI lacks in color, it more than makes up for in animation and style:&lt;br&gt;
🎁 New games show up in little present boxes like the 3DS/Wii U menus... Except the wrapping paper is customized for each game!&lt;br&gt;
🧶 The Settings menu has a breadcrumb navigation indicator, which each submenu having its own little icon when it shows up in the navigation.&lt;/p&gt;
&lt;p&gt;As for the games, I'm still waiting to get all my season 1 stuff, but I've had a lot of fun with &lt;em&gt;&lt;a href="https://play.date/games/casual-birder/"&gt;Casual Birder&lt;/a&gt;&lt;/em&gt;.&lt;br&gt;
&lt;em&gt;&lt;a href="https://play.date/games/crankin/"&gt;Crankin's Adventure&lt;/a&gt;&lt;/em&gt; is a game that could probably only exist on this device due to the fine analog control it requires...But the game itself hasn't grabbed me as much, it gets a bit too grindy and frustrating the further down the levels you go.&lt;br&gt;
&lt;img alt="A screenshot of Reel Steal, a game from the Playdate Catalog you can doodle in" src="images/games/playdate-2.gif"&gt;&lt;br&gt;
There are some nice free games in the &lt;a href="https://play.date/games/"&gt;Catalog/Store&lt;/a&gt;&lt;sup id="ref-3"&gt;&lt;a href="#note-3"&gt;***&lt;/a&gt;&lt;/sup&gt; as well now -- &lt;em&gt;&lt;a href="https://play.date/games/recommendation-dog/"&gt;Recommendation Dog&lt;/a&gt;&lt;/em&gt; and &lt;em&gt;&lt;a href="https://play.date/games/reel-steal/"&gt;Reel Steal&lt;/a&gt;&lt;/em&gt; are nice arcadey romps you should try.&lt;br&gt;
In another &lt;em&gt;probably-inspired-by-Nintendo-handhelds&lt;/em&gt; moment, the Store has its own &lt;a href="https://play.date/catalog/music"&gt;music.&lt;/a&gt; It's great&lt;sup id="ref-4"&gt;&lt;a href="#note-4"&gt;#&lt;/a&gt;&lt;/sup&gt;!  &lt;/p&gt;
&lt;h1&gt;Endless Monday: Dreams and Deadlines&lt;/h1&gt;
&lt;p&gt;A nice visual novel on Steam you should &lt;a href="https://endlessmonday.com/monthly/"&gt;consider buying&lt;/a&gt;.&lt;br&gt;
6 hours of reading, peppered with nice sub-games here and there, great pixel art, soundtrack is &lt;a href="https://chancethrash.bandcamp.com/track/cubical-for-two"&gt;good vibes&lt;/a&gt;. 👍  &lt;/p&gt;
&lt;p&gt;I'm more biased than usual on this one considering I've been following the creator &lt;a href="https://twitter.com/hcnone"&gt;on Twitter&lt;/a&gt; for a while, and you can totally see parallels between their real-life situation and the various plots in the game. &lt;br&gt;
&lt;img alt="look at this delightful pixelart corporate memphis... truly peak susan karecore" src="images/games/monday.jpg"&gt;&lt;br&gt;
It's also a very...current-feeling story at times? There's nothing super deep as things mostly remain comedic, but the game digs a bit into the consequences of AI&lt;sup id="ref-5"&gt;&lt;a href="#note-5"&gt;##&lt;/a&gt;&lt;/sup&gt; for current workforces, and how capital will drain you of resources and eventually come for your head if you let it run unchecked.&lt;br&gt;
&lt;img alt="diablo 4 in a nutshell" src="images/games/monday-2.jpg"&gt;&lt;br&gt;
I've talked previously about &lt;a href="./2022-recap.html"&gt;"art that is unabashedly sincere"&lt;/a&gt;, and I feel that game matches this -- It's very much something that could only be made in 2023 with those gnawing doubts about the future of artists in mind.&lt;/p&gt;
&lt;h1&gt;Quake 2&lt;/h1&gt;
&lt;p&gt;Full-on downgrade compared to Quake 1 holy hell&lt;br&gt;
Environments are worse, enemy design is boring, weapons are serviceable but that's it, and you can't break it in half by rocket/grenade launching 0/10&lt;br&gt;
&lt;img alt="rtx tim willits is not real he cannot hurt you" src="images/games/q2.jpg"&gt;&lt;br&gt;
I played the RTX port by toggling between the OpenGL and RTX renderers to see the differences.&lt;br&gt;
You could argue the RTX version breaks the mood of the game but it's not like it has much to begin with..&lt;br&gt;
Here are &lt;a href="./quake-rtx.html"&gt;some RTX off/on comparisons&lt;/a&gt; if you're interested.  &lt;/p&gt;
&lt;p&gt;My personal favorite has to be the nvidia logo slap-dashed in that one area you can rocketjump over to find a secret:&lt;br&gt;
&lt;img alt="nooo not the overpriced graphics card empire" src="images/games/q2-3-on.jpg"&gt;&lt;br&gt;
I like the attempt at making interconnected maps with objectives that require you to move back and forth, but it's not really exploited all that well until the final level, which feels a bit more like an actual dungeon.&lt;br&gt;
Oh and the fucking &lt;a href="https://www.youtube.com/watch?v=YBQqDZwSUrA"&gt;skewered dopefish&lt;/a&gt; easter egg which you can only find by backtracking?? This game has the cringiest easter eggs in all of id software history and I mean it    &lt;/p&gt;
&lt;p&gt;The soundtrack is of course a &lt;a href="https://www.youtube.com/watch?v=jY6yBTYxLko"&gt;banger&lt;/a&gt; everyone knows it, but I'm gonna have to go apologize to tim epic because Unreal was better than this mess&lt;/p&gt;
&lt;h1&gt;HROT&lt;/h1&gt;
&lt;p&gt;Oh lookie here, it's the better Quake 2!&lt;br&gt;
&lt;img alt="hit me with that slavsthetic NOW" src="images/games/hrot.jpg"&gt;&lt;br&gt;
Despite having an even blander palette (&lt;em&gt;it's all brown&lt;/em&gt;), this game manages to have quite the variety of environments, accompanied by rock-solid level design.&lt;br&gt;
There's quite the variety of enemies and bosses too! And the super shotgun is A+, as shrimple as that.&lt;/p&gt;
&lt;p&gt;Despite the advertised theme being "post-USSR horror freakshow gone wrong", the game actually gets &lt;em&gt;goofier&lt;/em&gt;&lt;sup id="ref-6"&gt;&lt;a href="#note-6"&gt;###&lt;/a&gt;&lt;/sup&gt; as the levels move on and you don't really get any kind of resolution out of the scraps of worldbuilding you're given...&lt;br&gt;
It's still a good time though!    &lt;/p&gt;
&lt;h1&gt;Slayers X: Terminal Aftermath: Vengance of the Slayer&lt;/h1&gt;
&lt;p&gt;Capping off with another FPS, spinoff of the excellent &lt;em&gt;Hypnospace Outlaw&lt;/em&gt;.&lt;br&gt;
&lt;img alt="this image weighs...420 kilobytes. woah......." src="images/games/slayers-2.jpg"&gt;&lt;br&gt;
Now, it doesn't have as much fake operating system-ness as &lt;em&gt;Outlaw&lt;/em&gt;, but it delivers in spades in being a magnificent shitpost homage to early 2000's culture.  &lt;/p&gt;
&lt;p&gt;The game...looks bad, but only when it &lt;em&gt;wants to&lt;/em&gt; -- There's real design and care here, and it walks that tightrope all the way to the end.&lt;br&gt;
There's something to be said about kamehameha-ing (literal)shit monsters with your sick &lt;code&gt;hacker powers&lt;/code&gt;, watching a cutscene with uncanny valley 3D rendering, then right afterwards finding a secret where the (fictional) single dad level designer put drawings by his three-year old in the map.     &lt;br&gt;
&lt;img alt="there's still &amp;quot;some&amp;quot; fake computer stuff tho look how sick this is" src="images/games/slayers-1.jpg"&gt;&lt;br&gt;
I really recommend this one, it's even on Game Pass right now if that's your thing.  &lt;/p&gt;
&lt;p&gt;Soundtrack is &lt;a href="https://www.youtube.com/watch?v=ZGo-MLA6YfU"&gt;triple A fucking incredible&lt;/a&gt;, &lt;s&gt;Jay Tholen&lt;/s&gt; &lt;em&gt;&lt;strong&gt;BIG Z. CORP&lt;/strong&gt;&lt;/em&gt; has done it again!&lt;br&gt;
It's also a &lt;strong&gt;dynamic&lt;/strong&gt; soundtrack with combat/stress/relaxed layers for some reason? I approve, even though the in-game mixing is sometimes a bit jank-&lt;span style="font-family:comic sans ms; font-size: 25px"/&gt;&lt;strong&gt;THE S-BLADE HAS A HACKBLOOD CHARGE&lt;/strong&gt;&lt;/span&gt;  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; Thankfully sakurai didn't do the same when &lt;a href="https://twitter.com/Sora_Sakurai/status/1589530503170109440?s=20"&gt;he reposted&lt;/a&gt; about his wwdiy game a few months ago, that'd have probably killed my bandwidth 🥲&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; The Gameboy Camera is a 2-bit photo device -- I should look into building the &lt;a href="https://hackaday.com/2023/07/20/game-boy-style-camera-for-playdate/"&gt;Playdate Camera&lt;/a&gt; addon.&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-3"&gt;&lt;a href="#ref-3"&gt;***&lt;/a&gt; I've thought the Playdate should have a Hudson &lt;a href="https://www.ign.com/wikis/hudson-shooting-watch/"&gt;Shooting Watch&lt;/a&gt;-inspired app due to the color scheme, and browsing the Catalog, someone apparently &lt;a href="https://play.date/games/mash-gadget/"&gt;had the same idea.&lt;/a&gt; There's also a &lt;a href="https://play.date/games/stars-of-the-screen/"&gt;fake OS game&lt;/a&gt; oh my god &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-4"&gt;&lt;a href="#ref-4"&gt;#&lt;/a&gt; The web version of the song has occasional advertisement cutoffs like a real store -- Except they're selling you other Panic software like &lt;a href="https://nova.app/"&gt;Nova&lt;/a&gt; and &lt;a href="https://panic.com/transmit/"&gt;Transmit&lt;/a&gt;. I can't hate it, but it is a bit distracting... &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-5"&gt;&lt;a href="#ref-5"&gt;##&lt;/a&gt; I don't like saying AI as those machine learning algorithms are anything but intelligent, but that's what everyone is running with these days..&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-6"&gt;&lt;a href="#ref-6"&gt;###&lt;/a&gt; There's a whole level about beating up giant newts as a reference to &lt;a href="https://en.wikipedia.org/wiki/War_with_the_Newts"&gt;this old czech book.&lt;/a&gt; I should read it!&lt;/sup&gt;  &lt;/p&gt;</content><category term="Blogposting"></category><category term="video games"></category><category term="game pass"></category><category term="playdate"></category><category term="quake 2"></category><category term="hrot"></category><category term="SLAYERS X TERMINAL REVERGEANSE"></category><category term="endless monday"></category><category term="soundtracks"></category></entry><entry><title>Total Internet Hyperdeath</title><link href="https://tvc-16.science/total-internet-hyperdeath.html" rel="alternate"></link><published>2023-07-02T00:00:00+02:00</published><updated>2023-07-02T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2023-07-02:/total-internet-hyperdeath.html</id><summary type="html">&lt;p&gt;A piece where I, in fact, do not talk about the total internet hyperdeath whatsoever&lt;/p&gt;</summary><content type="html">&lt;p&gt;Wow, it's been a hot minute! I'm writing this between farming race tickets in Sonic Speed Simulator&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt;  .&lt;br&gt;
While I'll never give money to anything touching Roblox, I do find some zen in repeatedly boosting away on a Sunday night...&lt;br&gt;
&lt;img alt="roblox have functional lightmaps challenge (impossible)" src="images/speedsim.jpg"&gt;&lt;br&gt;
So Web 2.0 is having a normal one at the moment or something - A lot of people have waxed poetic about the collapse of the corporate internet already so I don't feel like I could add anything particularly snarky to it...&lt;br&gt;
So how about reading something else? Here are some nice links I've stumbled upon recently.  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.bylinebyline.com/articles/no-vacancy-hotel"&gt;No Vacancy - A Night at the Motel&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;&lt;a href="https://docseuss.medium.com/i-beat-impostor-syndrome-and-heres-how-i-did-it-b6879422af2b"&gt;DocSeuss' 'I beat impostor syndrome (and here's how I did it)'&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.robinsloan.com/newsletters/sunshine-skyway/"&gt;Robin Sloan - Crossing the Sunshine Skyway&lt;/a&gt;  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I've also found this &lt;a href="https://www.youtube.com/watch?v=6qJe3rf-VFs"&gt;soundtrack project&lt;/a&gt; for a fictional PS1 megaten game a few days ago - Give it a listen if you're a fellow Devil Summoner enjoyer.  &lt;/p&gt;
&lt;h1&gt;A quick Devlog&lt;/h1&gt;
&lt;p&gt;I've finished up work on Sonic McOrigins' &lt;a href="https://elk.zone/kolektiva.social/@Difegue/110633333584428326"&gt;"Plus" update&lt;/a&gt; for SAGE a few months ago already, so I'm trying to go for a &lt;strong&gt;Double Donk&lt;/strong&gt; and get another game out the door before that deadline hits.  &lt;/p&gt;
&lt;p&gt;Well, I'm saying game but it's kinda like a demo as well?&lt;br&gt;
This fake OS I've been on-and-off building in Unity has a fair bit of meat to it already, including being able to run entire small applications/subgames.&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt;&lt;br&gt;
&lt;img alt="this screen is from january 2022 god where does the time go" src="images/gamedev/jan_22_screen.jpg"&gt;&lt;br&gt;
So I thought, why not take &lt;strong&gt;another&lt;/strong&gt; old game concept I never finished, and put it &lt;em&gt;in&lt;/em&gt; this OS?&lt;br&gt;
&lt;img alt="" src="images/gamedev/funto_screen1.png"&gt;&lt;br&gt;
And that's how I ended up with this Visual Novel in a multi-window UI like &lt;a href="https://macintoshgarden.org/games/pathways-darkness"&gt;old Mac OS games&lt;/a&gt;... Except it only uses &lt;a href="https://elk.zone/kolektiva.social/@Difegue/110018074118875940"&gt;Gameboy Camera&lt;/a&gt; pictures for its graphics and a terrible soundtrack I scrambled together years ago.&lt;br&gt;
I outlined most of the plot back in 2019, but as Doc said, filling out the blanks between all the setpieces your brain thought was cool... wow it's hard&lt;sup id="ref-3"&gt;&lt;a href="#note-2"&gt;***&lt;/a&gt;&lt;/sup&gt;  &lt;/p&gt;
&lt;p&gt;Even if I end up eventually trashing the bigger fake OS game because Unity 2019 falls out of support everywhere or something, it'd be nice to have at least shipped all this work in &lt;em&gt;some&lt;/em&gt; form.  &lt;/p&gt;
&lt;p&gt;I'll catch y'all in another "games i played post" or something later -- I played quake 2 recently and wow it's almost as boring as bluesky  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; 
Speaking of Sonic, I keep forgetting GSC is continuing the &lt;a href="http://whl4u.jp/wh36/gallery/en/#/images/181"&gt;Nendoroid series&lt;/a&gt; with Tails &amp;amp; Knuckles after &lt;strong&gt;years&lt;/strong&gt; -- Wonfes 36 was huge on non-animanga properties in general I wonder what 37 will bring&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; I've always been mad at &lt;a href="https://en.wikipedia.org/wiki/Digital:_A_Love_Story"&gt;Digital: A Love Story&lt;/a&gt; for showing you fake downloadable games and not having them actually do anything - That's probably one of my largest motivations with this thing. 🥸  &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-3"&gt;&lt;a href="#ref-3"&gt;***&lt;/a&gt; To go with the GBCam-piness(heh) of it all, the dialogue toggles different color palettes and borders for the gameboy photos as you progress through... Which makes the writing process take extra longer as I keep comparing color palettes. &lt;/sup&gt;  &lt;/p&gt;</content><category term="Gamedev"></category><category term="gamedev"></category><category term="faux-OS"></category></entry><entry><title>Introducing DialogueForest</title><link href="https://tvc-16.science/dialogueforest.html" rel="alternate"></link><published>2023-04-12T00:00:00+02:00</published><updated>2023-04-12T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2023-04-12:/dialogueforest.html</id><summary type="html">&lt;p&gt;An opinionated Dialogue Writing tool for Games.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Here's a new &lt;em&gt;certified dfugdrop&lt;/em&gt;™️! Let's not talk about how I left it dead for &lt;a href="https://twitter.com/Difegue/status/1488666105115824132"&gt;more than a year.&lt;/a&gt;  &lt;/p&gt;
&lt;p&gt;&lt;img alt="A screenshot of DialogueForest in light mode." src="images/dialogueforest/screenshot.png"&gt;  &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DialogueForest&lt;/strong&gt; is a (free!) Windows App built on WinUI 3 that allows you to write hierarchical rich text dialogue with custom characters and metadata, sprinkled with some basic productivity features like daily objectives/notifications. &lt;/p&gt;
&lt;p&gt;The reasoning behind this app is that I couldn't find a good tool to write medium-length branching dialogue for my burgeoning&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt; gamedev efforts.&lt;br&gt;
Most tools are based on node graphs, which very quickly become unmanageable&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt; if you're trying to write a lot of text.  &lt;/p&gt;
&lt;h3&gt;Check it out on &lt;a href="https://difegue.itch.io/dialogueforest"&gt;itch.io!&lt;/a&gt;&lt;/h3&gt;
&lt;iframe frameborder="0" src="https://itch.io/embed/2010301" width="552" height="167"&gt;&lt;a href="https://difegue.itch.io/dialogueforest"&gt;DialogueForest by dfug&lt;/a&gt;&lt;/iframe&gt;

&lt;p&gt;You can also browse the source code &lt;a href="https://github.com/Difegue/DialogueForest"&gt;here&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;I found out that tools like these are called Outliners - I recommend the following &lt;a href="https://rpg.stackexchange.com/questions/34816/tree-based-tool-to-create-dialog"&gt;SE question&lt;/a&gt; if you're looking for other tools like this one. I notably looked at &lt;a href="https://www.literatureandlatte.com/scrivener/overview"&gt;Scrivener&lt;/a&gt; for inspiration, but there's no way I was going to remake all of that...  &lt;/p&gt;
&lt;p&gt;You can read some more of my design/dev thoughts below, as always.  &lt;/p&gt;
&lt;h1&gt;The Icon and UX&lt;/h1&gt;
&lt;p&gt;I'm quite fond of the whole Dialogue Tree/Forest analogies, and wanted an icon that'd follow that kind of whimsy.  &lt;br&gt;
This one is very obviously inspired by the classic Mac &lt;a href="https://www.cnet.com/pictures/susan-kares-early-mac-icons-gave-computers-a-personality-photos/2/"&gt;Alert Icon&lt;/a&gt; with a dash of Whispy Woods -- I didn't think I had good enough 3D/Spline skills to make a "mascot" icon like all the cool guys do, but it turned out alright!&lt;br&gt;
&lt;img alt="wahoo" src="images/dialogueforest/icon_base.png"&gt;&lt;br&gt;
Compare it to this jank mockup I scribbled on last year's Hobonichi Techo:&lt;br&gt;
&lt;img alt="you can even see early UI designs behind it if you squint" src="images/dialogueforest/draft_icon.png"&gt;  &lt;/p&gt;
&lt;p&gt;Having a humanized icon makes the app fun, and the speech bubble makes it easy to re-use in a bunch of different scenarios as well:&lt;br&gt;
&lt;img alt="" src="images/dialogueforest/icons.png"&gt;&lt;br&gt;
&lt;sub&gt;&lt;sup&gt;The evening/night variants were planned for the welcome page to go alongside the time-based greeting, but I ultimately cut them 🤷&lt;/sub&gt;&lt;/sup&gt;  &lt;/p&gt;
&lt;p&gt;The app is laid out in a very personal way this time since I mostly built it for my own use.   &lt;/p&gt;
&lt;p&gt;I did aim for a three-column layout voluntarily though -- Those layouts have gotten quite popular on macOS, but I've rarely seen them on Windows so far? Even though I think they totally work in WinUI.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="damn that's packed son" src="images/dialogueforest/packed_ui.png"&gt;&lt;br&gt;
It's been a fun experiment to try and design a featureful/"crammed" WinUI app - You really can get a lot going in modern Windows apps now, even without the Compact controls.&lt;/p&gt;
&lt;h1&gt;Technical Thoughts&lt;/h1&gt;
&lt;p&gt;DialogueForest started life as an UWP app just like &lt;a href="./stylophone.html"&gt;Stylophone&lt;/a&gt;, but I moved it to &lt;a href="https://github.com/microsoft/WindowsAppSDK"&gt;Windows App SDK&lt;/a&gt; this year to try the platform out and see if it was usable to ship something.  &lt;/p&gt;
&lt;p&gt;Since you're reading this, I guess it was! It's still a bit rough&lt;sup id="ref-3"&gt;&lt;a href="#note-3"&gt;***&lt;/a&gt;&lt;/sup&gt; around the edges though...&lt;br&gt;
The experience kinda sucks for end-users when you look at the WinAppSDK runtime installer:&lt;br&gt;
&lt;img alt="A beautiful console window installing weird MSIX packages so you can use winappsdk" src="images/dialogueforest/wasdk.png"&gt;&lt;br&gt;
Now of course I couldve bothered to make a nice MSI/MSIX that'd install the runtimes on its own, but you're not gonna expect me to do this for a free app -- what's next, code signing?  &lt;/p&gt;
&lt;p&gt;I might eventually upload a packaged version to the MS Store anyway, maybe as a &lt;code&gt;"plus"&lt;/code&gt;&lt;sup id="ref-4"&gt;&lt;a href="#note-4"&gt;#&lt;/a&gt;&lt;/sup&gt; paid kind of deal with some junk like GPT integration that writes the dialogue for you? &lt;em&gt;everyone loves them LLMs i am a business genius&lt;/em&gt;  &lt;/p&gt;
&lt;p&gt;Rich Text support was both very easy thanks to included controls and a massive pain because nobody really works with RTF anymore. 🫠&lt;br&gt;
I did do the whole netstandard Core/Viewmodel thing &lt;a href="./stylophone-25.html"&gt;again&lt;/a&gt;, so a macOS version is certainly possible later down the line. I doubt I'll make one for something this niche though..  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; kindest word I can find for "not moving forward at all"&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; my best/previous functioning setup was an excel sheet with macros, so you can guess how messy that was&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-3"&gt;&lt;a href="#ref-3"&gt;***&lt;/a&gt; I'm very thankful for &lt;a href="https://github.com/dotMorten/WinUIEx"&gt;WinUIEx&lt;/a&gt;! &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-4"&gt;&lt;a href="#ref-4"&gt;#&lt;/a&gt; would be about as worth it as sonic origins plus, thanks for the game gear games again sega &lt;/sup&gt;  &lt;/p&gt;</content><category term="Software"></category><category term="dotnet"></category><category term="c#"></category><category term="gamedev"></category><category term="winui"></category><category term="windows"></category><category term="windows 11"></category><category term="winappsdk"></category><category term="outliner"></category><category term="dialogueforest"></category></entry><entry><title>LANraragi 0.8.9 ft. Holopin</title><link href="https://tvc-16.science/lrr-holopin.html" rel="alternate"></link><published>2023-03-19T00:00:00+01:00</published><updated>2023-03-19T00:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2023-03-19:/lrr-holopin.html</id><summary type="html">&lt;p&gt;It's just like gacha/steam trading cards, except you don't have to pay money!&lt;/p&gt;</summary><content type="html">&lt;p&gt;LANraragi v.0.8.9 just came out &lt;a href="https://github.com/Difegue/LANraragi/releases/tag/v.0.8.90"&gt;today!&lt;/a&gt;&lt;br&gt;
It's a surprisingly packed release with &lt;strong&gt;10&lt;/strong&gt; different people having worked on it, which is certainly a record as far as any OSS software I made goes.&lt;br&gt;
We finally have ComicInfo.xml support now! Alongside a few other nice improvements to search.  &lt;/p&gt;
&lt;p&gt;It's uncommon for me to get that many PRs... The LRR &lt;a href="https://github.com/Difegue/LANraragi#make-a-pr-get-stickers"&gt;free sticker promo&lt;/a&gt; has been a thing for a long time now, but most contributors don't take advantage of it.&lt;br&gt;
Since my sticker designs are objectively the coolest&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt;, it can only be because people are too kind and don't want me to pay for all that postage! Such acts of kindness go straight to my kokoro...&lt;br&gt;
&lt;img alt="A picture of the &amp;quot;LRR USER&amp;quot; badge on Holopin" src="images/holopin/pin_camel.png"&gt;&lt;br&gt;
But I'd still like to thank/reward contributors if I can help it, so to get rid of any physical-related troubles, we're going full metaverse 'n shit with &lt;a href="https://www.holopin.io/"&gt;Holopin&lt;/a&gt;! Wow! &lt;/p&gt;
&lt;p&gt;You can now get LANraragi stickers in virtual format and stick them on your GitHub profile...or everywhere! I'm just going to paste my badge board &lt;em&gt;here&lt;/em&gt; and you can't stop me from doing it&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt;:  &lt;/p&gt;
&lt;p&gt;&lt;img alt="An image of @difegue's Holopin badges" src="https://holopin.me/difegue" title="An image of @difegue's Holopin badges" /&gt;  &lt;/p&gt;
&lt;p&gt;While I don't actually want to turn my GH profile into social media, I do find the concept of a universal badge board for developers amusing, in the same way game achievements can be.  &lt;/p&gt;
&lt;p&gt;The fact I can use a bunch of previous graphic assets I made and write all sorts of nonsense in the descriptions also helps! My gold standard is TF2 item descriptions.   &lt;br&gt;
&lt;img alt="A picture of the &amp;quot;Burger Time&amp;quot; LRR badge on Holopin - yes this is a yuji naka reference" src="images/holopin/pin_burg.png"&gt;&lt;br&gt;
Stickers and other "swag" incentives ala Hacktoberfest are nice, but don't really feel very fun/indie anymore with all the corporation sponsorships going on in those.&lt;br&gt;
There are currently five LRR-related badges you can get as a Holopin user, either by snooping around a bit or by actively contributing to the LRR repository. I hope you'll consider giving &lt;a href="https://www.holopin.io/@lanraragi"&gt;it a look!&lt;/a&gt;&lt;br&gt;
&lt;img alt="A picture of the &amp;quot;DoujinSoft Truck&amp;quot; badge on Holopin" src="images/holopin/pin_truck.png"&gt;&lt;br&gt;
Now that I'm swimming in PRs thanks to those pngs, I can go back to working on personal &lt;a href="https://github.com/Difegue/LCDonald"&gt;passion projects&lt;/a&gt; that truly speak to my tamashii&lt;sup id="ref-3"&gt;&lt;a href="#note-3"&gt;***&lt;/a&gt;&lt;/sup&gt;:&lt;br&gt;
&lt;img alt="A picture of Sonic McOrigins running on Android/Surface Duo, featuring the elusive...Shadow Basketball" src="images/lcdonald/android.jpg"&gt;&lt;br&gt;
yes ha ha ha &lt;strong&gt;YES&lt;/strong&gt;  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; graphic design is my passion &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; Unless you use the internet with third-party JS turned off by default through ublock or some other extension, in which case yknow what? you're alright mate cheers&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-3"&gt;&lt;a href="#ref-3"&gt;***&lt;/a&gt; (tl note: soul) also android development actually makes me utterly miserable, but the people wanted it.. &lt;/sup&gt;  &lt;/p&gt;</content><category term="LANraragi"></category><category term="lanraragi"></category><category term="holopin"></category><category term="steam trading cards"></category><category term="hell yeah pngs"></category></entry><entry><title>January-February videogame grab bag</title><link href="https://tvc-16.science/games-february.html" rel="alternate"></link><published>2023-02-19T00:00:00+01:00</published><updated>2023-02-19T00:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2023-02-19:/games-february.html</id><summary type="html">&lt;p&gt;if the pizza tower soundtrack doesn't end up pressed on pizza-colored vinyl records i will be sorely disappointed&lt;/p&gt;</summary><content type="html">&lt;p&gt;Hey, here's that vidya james post I wanted to write before going off on a tangent about &lt;a href="./game-manuals.html"&gt;manuals!&lt;/a&gt;&lt;br&gt;
I had it kinda shelved, but the wacky woohoo pizza game got me wanting to write again, so let's get to it:  &lt;/p&gt;
&lt;h1&gt;Paradise Killer&lt;/h1&gt;
&lt;p&gt;This game was on my "clear it through game pass" list forever, but I'm glad I got to it.&lt;br&gt;
You could resume it as "vaporwave danganronpa" as far as gameplay goes I guess?&lt;br&gt;
&lt;img alt="A screenshot of Paradise Killer, in the civilian apartments at sunset" src="images/games/paradise-1.jpg"&gt;&lt;br&gt;
It's very much a different experience though, just massive chill vibes for a good couple of hours while you comb the island over to piece together the local murder mystery&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt;. It doesn't overstay its welcome, yet there's more than enough stuff to keep you entertained with minimal backtracking.  &lt;/p&gt;
&lt;p&gt;Just...get the movement upgrades fast, and dial the voice clip frequency &lt;strong&gt;way&lt;/strong&gt; down in the settings, lest you create your &lt;em&gt;own&lt;/em&gt; murder mystery after hearing &lt;em&gt;"The investigator is here!"&lt;/em&gt; for the tenth time in a conversation.&lt;br&gt;
&lt;img alt="Another screenshot of Paradise Killer. oh to be lounging on the beach alongside the obsidian obelisks looking at the alien pyramid in the far distance" src="images/games/paradise-2.jpg"&gt;&lt;br&gt;
The aesthetic is unparalleled in this and really contributes to the good vibes; This is one of those games I'd keep installed just to pop it in every now and then and walk around the environments.&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt;    &lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=Xfgbb0uXH4s"&gt;Also, the soundtrack is fantastic.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="NOSTALGIC SOUNDS. MEMORIES OF THE CAREFREE DAYS NOW LOST. even the music obtained screen goes so fucking hard on the vapor this should be illegal" src="images/games/paradise-3.jpg"&gt;  &lt;/p&gt;
&lt;h1&gt;SIGNALIS&lt;/h1&gt;
&lt;p&gt;I played this one a few months back already, but thought I'd still talk a bit about it.&lt;br&gt;
Survival horror with impeccable art direction, good gameplay and a story that feels like David Lynch wrote it after reading a bunch of Lovecraft books.  &lt;/p&gt;
&lt;p&gt;Some &lt;strong&gt;casuals&lt;/strong&gt; don't like the inventory management in this, but you can't really have a good horror game without the constant existential dread of not having enough space!&lt;/p&gt;
&lt;p&gt;Also there's a small fake operating system you can find near the end:&lt;br&gt;
&lt;img alt="A screenshot of &amp;quot;Postbox 4.3&amp;quot; from SIGNALIS - I'd post more of it but I don't want to spoil the game too much" src="images/games/signalis-1.jpg"&gt;&lt;br&gt;
We got that nice Windows 3.1 aesthetic with touches of 95, &lt;em&gt;good shit&lt;/em&gt;!  &lt;/p&gt;
&lt;p&gt;&lt;a href="https://signalis-ost.bandcamp.com/track/safe-room"&gt;Also, the soundtrack is fantastic.&lt;/a&gt;  &lt;/p&gt;
&lt;h1&gt;Digital Exorcist&lt;/h1&gt;
&lt;p&gt;A short Snatcher-inspired game on &lt;a href="https://coolbeansproductions.itch.io/digital-exorcist"&gt;itch.io.&lt;/a&gt;&lt;br&gt;
&lt;img alt="A screenshot of Digital Exorcist where the protag talks about SanctuariOS, &amp;quot;the holiest operating system&amp;quot;" src="images/games/digital_exorcist.jpg"&gt;&lt;br&gt;
&lt;sub&gt;&lt;sup&gt;TERRY DAVIS LIVES&lt;/sub&gt;&lt;/sup&gt;  &lt;/p&gt;
&lt;p&gt;I liked it a lot!&lt;br&gt;
Writing about demons crawling the internet tickles my Soul Hackers bone, so that always gets a thumbs up from me.&lt;br&gt;
It's only an hour or so, entirely worth the time -- wouldn't mind seeing more.  &lt;/p&gt;
&lt;p&gt;&lt;a href="https://djtakataka.bandcamp.com/track/battle-in-the-aethernet"&gt;Also, the soundtrack is pretty cool.&lt;/a&gt;  &lt;/p&gt;
&lt;h1&gt;Unreal&lt;/h1&gt;
&lt;p&gt;&lt;sub&gt;the game not the engine you dolts&lt;/sub&gt;  &lt;/p&gt;
&lt;p&gt;Having played Quake semi-recently through the remaster, I wanted to get back to Unreal and finally finish it!&lt;br&gt;
It was...kinda boring?  &lt;/p&gt;
&lt;p&gt;The game looks stunning and really showcases the large environments you could do on UE1... yet most of the combat loop is oriented towards shoving you in cramped hallways with ninja aliens. I wasn't asking for Serious Sam tier open area fights, but it'd have been nice to get a bit more of a balance.&lt;br&gt;
&lt;img alt="it do be looking good tho (screenshot from na pali haven kinoest map in the game)" src="images/games/unreal-1.jpg"&gt;&lt;br&gt;
Also some of the game design is fucking weird in this, what's the deal with the locked rooms with endless respawning enemies making you kill like 50 of them to proceed? The game didn't need padding, it's already 10 hours long!&lt;sup id="ref-3"&gt;&lt;a href="#note-3"&gt;***&lt;/a&gt;&lt;/sup&gt;&lt;br&gt;
I still think it was worth playing despite that, though. fuck you epic for delisting this   &lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=9IISjIMpepo&amp;amp;t=9256s"&gt;Also, the soundtrack is fantastic.&lt;/a&gt;&lt;br&gt;
You can never have enough Alex Brandon/Michiel van den Bos! It follows a similar structure to the Deus Ex soundtrack with dynamic pieces depending on what the level does, which is certainly one thing I'll give it over Quake.&lt;sup id="ref-4"&gt;&lt;a href="#note-4"&gt;#&lt;/a&gt;&lt;/sup&gt;    &lt;/p&gt;
&lt;h1&gt;Pizza Tower&lt;/h1&gt;
&lt;p&gt;This is it, the &lt;a href="https://store.steampowered.com/app/2231450/Pizza_Tower/"&gt;Wario Land where you go fast&lt;/a&gt;! I adore the artstyle of this game and can't imagine how many hours of work were poured into drawing all those unhinged italians. (Seriously, the amount of art assets in this 7h game? incredible)&lt;br&gt;
&lt;img alt="VERY...... A NICE!" src="images/games/pizza.png"&gt;&lt;br&gt;
I've thought a few times before of a combo system where you could keep a combo from the beginning to the end of a stage without dropping it, but always wondered how you'd make it fit with non-score attack aspects like finding secrets.&lt;br&gt;
How do you make those fun to find for normal players, while weaving them into your score attack so that the secrets aren't a one-time fun thing you do for completionism? Pizza Tower just fucking does it, &lt;strong&gt;and&lt;/strong&gt; nails it.  &lt;/p&gt;
&lt;p&gt;The ending is teased throughout the entire game so it doesn't come as a surprise when it lands, but the execution is so good you'll still love the absolute crap out of it.&lt;br&gt;
fucking goty we didn't even make it to march, pack it up zelda you're done  &lt;/p&gt;
&lt;p&gt;&lt;a href="https://ronandecastel.bandcamp.com/track/pumpin-hot-stuff"&gt;Also, the soundtrack is &lt;strong&gt;beyond&lt;/strong&gt; fantastic.&lt;/a&gt; It's got all my favorite samples in it!  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; Which isn't very complex but then again I wanted to chill, not unwind a komaeda-tier madhouse murder with this one&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; except you can't do that with gamepass god damn it phil fine i'll buy the game&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-3"&gt;&lt;a href="#ref-3"&gt;***&lt;/a&gt; And they had to cut a ton of shit from the game that ended up in the expansion pack! &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-4"&gt;&lt;a href="#ref-4"&gt;#&lt;/a&gt; sorry trent i still love you, I certainly recommend the &lt;a href="https://signalis-ost.bandcamp.com/track/mynah"&gt;signalis soundtrack&lt;/a&gt; again if you want some of that NiNcore &lt;/sup&gt;  &lt;/p&gt;</content><category term="Blogposting"></category><category term="video games"></category><category term="game pass"></category><category term="unreal"></category><category term="pizza tower"></category><category term="signalis"></category><category term="paradise killer"></category><category term="digital exorcist"></category><category term="faux-OS"></category><category term="soundtracks"></category></entry><entry><title>Bring back game manuals</title><link href="https://tvc-16.science/game-manuals.html" rel="alternate"></link><published>2023-01-25T00:00:00+01:00</published><updated>2023-01-25T00:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2023-01-25:/game-manuals.html</id><summary type="html">&lt;p&gt;just for old games tho, I don't care about your SOULLESS modern drivel........😤&lt;/p&gt;</summary><content type="html">&lt;p&gt;This was just going to be a post talking about Game Pass games I recently cleared in a hurry before my subscription&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt; ran out, but &lt;a href="https://en.wikipedia.org/wiki/Tunic_(video_game)"&gt;Tunic&lt;/a&gt; was one of those games, and then I watched a video essay about &lt;a href="https://www.youtube.com/watch?v=vs_HaPRm37A"&gt;the first Zelda&lt;/a&gt; so fuck it, I'm talking about game manuals!&lt;br&gt;
&lt;img alt="A page from the SM64 manual. Mario manuals always had those neat pictures of mario performing all the moves it's really good shit" src="images/games/sm64-manual.jpg"&gt;&lt;br&gt;
Game manuals faded away when video games started being offered as digital-only, since you couldn't really depend on paper anymore to explain how your game worked.&lt;br&gt;
That's perfectly fine since games are now designed to be played manual-less&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt;, but in a lot of cases before the sixth gen, manuals were often a &lt;strong&gt;necessary companion&lt;/strong&gt; to your video game experience.  &lt;/p&gt;
&lt;p&gt;Without even getting into meta uses like the &lt;a href="https://www.youtube.com/watch?v=2-JKpL8bK_A"&gt;StarTropics letter&lt;/a&gt; or the MGS Codec number&lt;sup id="ref-3"&gt;&lt;a href="#note-3"&gt;***&lt;/a&gt;&lt;/sup&gt;, manuals would often hold all the information the game couldn't carry itself. And the older the game, the worse it gets since 8-bit titles really didn't hold that much data!  &lt;/p&gt;
&lt;p&gt;The manual would contain the story, controls, hints and basic tricks to get started -- The &lt;a href="https://archive.org/details/LegendOfZeldaTheNESHiResScans"&gt;first Zelda&lt;/a&gt; is what got me thinking about this since it has all of the above, a map &lt;em&gt;and&lt;/em&gt; a tiny strategy guide. Without the manual, the game does feel cryptic even if you're a seasoned Zelda player...&lt;br&gt;
&lt;img alt="A page from Tunic's manual and another from the Zelda 1 manual" src="images/games/tunic-1.jpg"&gt;&lt;br&gt;
This is the part where I namedrop &lt;a href="https://www.rockpapershotgun.com/tunics-instruction-manual-and-the-zelda-art-that-inspired-it"&gt;Tunic again&lt;/a&gt;, since it reproduces this experience of playing a game where the manual is your companion and helps you travel through an otherwise completely alien land. Except that said manual is virtual now!&lt;br&gt;
I haven't felt that much joy reading a manual in quite a long time, so I can only recommand the game.  &lt;/p&gt;
&lt;p&gt;Surely companies would leverage this synergy between instruction booklet and game when re-releasing their old classics on newer consoles, right? I mean they already made the manuals back then, it's basically free real estate to pack them in!  &lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="images/1-2-hurts-just-a-little-bit.jpg"&gt; &lt;/p&gt;
&lt;p&gt;Of course not.&lt;/p&gt;
&lt;h1&gt;Electronic manuals suck&lt;/h1&gt;
&lt;p&gt;Most emulator re-releases usually pack in a barebones e-manual with the bare minimum to get you started.&lt;br&gt;
No map, no illustrations, almost no flavor text.&lt;br&gt;
And in some cases you don't even &lt;strong&gt;get&lt;/strong&gt; a manual anymore! Good luck finishing &lt;a href="https://www.reddit.com/r/NintendoSwitch/comments/b1qxg2/psa_the_nintendo_switch_online_version_of/"&gt;StarTropics&lt;/a&gt; now.&lt;br&gt;
&lt;img alt="A screenshot of the electronic manual for StarTropics on Wii U." src="images/games/elec-manual.png"&gt;&lt;br&gt;
It feels like an absolute waste for companies to not try and give the best experience possible to new players possibly going through these games for the first time. Of course it's easy to understand why:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It's probably more expensive&lt;sup id="ref-4"&gt;&lt;a href="#note-4"&gt;#&lt;/a&gt;&lt;/sup&gt; to find and scan an old manual than the entire cost of just chucking a ROM on the Store and making people pay $10 for it  &lt;/li&gt;
&lt;li&gt;For third-parties, you'd likely have to scrub the manual out of all information related to the console publisher, kinda like what WayForward did with the &lt;a href="https://www.youtube.com/watch?v=eZRzaGFWoz8"&gt;Shantae&lt;/a&gt; rerelease for Switch by removing mentions to Nintendo and Capcom from the game.  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So when it comes to video game preservation, are we stuck in a hell of &lt;em&gt;capitalist mediocrity&lt;/em&gt;  where our only salvation is unofficial efforts&lt;sup id="ref-5"&gt;&lt;a href="#note-5"&gt;##&lt;/a&gt;&lt;/sup&gt; by the common folk?&lt;br&gt;
Well yes, but there's one surprising exception: The &lt;strong&gt;Virtual Console&lt;/strong&gt; during the 3DS/Wii U era.&lt;br&gt;
&lt;img alt="The Wii U is an incredible console for retro emulation and you can't change my mind" src="images/games/wiiu-vc.jpg"&gt;&lt;br&gt;
While most releases there kept the crummy shorthand manuals, for some reason Nintendo saw the light and started including the original manuals for their N64/GBA/NDS/Wii Virtual Console releases and oh my god is that the &lt;a href="https://www.nintendo.co.jp/data/software/manual/man_pchj.pdf"&gt;ChuChu Rocket Japanese GBA manual?&lt;/a&gt; Look how &lt;code&gt;delightfully Y2K&lt;/code&gt; it is.   &lt;/p&gt;
&lt;p&gt;The Virtual Console had those occasional strokes of genius when it came to repackaging those old games&lt;sup id="ref-6"&gt;&lt;a href="#note-6"&gt;###&lt;/a&gt;&lt;/sup&gt; at times, but it really feels like this should be the norm.&lt;br&gt;
I'd rather have the original instruction booklet than a bunch of random promotional artwork thrown in a gallery in your re-release's menus, to be honest...  &lt;/p&gt;
&lt;p&gt;So I say, if you re-release a game, put in the manual as a pdf dangit!&lt;br&gt;
And give it a nice pagefilp animation, like what Tunic does, and what iOS used to do before Apple &lt;a href="https://www.theverge.com/2022/11/21/23471306/apple-books-ios-16-page-flip-animation-sucks"&gt;Jony Ive'd it.&lt;/a&gt;  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; I pay for my Game Pass with Microsoft Rewards points only -- Since all search engines resell your data, might as well get some of it back 💰&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; with copious amounts of tutorials in some cases i'm looking at you sonic frontiers&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-3"&gt;&lt;a href="#ref-3"&gt;***&lt;/a&gt; Sony actually does include Meryl's frequency in the virtual manual for MGS1 on PSN, but just like Startropics it's pretty much devoid of everything else... &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-4"&gt;&lt;a href="#ref-4"&gt;#&lt;/a&gt; Nintendo actually provides the &lt;a href="https://en-americas-support.nintendo.com/app/answers/detail/a_id/16881/~/downloadable-manuals"&gt;original PDFs&lt;/a&gt; for some manuals all the way back to the NDS, so it's likely they do have most of their manuals stashed in their secret mario vault. &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-5"&gt;&lt;a href="#ref-5"&gt;##&lt;/a&gt; While not manual-related, &lt;a href="https://www.emuvr.net/"&gt;EmuVR&lt;/a&gt; allows you to see cartridge/game disc art for your ROMs in VR, which I find really cool. &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-6"&gt;&lt;a href="#ref-6"&gt;###&lt;/a&gt; Did you know Pokémon Snap on the Wii Virtual Console has a feature to &lt;a href="https://www.serebii.net/snap/virtualconsole.shtml"&gt;copy your Pokémon photos&lt;/a&gt; to the Wii Message Board? Or that SMB3 GBA comes with all the e-Reader bonus levels unlocked? &lt;/sup&gt;  &lt;/p&gt;</content><category term="Physicality of Gaming"></category><category term="video games"></category><category term="xbox"></category><category term="game manuals"></category><category term="virtual console"></category><category term="preservation"></category></entry><entry><title>Converting a Mi Temperature and Humidity Monitor 2 to run off USB</title><link href="https://tvc-16.science/usb-mi-sensor.html" rel="alternate"></link><published>2023-01-20T00:00:00+01:00</published><updated>2023-01-20T00:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2023-01-20:/usb-mi-sensor.html</id><summary type="html">&lt;p&gt;It's all about balance.&lt;/p&gt;</summary><content type="html">&lt;p&gt;The recent &lt;a href="https://www.youtube.com/watch?v=bTpKM43VhwA"&gt;LTT video&lt;/a&gt; about converting battery-powered smart appliances to run on wired power instead reminded me that I did something similar recently for my &lt;a href="https://www.mi.com/pk/mi-temperature-and-humidity-monitor-2/"&gt;Mi Humidity sensor&lt;/a&gt; because I'm too cheap to buy a coin cell every year.&lt;br&gt;
&lt;img alt="A happy Mi monitor getting its current from USB" src="images/midivider/divider-1.jpg"&gt;&lt;br&gt;
Except that I don't care about going even &lt;em&gt;jankier&lt;/em&gt; than a usual LTT video would, so I just used my old pal the &lt;a href="./mcorigins.html"&gt;voltage divider.&lt;/a&gt;  &lt;/p&gt;
&lt;p&gt;Since the Mi monitor takes a classic CR2032, you should normally adjust to give it 3V -- But I noticed the monitor ran just fine on 2.5V, so it's easier to use the same resistor on both sides of the divider to slash the voltage by half.&lt;br&gt;
&lt;img alt="A jank-in-progress montage of the monitor having wires taped to its contacts" src="images/midivider/divider-2.jpg"&gt;&lt;br&gt;
Using stronger resistors will reduce the current going through your divider, so you'll want to try various ohm values&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt; to get a current that'll still let that lil' appliance do its job.  &lt;/p&gt;
&lt;p&gt;It's quite convenient if you have some of those newfangled wall plugs that have a USB port included! The first resistor does get a &lt;em&gt;tiny bit&lt;/em&gt; warm though. :^)   &lt;/p&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; I use &lt;a href="https://ohmslawcalculator.com/voltage-divider-calculator"&gt;https://ohmslawcalculator.com/voltage-divider-calculator&lt;/a&gt; to calculate my voltage dividers, but that tool doesn't give you amperage calculations. I can't remember which resistor value I used I'm sorry&lt;/sup&gt;  &lt;/p&gt;</content><category term="Hardware"></category><category term="xiaomi"></category><category term="jank"></category><category term="voltage dividers"></category></entry><entry><title>Some thoughts on the Arc Browser</title><link href="https://tvc-16.science/arc-thoughts.html" rel="alternate"></link><published>2023-01-08T00:00:00+01:00</published><updated>2023-01-08T00:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2023-01-08:/arc-thoughts.html</id><summary type="html">&lt;p&gt;Isn't 'The Browser Company' kind of a pretentious name?&lt;/p&gt;</summary><content type="html">&lt;p&gt;Remember when the Internet Explorer browser engine was the most ubiquitous thing because it was forcibly integrated into Windows and everyone could use it?&lt;br&gt;
Heck, you could even make &lt;em&gt;your&lt;/em&gt; own web browser by just slapping some Win32 controls on top of a webview, look at this old Winforms app someone made eons ago and that I kept in my "old stuff" folder for some reason:    &lt;/p&gt;
&lt;p&gt;&lt;img alt="yeah the IE11 engine is starting to show its age tho" src="images/arc/cakebrowser.png"&gt;  &lt;/p&gt;
&lt;p&gt;I got the opportunity to try out &lt;a href="https://arc.net/"&gt;Arc&lt;/a&gt;, a new web browser setting to &lt;code&gt;"think as quickly as we do, take work off of our plates, and pull our creativity forward"&lt;/code&gt; and &lt;code&gt;"let go of the old internet"&lt;/code&gt;, yknow, tech startup stuff.  &lt;/p&gt;
&lt;p&gt;Their brand/marketing is pretty good&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt;, and since the browser was shrouded in secrecy for a while I got interested...&lt;br&gt;
Like all tech startup stuff, the beta is an invite-only cool club thing because we can't just have &lt;em&gt;everyone&lt;/em&gt; try our software I guess ¯\_(ツ)_/¯ &lt;br&gt;
At least they &lt;strong&gt;actually&lt;/strong&gt; give you a member card here so I can't really complain too much, look how pretty it is!    &lt;/p&gt;
&lt;p&gt;&lt;img src="images/arc/cool_club.jpg" width="400"/&gt;  &lt;/p&gt;
&lt;h1&gt;Interface and Browser organization&lt;/h1&gt;
&lt;p&gt;The Arc browser is a UI wrapper around Chromium like most new browsers these days, but unlike Vivaldi I quite like the interface here! It's Mac-only for now and is actually using native UI components, which helps a &lt;strong&gt;lot&lt;/strong&gt;.  &lt;/p&gt;
&lt;p&gt;There are tons of micro-interaction animations and other niceties peppered throughout the interface -- 
Since I'm more of a Windows user, I can only hope they manage to do the same &lt;a href="https://www.theverge.com/2022/11/4/23438365/the-arc-browser-is-coming-to-windows"&gt;there.&lt;/a&gt;&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;The main idea behind Arc is to reduce on your browser clutter by automatically Marie Kondo-ing your tabs away after a few days, unless you specifically move them &lt;em&gt;above&lt;/em&gt; the New Tab button, where they'll never close.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="images/arc/arc_1.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;For "webapps" that you always keep open, you can also drag a tab all the way up, where it'll transform into a big fat toggle button. I personally prefer extracting webapps away from the browser when possible&lt;sup id="ref-3"&gt;&lt;a href="#note-3"&gt;***&lt;/a&gt;&lt;/sup&gt; so I can use the OS to multitask instead, because it'll usually be miles ahead of what a single app can do 😤  &lt;/p&gt;
&lt;p&gt;Having the area below New Tab be this ephemeral space allows for some interesting UI choices, like putting downloads alongside your tabs. I can appreciate the experiment, but as a dedicated Tab Hoarder™️ I'd probably end up pinning everything...  &lt;/p&gt;
&lt;p&gt;As far as Tab organization itself goes, you've got folders, which give you one level of hierarchy only(meh). But if you want one more level, you can use &lt;strong&gt;Spaces&lt;/strong&gt;, which I consider to be a very cool spin on web browsing compartmentalization.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="images/arc/arc_3.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;Spaces end up at the bottom of the sidebar with the icon/emoji you give them, and each space has its own set of tabs and folders. If you want, you can give a space a different &lt;strong&gt;profile&lt;/strong&gt;, which is basically a separate cookie/data storage like what Firefox Containers do.  &lt;/p&gt;
&lt;p&gt;This is quite powerful for Home/Work separation and other stuff like per-project tab bundles - But once you're on a space proper you'll still notice that the tab interface can't really hold a lot of tabs (even with folders), which is very much a deliberate choice by the browser.&lt;/p&gt;
&lt;h1&gt;Split View&lt;/h1&gt;
&lt;p&gt;Y'know how browsers all mostly embraced the "sidebar/side view" thing these days, either for extensions or additional lightweight multitasking? Arc just goes whole hog on the concept with a Split View feature that allows you to have up to 4 tabs side-by-side.&lt;br&gt;
The tabs merge in a single line on the sidebar, which is a nice touch.   &lt;/p&gt;
&lt;p&gt;&lt;img alt="A screenshot of Arc, showing four doujinsoft tabs side by side playing different games. this is quite silly" src="images/arc/arc_4.jpg"&gt;&lt;br&gt;
&lt;sub&gt;Playing DIY games on &lt;a href="http://diy.tvc-16.science"&gt;DoujinSoft&lt;/a&gt; certainly gets more challenging when you have to play four of them simultaneously...&lt;/sub&gt;  &lt;/p&gt;
&lt;p&gt;While the interface of Arc is overall very spacious, for some reason the multitasking menu is this small set of three dots on the right side of each tab - What is this, the iPad?  &lt;/p&gt;
&lt;p&gt;&lt;img alt="extension/multitasking menu on arc, with the tsukihi extension opened in a small adjacent window" src="images/arc/arc_extension.jpg"&gt;&lt;br&gt;
The multitasking menu is also where extensions live, and opening an extension shows it up in a little floating window -- Which is something I thought was &lt;strong&gt;really&lt;/strong&gt; cool at first, because sometimes I do want my extension to stick around on screen while I'm doing browser stuff instead of being a quick overlay like all other browsers do.&lt;br&gt;
Except that the window autocloses as soon as you click outside it, so it's basically just an overlay anyways. 😩😩  &lt;/p&gt;
&lt;h1&gt;Other features&lt;/h1&gt;
&lt;p&gt;Each space can have a dedicated &lt;strong&gt;theme&lt;/strong&gt;, which is a set of up to three accent colors you can choose. Although you can only really choose the primary colors, as the browser will "derive" the others for you so that the interface remains nice.&lt;br&gt;
&lt;sub&gt;no hot dog stand...&lt;/sub&gt;  &lt;/p&gt;
&lt;p&gt;The browser comes with uBlock Origin pre-installed as an adblocker, and comes with a "Boost" feature that's basically Stylish+Greasemonkey, allowing you to give any website custom CSS/JS. It's pretty good! The split view also makes working on custom styles and scripts a pleasant experience.&lt;/p&gt;
&lt;p&gt;I didn't take a screenshot of it (ha), but the &lt;strong&gt;screenshot&lt;/strong&gt; tool can always be quickly reached from the address bar, and it is &lt;em&gt;surprisingly&lt;/em&gt; powerful -- You can select DOM regions like other browsers, but there's also some basic editing tools on top of it.&lt;br&gt;
It ties into the "Easel" feature, which is some kind of collaborative whiteboard thing? This isn't really the kind of thing I'd do in a web browser, but it's there if you want it&lt;sup id="ref-4"&gt;&lt;a href="#note-4"&gt;#&lt;/a&gt;&lt;/sup&gt;. &lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="images/arc/arc_easel.jpg"&gt;  &lt;/p&gt;
&lt;h1&gt;Closing thoughts&lt;/h1&gt;
&lt;p&gt;It looks good and it tries to be your Internet swiss army knife... I appreciate some of the choices, but I'd probably end up only using a third of what Arc offers.  &lt;/p&gt;
&lt;p&gt;Spaces are a really nice feature and I'd like to use those instead of juggling two Firefox windows like I do, but the tab management side of things is a bit too limiting for me I'd say? Firefox + &lt;a href="https://github.com/piroor/treestyletab"&gt;Tree Style Tabs&lt;/a&gt; remains undefeated!  &lt;/p&gt;
&lt;p&gt;Here's an &lt;a href="https://arc.net/gift/ac63e7b"&gt;invite code&lt;/a&gt; for the Arc cool club in case you want to try it out.  &lt;/p&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt;I am a sucker for serifs/renaissance art mixed with corporate aesthetics I guess&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; Swift bindings to WinUI? they sure aren't making their lives easy on that one that's commendable&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-3"&gt;&lt;a href="#ref-3"&gt;***&lt;/a&gt; Using PWAs, third party clients or &lt;em&gt;shudders&lt;/em&gt; awful electron wrappers... &lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-4"&gt;&lt;a href="#ref-4"&gt;#&lt;/a&gt; One nice feature of easels is that you can make your screenshots "live", which basically turns them into small webviews. This won't work everywhere, but it's nice for dashboard stuff! &lt;/sup&gt;  &lt;/p&gt;</content><category term="Blogposting"></category><category term="macos"></category><category term="apple"></category><category term="internet browsers"></category><category term="startup stuff"></category></entry><entry><title>My 2023 Hobonichi Techo picks</title><link href="https://tvc-16.science/2023-techo.html" rel="alternate"></link><published>2023-01-01T00:00:00+01:00</published><updated>2023-01-01T00:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2023-01-01:/2023-techo.html</id><summary type="html">&lt;p&gt;The nice thing about buying a techo a year is that I never run out of 3-color ballpens now.&lt;/p&gt;</summary><content type="html">&lt;p&gt;New year, new &lt;a href="https://www.1101.com/store/techo/en/"&gt;Hobonichi Techo&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;This year's pick for my planner cover is the &lt;a href="https://www.1101.com/store/techo/en/2023/pc/detail_cover/oc23_shimatsuka/"&gt;Eri Shimatsuka&lt;/a&gt; design. Feels nice going back to a textile cover again, and the ball-shaped bookmarks are particularly lovely.  &lt;/p&gt;
&lt;p&gt;Every year I pick a random strap doodad from a box I have laying around and attach it to the pen holder;&lt;br&gt;
For this year I've picked this tiny green bag with a mascot character. Fun! I have no idea where it comes from, but the mascot has the same green hue as one of the bookmarks so it's a nice match.      &lt;/p&gt;
&lt;p&gt;&lt;img alt="k-kawaii..." src="images/techo/2023cover.jpg"&gt;&lt;br&gt;
The bag can fit &lt;em&gt;one&lt;/em&gt; small roll of washi tape, so for once this is actually practical and not just extra decoration. I've put one roll of &lt;a href="https://www.1101.com/store/techo/en/2023/pc/detail_toolstoys/s_pavilio/"&gt;Pavilio&lt;/a&gt; in there.  &lt;/p&gt;
&lt;p&gt;This year's techo came with a sampler for the new Tomoe River paper variant that'll be used starting next year: It's noticeably...smoother? Both to the touch and when writing on, which feels nice.&lt;br&gt;
&lt;img alt="2022 vs 2023 techo" src="images/techo/2023.jpg"&gt;&lt;br&gt;
The bundled pen is brown for 2023, which fits the cover and its leather bookmarks nicely as well.&lt;br&gt;
I liked 2022's mint a lot (that's usually the color I pick for the &lt;a href="https://knowyourmeme.com/photos/1082668-earthbound-mother"&gt;window theme&lt;/a&gt; in EarthBound too!), so I'm probably going to keep it around for random note writing.  &lt;/p&gt;
&lt;p&gt;Gonna need to put something else in that pen holder to keep the 2022 techo closed though, it's noticeably bulgy this year. 😌  &lt;/p&gt;
&lt;p&gt;I didn't get much in the way of new stationery this year -- I tried the new &lt;a href="https://www.1101.com/store/techo/en/magazine/tt_report/202209/"&gt;Deco Rush&lt;/a&gt;, and they're quite fun!&lt;br&gt;
They run out quickly, but I feel part of that is because I botched up quite a few times when using them, wasting some of the sticker tape...  &lt;/p&gt;
&lt;p&gt;For kicks, here are the pictures for the previous years - had to dig a bit in the ol' twitter archives to find those again.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="2020 vs 2021 techo" src="images/techo/2021.jpg"&gt;&lt;br&gt;
&lt;img alt="2021 vs 2022 techo" src="images/techo/2022.jpg"&gt;&lt;/p&gt;</content><category term="Techoposting"></category><category term="techo"></category><category term="hobonichi"></category><category term="stationery"></category></entry><entry><title>The 2022 Recap</title><link href="https://tvc-16.science/2022-recap.html" rel="alternate"></link><published>2022-12-30T00:00:00+01:00</published><updated>2022-12-30T00:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2022-12-30:/2022-recap.html</id><summary type="html">&lt;p&gt;It's the most wonderful time of the year!&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've been mostly winding down playing &lt;a href="https://kolektiva.social/@Difegue/109503827535767577"&gt;Sonic games&lt;/a&gt;, catching up on seasonal anime and trying to get through this box of Finnish sweets I received as part of a secret santa. (I have no idea how they eat &lt;a href="https://fr.wikipedia.org/wiki/Salmiakki"&gt;this stuff&lt;/a&gt;, it is an absolute ordeal)  &lt;/p&gt;
&lt;p&gt;As far as social media goes, I've been enjoying reading my timeline on the &lt;a href="https://kolektiva.social/@Difegue"&gt;funny elephant website&lt;/a&gt; much more than Twitter recently, but it's quite hard to drop the blue bird entirely considering I follow a bunch of artists on it...&lt;br&gt;
Mastodon is too tech-oriented to catch on with art twitter imo, maybe &lt;a href="https://twitter.com/photomatt/status/1594577983028740096"&gt;Tumblr implementing ActivityPub&lt;/a&gt; will be the savior? Still feels quite unlikely.  &lt;/p&gt;
&lt;p&gt;2022 was a thoroughly packed year for me on pretty much all levels, so when looking back for this end-of-year blogpost I kinda wonder how the fuck I made all this stuff -- Let's aim to be a better &lt;a href="https://twitter.com/cassiecodes/status/1592974814352146432"&gt;⅒&lt;sub&gt;x&lt;/sub&gt; rockstar dev&lt;/a&gt; in the coming year!&lt;/p&gt;
&lt;h1&gt;LANraragi&lt;/h1&gt;
&lt;p&gt;I've put out 4 (and a half) LRR releases this year for a total of &lt;code&gt;+7838/-3539&lt;/code&gt; LoC, ending at &lt;a href="https://github.com/Difegue/LANraragi/releases/tag/v.0.8.8"&gt;0.8.8&lt;/a&gt;.&lt;br&gt;
This year was mostly performance improvements on the thumbnail and search front -- I feel the codebase is in a pretty good shape atm, so maybe we'll land some nice features for 0.9.0 in 2023 if we get there.  &lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/Difegue/LANraragi/issues/519"&gt;Meta-archives/tankobon&lt;/a&gt; support is a big one, but I'd also like to finally work on a better onboarding UI and an external plugin repository. &lt;/p&gt;
&lt;p&gt;I added &lt;a href="https://github.com/Difegue/LANraragi/issues/665"&gt;JPEGXL support&lt;/a&gt; right before Google killed it by &lt;a href="https://www.reddit.com/r/jpegxl/comments/zhbiy1/jpegxl_removed_from_chromium_source/"&gt;removing it from Chromium&lt;/a&gt;...&lt;br&gt;
&lt;img alt="smh google i swear to god" src="images/coolmeme.jpg"&gt;&lt;/p&gt;
&lt;h1&gt;DoujinSoft&lt;/h1&gt;
&lt;p&gt;I was planning to do...absolutely nothing with DoujinSoft this year?&lt;br&gt;
But of course &lt;a href="./doujinsoft-3.html"&gt;life found a way&lt;/a&gt; and we got &lt;a href="https://gonintendo.com/contents/10503-website-allows-you-to-play-fan-made-warioware-d-i-y-microgames-in-your-browser"&gt;an article&lt;/a&gt; on GoNintendo. What the fuck man&lt;br&gt;
&lt;img alt="Post this cat when they'll least expect it" src="images/doujinsoft/necoarc.jpg"&gt;&lt;br&gt;
We broke the &lt;code&gt;105 000&lt;/code&gt; games uploaded mark this year I believe, and well, I'm still approving new DIY content and receiving weird shit via Wii Mail on a weekly basis. I also &lt;a href="https://gamingreinvented.com/interview/lets-interview-doujinsoft-creator-difegue/"&gt;got interviewed!&lt;/a&gt; That was kinda fun.  &lt;/p&gt;
&lt;p&gt;I'd like to eventually add missing features to the webplayer to make it a bit more like real WarioWare and allow users to share their own game mixes, but in the meantime, why not try out this &lt;a href="https://diy.tvc-16.science/collection?id=z_dfugselects"&gt;custom playlist&lt;/a&gt; I put together?  &lt;/p&gt;
&lt;h1&gt;SAGE 2022 and Sonic McOrigins&lt;/h1&gt;
&lt;p&gt;This year has been the best for Sonic in...quite a long time!&lt;br&gt;
I'm happy to have contributed to it in a small way by putting together that &lt;a href="https://sonicfangameshq.com/forums/showcase/sonic-mcorigins.1352/"&gt;LCD simulator archive&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;I got a lot of nice comments from people about how those games were parts of their childhood they'd forgotten about; One of the major reasons for building this was to make sure this stuff would be remembered (as cheap as the games are), so it's great to see others share that sentiment&lt;sup id="ref-1"&gt;&lt;a href="#note-1"&gt;*&lt;/a&gt;&lt;/sup&gt;.&lt;br&gt;
&lt;img alt="The cursed missing three games..." src="images/lcdonald/missing_three.jpg"&gt;&lt;br&gt;
I do have the missing three games now, so you can probably expect a &lt;em&gt;McOrigins DX &amp;amp; Knuckles&lt;/em&gt; for SAGE 2023&lt;sup id="ref-2"&gt;&lt;a href="#note-2"&gt;**&lt;/a&gt;&lt;/sup&gt;.  &lt;/p&gt;
&lt;p&gt;SAGE is always chock full of &lt;a href="https://twitter.com/BrockCrocodile/status/1569030514224365573"&gt;incredible stuff&lt;/a&gt;, so I was quite humbled to still receive some &lt;a href="https://segabits.com/blog/2022/09/04/playable-at-sage-2022-sonic-mcorigins-featuring-the-mcd-lcd-sonic-toys/"&gt;coverage&lt;/a&gt; in the midst of all the heavy-hitters. It made for pretty good Twitch content as well, but most of the VODs for those are gone now. 😔  &lt;/p&gt;
&lt;p&gt;Speaking of Sonic stuff, there's a recording of the &lt;a href="https://www.youtube.com/watch?v=_-BDnNia11s"&gt;Re: Sonic the Hedgehog&lt;/a&gt; Sega CD pixel art/music album on YouTube now -- I recommend giving it a look/listen, it's &lt;code&gt;cool&lt;/code&gt;!&lt;/p&gt;
&lt;h1&gt;Stylophone&lt;/h1&gt;
&lt;p&gt;&lt;a href="./stylophone-25.html"&gt;Stylophone 2.5&lt;/a&gt; was mostly just polishing off and releasing work from 2021 because I hate letting work go to waste; Financially speaking it wasn't a particularly smart move considering the 100€ apple tax&lt;sup id="ref-3"&gt;&lt;a href="#note-3"&gt;***&lt;/a&gt;&lt;/sup&gt;, but I almost made back half of it, ain't that neat?&lt;br&gt;
&lt;img alt="" src="images/stylophone/v25-ipad.jpg"&gt;&lt;br&gt;
I might release a 2.6 with some bugfixes and layout improvements (it's a bit messy on some iPhones...), but I don't think I'll pay the 100€ again next year?   &lt;/p&gt;
&lt;p&gt;Considering the EU directives will likely force Apple to &lt;a href="https://www.bloomberg.com/news/articles/2022-12-13/will-apple-allow-users-to-install-third-party-app-stores-sideload-in-europe"&gt;allow side-loading in the next year&lt;/a&gt;, it'd certainly be nice if that yearly tax went away/was reduced as well; Probably just wishful thinking. &lt;/p&gt;
&lt;h1&gt;DialogueForest and Gamedev&lt;/h1&gt;
&lt;p&gt;&lt;img alt="A screenshot of DialogueForest, editing some text" src="images/dialogueforest.jpg"&gt;&lt;br&gt;
Keen followers might know I've been dabbling in &lt;a href="https://twitter.com/Difegue/status/1476936024752365569?s=20"&gt;some gamedev&lt;/a&gt; for a little while now.&lt;br&gt;
The basic pitch for the game is "&lt;code&gt;Digital: A Love Story/Hypnospace Outlaw but in 1989/NeXtSTEP aesthetics&lt;/code&gt;", so there's quite a bit of reading on BBSes and emails to do -- And consequently, quite a bit of dialogue for me to write...  &lt;/p&gt;
&lt;p&gt;&lt;em&gt;DialogueForest&lt;/em&gt; is an UWP/WinUI 2.6 app I cooked at the beginning of the year to make writing those longer form mail/BBS dialogue pieces easier to organize and link to each other. It works pretty well! And I barely used it. 😤&lt;br&gt;
The app has some rough edges, but I should finish it up and make it a free download on itch.io or something.&lt;br&gt;
Adding a daily wordcount/notification system is one of the missing bits, and it would probably make me use the thing more too!&lt;/p&gt;
&lt;p&gt;I didn't make much game progress this year, which makes sense considering basically &lt;em&gt;everything else&lt;/em&gt; took time instead; I also probably underestimated the workload of writing quite a bit! &lt;strong&gt;It's hard!&lt;/strong&gt;&lt;br&gt;
Nevertheless, here's some footage of the faux-OS and the application system:   &lt;/p&gt;
&lt;video width="800" autoplay="true" loop="true" src="images/gamedev/animationsucc.webm"&gt;&lt;/video&gt;

&lt;h1&gt;Misc 2023 thoughts&lt;/h1&gt;
&lt;p&gt;I've been looking at a bunch of &lt;a href="https://cinni.net/"&gt;small&lt;/a&gt; &lt;a href="https://maya.land/"&gt;personal&lt;/a&gt; &lt;a href="http://tilde.town/"&gt;websites&lt;/a&gt; &lt;a href="https://sadgrl.online/"&gt;recently&lt;/a&gt; - kinda like what this blog is!&lt;br&gt;
While Mastodon is fun, I think there's a lot of value in keeping your own little corner of the Internet for people to look at.  &lt;/p&gt;
&lt;p&gt;Heck, you don't even need to know HTML like in the geocities days now, it's all &lt;a href="https://mmm.page/"&gt;wysiwyg&lt;/a&gt;&lt;sup id="ref-4"&gt;&lt;a href="#note-4"&gt;#&lt;/a&gt;&lt;/sup&gt; and shit!&lt;br&gt;
I'd like to use this blog more in the upcoming year to just write about whatever - I don't have a fancy newsletter&lt;sup id="ref-5"&gt;&lt;a href="#note-5"&gt;##&lt;/a&gt;&lt;/sup&gt; button or anything but ol' reliable &lt;a href="https://tvc-16.science/feeds/all.atom.xml"&gt;RSS&lt;/a&gt; is still a thing.  &lt;/p&gt;
&lt;p&gt;It does feel like the winds of the Internet are shifting a bit in 2023(here's a &lt;a href="https://www.robinsloan.com/lab/new-avenues/"&gt;nice read&lt;/a&gt;), so keeping the effortposting to the TVC-16 helps in case everyone moves to yet another platform&lt;sup id="ref-6"&gt;&lt;a href="#note-6"&gt;###&lt;/a&gt;&lt;/sup&gt;!&lt;br&gt;
All the blue bird will get from me is shitposting and external links.  &lt;br&gt;
&lt;img alt="lmao" src="images/memupatcher/lmao.gif"&gt;  &lt;/p&gt;
&lt;p&gt;So 'bout that dreaded year of 2023, what's in the cards? uhhh I dunno quick shit out some links   &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I've been looking at &lt;a href="https://blog.holopin.io/posts/holobytes-and-holopals"&gt;Holopin&lt;/a&gt; since last Hacktoberfest - I find the concept amusing and hope they'll accept my application for the OSS plan so I can make virtual badges out of all the Spline 3D stuff I made this year. 😌  &lt;/li&gt;
&lt;li&gt;Windows 11 Widgets are opening up to &lt;a href="https://learn.microsoft.com/en-us/windows/apps/develop/widgets/implement-widget-provider-win32"&gt;third-party&lt;/a&gt; app developers: That's a pretty good incentive for me to dig RSS Live Tiles from its grave!  &lt;/li&gt;
&lt;li&gt;The transitions made by &lt;a href="https://twitter.com/kamikazenosyain/status/1604159235507437568"&gt;Kamikaze Douga&lt;/a&gt; for Pop Team Epic S2 are awesome and would make for a nice screensaver ala &lt;a href="https://github.com/pedrommcarrasco/Brooklyn"&gt;Brooklyn&lt;/a&gt;.  &lt;/li&gt;
&lt;li&gt;Obsidian has a cool new &lt;a href="https://obsidian.md/canvas"&gt;graph view&lt;/a&gt; now - Sometimes I feel like I should give it a shot..  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On more generic terms, I read a post recently about "&lt;code&gt;art that is unabashedly sincere/only you can make&lt;/code&gt;", and that's something I wish I was doing more - You could argue that some of the projects here are already very much personal expression/stuff that nobody else would make, but ehhhh I wouldn't really consider it art yknow?  &lt;/p&gt;
&lt;p&gt;There's something awesome about being able to put a bit of yourself out there like that - Maybe I can do some more of that in the gamedev stuff.  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;Thanks for reading this far! (or skipping to the end)&lt;br&gt;
I don't do longer-form writing like this too often so I've been looking at the draft in the Firefox Reader View to make sure the estimated reading time doesn't go beyond 9 minutes. 😤 &lt;/p&gt;
&lt;h3&gt;Happy New Year!&lt;/h3&gt;
&lt;p&gt;&lt;sup id="note-1"&gt;&lt;a href="#ref-1"&gt;*&lt;/a&gt; Most of the other comments were memes about the pakistani commercials, of course.&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-2"&gt;&lt;a href="#ref-2"&gt;**&lt;/a&gt; There are some scans I should upload to Sonic Retro as well...&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-3"&gt;&lt;a href="#ref-3"&gt;***&lt;/a&gt; The dev certificate also allowed me to sign/notarize the macOS version of Sonic McOrigins, which is cool I guess&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-4"&gt;&lt;a href="#ref-4"&gt;#&lt;/a&gt; &lt;a href="https://youtu.be/KCvW-2mZBlU?t=28"&gt;Just a guy who loves adventure!&lt;/a&gt;&lt;/sup&gt;&lt;br&gt;
&lt;sup id="note-5"&gt;&lt;a href="#ref-5"&gt;##&lt;/a&gt; I've been enjoying the resurgence of email newsletters as well - While Revue has already been murdered, thank god for Substack! Hope it stays.&lt;/sup&gt;
&lt;sup id="note-6"&gt;&lt;a href="#ref-6"&gt;###&lt;/a&gt; &lt;a href="https://github.com/robinsloan/spring-83"&gt;Spring '83&lt;/a&gt; isn't really a platform per se, but I like the concept/thought experiment behind it. It kinda feels like everyone would have his lil' widget/card and you could just smash 'em all in one big page...&lt;/sup&gt;
&lt;/sup&gt;&lt;/p&gt;</content><category term="Blogposting"></category><category term="lanraragi"></category><category term="stylophone"></category><category term="doujinsoft"></category><category term="uwp"></category><category term="sonic"></category><category term="mcdonalds"></category><category term="gamedev"></category></entry><entry><title>Monkey Trick: Use ResX localization in a Xamarin Mac/iOS app with Interface Builder</title><link href="https://tvc-16.science/xamarin-resx.html" rel="alternate"></link><published>2022-12-02T00:00:00+01:00</published><updated>2022-12-02T00:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2022-12-02:/xamarin-resx.html</id><summary type="html">&lt;p&gt;I have to get those monkey puns out before Microsoft fully gets rid of the Xamarin name.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Apple recently removed their page about &lt;a href="https://web.archive.org/web/20220406135312/https://developer.apple.com/xcode/interface-builder/"&gt;Interface Builder&lt;/a&gt; from the Xcode documentation, likely as part of their push to dogfood SwiftUI to as many people as possible.&lt;br&gt;
But I &lt;strong&gt;like&lt;/strong&gt; &lt;a href="https://medium.com/swlh/a-bit-about-interface-builder-ceffaf484580"&gt;Interface Builder&lt;/a&gt;! Hell I'll go one beyond and even say I like Auto-Layout! ...When it works.  &lt;/p&gt;
&lt;p&gt;Keeping UI code separated from your main logic is always a sound architectural choice for me, and while I wish you could edit &lt;a href="https://developer.apple.com/library/archive/documentation/General/Conceptual/Devpedia-CocoaApp/Storyboard.html"&gt;nibs/storyboards&lt;/a&gt; by hand like with XAML, I'll still take IB and its visual designer over writing &lt;code&gt;new UIButton()&lt;/code&gt; a thousand times.  &lt;/p&gt;
&lt;p&gt;&lt;a href="./stylophone-25.html"&gt;Stylophone&lt;/a&gt;'s iOS port mostly uses Storyboards for its UI, but much of the logic code is written in .NET, and shared with its Windows/UWP brethren.&lt;br&gt;
That includes localized text! I use &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/localization#resource-files"&gt;.resx&lt;/a&gt; files to hold the localizations, which is very much a .NET concept.  &lt;/p&gt;
&lt;p&gt;So, here's a fairly easy way to use the interop magic in Xamarin/Microsoft.iOS to &lt;strong&gt;directly&lt;/strong&gt; reference your localization keys in Interface Builder and have it just work.  &lt;/p&gt;
&lt;h1&gt;Subclassing UILabel&lt;/h1&gt;
&lt;p&gt;What we're going to do is simply pull the localized text once the view is loaded and assign it to all the &lt;code&gt;UILabels&lt;/code&gt; objects in the view, but:&lt;br&gt;
- in an automated fashion instead of having to write additional view code
- relying only on data from Interface Builder(IB).  &lt;/p&gt;
&lt;p&gt;One of the nice things IB allows you to do is set &lt;a href="https://sam.dods.co/blog/2014/04/08/user-defined-runtime-attributes-in-interface-builder.html"&gt;User Defined Runtime Attributes&lt;/a&gt; on any control you lay out, which are basically arbitary key/value pairs.  &lt;/p&gt;
&lt;p&gt;If we rely on that, it becomes quite easy to write a &lt;code&gt;UILabel&lt;/code&gt; subclass that looks in those attributes for a localization key, and then asks .NET about it:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Foundation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Stylophone.Localization.Strings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UIKit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Stylophone.iOS.Helpers&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Make this subclass visible from the UIKit side of things&lt;/span&gt;
&lt;span class="na"&gt;    [Register(nameof(LocalizedLabel))]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LocalizedLabel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UILabel&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="c1"&gt;// This is our runtime attribute - name can be whatever you want!&lt;/span&gt;
&lt;span class="na"&gt;        [Export(nameof(stringIdentifier))]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;NSString&lt;/span&gt; &lt;span class="n"&gt;stringIdentifier&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;LocalizedLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ReleaseDesignerOutlets&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;AwakeFromNib&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AwakeFromNib&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="c1"&gt;// Use the text set in IB to find the matching property.&lt;/span&gt;
            &lt;span class="c1"&gt;// Set the identifier in &amp;quot;User Defined Runtime Attributes&amp;quot;.&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;identifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stringIdentifier&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;AppDisplayName&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="c1"&gt;// Get the property value to have the localized string.&lt;/span&gt;
            &lt;span class="n"&gt;Text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Resources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResourceManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;Resources&lt;/code&gt; class here is the classic static class generated by ResXGenerator, which you should normally have if you store your localizations that way.  &lt;/p&gt;
&lt;h1&gt;Using the subclass in Interface Builder&lt;/h1&gt;
&lt;p&gt;Once your subclass is created, you should be able to define it as a "Custom class" for all the &lt;code&gt;UILabels&lt;/code&gt; you want localized.    &lt;/p&gt;
&lt;p&gt;From that point on, all that's left to do is add your localization key as a runtime attribute under the &lt;code&gt;stringIdentifier&lt;/code&gt; key:&lt;br&gt;
&lt;img alt="a screenshot of interface builder, my beloved" src="images/stylophone/xam-localization.png"&gt;  &lt;/p&gt;
&lt;p&gt;And you're done!  &lt;/p&gt;
&lt;p&gt;&lt;img src="/images/stylophone/stylo_ios_settings.png" style="width:300px" /&gt;  &lt;/p&gt;
&lt;h1&gt;Notes&lt;/h1&gt;
&lt;p&gt;This approach obviously only works if your text is in a &lt;code&gt;UILabel&lt;/code&gt; -- While that should cover 95% of your app, it's likely you'll still have to hodgepodge some view code for more esoteric text placement, like in &lt;code&gt;UITableView&lt;/code&gt; headers:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// Localization not covered by LocalizedLabel&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;TitleForHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UITableView&lt;/span&gt; &lt;span class="n"&gt;tableView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nint&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Resources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SettingsServer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Resources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SettingsLocalPlaybackHeader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Resources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SettingsDatabase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Resources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SettingsAnalytics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Resources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SettingsAbout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;As a final note, keep in mind that ResX lookup will use the .NET Culture system, which has &lt;a href="https://learn.microsoft.com/en-us/xamarin/ios/app-fundamentals/localization/#locale"&gt;slightly different language combinations&lt;/a&gt; than iOS/macOS.&lt;br&gt;
See &lt;a href="https://learn.microsoft.com/en-us/xamarin/ios/app-fundamentals/localization/#specifying-default-and-supported-languages-in-infoplist"&gt;this&lt;/a&gt; post for more information and how to declare languages in your Xamarin app.  &lt;/p&gt;
&lt;p&gt;Thanks for reading!  &lt;/p&gt;</content><category term="Cool Tricks"></category><category term="interface builder"></category><category term="xamarin"></category><category term="dotnet"></category><category term="c#"></category><category term="macos"></category><category term="ios"></category><category term="resx"></category><category term="localization"></category><category term="monkey trick"></category></entry><entry><title>Running cool-retro-term in Windows through WSL2 and WSLg</title><link href="https://tvc-16.science/cool-retro-term-wsl2.html" rel="alternate"></link><published>2022-11-27T20:00:00+01:00</published><updated>2022-11-27T20:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2022-11-27:/cool-retro-term-wsl2.html</id><summary type="html">&lt;p&gt;Javascript terminal emulators &lt;em&gt;still&lt;/em&gt; have nothing on this.&lt;/p&gt;</summary><content type="html">&lt;p&gt;The original &lt;a href="./cool-retro-term-wsl.html"&gt;cool-retro-term on WSL&lt;/a&gt; blogpost is still one of the most-viewed ones on this website for some reason, but it's gotten quite a bit out of date now that WSL ships with a built-in X server. &lt;sup&gt;well achctually it's not X, wslg uses &lt;a href="https://devblogs.microsoft.com/commandline/wslg-architecture/"&gt;wayland&lt;/a&gt; with Xwayland etc etc whatever&lt;/sup&gt;  &lt;/p&gt;
&lt;p&gt;WSLg + WSL2 was limited to Windows 11 for quite a while, but that &lt;a href="https://devblogs.microsoft.com/commandline/the-windows-subsystem-for-linux-in-the-microsoft-store-is-now-generally-available-on-windows-10-and-11/"&gt;very recently&lt;/a&gt; changed, so I feel it's a nice time to re-try the whole cool-retro-term in Windows experience. Let's get rolling!  &lt;/p&gt;
&lt;h1&gt;Install WSL2 and the required dependencies&lt;/h1&gt;
&lt;p&gt;WSL is much easier to install these days, you can just run: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;wsl.exe --install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;to get WSL2, an Ubuntu default distro and the WSLg system distro.  &lt;/p&gt;
&lt;h1&gt;Download the cool-retro-term AppImage and run it&lt;/h1&gt;
&lt;p&gt;cool-retro-term doesn't seem to have an AppImage up for their recent 1.2.0 due to &lt;a href="https://github.com/Swordfish90/cool-retro-term/issues/698"&gt;CI issues&lt;/a&gt;, so I'll just keep using the ol' reliable 1.1.1 for this.  &lt;/p&gt;
&lt;p&gt;WSL2 supports AppImages much better than WSL1 used to do, so you just have to run the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;wget https://github.com/Swordfish90/cool-retro-term/releases/download/1.1.1/Cool-Retro-Term-1.1.1-x86_64.AppImage&lt;/span&gt;
&lt;span class="err"&gt;chmod a+x Cool-Retro-Term-1.1.1-x86_64.AppImage&lt;/span&gt;
&lt;span class="err"&gt;./Cool-Retro-Term-1.1.1-x86_64.AppImage &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And that's it, you're done!  &lt;/p&gt;
&lt;p&gt;&lt;img alt="wow, no more pegging my cpu at 100%!" src="./images/crt-wsl2.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;Similarly, the .bat shortcut to start CRT becomes much, &lt;em&gt;much&lt;/em&gt; shorter:  &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;cool-retro-term.bat&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;start /min wsl -d ubuntu [folder where you saved the appimage]/Cool-Retro-Term-1.1.1-x86_64.AppImage &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h1&gt;Potential issues and troubleshooting&lt;/h1&gt;
&lt;p&gt;If you get an error like &lt;code&gt;QXcbConnection: Could not connect to display&lt;/code&gt;, it's likely the WSLg Wayland compositor is acting up for some reason.&lt;br&gt;
I recommend first trying a full restart of the WSL VM using &lt;code&gt;wsl.exe --shutdown&lt;/code&gt; in a PowerShell window.&lt;br&gt;
If that doesn't help, you might have some luck following the instructions &lt;a href="https://github.com/microsoft/wslg/wiki/Diagnosing-%22cannot-open-display%22-type-issues-with-WSLg"&gt;here.&lt;/a&gt;  &lt;/p&gt;
&lt;p&gt;WSL2+WSLg fixes essentially all the &lt;a href="./cool-retro-term-wsl.html#caveats"&gt;caveats&lt;/a&gt; from the previous article, so you now have GPU acceleration, working copy-paste, all the bells and whistles. 🎊&lt;br&gt;
My only gripe with WSLg is that it tries its best to apply Windows DPI settings but &lt;a href="https://github.com/microsoft/wslg/issues/3"&gt;doesn't always succeed&lt;/a&gt;, so if you're running at high DPI, you might have to tweak the CRT settings a bit to have it look nice for you.  &lt;/p&gt;
&lt;h1&gt;Using apt instead of the AppImage&lt;/h1&gt;
&lt;p&gt;I personally think the AppImage is the fastest way to setup CRT and have it up to date, but since the WSL2 default user distro is Ubuntu, you can easily just do &lt;code&gt;apt update &amp;amp;&amp;amp; apt install cool-retro-term&lt;/code&gt;.  &lt;/p&gt;
&lt;p&gt;This approach has the advantage of bundling a &lt;code&gt;.desktop&lt;/code&gt; file, so you can start CRT directly from the Windows Start Menu:  &lt;/p&gt;
&lt;p&gt;&lt;img alt="Cool Retro Term(Ubuntu) in the Start Menu." src="./images/crt-start.jpg"&gt;&lt;/p&gt;</content><category term="Cool Tricks"></category><category term="wsl"></category><category term="cool-retro-term"></category><category term="wslg"></category><category term="terminal"></category><category term="crt"></category></entry><entry><title>I made a Pebble watchface</title><link href="https://tvc-16.science/pebble-copland.html" rel="alternate"></link><published>2022-11-21T00:00:00+01:00</published><updated>2022-11-21T00:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2022-11-21:/pebble-copland.html</id><summary type="html">&lt;p&gt;In 2008, PC World included Copland on a list of the biggest project failures in information technology history.&lt;/p&gt;</summary><content type="html">&lt;p&gt;The &lt;a href="https://rebble.io/hackathon-001/"&gt;Rebble Hackathon #001&lt;/a&gt; happened this weekend, so it was a nice opportunity to finally dig into the &lt;a href="https://developer.rebble.io/developer.pebble.com/index.html"&gt;Pebble SDK&lt;/a&gt; and try and make something for my favorite dead smartwatch.  &lt;/p&gt;
&lt;p&gt;I always wanted to make a watchface of some form, but design ideas are pretty tough to come by! I ended up going for a recreation of &lt;a href="https://www.hodinkee.com/articles/the-apple-watch-from-1995"&gt;this&lt;/a&gt; 90's design for a promotional MacOS watch I find cool.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="Copland Watchface for Pebble" src="images/copland-watchface.png"&gt;  &lt;/p&gt;
&lt;p&gt;It's called "Copland", after the 1995 &lt;a href="https://en.wikipedia.org/wiki/Copland_(operating_system)"&gt;failed Apple OS&lt;/a&gt;. (Thought it was funny to call a face for a dead watch after a dead OS, sue me) &lt;/p&gt;
&lt;h3&gt;You can grab it on the Rebble Appstore &lt;a href="https://apps.rebble.io/en_US/application/637abd01fdf3e30009f6399c"&gt;here&lt;/a&gt;!&lt;/h3&gt;
&lt;p&gt;There aren't many watchfaces with a seconds hand since that drains battery quite a bit faster; I added it because it's &lt;strong&gt;cool&lt;/strong&gt;, but being able to disable it will certainly come in a future update. 😅  &lt;/p&gt;
&lt;p&gt;&lt;img alt="Copland on Pebble Time and Time Round" src="images/copland-chalk.jpg"&gt;&lt;/p&gt;
&lt;p&gt;I also wanted to add a &lt;a href="https://developer.rebble.io/developer.pebble.com/guides/graphics-and-animations/vector-graphics/index.html"&gt;PDC vector image&lt;/a&gt; to the top of the watchface and went with the C SDK as a result (The JS one didn't get to support them before Pebble died), so that'd also be a nice thing to add.  &lt;/p&gt;
&lt;p&gt;And apart from that ehh, maybe being able to set custom colors for the hands and a custom font for that date at the bottom? We'll see about that next time.  &lt;/p&gt;</content><category term="Software"></category><category term="pebble"></category><category term="rebble"></category><category term="watchface"></category><category term="apple"></category><category term="hackathon"></category></entry><entry><title>DoujinSoft is 5 years old -- v3.5 is now live!</title><link href="https://tvc-16.science/doujinsoft-3.html" rel="alternate"></link><published>2022-10-06T00:00:00+02:00</published><updated>2022-10-06T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2022-10-06:/doujinsoft-3.html</id><summary type="html">&lt;p&gt;Freeing DIY from the shackles of Nintendo hardware...almost.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://diy.tvc-16.science/"&gt;The DoujinSoft Store&lt;/a&gt; is 5 already! It still stands as the last thing I &lt;a href="/doujinsoft-2.html"&gt;wrote in Java&lt;/a&gt; and I wasn't expecting it to hold that long ¯\_(ツ)_/¯  &lt;/p&gt;
&lt;p&gt;It still receives new content fairly often - Thanks to everyone who contributes!&lt;br&gt;
To celebrate the occasion &lt;sup&gt;(not really it's just lining up nicely)&lt;/sup&gt;, the store has been given a fresh coat of paint... alongside some new mysterious buttons?  &lt;/p&gt;
&lt;p&gt;&lt;img alt="what could this play button possibly do?" src="/images/doujinsoft/playbtn.jpg"&gt;  &lt;/p&gt;
&lt;h1&gt;The mystery button&lt;/h1&gt;
&lt;p&gt;DoujinSoft has been able to &lt;em&gt;play music&lt;/em&gt; and &lt;em&gt;read comics&lt;/em&gt; in the browser since it was released, but the biggest part of WarioWare DIY's user-generated content has always been absent from the browser...until now.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="not gonna lie I was pretty good at tracing pixel art back then" src="/images/doujinsoft/gamepreview.jpg"&gt;&lt;br&gt;
If you told me 12 years ago I'd still be looking at my microgames but on a 4K display through the Internet, I probably wouldn't have believed you.  &lt;/p&gt;
&lt;h2&gt;Available &lt;strong&gt;now&lt;/strong&gt;, the DoujinSoft store now allows you to play &lt;em&gt;every single WarioWare DIY game in your browser.&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;This is something I was secretly hoping for ever since launching the service, but I wasn't really expecting anyone to actually go and reverse-engineer DIY AI to make it happen.&lt;br&gt;
95% of the work that went into this update comes from &lt;strong&gt;&lt;a href="https://yeahross.itch.io/"&gt;yeahross'&lt;/a&gt;&lt;/strong&gt; exceptional Mio-Micro project, which has been cooking for a few months already. You should certainly go throw some flowers his way!  &lt;/p&gt;
&lt;h1&gt;Player integration&lt;/h1&gt;
&lt;p&gt;I'd have felt kinda bad just pushing out this update and writing a blogpost about it without contributing &lt;em&gt;something&lt;/em&gt;, so by leveraging the mio-micro player, there's a fun little bonus in DoujinSoft's &lt;strong&gt;collections&lt;/strong&gt; pages now:  &lt;/p&gt;
&lt;video controls repeat autoplay muted src="images/doujinsoft/player_demo.mp4"&gt;&lt;/video&gt;

&lt;p&gt;Huh, it's &lt;em&gt;almost&lt;/em&gt; like a real WarioWare game now!&lt;br&gt;
The animations are taken straight from &lt;a href="https://animate.style/"&gt;Animate.css&lt;/a&gt; which isn't really designed for that sort of thing, but it's unreal how well it works.&lt;br&gt;
The sounds are a bit of a &lt;a href="https://www.youtube.com/watch?v=40GhbBZGYZY"&gt;deep cut&lt;/a&gt;, but if like me you've been listening to the DIY jingles for years, a bit of change is nice. 😩  &lt;/p&gt;
&lt;p&gt;Some caveats in this first version:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There is no speed-up mechanic, but considering how obtuse some of the games are to win, I wonder if you'll really need it 🙃 &lt;/li&gt;
&lt;li&gt;Boss games &lt;strong&gt;are&lt;/strong&gt; included and will give you 1-UPs, but they'll come in just as randomly as normal games do.&lt;/li&gt;
&lt;li&gt;While mio-micro is &lt;strong&gt;very&lt;/strong&gt; good at simulating DIY games, there might still be some emulation errors that make some games unwinnable. Please let us know on the &lt;a href="https://github.com/yeahross0/Mio-Micro"&gt;mio-micro Github page!&lt;/a&gt;    &lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Some other stuff&lt;/h1&gt;
&lt;p&gt;In yet &lt;a href="/mcorigins.html"&gt;another&lt;/a&gt; Spline &lt;a href="/stylophone-25.html"&gt;frenzy&lt;/a&gt;, the store now has some new backgrounds and promo art!  &lt;/p&gt;
&lt;p&gt;&lt;img alt="haha truck goes brrrrrrrr" src="/images/doujinsoft/diy-hero.jpg"&gt;&lt;/p&gt;
&lt;p&gt;After 5 years without it, DoujinSoft now also has proper &lt;em&gt;blurred previews&lt;/em&gt; for NSFW content.&lt;br&gt;
You can disable those if you want. &lt;sub&gt;&lt;sup&gt;And yes you can play them in the web player&lt;/sup&gt;&lt;/sub&gt;&lt;br&gt;
Flagging NSFW content in the database is an ongoing work, so if you find some unblurred stuff make sure to let me know!  &lt;/p&gt;
&lt;p&gt;The collection minigame player is limited to the prebuild collections for now, but I hope to &lt;em&gt;eventually&lt;/em&gt; add a feature to allow people to craft their own collections/playlists and share them via QR codes or similar.&lt;br&gt;
Maybe I'll put up a &lt;em&gt;Halloween microgame playlist&lt;/em&gt; for people to enjoy? Let me know if you have any spooky games you like. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://hacktoberfest.com/"&gt;Hacktoberfest&lt;/a&gt; has just started&lt;/strong&gt;, so if you want to contribute to the DoujinSoft experience &lt;strong&gt;yourself&lt;/strong&gt;, I'll gladly accept any help, and you can get a shirt out of it! &lt;sub&gt;&lt;sup&gt;Please!&lt;/sub&gt;&lt;/sup&gt;  &lt;/p&gt;
&lt;p&gt;Apart from that uhhhh  &lt;/p&gt;
&lt;h3&gt;Some General system stability improvements have been made to enhance the user's experience.&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://diy.tvc-16.science/"&gt;Please enjoy!&lt;/a&gt;  &lt;/p&gt;</content><category term="Software"></category><category term="nintendo"></category><category term="wii"></category><category term="wiiconnect24"></category><category term="riiconnect24"></category><category term="warioware"></category><category term="doujinsoft"></category><category term="mio"></category></entry><entry><title>Stylophone 2.5 is now out for Windows...and iOS!</title><link href="https://tvc-16.science/stylophone-25.html" rel="alternate"></link><published>2022-09-17T00:00:00+02:00</published><updated>2022-09-17T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2022-09-17:/stylophone-25.html</id><summary type="html">&lt;p&gt;I'm coming for &lt;strong&gt;your&lt;/strong&gt; dynamic island!&lt;/p&gt;</summary><content type="html">&lt;p&gt;It only took me &lt;a href="./stylophone-2.html"&gt;a year&lt;/a&gt;, but Stylophone is finally available on iOS and iPadOS!  &lt;/p&gt;
&lt;p&gt;&lt;img alt="Stylophone 2.5 on iPad" src="https://tvc-16.science/images/stylophone/v25-ipad.jpg"&gt;&lt;br&gt;
It looks quite similar to the Windows version, doesn't it?&lt;br&gt;
&lt;img alt="Stylophone 2.5 on Windows" src="https://tvc-16.science/images/stylophone/v25-win.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;Version 2.5 is now available on both the &lt;em&gt;Microsoft Store&lt;/em&gt; for the UWP version, and the &lt;em&gt;App Store&lt;/em&gt; for your iDevices.&lt;br&gt;
(And of course, still open source on &lt;a href="https://github.com/Difegue/Stylophone/releases/tag/2.5.4"&gt;GitHub&lt;/a&gt;!)&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.microsoft.com/store/apps/9NCB693428T8?cid=storebadge&amp;amp;ocid=badge"&gt;&lt;img src="https://get.microsoft.com/images/en-us%20dark.svg" width="200"/&gt;&lt;/a&gt; &lt;a href="https://apps.apple.com/us/app/stylophone/id1644672889?itsct=apps_box_link&amp;amp;itscg=30200"&gt;&lt;img src="https://developer.apple.com/assets/elements/badges/download-on-the-app-store.svg" width="216"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Thanks to a common .NET core codebase, both versions are &lt;strong&gt;near-identical&lt;/strong&gt; as far as features go.&lt;br&gt;
&lt;sub&gt;(iOS is missing Queue reordering! That'll come in time.)&lt;/sub&gt;  &lt;/p&gt;
&lt;p&gt;That includes &lt;strong&gt;Local Playback&lt;/strong&gt;, integration with System playback controls, and all that good stuff!&lt;br&gt;
The new iOS 16 Lockscreen looks particularly snazzy with Stylophone on it:&lt;br&gt;
&lt;img src="https://tvc-16.science/images/stylophone/v25-lockscreen.jpg" alt="Stylophone 2.5 on the iOS 16 Lock screen" width="300"/&gt;  &lt;/p&gt;
&lt;h1&gt;Multiplatform bonanza&lt;/h1&gt;
&lt;p&gt;I'm pretty stoked about breaking out into Apple Developer land!&lt;br&gt;
With the combined powers of UWP and UIKit, you can now manage your MPD server using Stylophone on:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows 10/11 PCs&lt;/li&gt;
&lt;li&gt;Xbox One/Series&lt;/li&gt;
&lt;li&gt;iPhone/iPad&lt;/li&gt;
&lt;li&gt;macOS, if using &lt;a href="https://developer.apple.com/macos/iphone-and-ipad-apps/"&gt;Apple Silicon&lt;/a&gt;.&lt;br&gt;
&lt;sub&gt;(Sadly, Apple TV/tvOS isn't as easy to develop for as the Xbox is..)&lt;/sub&gt;  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I don't have an ARM Mac to test the app on, but I did try chucking it in &lt;a href="https://developer.apple.com/mac-catalyst/"&gt;Mac Catalyst&lt;/a&gt; to see how it'd end up:&lt;br&gt;
&lt;img alt="Please do not dunk on my music tastes too hard" src="https://tvc-16.science/images/stylophone/v25-catalyst.jpg"&gt;&lt;br&gt;
Doesn't look too bad, and could probably be improved a little bit with some Mac-specific magic!  &lt;/p&gt;
&lt;p&gt;Catalyst support required Microsoft.iOS, aka the &lt;a href="https://github.com/xamarin/xamarin-macios/wiki/.NET-release-notes-Xcode-13.3"&gt;new version of Xamarin.iOS&lt;/a&gt;.&lt;br&gt;
As that new version changed TFMs, &lt;a href="https://code.videolan.org/videolan/LibVLCSharp/-/issues/346"&gt;LibVLC&lt;/a&gt; is currently broken on it; So no Catalyst for now!  &lt;/p&gt;
&lt;p&gt;I don't expect to support any additional platforms for the time being; Previous experiments with Uno and MAUI were &lt;a href="https://twitter.com/Difegue/status/1329221609652105217?ref_src=twsrc%5Etfw"&gt;painful&lt;/a&gt;.&lt;br&gt;
Xamarin.Android could be an interesting avenue, but I don't feel like developing for Android even though it's my daily driver...    &lt;/p&gt;
&lt;h1&gt;The icon changed (again)&lt;/h1&gt;
&lt;p&gt;If youuuuu can believe it, it's &lt;s&gt;a Friday&lt;/s&gt; &lt;a href="https://spline.design"&gt;Spline&lt;/a&gt; time once again!&lt;br&gt;
I've refined the existing icon design by giving it the 3D treatment.  &lt;/p&gt;
&lt;p&gt;&lt;img src="https://tvc-16.science/images/stylophone/v25-icon.png" width="256"/&gt;
&lt;sub&gt;Hey, the S from the &lt;a href="https://tvc-16.science/images/stylophone/styloicon.jpg"&gt;v1 icon&lt;/a&gt; is back! Can't this guy make up his mind for once?&lt;/sub&gt;  &lt;/p&gt;
&lt;p&gt;A far cry from the 2020 MS Office ripoff icon -- Playing with materials in 3D is fun!&lt;br&gt;
Here are a few other iterations and abominations:&lt;br&gt;
&lt;img alt="That lower left one is some Gucci shit I tell you what" src="https://tvc-16.science/images/stylophone/v25-icontests.png"&gt;  &lt;/p&gt;
&lt;h1&gt;Closing thoughts&lt;/h1&gt;
&lt;p&gt;&lt;img alt="funny gnu meme man" src="https://tvc-16.science/images/rmshacking.png"&gt;&lt;br&gt;
&lt;em&gt;Actually, we encourage people who redistribute free software to charge as much as they wish or can.&lt;/em&gt;&lt;br&gt;
- &lt;a href="https://www.gnu.org/philosophy/selling.en.html"&gt;Free Software Foundation&lt;/a&gt;  &lt;/p&gt;
&lt;p&gt;While Stylophone itself is open-source software, I charge for it on both Stores;&lt;br&gt;
You get &lt;em&gt;easy updates&lt;/em&gt;, and I get to make back the $99 &lt;strong&gt;[FRUIT COMPUTING COMPANY]&lt;/strong&gt; took from me.  &lt;/p&gt;
&lt;p&gt;However, to celebrate the iOS release, the UWP/Windows version is currently &lt;a href="https://www.microsoft.com/store/apps/9NCB693428T8?cid=storebadge&amp;amp;ocid=badge"&gt;&lt;strong&gt;[50% OFF!]&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
It also still has a free trial, so you can see if it's a good fit for you/your MPD server setup before buying.  &lt;/p&gt;
&lt;p&gt;&lt;sup&gt;&lt;sub&gt;...There is no cr&lt;a href="https://cutt.ly/LVu1GDo"&gt;o&lt;/a&gt;ss-buy.&lt;/sup&gt;&lt;/sub&gt;  &lt;/p&gt;</content><category term="Software"></category><category term="mpd"></category><category term="xamarin"></category><category term="dotnet"></category><category term="c#"></category><category term="uwp"></category><category term="windows"></category><category term="ios"></category><category term="app store"></category><category term="music"></category><category term="client"></category><category term="stylophone"></category></entry><entry><title>Sonic McOrigins available at SAGE 2022!</title><link href="https://tvc-16.science/mcorigins.html" rel="alternate"></link><published>2022-09-02T00:00:00+02:00</published><updated>2022-09-02T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2022-09-02:/mcorigins.html</id><summary type="html">&lt;p&gt;heh get it it's like sonic origins but for fast food games god that's hilarious&lt;/p&gt;</summary><content type="html">&lt;h1&gt;The Sonic McOrigins timeline&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="./lcdonald.html"&gt;Original announcement&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;2022 SAGE release (You are here)  &lt;/li&gt;
&lt;li&gt;&lt;a href="./mcorigins-plus.html"&gt;2023 "Plus" SAGE release&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="./mcorigins-xmas.html"&gt;Tony Hawk's McOrigins Plus Chrismtas&lt;/a&gt;  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Check &lt;a href="./mcorigins-xmas.html"&gt;here&lt;/a&gt; for the latest McOrigins-related post (and download links 🍟)  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;Well, it's been 4 months since work started on that &lt;a href="./lcdonald.html"&gt;LCD simulator engine&lt;/a&gt;, and what's been done since then?  &lt;/p&gt;
&lt;h2&gt;17 games!&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Sonic McOrigins Mac screenshot" src="https://tvc-16.science/images/lcdonald/lcdmain.png"&gt;  &lt;/p&gt;
&lt;p&gt;I've digitized my entire collection, and found time to even get some extra ones off eBay to add to the SAGE build.&lt;br&gt;
(And yes, that includes &lt;a href="https://twitter.com/Difegue/status/1547953057660514304?s=20&amp;amp;t=hV9EoNuuFeQgdZ-T8SvxRg"&gt;Big's Fishing!&lt;/a&gt; It almost lived up to my 16-year hype.)  &lt;/p&gt;
&lt;h3&gt;You can now go grab the SAGE build at the matching &lt;a href="https://sonicfangameshq.com/forums/showcase/sonic-mcorigins.1352/"&gt;booth&lt;/a&gt;, for Windows, macOS, and Linux!&lt;/h3&gt;
&lt;p&gt;While I've already talked about the tech stack in the &lt;a href="./lcdonald.html"&gt;previous blogpost&lt;/a&gt;, here are a few extra words about the actual digitizing process, and other fun things I got to do along the way.&lt;br&gt;
So read on! (Or go have fun with the games and then read on, I'll be waiting)  &lt;/p&gt;
&lt;h1&gt;Digitizing them LCD Games (⚠️ jank warning)&lt;/h1&gt;
&lt;p&gt;The digitizing process was certainly the longest part of all this; Thankfully all of the McD LCD games are built the same (&lt;em&gt;read: extremely cheaply&lt;/em&gt;), so it was not necessarily difficult, just tedious.&lt;/p&gt;
&lt;p&gt;How to digitize a McD game you ask? I'll get to it, but first we must talk about &lt;strong&gt;voltage dividers&lt;/strong&gt;!&lt;br&gt;
&lt;img alt="I restrained myself from making a parallel universe joke up here but I can't hold it anymore! It's going in the alt text!" src="https://tvc-16.science/images/lcdonald/divider.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;The McDonald LCD games all run off a 1.5V coin battery, and most of the batteries in my OG games were halfway dead/leaking.&lt;br&gt;
I &lt;em&gt;"could"&lt;/em&gt; have just gone and bought new batteries since they're replaceable (only in the &lt;a href="https://twitter.com/Difegue/status/1557107418810777600?s=20&amp;amp;t=hV9EoNuuFeQgdZ-T8SvxRg"&gt;European&lt;/a&gt; versions though!), but that'd be no fun.  &lt;/p&gt;
&lt;p&gt;A &lt;a href="https://electronics.stackexchange.com/questions/214200/converting-3-3v-to-1-2v"&gt;Voltage divider&lt;/a&gt; essentially allows you to get a lower voltage out of a higher one for small currents -- In this case the 5V coming out of a USB plug. This allows us to power the games with no batteries!  &lt;/p&gt;
&lt;p&gt;&lt;em&gt;"But m8"&lt;/em&gt; you might ask, &lt;em&gt;"why is there an Arduino chucked in the middle of this hot mess?"&lt;/em&gt;  &lt;/p&gt;
&lt;p&gt;If you've ever played a LCD handheld game, you might know than when the game is turned on, &lt;strong&gt;all the elements&lt;/strong&gt; will show up for a few seconds, then blank out so the game can be started.  &lt;/p&gt;
&lt;p&gt;Having all the elements shown at once is paramount to digitizing the game!&lt;br&gt;
It allows you to take a clean scan of all of them at once, which we can then turn into a vectorized graphic. The main issue is that you can't really take a good picture/scan in only two seconds.  &lt;/p&gt;
&lt;p&gt;&lt;em&gt;If only there was a way to have the game stay in that initial state forever...&lt;/em&gt;&lt;br&gt;
Well, that's exactly what the Arduino is here for, and it is &lt;strong&gt;atrocious!&lt;/strong&gt;  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;RXLED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// The RX LED has a defined Arduino pin&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// put your setup code here, to run once:&lt;/span&gt;
  &lt;span class="n"&gt;pinMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OUTPUT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;pinMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RXLED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OUTPUT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// power on&lt;/span&gt;
  &lt;span class="n"&gt;digitalWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RXLED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;digitalRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;digitalWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;digitalRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="c1"&gt;// wait 2s&lt;/span&gt;
  &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// power off&lt;/span&gt;
  &lt;span class="n"&gt;digitalWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RXLED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;digitalRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;digitalWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;digitalRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="c1"&gt;// wait just long enough for the logic to reset but not the screen&lt;/span&gt;
  &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Real engineers would probably hurl at the mere thought of using a 5$ microcontroller as a glorified timed power switch. But it works!&lt;br&gt;
Once the game disassembled and the background removed, it's now pretty easy to take a picture of the whole thing.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="sent from my iphone" src="https://tvc-16.science/images/lcdonald/se_closeup.jpg"&gt;&lt;br&gt;
...with an iPhone SE, which is the best close-up/macro camera I had on hand. &lt;sub&gt;I don't think I'm cut out for this preservation business lads&lt;/sub&gt;  &lt;/p&gt;
&lt;p&gt;The way the LCD screens are built in those games sadly prevents flatbed scanners from being used, so I used shots like this to vectorize the elements to SVG. Which wasn't that painful all things considered!  &lt;/p&gt;
&lt;p&gt;&lt;img src="https://tvc-16.science/images/lcdonald/bfishing.svg"/&gt;  &lt;/p&gt;
&lt;p&gt;The backgrounds are simple flat bits of cardboard, and those could be scanned easily.  &lt;/p&gt;
&lt;p&gt;As a fun side-note, there was one game where I didn't need to do any of this stuff, as it'd been ported to...&lt;a href="https://www.youtube.com/watch?v=jhkwLN6PUss"&gt;Flash&lt;/a&gt; back in 2005 for some weird cross-promotion.&lt;br&gt;
I didn't think I'd end up using a flash decompiler for this project, but here we are!&lt;br&gt;
&lt;img alt="finding a flash decompiler was surprisingly annoying" src="https://tvc-16.science/images/lcdonald/flashdecomp.jpg"&gt;  &lt;/p&gt;
&lt;h1&gt;The Icon&lt;/h1&gt;
&lt;p&gt;I used &lt;a href="https://spline.design/"&gt;Spline&lt;/a&gt; to make the icon:&lt;br&gt;
&lt;img alt="I have a soft spot for mac icons that have some elements break the squircle pattern" src="https://tvc-16.science/theme/img/mcd.png"&gt;  &lt;/p&gt;
&lt;p&gt;As with everything else related to this project, it looks more like an app than a fangame logo...&lt;br&gt;
I had this &lt;em&gt;"Sonic item monitor containing the mcdonald arches"&lt;/em&gt; idea stuck in my head, and I think it looks cool!  &lt;/p&gt;
&lt;p&gt;3D abstract forms are one of the easiest thing to make in Spline, so it seemed logical to use that to slap some Sonic box art shapes on top as well.&lt;br&gt;
&lt;img alt="Some icon alternatives" src="https://tvc-16.science/images/lcdonald/icons.jpg"&gt;&lt;br&gt;
&lt;sub&gt;&lt;sup&gt;The burger alt is in case mcd lawyers come after my ass&lt;/sup&gt;&lt;/sub&gt;  &lt;/p&gt;
&lt;p&gt;As another fun fact, the texture used for the box is the one from &lt;a href="https://www.models-resource.com/pc_computer/sonicgenerations/model/12800/"&gt;Generations.&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Avalonia X-Plat UI fun&lt;/h1&gt;
&lt;p&gt;While &lt;a href="http://avaloniaui.net"&gt;Avalonia&lt;/a&gt; allows you to essentially have the same UI/UX on all operating systems, that doesn't mean you can't actually customize things a bit!&lt;/p&gt;
&lt;p&gt;I won't pretend to fully follow the Mac &lt;a href="https://developer.apple.com/design/human-interface-guidelines/macos/"&gt;HIG&lt;/a&gt; with this silly fangame either, but with some tweaks it's fairly easy to go from the default &lt;a href="https://github.com/amwx/FluentAvalonia"&gt;FluentAvalonia&lt;/a&gt; Windows-friendly look:&lt;br&gt;
&lt;img alt="Sonic McOrigins Windows screenshot" src="https://tvc-16.science/images/lcdonald/win.jpg"&gt;&lt;br&gt;
to a look that fits on the Mac:&lt;br&gt;
&lt;img alt="Sonic McOrigins Mac screenshot" src="https://tvc-16.science/images/lcdonald/mac.png"&gt;&lt;br&gt;
Instead of what it'd look like by default:&lt;br&gt;
&lt;img alt="Sonic McOrigins default Mac screenshot" src="https://tvc-16.science/images/lcdonald/mac_old.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;One might argue that this mostly works just because post Win-11 Windows and post-Big Sur macOS look similar in a &lt;em&gt;lot&lt;/em&gt; of ways, but I'll take the easy win here!  &lt;/p&gt;
&lt;p&gt;Modifying WinUI to fit on macOS does require some retemplating(notably to change the size of the menu items on the left there), but some simple resource overriding will already take you quite a few steps further:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// At app initialization&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OperatingSystem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsMacOS&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Macify the styling a bit&lt;/span&gt;
    &lt;span class="n"&gt;Resources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;ControlContentThemeFontSize&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Make the font size smaller&lt;/span&gt;
    &lt;span class="n"&gt;Resources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;ContentControlThemeFontFamily&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;FontFamily&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;SF Pro Text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Forbidden Apple font&lt;/span&gt;
    &lt;span class="n"&gt;Resources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;ControlCornerRadius&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;CornerRadius&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// HIG corner radius&lt;/span&gt;
    &lt;span class="n"&gt;Resources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;NavigationViewContentGridCornerRadius&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;CornerRadius&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Remove the navview corner radius so it looks like a straight line instead&lt;/span&gt;
    &lt;span class="n"&gt;Resources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;NavigationViewExpandedPaneBackground&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transparent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Resources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;NavigationViewDefaultPaneBackground&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transparent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// When instantiating your main window&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OperatingSystem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsMacOS&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// More Macification&lt;/span&gt;
    &lt;span class="n"&gt;NavigationView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PaneDisplayMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NavigationViewPaneDisplayMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Left&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;NavigationView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenPaneLength&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;248&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Width of the menu&lt;/span&gt;
    &lt;span class="n"&gt;NavigationView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsPaneToggleButtonVisible&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Leftside menu is always open&lt;/span&gt;
    &lt;span class="n"&gt;NavigationView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PaneCustomContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Height&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Custom padding &lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;thm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AvaloniaLocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FluentAvaloniaTheme&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thm&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;RequestedTheme&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Dark&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Custom outer border to simulate macOS&amp;#39; dark theme window decoration -- Add this in XAML yourself!&lt;/span&gt;
        &lt;span class="n"&gt;MacWindowBorder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsVisible&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Another trick involved is to hide the (ugly) default title bar and use custom &lt;strong&gt;drag regions&lt;/strong&gt; to make the window draggable.&lt;br&gt;
You can check the &lt;a href="https://github.com/Difegue/LCDonald"&gt;actual source code&lt;/a&gt; if you're interested in learning more. 🧑‍💻  &lt;/p&gt;
&lt;p&gt;I didn't bother on doing something similar for Linux since it's not like Linux has a coherent design language to begin with! &lt;strong&gt;OHOHOHOHOHO&lt;/strong&gt;  &lt;/p&gt;</content><category term="Software"></category><category term="sonic"></category><category term="mcdonalds"></category><category term="c#"></category><category term="avalonia"></category><category term=".net"></category><category term="lcd"></category><category term="game&amp;watch"></category><category term="video games"></category><category term="simulator"></category><category term="sage"></category></entry><entry><title>Introducing the McD's Sonic LCD Games Simulator</title><link href="https://tvc-16.science/lcdonald.html" rel="alternate"></link><published>2022-04-13T00:00:00+02:00</published><updated>2022-04-13T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2022-04-13:/lcdonald.html</id><summary type="html">&lt;p&gt;Yuji Naka: They probably eat McDonald's® hamburgers, I suppose? And I think they will get a complete line-up of Happy Meal® premiums.&lt;/p&gt;</summary><content type="html">&lt;h1&gt;The Sonic McOrigins timeline&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Original announcement (you are here)  &lt;/li&gt;
&lt;li&gt;&lt;a href="./mcorigins.html"&gt;2022 SAGE release&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;&lt;a href="./mcorigins-plus.html"&gt;2023 "Plus" SAGE release&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="./mcorigins-xmas.html"&gt;Tony Hawk's McOrigins Plus Chrismtas&lt;/a&gt;  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Check &lt;a href="./mcorigins-xmas.html"&gt;here&lt;/a&gt; for the latest McOrigins-related post (and download links 🍟)  &lt;/p&gt;
&lt;h1&gt;&lt;/h1&gt;
&lt;p&gt;Happy Sonic 2 Movie release day!... Is what I'd say if I'd actually managed to publish this on April 8th.&lt;br&gt;
It's been a while since Sonic's last been that present in popular culture, and I think I only fully realized that fact upon seeing the new &lt;a href="https://www.sonicstadium.org/2022/03/more-mcdonalds-sonic-2-happy-meal-toys-leaked/"&gt;McDonald's toys&lt;/a&gt; planned for the movie.  &lt;/p&gt;
&lt;p&gt;While ol' McD's and Sonic have had a &lt;a href="https://www.sonicstadium.org/2019/02/how-mcdonalds-couldnt-keep-up-with-the-worlds-fastest-hedgehog/"&gt;long&lt;/a&gt; and &lt;a href="https://www.sonicstadium.org/2006/01/uk-mcdonalds-sonic-x-happy-meal-promotion-begins/"&gt;fruitful&lt;/a&gt; &lt;a href="http://info.sonicretro.org/Yuji_Naka_interview_by_Sega.com_(June_14,_2003)"&gt;relationship&lt;/a&gt;, there hasn't been a line of Sonic toys released since I was a kid all hyped up for the then-upcoming Sonic X and the Shadow spinoff. &lt;sub&gt;Blissful years&lt;/sub&gt;  &lt;/p&gt;
&lt;p&gt;Thinking that a brand new generation of kids will fall prey to Hedgehog fandom makes me feel slightly older, but it mostly motivated me to take out my old collection of &lt;a href="http://info.sonicretro.org/McDonald%27s_Sonic_LCD_games"&gt;Sonic McDonald's LCD games.&lt;/a&gt;  &lt;/p&gt;
&lt;p&gt;&lt;img alt="Have I made you proud, 12yo myself?" src="https://tvc-16.science/images/lcdonald/games.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;Those games were released in 2005/2006 and are both pretty well known... and kinda obscure!&lt;br&gt;
15 years later, the built-in batteries have started corroding (which isn't that big a deal considering they're quite far from the electronics), and I think most people won't get to experience those games.  &lt;/p&gt;
&lt;p&gt;I've followed the &lt;a href="http://blog.archive.org/2018/03/18/some-very-entertaining-plastic-emulated-at-the-archive/"&gt;MAME LCD&lt;/a&gt; game emulation efforts for a little while now, and entertained the project of doing something similar with those Sonic games.&lt;br&gt;
I don't have the skills or hardware to do "real" emulation (&lt;a href="http://seanriddle.com/decap.html"&gt;processor decapping&lt;/a&gt; is a helluva thing), but I can certainly go as far as &lt;a href="https://github.com/BdR76/lcdgame.js#simulation-vs-emulation"&gt;simulation.&lt;/a&gt;  &lt;/p&gt;
&lt;p&gt;And y'know, better do what you can do, right? So I'm happy to release a first alpha for my Sonic LCD simulator.&lt;br&gt;
&lt;img alt="Even when doing game stuff I can't help but make it look like a UWP app god damn" src="https://tvc-16.science/images/lcdonald/alpha1.jpg"&gt;  &lt;/p&gt;
&lt;h2&gt;Grab it &lt;a href="https://github.com/Difegue/LCDonald/releases"&gt;here&lt;/a&gt;!&lt;/h2&gt;
&lt;p&gt;(Windows only for now - I'll add Mac/Linux support later) &lt;/p&gt;
&lt;p&gt;I started out by simulating the (probably) most complex game of the collection, &lt;a href="http://info.sonicretro.org/Tails_Sky_Adventure"&gt;Tails' Sky Adventure&lt;/a&gt;.&lt;br&gt;
This &lt;em&gt;should&lt;/em&gt; ensure the simulator engine can handle all the other games, and well, haha Sonic 2 movie I guess! 
&lt;sub&gt;&lt;sup&gt;Although you might argue Knuckles played a bigger role in the movie but eh w/e&lt;/sup&gt;&lt;/sub&gt;&lt;/p&gt;
&lt;p&gt;The simulator currently features dynamic view switching (so you can look at all those hires photos I took of the plastic things), and basic play/pause/stop controls. Keyboard only for now, although I'd like to add touch controls in the future.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="It does look nice tho" src="https://tvc-16.science/images/lcdonald/alpha2.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;I'd ideally like to have most of the games simulated in a beta for &lt;a href="https://twitter.com/SAGExpo/status/1513547421812363266?s=20&amp;amp;t=cxa2H5mMauV4-_UCdRy5NA"&gt;SAGE&lt;/a&gt;!&lt;br&gt;
We'll see how that goes.  &lt;/p&gt;
&lt;p&gt;If you'd like to help out by digitizing some games(my scanner isn't the best) or even coding a simulator, you can keep reading for some tech details. &lt;/p&gt;
&lt;h1&gt;Very Quick Technical Breakdown&lt;/h1&gt;
&lt;p&gt;For the layout of the games themselves, I decided to use the same format as &lt;a href="https://docs.mamedev.org/techspecs/layout_files.html"&gt;MAME&lt;/a&gt;.&lt;br&gt;
That makes it so that editing layout doesn't require any coding -- And in the off-chance some crazy dude actually decaps the games for real in the future, hopefully this'll lay the groundwork for integrating them to MAME.  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- tskyadventure.lay --&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;mamelayout&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;2&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Define Elements --&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;element&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;manual_fr&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;image&lt;/span&gt; &lt;span class="na"&gt;file=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;tskyadventure_manual.jpg&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/element&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;element&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;front_open&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;image&lt;/span&gt; &lt;span class="na"&gt;file=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;tskyadventure_front_open.jpg&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/element&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;element&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;front_closed&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;image&lt;/span&gt; &lt;span class="na"&gt;file=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;tskyadventure_front_closed.jpg&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/element&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;element&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;back_open&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;image&lt;/span&gt; &lt;span class="na"&gt;file=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;tskyadventure_back_open.jpg&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/element&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;element&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;back_closed&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;image&lt;/span&gt; &lt;span class="na"&gt;file=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;tskyadventure_back_closed.jpg&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/element&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;element&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;game_bg&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;image&lt;/span&gt; &lt;span class="na"&gt;file=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;tskyadventure_bg.jpg&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/element&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Define Views --&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;view&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Front Open&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;screen&lt;/span&gt; &lt;span class="na"&gt;index=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;bounds&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;1832&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;1313&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;300&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;380&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/screen&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;element&lt;/span&gt; &lt;span class="na"&gt;ref=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;front_open&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;bounds&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;3959&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;2639&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/element&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;element&lt;/span&gt; &lt;span class="na"&gt;ref=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;game_bg&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;bounds&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;1832&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;1313&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;300&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;380&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/element&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/view&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;view&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Front Closed&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;element&lt;/span&gt; &lt;span class="na"&gt;ref=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;front_closed&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;bounds&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;3959&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;2639&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/element&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/view&amp;gt;&lt;/span&gt;

    [...]

&lt;span class="nt"&gt;&amp;lt;/mamelayout&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The simulator itself runs on good ol' .NET -- While I decided to use &lt;a href="http://avaloniaui.net/"&gt;Avalonia&lt;/a&gt; for the front-facing app, the simulator core itself is separated &lt;a href="https://christianfindlay.com/2020/12/21/net-standard/"&gt;.NET Standard 2.1&lt;/a&gt;, and can run on anything .NET runs on.  &lt;/p&gt;
&lt;p&gt;In case you think I'm bloody insane for using a Desktop app framework for games, nothing's stopping you from dunking the core into Unity and making your own frontend! That might be fun.  &lt;/p&gt;
&lt;p&gt;Avalonia was interesting due to its easy SVG integration, which I considered critical for LCD game simulation.&lt;br&gt;
Once again copying what MAME does, the LCD layer is rendered as a SVG image, with specific groups toggled on and off depending on the game state.  &lt;/p&gt;
&lt;p&gt;&lt;img src="https://tvc-16.science/images/lcdonald/tskyadventure.svg"/&gt;&lt;br&gt;
☝️ Also SVGs weigh nothing, which helps offset a bit the huge sizes of the photos/scans of the simulated games.  &lt;/p&gt;
&lt;p&gt;In case you'd like to help, I've opened &lt;a href="https://github.com/Difegue/LCDonald/issues"&gt;tracking issues&lt;/a&gt; for each game!&lt;br&gt;
While I do own most of them, I don't have the manuals for most, and some of the games have eluded me for over 12 years, like &lt;a href="http://info.sonicretro.org/Big%27s_Fishing"&gt;Big's Fishing&lt;/a&gt;.&lt;br&gt;
The issues go into detail as to what I own for each game, and what'd be needed to integrate it into the simulator.  &lt;/p&gt;
&lt;p&gt;Thanks for reading!&lt;/p&gt;</content><category term="Software"></category><category term="sonic"></category><category term="mcdonalds"></category><category term="c#"></category><category term="avalonia"></category><category term=".net"></category><category term="lcd"></category><category term="game&amp;watch"></category><category term="video games"></category><category term="simulator"></category></entry><entry><title>Display a X509 Certificate with SFCertificatePanel on Xamarin.Mac</title><link href="https://tvc-16.science/sfcertificate-mac-xamarin.html" rel="alternate"></link><published>2021-12-17T00:00:00+01:00</published><updated>2021-12-17T00:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2021-12-17:/sfcertificate-mac-xamarin.html</id><summary type="html">&lt;p&gt;Nobody ever cares about Certificate UIs...but I do. (Or at least I had to)&lt;/p&gt;</summary><content type="html">&lt;p&gt;Showing the details of a &lt;a href="https://en.wikipedia.org/wiki/X.509"&gt;X.509 certificate&lt;/a&gt; on Windows is fairly simple through the &lt;a href="https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509certificate2ui?view=dotnet-plat-ext-6.0"&gt;X509Certificate2UI&lt;/a&gt; class, which wraps the native Win32 certificate UI:  &lt;/p&gt;
&lt;p&gt;&lt;img alt="The Win32 certificate details window" src="https://tvc-16.science/images/certs/win32-cert.png"&gt;&lt;/p&gt;
&lt;p&gt;Doing the same on macOS proves to be much less documented, but not that hard!  &lt;/p&gt;
&lt;h1&gt;SFCertificatePanel&lt;/h1&gt;
&lt;p&gt;&lt;img alt="A SFCertificatePanel." src="https://tvc-16.science/images/certs/appkit-cert.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.apple.com/documentation/securityinterface/sfcertificatepanel?language=objc"&gt;SFCertificatePanel&lt;/a&gt; is the AppKit class that handles displaying Certificates and certificate chains.  &lt;/p&gt;
&lt;p&gt;It's...not very well exposed but fairly easy to use:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// Objective-C, show Modal&lt;/span&gt;
&lt;span class="c1"&gt;// trustCertificates is a NSArray of SecCertificate objects&lt;/span&gt;
&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;SFCertificatePanel&lt;/span&gt; &lt;span class="n"&gt;sharedCertificatePanel&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nl"&gt;runModalForCertificates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;trustCertificates&lt;/span&gt; &lt;span class="nl"&gt;showGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;YES&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// Swift, show Sheet in a parent window&lt;/span&gt;
&lt;span class="n"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;certData&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;//read certificate file&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;cert&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SecCertificateCreateWithData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kCFAllocatorDefault&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;certData&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;CFData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;SFCertificatePanel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;beginSheet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modalDelegate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;didEnd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contextInfo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;certificates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cert&lt;/span&gt;&lt;span class="p"&gt;!],&lt;/span&gt; &lt;span class="n"&gt;showGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The Panel can show details for either a single &lt;a href="https://developer.apple.com/documentation/security/seccertificateref?language=objc"&gt;SecCertificate&lt;/a&gt;, an array of them symbolizing a Certificate chain, or a &lt;a href="https://developer.apple.com/documentation/security/sectrustref?language=objc"&gt;SecTrust&lt;/a&gt; object.&lt;/p&gt;
&lt;h1&gt;Usage from a Xamarin.Mac app&lt;/h1&gt;
&lt;p&gt;Sadly, Xamarin.Mac does not &lt;a href="https://github.com/xamarin/xamarin-macios/issues/4177"&gt;wrap the SecurityInterface&lt;/a&gt; library that contains this class, so we have to dig a bit deeper to call on it.  &lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;objc_msgSend&lt;/code&gt;, we can essentially &lt;a href="http://jonathanpeppers.com/Blog/xamarin-ios-under-the-hood-calling-objective-c-from-csharp"&gt;call Objective-C methods&lt;/a&gt; on any class we want, including the unwrapped ones:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SecurityInterface&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// https://developer.apple.com/documentation/securityinterface/sfcertificatepanel&lt;/span&gt;
        &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Class&lt;/span&gt; &lt;span class="n"&gt;_sfCertificatePanelClass&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;SFCertificatePanel&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Selector&lt;/span&gt; &lt;span class="n"&gt;_sharedCertificatePanelSelector&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;sharedCertificatePanel&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Selector&lt;/span&gt; &lt;span class="n"&gt;_runModalForCertificatesSelector&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;runModalForCertificates:showGroup:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Selector&lt;/span&gt; &lt;span class="n"&gt;_beginSheetForWindowSelector&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;beginSheetForWindow:modalDelegate:didEndSelector:contextInfo:certificates:showGroup:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Since we&amp;#39;re not doing a full Xamarin binding project for SecurityInterface.framework,&lt;/span&gt;
        &lt;span class="c1"&gt;// We need to re-declare some of the ObjC messaging functions since they&amp;#39;re normally hidden from us.&lt;/span&gt;
        &lt;span class="c1"&gt;// (http://jonathanpeppers.com/Blog/xamarin-ios-under-the-hood-calling-objective-c-from-csharp)&lt;/span&gt;
&lt;span class="na"&gt;        [DllImport(Constants.ObjectiveCLibrary, EntryPoint = &amp;quot;objc_msgSend&amp;quot;)]&lt;/span&gt;
        &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="nf"&gt;IntPtr_objc_msgSend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;receiver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="na"&gt;        [DllImport(Constants.ObjectiveCLibrary, EntryPoint = &amp;quot;objc_msgSend&amp;quot;)]&lt;/span&gt;
        &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;global&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nint&lt;/span&gt; &lt;span class="n"&gt;nint_objc_msgSend_IntPtr_bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;receiver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;arg1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;arg2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="na"&gt;        [DllImport(Constants.ObjectiveCLibrary, EntryPoint = &amp;quot;objc_msgSend&amp;quot;)]&lt;/span&gt;
        &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;void_objc_msgSend_IntPtr_IntPtr_IntPtr_IntPtr_bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;receiver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;arg1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;arg2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;arg3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;arg4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;arg5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;arg6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


        &lt;span class="c1"&gt;// + (SFCertificatePanel *)sharedCertificatePanel;&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="nf"&gt;GetSharedCertificatePanel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;IntPtr_objc_msgSend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_sfCertificatePanelClass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_sharedCertificatePanelSelector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;//- (NSInteger)runModalForCertificates:(NSArray *)certificates showGroup:(BOOL)showGroup;&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;nint&lt;/span&gt; &lt;span class="nf"&gt;RunModalForCertificates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;certificatePanel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NSArray&lt;/span&gt; &lt;span class="n"&gt;certificates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;showGroup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;nint_objc_msgSend_IntPtr_bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;certificatePanel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_runModalForCertificatesSelector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;certificates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;showGroup&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// - (void)beginSheetForWindow:(NSWindow *)docWindow modalDelegate:(id)delegate didEndSelector:(SEL)didEndSelector contextInfo:(void *)contextInfo certificates:(NSArray *)certificates showGroup:(BOOL)showGroup;&lt;/span&gt;
        &lt;span class="c1"&gt;// delegate, didEndSelector and contextInfo are unmapped. (IntPtr.Zero)&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;BeginCertificateSheetForWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;certificatePanel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;windowHandle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NSArray&lt;/span&gt; &lt;span class="n"&gt;certificates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;showGroup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;void_objc_msgSend_IntPtr_IntPtr_IntPtr_IntPtr_bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;certificatePanel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_beginSheetForWindowSelector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;windowHandle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;certificates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;showGroup&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;(A full-on &lt;a href="https://docs.microsoft.com/en-us/xamarin/cross-platform/macios/binding/?context=xamarin/mac"&gt;Xamarin Binding Library&lt;/a&gt; would obviously be cleaner than this, but it's not worth the effort considering we're not using all of SecurityInterface...)  &lt;/p&gt;
&lt;p&gt;With those few methods on hand, we can easily invoke a SFCertificatePanel from .NET code:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;DisplayCertificate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X509Certificate2&lt;/span&gt; &lt;span class="n"&gt;certificate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;windowParent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;SecCertificate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;certificate&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Put the certificate in a NSArray for compliance with the API&lt;/span&gt;
      &lt;span class="n"&gt;NSArray&lt;/span&gt; &lt;span class="n"&gt;certificates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NSArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FromNSObjects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;certificatePanel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SecurityInterface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetSharedCertificatePanel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;windowParent&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;SecurityInterface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunModalForCertificates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;certificatePanel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;certificates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;SecurityInterface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BeginCertificateSheetForWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;certificatePanel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;windowParent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;certificates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And things just work! Although there are a few issues...  &lt;/p&gt;
&lt;h2&gt;Mono and X509Certificate2&lt;/h2&gt;
&lt;p&gt;Since we're still using a version of Xamarin that relies on &lt;a href="https://github.com/mono/mono/blob/main/mcs/class/System/System.Security.Cryptography.X509Certificates/X509Certificate2.cs"&gt;Mono&lt;/a&gt; instead of .NET 6, the X509Certificate2 class isn't fully implemented and won't show full certificate chains:&lt;br&gt;
&lt;img alt="A lone, single certificate" src="https://tvc-16.science/images/certs/no-chain.png"&gt;&lt;br&gt;
This is troublesome if you want to show a certificate chain where some intermediates are not in the system keychain: it will show as untrusted...even though the full chain is valid!  &lt;/p&gt;
&lt;p&gt;The easiest solution would be to move to .NET 6, but as that's not quite available yet, we have to bypass X509Certificate2 entirely and load the certificate using only macOS APIs:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pfx&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...]&lt;/span&gt; &lt;span class="c1"&gt;// class that contains both path to a .pfx certificate file and its password &lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;NSMutableDictionary&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="na"&gt;    [SecImportExport.Passphrase]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;NSString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pfx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Password&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;// ImportPkcs12 imports the given certificate to the Keychain by default.&lt;/span&gt;
    &lt;span class="c1"&gt;// Since we just want to check the certificate, we can avoid this behavior by setting ImportExportKeychain to nil.&lt;/span&gt;
    &lt;span class="c1"&gt;// (or an empty NSObject, since passing null isn&amp;#39;t allowed here)&lt;/span&gt;
&lt;span class="na"&gt;    [new NSString(&amp;quot;kSecImportExportKeychain&amp;quot;)]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;NSObject&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Use SecImportExport to get SecCertificateRefs from the .pfx file&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SecImportExport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ImportPkcs12&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NSData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pfx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FilePath&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;outData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;SecStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;certificateInfo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;outData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="c1"&gt;// Get the chain as an array of SecCertificates&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;certificateInfo&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;chain&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;NSArray&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Proceed as we did before&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;certificatePanel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SecurityInterface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetSharedCertificatePanel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;windowParent&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;SecurityInterface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunModalForCertificates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;certificatePanel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;SecurityInterface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BeginCertificateSheetForWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;certificatePanel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;windowParent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And then we get a full chain!  &lt;/p&gt;
&lt;p&gt;&lt;img alt="A certificate and his family 😊" src="https://tvc-16.science/images/certs/yes-chain.png"&gt;  &lt;/p&gt;
&lt;p&gt;The first time, at least.&lt;/p&gt;
&lt;h2&gt;Showing the panel multiple times&lt;/h2&gt;
&lt;p&gt;There seems to be a weird bug with the shared Certificate Panel on Big Sur where if you show it multiple times, the top part showing the certificate chain doesn't show anymore and stays blank. 😔   &lt;/p&gt;
&lt;p&gt;To solve this, we have to &lt;strong&gt;instantiate&lt;/strong&gt; the panel each time we want to show it.&lt;br&gt;
This requires a few more modifications to our static SecurityInterface class:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// Instantiate a SFCertificatePanel object, wrapped in the Xamarin container.&lt;/span&gt;
&lt;span class="c1"&gt;/// We can&amp;#39;t use sharedCertificatePanel: since it has display issues if we show a certificate chain multiple times.&lt;/span&gt;
&lt;span class="c1"&gt;/// &lt;/span&gt;
&lt;span class="c1"&gt;/// From the Apple documentation (https://developer.apple.com/documentation/securityinterface/sfcertificatepanel/1543245-shared): &lt;/span&gt;
&lt;span class="c1"&gt;/// If your application can display multiple certificate panels or sheets at once, you must allocate separate object instances&lt;/span&gt;
&lt;span class="c1"&gt;/// (using the alloc class method inherited from NSObject) and initialize them (using the init() instance method,&lt;/span&gt;
&lt;span class="c1"&gt;/// also inherited from NSObject) instead of using this class method.&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;NSObject&lt;/span&gt; &lt;span class="nf"&gt;CreateCertificatePanel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetNSObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;IntPtr_objc_msgSend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IntPtr_objc_msgSend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_sfCertificatePanelClass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Selector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetHandle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;alloc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="n"&gt;Selector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetHandle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;init&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

&lt;span class="c1"&gt;//- (NSInteger)runModalForCertificates:(NSArray *)certificates showGroup:(BOOL)showGroup;&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;nint&lt;/span&gt; &lt;span class="nf"&gt;RunModalForCertificates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NSObject&lt;/span&gt; &lt;span class="n"&gt;certificatePanel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NSArray&lt;/span&gt; &lt;span class="n"&gt;certificates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;showGroup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;nint_objc_msgSend_IntPtr_bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;certificatePanel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_runModalForCertificatesSelector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;certificates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;showGroup&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// - (void)beginSheetForWindow:(NSWindow *)docWindow modalDelegate:(id)delegate didEndSelector:(SEL)didEndSelector contextInfo:(void *)contextInfo certificates:(NSArray *)certificates showGroup:(BOOL)showGroup;&lt;/span&gt;
&lt;span class="c1"&gt;// delegate, didEndSelector and contextInfo are unmapped. (IntPtr.Zero)&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;BeginCertificateSheetForWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NSObject&lt;/span&gt; &lt;span class="n"&gt;certificatePanel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;windowHandle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NSArray&lt;/span&gt; &lt;span class="n"&gt;certificates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;showGroup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;void_objc_msgSend_IntPtr_IntPtr_IntPtr_IntPtr_bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;certificatePanel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_beginSheetForWindowSelector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;windowHandle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;certificates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;showGroup&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;In this block of code, we now instantiate a SFCertificatePanel using the regular objc alloc/init selectors, and wrap it into a Xamarin NSObject to make the code &lt;em&gt;slightly&lt;/em&gt; clearer. (although it doesn't help that much...)  &lt;/p&gt;
&lt;p&gt;Using the new methods, we can now show the certificate panel multiple times without any issues:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// Make sure to deinitialize the created CertificatePanel.&lt;/span&gt;
&lt;span class="c1"&gt;// We use xamarin&amp;#39;s built-in dispose for this, which calls the objc &amp;quot;release&amp;quot; selector on its own.&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;certificatePanel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SecurityInterface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateCertificatePanel&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;windowParent&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;SecurityInterface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunModalForCertificates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;certificatePanel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;SecurityInterface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BeginCertificateSheetForWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;certificatePanel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;windowParent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h1&gt;Closing thoughts&lt;/h1&gt;
&lt;p&gt;I added syntax highlighting to the blog after writing this article since all the giant blobs of &lt;code&gt;objc_msgSend&lt;/code&gt; are already unreadable enough 😅  &lt;/p&gt;
&lt;p&gt;It was simple enough:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pygmentize -S perldoc -f html -a .highlight &amp;gt; theme/static/css/pygment.css
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Followed by adding this new CSS into the headers of the template.  &lt;/p&gt;</content><category term="Software"></category><category term="macos"></category><category term="c#"></category><category term="xamarin"></category><category term=".net"></category><category term="certificate"></category><category term="sfcertificatepanel"></category><category term="x509certificate2ui"></category></entry><entry><title>Apply Mica to a WPF app on Windows 11</title><link href="https://tvc-16.science/mica-wpf.html" rel="alternate"></link><published>2021-10-13T00:00:00+02:00</published><updated>2021-10-13T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2021-10-13:/mica-wpf.html</id><summary type="html">&lt;p&gt;I don't need no Windows.UI.Composition.&lt;/p&gt;</summary><content type="html">&lt;p&gt;The &lt;a href="https://docs.microsoft.com/en-us/windows/apps/design/style/mica"&gt;Mica&lt;/a&gt; material is one of the hypest parts of Windows 11 app design, but just like Acrylic before it,&lt;br&gt;
it's a royal pain to use if you're unable to move your entire app to rely on the UWP/WinUI stack.  &lt;br&gt;
&lt;sup&gt;(Even &lt;a href="https://github.com/microsoft/microsoft-ui-xaml/issues/5319"&gt;XAML Islands&lt;/a&gt; can't save you here)&lt;/sup&gt;  &lt;/p&gt;
&lt;p&gt;Now sure, you could add a WinRT &lt;a href="https://github.com/microsoft/Windows.UI.Composition-Win32-Samples"&gt;Visual Brush&lt;/a&gt; to your app, then use raw Windows.UI.Composition APIs to build the material, mimicking what &lt;a href="https://github.com/microsoft/microsoft-ui-xaml/blob/main/dev/Materials/Backdrop/MicaController.cpp"&gt;WinUI&lt;/a&gt; does... but I guess not even Microsoft wants to suffer through that, since they added a &lt;a href="https://docs.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute"&gt;DwmWindowAttribute&lt;/a&gt; to apply a Mica brush to any given HWND.  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DWMWINDOWATTRIBUTE&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;DWMWA_NCRENDERING_ENABLED&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;Is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;non&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rendering&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;disabled&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;DWMWA_USE_HOSTBACKDROPBRUSH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BOOL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Allows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;backdrop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;brushes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;DWMWA_USE_IMMERSIVE_DARK_MODE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BOOL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Allows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;window&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;either&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;accent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;according&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Mode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;preferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;DWMWA_WINDOW_CORNER_PREFERENCE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;WINDOW_CORNER_PREFERENCE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Controls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rounds&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;top&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;level&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;window&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;corners&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;DWMWA_BORDER_COLOR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                         &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;COLORREF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;thin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;border&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;around&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;top&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;level&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;window&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;DWMWA_CAPTION_COLOR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;COLORREF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;caption&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;DWMWA_TEXT_COLOR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                           &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;COLORREF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;caption&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;text&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;DWMWA_VISIBLE_FRAME_BORDER_THICKNESS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;visible&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;border&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;around&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;thick&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;window&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;DWMWA_MICA_EFFECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1029&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;                   &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BOOL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;undocumented&lt;/span&gt;&lt;span class="vm"&gt;??&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;DWMWA_LAST&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;a href="https://devblogs.microsoft.com/oldnewthing/?p=41373"&gt;usual warnings&lt;/a&gt; apply since this attribute is undocumented, but if it's anything like the &lt;a href="https://withinrafael.com/2018/02/02/adding-acrylic-blur-to-your-windows-10-apps-redstone-4-desktop-apps/"&gt;Acrylic&lt;/a&gt; WindowCompositionAttribute trick, &lt;strike&gt;it should at least work until WinUI 3 is far enough in development.&lt;/strike&gt; lmao &lt;/p&gt;
&lt;p&gt;(&lt;strong&gt;Note&lt;/strong&gt;: This attribute was removed from Windows 11 in insider build &lt;em&gt;22494&lt;/em&gt;, and replaced by &lt;code&gt;DWMWA_SYSTEMBACKDROP_TYPE&lt;/code&gt; in builds &lt;em&gt;22523&lt;/em&gt; and up. Scroll to the bottom of the article for more info!)&lt;/p&gt;
&lt;p&gt;Applying this flag to a HWND in WPF is quite easy:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;partial&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MainWindow&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Window&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="na"&gt;    [DllImport(&amp;quot;dwmapi.dll&amp;quot;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;DwmSetWindowAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;hwnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DwmWindowAttribute&lt;/span&gt; &lt;span class="n"&gt;dwAttribute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;pvAttribute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;cbAttribute&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="na"&gt;    [Flags]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;DwmWindowAttribute&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;uint&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;DWMWA_USE_IMMERSIVE_DARK_MODE&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;DWMWA_MICA_EFFECT&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1029&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;MainWindow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;InitializeComponent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;ContentRendered&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;Window_ContentRendered&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Window_ContentRendered&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EventArgs&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Apply Mica brush&lt;/span&gt;
        &lt;span class="n"&gt;UpdateStyleAttributes&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;HwndSource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;UpdateStyleAttributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HwndSource&lt;/span&gt; &lt;span class="n"&gt;hwnd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;trueValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="n"&gt;x01&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;DwmSetWindowAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hwnd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DwmWindowAttribute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DWMWA_MICA_EFFECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="n"&gt;trueValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SizeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Window_Loaded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RoutedEventArgs&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Get PresentationSource&lt;/span&gt;
        &lt;span class="n"&gt;PresentationSource&lt;/span&gt; &lt;span class="n"&gt;presentationSource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PresentationSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FromVisual&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;Visual&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Subscribe to PresentationSource&amp;#39;s ContentRendered event&lt;/span&gt;
        &lt;span class="n"&gt;presentationSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentRendered&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;Window_ContentRendered&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To get the brush to actually show, we also need to remove WPF's built-in chrome with a &lt;a href="https://docs.microsoft.com/en-us/dotnet/api/system.windows.shell.windowchrome"&gt;WindowChrome&lt;/a&gt; override:&lt;br&gt;
(You can also use &lt;code&gt;WindowStyle.None&lt;/code&gt;, but &lt;code&gt;WindowChrome&lt;/code&gt; lets you keep the system controls, which will come in handy.)  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Window&lt;/span&gt; &lt;span class="na"&gt;x:Class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;MicaTest.MainWindow&amp;quot;&lt;/span&gt;
        &lt;span class="na"&gt;Background=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Transparent&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;WindowChrome.WindowChrome&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;WindowChrome&lt;/span&gt;
            &lt;span class="na"&gt;CaptionHeight=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;20&amp;quot;&lt;/span&gt;
            &lt;span class="na"&gt;ResizeBorderThickness=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;8&amp;quot;&lt;/span&gt;
            &lt;span class="na"&gt;CornerRadius=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0&amp;quot;&lt;/span&gt;
            &lt;span class="na"&gt;GlassFrameThickness=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;-1&amp;quot;&lt;/span&gt;
            &lt;span class="na"&gt;UseAeroCaptionButtons=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;True&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/WindowChrome.WindowChrome&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;TextBlock&lt;/span&gt; &lt;span class="na"&gt;HorizontalAlignment=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Center&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;VerticalAlignment=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Center&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Hello from Mica on WPF!&lt;span class="nt"&gt;&amp;lt;/TextBlock&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Window&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img alt="a perfectly cromulent white wpf window with mica on top." src="https://tvc-16.science/images/mica/mica_wpf_white.png"&gt;  &lt;/p&gt;
&lt;p&gt;At this point, you'll have the material, but it &lt;strong&gt;won't change with the Windows theme&lt;/strong&gt;.&lt;br&gt;
Mica looks way better in Dark Mode than in Light&lt;sup&gt;&lt;sub&gt;(Don't @ me)&lt;/sub&gt;&lt;/sup&gt;, so to handle that, you'll need to add the &lt;code&gt;DWMWA_USE_IMMERSIVE_DARK_MODE&lt;/code&gt; flag to the mix.  &lt;/p&gt;
&lt;p&gt;This flag, when set, currently &lt;strong&gt;forces&lt;/strong&gt; the Mica brush to render in dark mode, so it has to be toggled on or off depending on the current Windows theme.&lt;br&gt;
That means we need some way to detect theme changes, and &lt;em&gt;while&lt;/em&gt; you could use some more Win32 for that, I got lazy and just slapped &lt;a href="https://github.com/Kinnara/ModernWpf"&gt;ModernWpf&lt;/a&gt; on top of the app:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Window_ContentRendered&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EventArgs&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Apply Mica brush and ImmersiveDarkMode if needed&lt;/span&gt;
        &lt;span class="n"&gt;UpdateStyleAttributes&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;HwndSource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Hook to Windows theme change to reapply the brushes when needed&lt;/span&gt;
        &lt;span class="n"&gt;ModernWpf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ThemeManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActualApplicationThemeChanged&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;UpdateStyleAttributes&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;HwndSource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;UpdateStyleAttributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HwndSource&lt;/span&gt; &lt;span class="n"&gt;hwnd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// You can avoid using ModernWpf here and just rely on Win32 APIs or registry parsing if you want to.&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;darkThemeEnabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ModernWpf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ThemeManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActualApplicationTheme&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ModernWpf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApplicationTheme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dark&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;trueValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="n"&gt;x01&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;falseValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="n"&gt;x00&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Set dark mode before applying the material, otherwise you&amp;#39;ll get an ugly flash when displaying the window.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;darkThemeEnabled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;DwmSetWindowAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DwmWindowAttribute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DWMWA_USE_IMMERSIVE_DARK_MODE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="n"&gt;trueValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SizeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
            &lt;span class="nf"&gt;DwmSetWindowAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DwmWindowAttribute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DWMWA_USE_IMMERSIVE_DARK_MODE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="n"&gt;falseValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SizeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

        &lt;span class="n"&gt;DwmSetWindowAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DwmWindowAttribute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DWMWA_MICA_EFFECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="n"&gt;trueValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SizeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;ModernWpf also allows you to easily have Theme-aware resources on WPF, which is super convenient and basically gives us our final sample:&lt;br&gt;
&lt;img alt="look ma no composition" src="https://tvc-16.science/images/mica/mica_wpf.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;And since we left in system controls, that means we get Windows 11's snap assist out of the box like all the cool kids do:&lt;br&gt;
&lt;img alt="everyday i'm snapping" src="https://tvc-16.science/images/mica/mica_wpf_snap.png"&gt;  &lt;/p&gt;
&lt;h3&gt;You can find the full sample &lt;a href="https://github.com/Difegue/Mica-WPF-Sample"&gt;here.&lt;/a&gt;&lt;/h3&gt;
&lt;h2&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;If you run this sample on Windows 10, both those DWM Attributes do nothing, so you're left with the kinda boring Windows 10 chrome:  &lt;/p&gt;
&lt;p&gt;&lt;img alt="modernwpf styles voluntarily removed to prove this isn't just a UWP app or something" src="https://tvc-16.science/images/mica/mica_win10.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;Which will respect the user's &lt;em&gt;Apply the accent color to the window borders&lt;/em&gt; preference, so you'll either have an accent color like I do, or pure black/white.&lt;br&gt;
You can toy with the &lt;code&gt;GlassFrameThickness&lt;/code&gt; attribute if you want to reclaim some non-chrome space. 👍  &lt;/p&gt;
&lt;p&gt;You might've noticed the system controls have a weird padding to the right: This comes from &lt;code&gt;WindowChrome&lt;/code&gt; not having been updated for Windows 10 and keeping the old window border sizes.  &lt;/p&gt;
&lt;p&gt;This is fixable by adding &lt;code&gt;NonClientFrameEdges="Bottom,Left,Right"&lt;/code&gt;, but it causes a host of other issues so I didn't include it in the sample.&lt;br&gt;
You can find some more info about that &lt;a href="https://github.com/dotnet/wpf/issues/3887"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Windows 11 22523+ Update&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;DWMWA_MICA_EFFECT = 1029&lt;/code&gt; has been replaced by the public API (!) &lt;code&gt;DWMWA_SYSTEMBACKDROP_TYPE = 38&lt;/code&gt; in &lt;a href="https://twitter.com/StartIsBack/status/1471262840313065474?s=20"&gt;build 22523&lt;/a&gt;, which functions mostly the same although with some much-desired improvements.  &lt;/p&gt;
&lt;p&gt;The attribute accepts an int instead of just a boolean and can enable different backdrop types:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;DWMSBT_AUTO = 0,&lt;/span&gt;
&lt;span class="err"&gt;DWMSBT_DISABLE = 1, // None&lt;/span&gt;
&lt;span class="err"&gt;DWMSBT_MAINWINDOW = 2, // Mica&lt;/span&gt;
&lt;span class="err"&gt;DWMSBT_TRANSIENTWINDOW = 3, // Acrylic&lt;/span&gt;
&lt;span class="err"&gt;DWMSBT_TABBEDWINDOW = 4 // Tabbed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;As per StartIsBack, "Tabbed is just untinted / unblended Mica, i.e. heavily blurred wallpaper."  &lt;/p&gt;
&lt;p&gt;You can find updated WPF sample code that uses this attribute &lt;a href="https://github.com/dongle-the-gadget/SystemBackdropTypes"&gt;here.&lt;/a&gt;  &lt;/p&gt;
&lt;p&gt;&lt;img alt="Acrylic attribute" src="https://user-images.githubusercontent.com/29563098/146360322-5ee76a3e-49ac-4ef4-881b-e1a8e5dd959a.png"&gt;  &lt;/p&gt;
&lt;p&gt;&lt;img alt="Mica attribute" src="https://user-images.githubusercontent.com/29563098/146360362-8b7cb5f1-6053-4c13-a7b8-b2b910500f50.png"&gt;  &lt;/p&gt;
&lt;p&gt;&lt;img alt="Tabbed attribute" src="https://user-images.githubusercontent.com/29563098/146360394-4f6773f1-35b1-4136-9ad8-1e1a923afc0b.png"&gt;  &lt;/p&gt;</content><category term="Cool Tricks"></category><category term="mica"></category><category term="wpf"></category><category term="C#"></category><category term="modernwpf"></category><category term="windows 11"></category><category term="dwm"></category><category term="very dangerous windows hack age 18 and up content"></category></entry><entry><title>LANraragi User Survey 3 Results</title><link href="https://tvc-16.science/lrr-survey-3-results.html" rel="alternate"></link><published>2021-10-09T00:00:00+02:00</published><updated>2021-10-09T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2021-10-09:/lrr-survey-3-results.html</id><summary type="html">&lt;p&gt;Excel kept crashing, making compiling all the data for this extra painful 😭🔫.&lt;/p&gt;</summary><content type="html">&lt;p&gt;LRR &lt;a href="./lrr-survey-3"&gt;Survey 3&lt;/a&gt; results are here! &lt;/p&gt;
&lt;p&gt;I've recently released Version &lt;a href="https://github.com/Difegue/LANraragi/releases/tag/v.0.8.0"&gt;0.8.0&lt;/a&gt; "&lt;em&gt;Black Tie White Noise&lt;/em&gt;", which is mostly a polish release this time instead of a big bowl o' features.   &lt;/p&gt;
&lt;p&gt;This makes for a nice, clean slate, and the 0.9.x development cycle &lt;em&gt;should&lt;/em&gt; therefore mostly be driven by your suggestions -- Downloader support was the last "personal" feature I really wanted in.  &lt;/p&gt;
&lt;p&gt;So let's get to analyzing those results!  &lt;/p&gt;
&lt;h1&gt;Users and platforms&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operating System&lt;/th&gt;
&lt;th&gt;2020 Users&lt;/th&gt;
&lt;th&gt;2021 Users&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;macOS&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DSM&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unraid&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Doesn't actually use LRR&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;129&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Lots more replies this year -- I think actually advertising the survey properly yielded some results! 😅  &lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Install method/location&lt;/th&gt;
&lt;th&gt;2020 Users&lt;/th&gt;
&lt;th&gt;2021 Users&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Windows Installer - Server&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows Installer - Local&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker - Server&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;67&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker - Local&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Built it from source - Server&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Built it from source - Local&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Homebrew&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UnRAID Package&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total&lt;/td&gt;
&lt;td&gt;66&lt;/td&gt;
&lt;td&gt;126&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Nothing too surprising here.&lt;br&gt;
The UnRAID package is technically a glorified Docker container&lt;sup&gt;&lt;sub&gt;(just like the Windows installer lmao)&lt;/sup&gt;&lt;/sub&gt;, but I thought it'd be interesting to count it separately.  &lt;/p&gt;
&lt;p&gt;Last year I was impressed at the Github star count being at 250, but somehow now it's at &lt;a href="https://github.com/Difegue/LANraragi/stargazers"&gt;600+&lt;/a&gt;?? Please stop botting the starcount I can't believe there are that many people &lt;/p&gt;
&lt;h1&gt;Usage and external readers&lt;/h1&gt;
&lt;h2&gt;Favorite features&lt;/h2&gt;
&lt;p&gt;The original question for this was &lt;em&gt;"Which of the following features do you use most/care the most about?"&lt;/em&gt;  &lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Users&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Batch Tagging&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Categories&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;URL Downloading&lt;/td&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Column Customization&lt;/td&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backup/Restore&lt;/td&gt;
&lt;td&gt;38&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client API&lt;/td&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compact/Table View&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom Themes&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Some of those are not surprising at all (Batch Tagging is always a hit and I should probably expand it a bit in the future), but I'm glad people enjoy column customization!&lt;br&gt;
Its current implementation in thumbnail view is a bit weird, but it works.  &lt;/p&gt;
&lt;p&gt;I've really enjoyed using URL Downloading (especially since building the Tsukihi Browser Extension), so I'm glad it's been of use to others as well.  &lt;/p&gt;
&lt;h2&gt;Themes&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Theme&lt;/th&gt;
&lt;th&gt;Users&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Default&lt;/td&gt;
&lt;td&gt;83&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sad Panda&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HentaiVerse&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nadeko&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Yotsugi&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total&lt;/td&gt;
&lt;td&gt;126&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;That one kinda came out of left field since I was somehow expecting themes to be used more. (despite never having ran a survey for it 🥲)  &lt;/p&gt;
&lt;p&gt;The current custom-CSS-dropdown system is one of the oldest pieces of code in the app, but it's not very well exposed. (just being a small link at the footer of every page).&lt;br&gt;
I'm likely going to move theme selection into settings and &lt;strong&gt;try&lt;/strong&gt; to expose it better. &lt;sup&gt;And if that doesn't work I'll just gut themes maintaining 5 CSS files is painful&lt;/sup&gt;&lt;/p&gt;
&lt;h2&gt;Third-party clients&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;External Readers used&lt;/th&gt;
&lt;th&gt;2020 Users&lt;/th&gt;
&lt;th&gt;2021 Users&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No, I only use the Web Reader&lt;/td&gt;
&lt;td&gt;49&lt;/td&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tachiyomi Extension&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ichaival (Android)&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LRReader (Windows)&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generic OPDS&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DuManga (iOS)&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tsukihi (WebExtension)&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;It's great to see more people use the third-party clients!&lt;br&gt;
There's a fair amount of work involved in them, and I'd never have thought there'd be a client for every major OS somehow.  &lt;/p&gt;
&lt;p&gt;I also suspect they're used way more than this survey lets on due to, ahem, &lt;em&gt;external services&lt;/em&gt; reimplementing the LRR API to distribute manga without having to expose an HTML front-end.  &lt;/p&gt;
&lt;p&gt;Which I think is awesome! Although said services should probably implement the API properly to avoid a &lt;a href="https://github.com/Guerra24/LRReader/issues/20"&gt;bad user-experience&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;I'm planning to add links to all third-party clients in the main app when I finally get around to implementing a good out-of-box wizard, so hopefully users will notice said clients even more in the future. 🙏  &lt;/p&gt;
&lt;h1&gt;Feature requests and suggestions&lt;/h1&gt;
&lt;h2&gt;The Featurebowl&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Wished by&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Improved Reader Performance&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Improved Search Performance&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplicate Detection&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deeper Category Integration&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Better Out of Box Experience&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Yeah, I pretty much rigged the results by adding those two improved performance choices at the top. 🎰  &lt;/p&gt;
&lt;p&gt;I have some stuff in the works for Reader performance, and Search has been &lt;em&gt;slightly&lt;/em&gt; improved in 0.8, although there's still a bunch of work to be made here.  &lt;/p&gt;
&lt;p&gt;Dupe detection is still a favorite, and I'm sorry for not having worked on it this year 🥲 However LRReader now has a &lt;a href="https://github.com/Guerra24/LRReader"&gt;deduplicator&lt;/a&gt; available, and it works quite well!&lt;br&gt;
A built-in implementation would only be marginally faster, so I'm probably going to redirect people to use LRReader for this for the time being. (And someday I'll sherlock it I guess)&lt;/p&gt;
&lt;p&gt;And uh here's a direct quote from last year:  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I still want to do the OoB experience thing at some point, it's logical that existing users wouldn't care too much about it.   &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Suggestion Box&lt;/h2&gt;
&lt;p&gt;Here are a few interesting messages/reqs I got from the feature suggestion box.  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 Situational compression/Compression options like on sadpanda. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This feature isn't advertised too well but does exist -- Check "Resize Images in Reader" under Global Settings.&lt;br&gt;
If you think it's not as powerful/efficient as you'd like, a GH Issue would be most welcome!  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 Somekind of way to check whether X gallery on panda is already on the server. Example: You load the page and somewhere in the metadata of the gallery it's writte it's not currently uploaded on the LRR server.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Please check out the &lt;a href="https://github.com/Difegue/Tsukihi"&gt;Tsukihi&lt;/a&gt; Browser Extension for this!  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 Also a watched folder that auto-imports.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The content folder normally auto-imports whatever lands in it and that essentially &lt;em&gt;just works&lt;/em&gt; for your case (Linux/Docker user), if it's not importing properly I'd recommend checking if the background worker is running.  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 Wish there's a way to disable or pause background worker Shinobu, maybe start checking new files manually is better way for my poor hdd, they always been reading and cannot go sleep mode.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There are &lt;a href="https://sugoi.gitbook.io/lanraragi/api-documentation/shinobu-api"&gt;API Endpoints&lt;/a&gt; to stop/start Shinobu, but I haven't made them accessible in the main interface since I really don't think users should run with the worker off.&lt;br&gt;
&lt;sup&gt;LRReader does provide the option though if you really want to do it&lt;/sup&gt;&lt;br&gt;
Your HDD woes might've been caused by Redis writing to disk a bit too often since I've made Minion use it as a database instead of SQLite -- I've &lt;a href=""&gt;changed&lt;/a&gt; the Docker/Windows Redis config in 0.8.0 to write to disk much less often, so hopefully this won't be an issue anymore. 👍  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 custom rules to merge and replace redundant tags, select and delete multiple archives at once&lt;br&gt;
👉 A feature to scour the database and replace all tags reading 'x' with a 'y' equiv would be fantastic.&lt;br&gt;
👉 Tag replacement  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://sugoi.gitbook.io/lanraragi/advanced-usage/tag-rules"&gt;Tag Rules&lt;/a&gt;, recently added in 0.8.0, should cover those requests pretty well! There's no support yet for going back and applying them across the entire database, but I'll try adding that in a future release.  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 Better batch tagging (tag merging/renaming, mostly for dealing with author names) (also a list of all tags would be awesome)&lt;br&gt;
👉 feature to help edit tags of multiple manga/doujin at once&lt;br&gt;
👉 Checkboxes or ability to select mutiple files at once (for batch deletion or adding to categories).&lt;br&gt;
👉 batch tagging in minion, change job delay according to service response (avoid panda bans)   &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I'm considering expanding on batch tagging, so stuff like batch deletion is certainly on the table.&lt;br&gt;
Now that I'm thinking of it, it'd also be a convenient place to integrate tag rules, so you can apply them across multiple files in one click.&lt;/p&gt;
&lt;p&gt;I probably won't move batch tagging to Minion however, since the current implementation using websockets allows one to easily monitor the state of the batch operation from the browser. (At the expanse of having to keep the browser tab open, I know)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 Clickable tags in the tag cloud and some way to filter out meta tags like "translated" and "language" from it.&lt;br&gt;
👉 Infinite scrolling for double page mode and a tags index page with counts.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I'm planning to expand the tag cloud to also include a list of all tags and their count -- The data already exists so it's a waste to not expose it better.  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 Put reading behind a password please. My instance is publicly accessible under my domain name, but I don't want other people reading it.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Please check the &lt;a href="https://sugoi.gitbook.io/lanraragi/basic-operations/first-steps#security"&gt;Documentation.&lt;/a&gt;  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 Edit gallery metadata while reading. Also add to certain category while reading. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You can already add stuff to a category within the reader, but metadata edition is indeed lacking. I'm not sure how to integrate it properly at the time however. 🤔  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 show archive tags/page overview by default instead of 1st page&lt;br&gt;
👉 I'm the guy who was asking for more of an EH style gallery view during the last survey with the tags on top and thumbnails below without it going directly in to the full image reader view.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This has been asked a few times already so I'm &lt;em&gt;prooobably&lt;/em&gt; going to make it an option, the main blocker with this is that I'll have to add incremental loading of some sort to the gallery overlay so it doesn't fire a billion API calls for each page as it pops up.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 Random based on current search&lt;br&gt;
👉 Random button that uses entries from the search query&lt;br&gt;
👉 Random Order for searchs&lt;br&gt;
👉 Random archive adhere the currently chosen categories and filters.&lt;br&gt;
👉 A nice feature to add is random gallery button matching the current search or random gallery inside a category, which open the random gallery in a new tab.&lt;br&gt;
👉 Random archive "list" at home(index) page  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;god okay okay I get it  &lt;/p&gt;
&lt;p&gt;I've been thinking about overhauling the index page for a while now by adding some form of carousel views, similar to what Plex and others do:  &lt;/p&gt;
&lt;p&gt;&lt;img alt="Plex Carousel Views" src="https://tvc-16.science/images/plex.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;I'll likely make one of these carousels contain random archives based on the current search filters.&lt;br&gt;
In the future, I might even make the current index view secondary, and have the landing page be a bunch of carousels like Komga does -- We'll see.  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 i guess since downloader additions are plugins this is maybe not the best place to ask, but i'd like a pixiv downloader since some artists will re-up their older doujins onto pixiv a while after they go out of print.&lt;br&gt;
👉 We need more download options. Please, add nhentai one.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;While the LRR downloader plugin structure allows for basically anything, it gets annoying real fast to write downloaders for websites that don't offer a straight .zip download link.&lt;br&gt;
Go yell at nH to provide real download links for their downsampled content. 🤷‍♂️    &lt;/p&gt;
&lt;p&gt;External tools like &lt;code&gt;gallery-dl&lt;/code&gt; already do the whole scrape-and-download dance, so I don't fancy reinventing the wheel here. 
(Although as always, I'm open to external contributions. 😇)  &lt;/p&gt;
&lt;p&gt;I recall that Pixiv is particularly annoying to maintain a scraper for due to the whole thing constantly changing and requiring a bunch of cookies...  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 backup database currently times out on large libraries (mine is at 8723), and also being able to delete archive without the library view refreshing&lt;br&gt;
👉 Deleting a file without going back to the home (no url change).&lt;br&gt;
👉 Persistent Search in order to make the deletion of unnecessary files easier  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is tracked &lt;a href="https://github.com/Difegue/LANraragi/issues/454"&gt;here&lt;/a&gt;. I'll try prioritizing it since it's not too hard.  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 An option to have the "date uploaded" (different from the "date added" that is present already) data visible and ability to sort by it.&lt;br&gt;
👉 Option to make sorting by date_added default&lt;br&gt;
👉 Ability to show the date_added as human readable instead of Unix timestamp&lt;br&gt;
👉 I suggest adding a newest gallery sort and a history section.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I'm planning to (finally) make &lt;code&gt;date_added&lt;/code&gt; a default feature, but it'll only use the time the file was scanned by the server -- For stuff like last modified time, you'll have to keep relying on the existing plugin.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 fixing ordering issues from the web interface (e.g. cases where the cover is alphabetically not the first image in the zip file)  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is actually kinda difficult; I experimented once with using natural sort instead of alphabetical to figure out the cover image, but it introduced more problems than anything.&lt;br&gt;
Maybe a simple "set this image as the cover thumbnail" option in the reader would be enough to catch the few edge cases where the cover isn't the first image. 🤔  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 Plugin support for other sites, specially sites that host mostly western content and provide metadata information.&lt;br&gt;
👉 more non-h related features e.g. retrieving artist/genre/etc. from anilist  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I've quickly looked at the comicvine API and might integrate in a plugin further down the line. No real promises for now tho!  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 Renaming &amp;amp; sorting the comic files based on scraped info. Sort of similar to how iTunes handles files.&lt;br&gt;
👉 saving metadata inside or alongside the archive  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;My policy for user content is to not touch it in any way since that usually annoys people more than anything, but this stuff could be doable through script/tool plugins.&lt;br&gt;
Similarly, saving metadata files next to the zips would break users who set their content folder as read-only.  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 nHentai theme&lt;br&gt;
👉 More UI customization  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;How &lt;strong&gt;dare&lt;/strong&gt; you ask for more themes after those survey results.&lt;br&gt;
Utterly unforgivable.&lt;br&gt;
&lt;sup&gt;I don't plan on introducing more themes since 5 is already a lot to maintain&lt;/sup&gt;  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 Also not essential but would adding a column with the number of pages or file size be possible??&lt;br&gt;
👉 More columns to sort homepage  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Adding columns to the index, while possible, is very painful since DataTables is awful to work with and I hate it, so it's probably not in the cards for the time being.  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 fully support wsl2, forwarding ip  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;go &lt;a href="https://github.com/microsoft/WSL/issues/4150#issuecomment-504209723"&gt;yell at microsoft&lt;/a&gt;, bridge mode in wsl2 is...not very convenient we'll say.  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 Similiar Doujin Recommendation when done with one&lt;br&gt;
👉 Some sort of Similarity search, for example recommendations based on tag similarity for a currently viewed gallery.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is interesting and I wouldn't mind adding it, although I'm not sure how to best calculate tag similarity. (And it'd probably require me to make actual tag indexes in the database but I think I'll have to do that for search speed nonetheless)&lt;br&gt;
I'll make a note of it.  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 going back from a reader view the library position should be remembered&lt;br&gt;
👉 A feature to not immediately go back to the first page of the site when leaving the reader. Like maybe go to the page you were on before opening the reader.&lt;br&gt;
👉 more persistence on archive page. For example, if I toggled "new archives only", when I press the "return" button after reading, the filter should still be there.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You can technically use your browser history to do that, since search parameters are now saved in the URL.&lt;br&gt;
Past that, I suppose this could be solved by putting your last search in localStorage and serving it back when you open the index again -- Food for thought.  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 A better series integration. Having categories for every composite tank is cumbersome. Being able to create a faux archive of galleries to create a searchable meta-tank would be great.&lt;br&gt;
👉 Better implementation for normal manga series that expanding volumes and so on.&lt;br&gt;
👉 bookmark pages, chapter separation (mainly for tankoubon/anthology), better user friendliness for archive status (new/completed etc.)  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I've been thinking about adding "Meta-Archives" that batch multiple IDs under one element and are shown as such, but I didn't want to introduce too much complexity and confusion with Categories.&lt;br&gt;
Although I'm seeing komga has both tanks and simili-categories, so it might be fine?  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 Of all the more unusual features I may suggest, being able to have a more piecemeal "no fun mode" setting (like being able to hide certain archives in a category or ones that say have a certain tag) would be very useful.&lt;br&gt;
👉 I think you have mentioned that you don't want multiple user accounts. I think if there was a way to just have account roles or tag blacklists is what prevents me from using LANraragi full time, and instead I also run Komga on my unraid, using the docker setup.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;While I'm still not too keen on multiple user accounts(mostly because it'd be a fair amount of work and I don't think most users want/need those), I could add whitelisted categories that'd be accessible even with no-fun on. Lemme know if that's something you want!  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 Automatic status/tag for Reading, Completed manga/doujin. The ability to filter these (show only unread, reading, etc.). The ability to edit this using right-click on the manga/doujin (right-click on manga -&amp;gt; Mark as Read / Unread).&lt;br&gt;
👉 It's something pretty simple but I'd just like to have a mark as read/unread button, I've still not managed to find that if it exists.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I'm considering adding inbox/archive functionality ala Hydrus, since I personnally have a few "To Read" categories already and it probably warrants bring more tightly integrated.&lt;br&gt;
Follow &lt;a href="https://github.com/Difegue/LANraragi/issues/480"&gt;this issue&lt;/a&gt; to keep tabs on this!  &lt;/p&gt;
&lt;h1&gt;Closing thoughts&lt;/h1&gt;
&lt;p&gt;There were &lt;strong&gt;way&lt;/strong&gt; more replies this year compared to last time, but I hope I caught most of the major requests here! &lt;br&gt;
I don't work as much on LRR as I did in the past as I'm trying to get other projects off the ground, but for the time being, as long as I'm using it it's certainly not dead. 👏 &lt;/p&gt;
&lt;p&gt;If you want to ask something else or just yell because I &lt;em&gt;totally misinterpreted&lt;/em&gt; what you were saying, there's always the &lt;a href="https://discord.gg/aRQxtbg"&gt;Discord&lt;/a&gt;, &lt;a href="https://github.com/Difegue/LANraragi/discussions"&gt;Github Discussions&lt;/a&gt;, and the comments of this very post.  &lt;/p&gt;
&lt;p&gt;Thanks for reading all the way to the end and supporting the project!&lt;br&gt;
I can't copy all the kind words I got from the survey here lest I bloat the article by another 100 lines, but know that it's all very much appreciated. 🥲  &lt;/p&gt;</content><category term="LANraragi"></category><category term="lanraragi"></category><category term="survey"></category><category term="philosophical software architecture ramblings"></category></entry><entry><title>Publishing MpcNET on NuGet.org (feat. Github Actions)</title><link href="https://tvc-16.science/mpcnet-nuget.html" rel="alternate"></link><published>2021-10-04T00:00:00+02:00</published><updated>2021-10-04T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2021-10-04:/mpcnet-nuget.html</id><summary type="html">&lt;p&gt;Build your own MPD ✨experiences✨ in pure .NET!&lt;/p&gt;</summary><content type="html">&lt;p&gt;While building &lt;a href="./stylophone.html"&gt;Stylophone&lt;/a&gt;, I based my initial work on the &lt;a href="https://github.com/glucaci/MpcNET"&gt;LibMpc.net&lt;/a&gt; library, which I forked and improved with support for:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MPD Command Lists&lt;/li&gt;
&lt;li&gt;Binary Responses for &lt;code&gt;albumart&lt;/code&gt; commands&lt;/li&gt;
&lt;li&gt;Various other commands that weren't implemented&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This put it a bit above existing offerings (well except &lt;a href="https://musicpd.org/libs/libmpdclient/"&gt;libmpdclient&lt;/a&gt; but that's not managed code), so I always wanted to release said fork as a standalone NuGet package.&lt;br&gt;
And well, &lt;a href="https://www.nuget.org/packages/MpcNET/"&gt;here we are!&lt;/a&gt;  &lt;/p&gt;
&lt;p&gt;This package auto-builds and auto-uploads through GitHub Actions, so hopefully I won't have to do too much maintenance. ✌️  &lt;/p&gt;
&lt;h1&gt;GitHub Actions Workflow&lt;/h1&gt;
&lt;p&gt;Generating the NuGet package itself is pretty easy: Just build and &lt;code&gt;dotnet pack&lt;/code&gt;!&lt;br&gt;
To automatically generate different version numbers for each commit, I've used the awesome &lt;a href="https://github.com/adamralph/minver"&gt;MinVer&lt;/a&gt; NuGet package.&lt;br&gt;
While it requires you to work with Git tags, I already do that for my release workflow, so it's 🆒!  &lt;/p&gt;
&lt;p&gt;NuGet automatically treats packages that have a prerelease string as &lt;a href="https://docs.microsoft.com/en-us/nuget/create-packages/prerelease-packages"&gt;pre-versions&lt;/a&gt;, but I didn't want to litter the NuGet repo with a package build for every commit...&lt;/p&gt;
&lt;p&gt;So I'm going to litter* &lt;a href="https://github.com/Difegue/MpcNET/packages"&gt;GitHub Packages&lt;/a&gt; instead!  &lt;/p&gt;
&lt;p&gt;&lt;img alt="hee ho here we go" src="https://tvc-16.science/images/packages.png"&gt;  &lt;/p&gt;
&lt;p&gt;GH Packages pairs exceptionally well with Actions, since you can just use the provided &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; for everything:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;Build and Test MpcNET&lt;/span&gt;

&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p p-Indicator"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;dev&lt;/span&gt; &lt;span class="p p-Indicator"&gt;]&lt;/span&gt;

&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

  &lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;windows-latest&lt;/span&gt;  

    &lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nt"&gt;Solution_Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;MpcNET&lt;/span&gt;    
      &lt;span class="nt"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;Release&lt;/span&gt;     

    &lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;Checkout&lt;/span&gt;
      &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nt"&gt;fetch-depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;0&lt;/span&gt;

    &lt;span class="c1"&gt;# Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild&lt;/span&gt;
    &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;Setup MSBuild.exe&lt;/span&gt;
      &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;microsoft/setup-msbuild@v1.0.2&lt;/span&gt;

    &lt;span class="c1"&gt;# Build package and upload to github packages&lt;/span&gt;
    &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;Build package&lt;/span&gt;
      &lt;span class="nt"&gt;working-directory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;./Sources&lt;/span&gt;
      &lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p p-Indicator"&gt;|&lt;/span&gt;
        &lt;span class="no"&gt;dotnet nuget add source --username Difegue --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github &amp;quot;https://nuget.pkg.github.com/Difegue/index.json&amp;quot;&lt;/span&gt;
        &lt;span class="no"&gt;dotnet build $env:Solution_Name --configuration $env:Configuration&lt;/span&gt;
        &lt;span class="no"&gt;dotnet pack --configuration $env:Configuration -o ./ &lt;/span&gt;
        &lt;span class="no"&gt;dotnet nuget push *.nupkg  --api-key ${{ secrets.GITHUB_TOKEN }} --source &amp;quot;github&amp;quot; --skip-duplicate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;For release packages, the recipe is essentially the same, except even simpler since we don't need to add GitHub as a source (We do need to add a nuget.org &lt;a href="https://www.nuget.org/account/apikeys"&gt;API Key&lt;/a&gt; to our repo secrets however):  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;New Version Release&lt;/span&gt;

&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
  &lt;span class="nt"&gt;release&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nt"&gt;types&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;published&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;

&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

  &lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;windows-latest&lt;/span&gt;  

    &lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nt"&gt;Solution_Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;MpcNET&lt;/span&gt;    
      &lt;span class="nt"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;Release&lt;/span&gt;     

    &lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;Checkout&lt;/span&gt;
      &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nt"&gt;fetch-depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;0&lt;/span&gt;

    &lt;span class="c1"&gt;# Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild&lt;/span&gt;
    &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;Setup MSBuild.exe&lt;/span&gt;
      &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;microsoft/setup-msbuild@v1.0.2&lt;/span&gt;

    &lt;span class="c1"&gt;# Build package and upload to nuget.org&lt;/span&gt;
    &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;Build package&lt;/span&gt;
      &lt;span class="nt"&gt;working-directory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;./Sources&lt;/span&gt;
      &lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p p-Indicator"&gt;|&lt;/span&gt;
        &lt;span class="no"&gt;dotnet build $env:Solution_Name --configuration $env:Configuration&lt;/span&gt;
        &lt;span class="no"&gt;dotnet pack --configuration $env:Configuration -o ./&lt;/span&gt;
        &lt;span class="no"&gt;dotnet nuget push *.nupkg  --api-key ${{ secrets.NUGET_API }} --source &amp;quot;nuget.org&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;* I'm probably going to have to add the &lt;a href="https://github.com/marketplace/actions/delete-package-versions"&gt;Delete Package Versions&lt;/a&gt; action to the mix at some point to avoid overloading GH's storage space. 😥 We'll see how popular the lib gets on its own. 😗  &lt;/p&gt;
&lt;h1&gt;Extra Links&lt;/h1&gt;
&lt;p&gt;See here for a more detailed walkthrough of NuGet builds on Actions:&lt;br&gt;
&lt;a href="https://acraven.medium.com/a-nuget-package-workflow-using-github-actions-7da8c6557863"&gt;https://acraven.medium.com/a-nuget-package-workflow-using-github-actions-7da8c6557863&lt;/a&gt;  &lt;/p&gt;
&lt;p&gt;And here for explanations on MinVer: &lt;a href="https://rehansaeed.com/the-easiest-way-to-version-nuget-packages/"&gt;https://rehansaeed.com/the-easiest-way-to-version-nuget-packages/&lt;/a&gt;  &lt;/p&gt;</content><category term="Software"></category><category term="mpd"></category><category term="c#"></category><category term="nuget"></category><category term=".net"></category><category term="music"></category><category term="stylophone"></category><category term="github actions"></category></entry><entry><title>Announcing Stylophone v2</title><link href="https://tvc-16.science/stylophone-2.html" rel="alternate"></link><published>2021-09-01T00:00:00+02:00</published><updated>2021-09-01T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2021-09-01:/stylophone-2.html</id><summary type="html">&lt;p&gt;Timing both a visual refresh and a major refactoring in one? Don't mind if I do.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="./stylophone.html"&gt;Stylophone&lt;/a&gt; has been out for almost a year now, and has received as warm a welcome as I could've hoped in the (very) niche world of MPD clients.&lt;br&gt;
There's been more than 400 trials, and about 100 paid users, which makes this the first time I got any significant form of money from the Microsoft Store. 💰💰💰  &lt;/p&gt;
&lt;p&gt;Version 2 of the app has been in the works for a few months already, as I was expecting some form of rejuvenation in the UWP space due to WinUI 3/Windows App SDK. &lt;br&gt;
The &lt;a href="https://blogs.windows.com/windowsexperience/2021/06/24/introducing-windows-11/"&gt;Windows 11&lt;/a&gt; announcement delivered all of that and some!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Please do not dunk on my music tastes too hard" src="https://tvc-16.science/images/stylophone/v2-stylophone.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;As a part of &lt;a href="https://uwpcommunity.com/launch/"&gt;Launch 2021&lt;/a&gt;, I'm releasing &lt;strong&gt;Stylophone v2&lt;/strong&gt;, featuring :  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A fully rebuilt app, eliminating a bunch of bugs&lt;/li&gt;
&lt;li&gt;Complete restyling using WinUI 2.6 (The bottom area is finally not stuck in dark theme anymore 🙏)&lt;/li&gt;
&lt;li&gt;Support for password-protected MPD servers  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Local Playback&lt;/strong&gt; if your server has the httpd stream output enabled  &lt;/li&gt;
&lt;li&gt;Random shuffling of tracks from your library into the play queue  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;V2 is now &lt;strong&gt;live&lt;/strong&gt; on &lt;a href="https://github.com/Difegue/Stylophone"&gt;GitHub&lt;/a&gt; and the &lt;a href="https://www.microsoft.com/store/apps/9NCB693428T8"&gt;Microsoft Store&lt;/a&gt;!  &lt;/p&gt;
&lt;div id="mspb-4eo0ll5vuo5c" class="9NCB693428T8"&gt;&lt;/div&gt;

&lt;script src="https://storebadge.azureedge.net/src/badge-1.8.4.js"&gt;&lt;/script&gt;

&lt;script&gt;
  mspb('9NCB693428T8', function(badge) {
    document.getElementById('mspb-4eo0ll5vuo5c').innerHTML = badge;
  });
&lt;/script&gt;

&lt;p&gt;I'll be rambling a bit more about what I did below.  &lt;/p&gt;
&lt;h1&gt;Windows 11 Redesign&lt;/h1&gt;
&lt;p&gt;In v2, I spaced most of the UI elements further, both to fit the new card-inspired design language of Windows 11 and to give some elements the extra breathing room they really needed.&lt;br&gt;
&lt;sub&gt;(Those image comparison sliders are iframes! If they're not showing up your browser might be blocking 'em for some reason. 🤔)&lt;/sub&gt;&lt;br&gt;
&lt;iframe frameborder="0" class="juxtapose" width="100%" height="640" src="https://cdn.knightlab.com/libs/juxtapose/latest/embed/index.html?uid=43f0a5f0-f7b9-11eb-abb7-b9a7ff2ee17c"&gt;&lt;/iframe&gt;&lt;br&gt;
v1's playback controls always felt borderline-claustrophobic to me, with the time slider &lt;em&gt;almost&lt;/em&gt; touching both the Play/Pause buttons and the window border.&lt;br&gt;
This comes at the loss of a bit of vertical space for the content, but I felt it was fine. (And if it's not, you can always enable compact sizing in the settings!)  &lt;/p&gt;
&lt;iframe frameborder="0" class="juxtapose" width="100%" height="640" src="https://cdn.knightlab.com/libs/juxtapose/latest/embed/index.html?uid=c567f9a8-f7b9-11eb-abb7-b9a7ff2ee17c"&gt;&lt;/iframe&gt;

&lt;p&gt;This time around, I've mostly &lt;strike&gt;copied&lt;/strike&gt; looked at the revamped Settings and Microsoft Store for inspiration. (There's not much else in terms of released WinUI 2.6 apps at the moment. 😛)  &lt;/p&gt;
&lt;p&gt;&lt;center&gt;&lt;img alt="well at leasy it's not that weird S everyone used to draw in middle school" src="https://tvc-16.science/images/stylophone/v2-icon.png"&gt;&lt;/center&gt;&lt;br&gt;
I've also updated the icon! I liked v1's icon a lot, but it looked a bit too much like the MS Office icons. (A &lt;a href="https://www.microsoft.com/en-gb/p/quarrel-unofficial-discord-client/9nbrwj777c8r"&gt;recurring&lt;/a&gt; theme for &lt;a href="https://www.microsoft.com/fr-fr/p/flowpad/9pmt6j2wvb10?rtc"&gt;third-party&lt;/a&gt; Fluent Design &lt;a href="https://www.microsoft.com/fr-fr/p/yugen-mosaic/9pf0s24cx0d4"&gt;apps&lt;/a&gt; at the time for some reason)   &lt;/p&gt;
&lt;p&gt;It also looked kinda muddy at small sizes, so I've cleared it up and changed the shape to something...still generic, but more legible at small sizes. The "S" is much less noticeable, which I think is fine since it's kinda just a signature.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="Comparison of the icons in the new Win11 Start Menu" src="https://tvc-16.science/images/stylophone/v2-iconstart.png"&gt;  &lt;/p&gt;
&lt;p&gt;If you preferred the old icon, well, how about buying &lt;a href="https://ko-fi.com/s/9fcf421b6e"&gt;a sticker of it&lt;/a&gt; to reminisce about the good old days? 😉  &lt;/p&gt;
&lt;h1&gt;(Re)building the app&lt;/h1&gt;
&lt;p&gt;The structure of the app has switched from &lt;strong&gt;2&lt;/strong&gt; projects to about &lt;strong&gt;5&lt;/strong&gt;:&lt;br&gt;
Alongside the existing &lt;em&gt;MpcNET&lt;/em&gt; library that's used to handle all the communication with MPD servers and the UWP project itself, I've split most of the core business functionality into a separate &lt;em&gt;.NET Standard&lt;/em&gt; class library, which can be reused outside of UWP easily. (More on that later)&lt;br&gt;
&lt;img src="https://tvc-16.science/images/stylophone/v2-structure.jpg" style="width:468px" /&gt;&lt;br&gt;
Achieving this was relatively easy(albeit time-consuming), as the app already uses the MVVM paradigm through the Community Toolkit's &lt;a href="https://docs.microsoft.com/en-us/windows/communitytoolkit/mvvm/introduction"&gt;MVVM library.&lt;/a&gt;&lt;br&gt;
The major switch I made was to use &lt;strong&gt;Dependency Injection&lt;/strong&gt;, which allows me to easily use the OS-specific services within the common ViewModel code, by simply injecting them as implementing the interface.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="Under the Sycamore treee-wait crap wrong franchise" src="https://tvc-16.science/images/stylophone/v2-stylophone2.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;Another big change I made was to handle all the album art decoding and storage in the .NET Standard portion of the code, using &lt;a href="https://github.com/mono/SkiaSharp"&gt;SkiaSharp.&lt;/a&gt;&lt;br&gt;
This allows me to &lt;strong&gt;greatly&lt;/strong&gt; cut into the amount of Dispatcher calls I had to make to use the native/UWP image functions, at no real performance loss. (And a great improvement to code readability.)  &lt;/p&gt;
&lt;p&gt;The new &lt;strong&gt;Local Playback&lt;/strong&gt; feature relies on the MPD server's &lt;a href="https://mpd.readthedocs.io/en/latest/plugins.html#httpd"&gt;&lt;em&gt;httpd&lt;/em&gt;&lt;/a&gt; output, which makes a nice stream we can consume and play back on the Windows machine.&lt;br&gt;
I used the UWP &lt;code&gt;MediaPlayer&lt;/code&gt; for this, which does the job well enough. Your mileage with this feature may vary, as I sadly have no way to figure out the encoding used by the server and use its default, which is &lt;code&gt;ogg&lt;/code&gt;.  &lt;/p&gt;
&lt;h1&gt;Ports?&lt;/h1&gt;
&lt;p&gt;As said above, I rebuilt the entire app to have as much .NET Standard code as possible.&lt;br&gt;
The main goal behind this was to &lt;strong&gt;port the app&lt;/strong&gt; to other platforms. I tried &lt;a href="https://platform.uno"&gt;Uno Platform&lt;/a&gt;, sadly walked back dissatisfied with the results (forced solution structure, lots of time wasted installing nuget package clones, etc), and tried elsewhere.  &lt;/p&gt;
&lt;p&gt;👉 My first look was at Xamarin Forms/MAUI: I quickly reached a working prototype but felt I wouldn't be happy with the UI options available and stopped there. (Besides, I don't really care about Android) 
&lt;img alt="ah yes, good old boring material design v1" src="https://tvc-16.science/images/stylophone/v2-xamarinforms.jpg"&gt;&lt;br&gt;
👉 I briefly considered &lt;a href="https://avaloniaui.net/"&gt;Avalonia&lt;/a&gt;, but I generally don't like UI frameworks that don't try to look native to the platform they're running on. (This has been greatly improved recently! I might take another stab at it one day.)  &lt;/p&gt;
&lt;p&gt;👉 In the end, I decided to try a port to &lt;strong&gt;iOS/UIKit&lt;/strong&gt;, using &lt;a href="https://github.com/xamarin/xamarin-macios"&gt;Xamarin.iOS&lt;/a&gt; whose macOS variant I was already familiar with.&lt;br&gt;
Y'know, just 'cause it'd be funny to port a UWP app to run on Apple's own twist on Universal Apps.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="my brain is about 200 years old probably" src="https://tvc-16.science/images/stylophone/v2-uikit.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;As you might see, I got a lot further with this port!&lt;br&gt;
This is all native iOS UI, powered by the &lt;strong&gt;same&lt;/strong&gt; .NET Standard core as the UWP app.&lt;br&gt;
I learned iOS development from scratch while doing this, which probably led to some bad decisions on the way. 😛  &lt;/p&gt;
&lt;p&gt;&lt;img alt="never stopping with the bowie references" src="https://tvc-16.science/images/stylophone/v2-uikit3.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;This is where I'd normally drop a surprise App Store link, but I'm not sure if the app actually looks good enough/would be successful on Apple devices?  &lt;br&gt;
&lt;sup&gt;&lt;sub&gt;And if I'd eventually make back the 90$ a year Apple charges for a developer license, good lord Microsoft has spoiled me with the one-time 100$ payment which I didn't even pay since I got a student deal back in 2013&lt;/sub&gt;&lt;/sup&gt;&lt;br&gt;
So, consider those screenshots kind of a pitch from me for the time being. 😅  &lt;/p&gt;
&lt;p&gt;It's &lt;a href="https://github.com/Difegue/Stylophone/tree/dev/Sources/Stylophone.iOS"&gt;open source&lt;/a&gt; just like the UWP variant, so if you like what you see, compile it and give it a try! It's about 80% finished.&lt;br&gt;
I might come back to it when Xamarin adds support for &lt;a href="https://github.com/xamarin/xamarin-macios/issues/6210"&gt;Mac Catalyst&lt;/a&gt;, as it'd allow me to target three platforms with this port.  &lt;/p&gt;
&lt;h1&gt;Closing words&lt;/h1&gt;
&lt;p&gt;Some line of code counts! It's not really a meaningful metric but it's fun:   &lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;LoC (C# only)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;.NET Standard&lt;/td&gt;
&lt;td&gt;4963&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UWP&lt;/td&gt;
&lt;td&gt;3111&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iOS/Xamarin&lt;/td&gt;
&lt;td&gt;4537&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The UIKit port needed a bit more glue code than I was expecting since unlike macOS/AppKit, UIKit doesn't really have a simple way to do data binding.&lt;br&gt;
(I'm aware of &lt;a href="https://developer.apple.com/documentation/combine"&gt;Combine&lt;/a&gt;, but Xamarin.iOS doesn't really allow you to write Swift at the time. 😔)&lt;br&gt;
&lt;img alt="The iOS UI is all storyboards btw, they're polarizing but I like em" src="https://tvc-16.science/images/stylophone/v2-uikit2.jpg"&gt;&lt;br&gt;
iOS has slide actions on rows which I find really cool and never really used before -- I think it's a bit too hard for users to find out about, though. 😐  &lt;/p&gt;</content><category term="Software"></category><category term="mpd"></category><category term="winui"></category><category term="c#"></category><category term="uwp"></category><category term="windows"></category><category term="windows 10"></category><category term="windows 11"></category><category term="music"></category><category term="client"></category><category term="stylophone"></category></entry><entry><title>The 2021 LANraragi User Survey</title><link href="https://tvc-16.science/lrr-survey-3.html" rel="alternate"></link><published>2021-07-07T00:00:00+02:00</published><updated>2021-07-07T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2021-07-07:/lrr-survey-3.html</id><summary type="html">&lt;p&gt;The &lt;em&gt;absolute current state&lt;/em&gt; of the Perl5 manga reader device.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="it's actually eternal summer but everyone chooses to forget about it" src="https://tvc-16.science/images/lrr-survey/summer.gif"&gt;&lt;br&gt;
It's summer! That means AC prices shooting up through the roof, major cities being deserted, and the yearly &lt;strong&gt;LRR User Survey&lt;/strong&gt;.&lt;br&gt;
&lt;sub&gt;Pouring one out for my Canada homies currently dying under the heat dome&lt;/sub&gt;  &lt;/p&gt;
&lt;p&gt;The User Survey is a quick means for me to gauge how many users are out there (as the app itself has zero telemetry), and figure out which features are wanted to drive development forward.  &lt;/p&gt;
&lt;p&gt;Here's a quick rundown of everything that landed between the &lt;a href="./lrr-survey-2-results.html"&gt;2020 Survey results&lt;/a&gt; and the latest release, &lt;a href="https://github.com/Difegue/LANraragi/releases/tag/v.0.7.9"&gt;"Lucy Can't Dance"&lt;/a&gt;:&lt;br&gt;
&lt;img alt="wow, cool reader!" src="https://tvc-16.science/images/lrr-survey/survey3-webapp.png"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fully rewritten Web Reader&lt;/strong&gt;, with new options, tags shown in the page overlay, better keyboard shortcuts and built-in infinite scrolling!  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clickable tags&lt;/strong&gt;, to trigger searches in a much easier fashion.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;URL Downloading support&lt;/strong&gt;, with a matching &lt;a href="https://github.com/Difegue/Tsukihi"&gt;Browser Extension&lt;/a&gt; to queue downloads directly from your Browser.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Column customization&lt;/strong&gt; on a namespace basis in the Index to expose your most important tags directly.&lt;/li&gt;
&lt;li&gt;More &lt;strong&gt;external reader&lt;/strong&gt; work, with a new client for &lt;a href="https://github.com/Doraemoe/DuReader"&gt;iOS&lt;/a&gt;, and LRReader now available in the &lt;a href="https://www.microsoft.com/store/apps/9MZ6BWWVSWJH"&gt;Microsoft Store&lt;/a&gt;.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Server-side progress tracking&lt;/strong&gt;, with an option to come back to client-side tracking for multi-user instances.&lt;/li&gt;
&lt;li&gt;Multiple improvements to Categories, by making them more accessible across the UI.&lt;/li&gt;
&lt;li&gt;Support for AVIF and HEIF, already covered by some external readers as well.&lt;/li&gt;
&lt;li&gt;Being able to &lt;strong&gt;move the Thumbnail Directory&lt;/strong&gt; 🎊🎊🎊&lt;/li&gt;
&lt;li&gt;Offloading of many tasks to a Job Queue using &lt;a href="https://mojolicious.org/perldoc/Minion"&gt;Minion&lt;/a&gt;, to speed up many parts of the server.&lt;/li&gt;
&lt;li&gt;A Shinobu rewrite to avoid murdering hard drives on server restarts 🍩💿&lt;/li&gt;
&lt;li&gt;Basic support for FAKKU metadata.&lt;/li&gt;
&lt;li&gt;A &lt;a href="./lrr-icon-study.html"&gt;logo change&lt;/a&gt;!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And even more stuff I can't write about, lest the blogpost triples in size!  &lt;/p&gt;
&lt;p&gt;The downloader stuff took most of the work this year, although it has been infinitely useful to me at the very least, so I'm glad I put the time into it.&lt;br&gt;
I managed to tackle most of the asks from Survey 2 (with the help of a few contributors I am eternally thankful to have), with the exception of &lt;strong&gt;duplicate detection&lt;/strong&gt;.&lt;br&gt;
That just means I get to put it again in the suggestions for this 2021 edition! :^)  &lt;/p&gt;
&lt;h2&gt;&lt;a href="https://forms.office.com/r/8TVSTXKVsm"&gt;You can answer the survey here!&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I've also added some new sticker designs to the &lt;a href="https://ko-fi.com/lanraragi/shop"&gt;LRR Ko-Fi Shop&lt;/a&gt;, if you'd like to sponsor development and get a little bonus out of it!&lt;br&gt;
The V2 logo sticker now has a clear background, so you can stickerbomb your old &lt;em&gt;lame stickers&lt;/em&gt; with the wonderful &lt;code&gt;vapor-retro-neumorphic&lt;/code&gt; shapes of the LANraragi logo.&lt;br&gt;
&lt;img alt="I really like my Perl 5 sticker and dread the day Perl 7 will finally release and make it obsolete" src="https://tvc-16.science/images/lrr-survey/survey3-image.jpg"&gt;&lt;/p&gt;</content><category term="LANraragi"></category><category term="lanraragi"></category><category term="survey"></category><category term="lookback"></category></entry><entry><title>Updating the TVC-16 to Caddy 2</title><link href="https://tvc-16.science/caddy-2-update.html" rel="alternate"></link><published>2021-07-06T22:00:00+02:00</published><updated>2021-07-06T22:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2021-07-06:/caddy-2-update.html</id><summary type="html">&lt;p&gt;Put on some music and spend a comfy evening whooping out some webhook wizardry. 🧙‍♂️&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://caddyserver.com/v2"&gt;Caddy 2&lt;/a&gt; has been out for a little more than a year and I finally got off my ass to update my server setup. 🤠&lt;br&gt;
Feel free to read the &lt;a href="./blogopolis-docker"&gt;OG post&lt;/a&gt; about the Caddy 1 setup to get some extra context.  &lt;/p&gt;
&lt;h1&gt;Updating the Caddyfile&lt;/h1&gt;
&lt;p&gt;For non-initiates, the &lt;a href="https://caddyserver.com/docs/caddyfile"&gt;Caddyfile&lt;/a&gt; is essentially the entire server configuration.
There's surprisingly little changes to it!&lt;br&gt;
I essentially went from something like this:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;tvc&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;16.&lt;/span&gt;&lt;span class="nx"&gt;science&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;www&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tvc&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;16.&lt;/span&gt;&lt;span class="nx"&gt;science&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;tls&lt;/span&gt; &lt;span class="o"&gt;*******&lt;/span&gt;
    &lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="err"&gt;/www/html&lt;/span&gt;
    &lt;span class="nx"&gt;git&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;repo&lt;/span&gt;     &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;difegue&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;TVC&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;
        &lt;span class="nx"&gt;branch&lt;/span&gt;   &lt;span class="nx"&gt;gh&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;
        &lt;span class="nx"&gt;pull&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;allow&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;unrelated&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;histories&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="nx"&gt;recursive&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;X&lt;/span&gt; &lt;span class="nx"&gt;theirs&lt;/span&gt;
        &lt;span class="nx"&gt;hook&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;webhook&lt;/span&gt; &lt;span class="nx"&gt;mywebhooksecret&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;lrr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tvc&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;16.&lt;/span&gt;&lt;span class="nx"&gt;science&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;tls&lt;/span&gt; &lt;span class="o"&gt;*******&lt;/span&gt;
    &lt;span class="nx"&gt;proxy&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;localhost&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;diy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tvc&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;16.&lt;/span&gt;&lt;span class="nx"&gt;science&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;tls&lt;/span&gt; &lt;span class="o"&gt;*******&lt;/span&gt;
    &lt;span class="nx"&gt;proxy&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;localhost&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;dingus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tvc&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;16.&lt;/span&gt;&lt;span class="nx"&gt;science&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;tls&lt;/span&gt; &lt;span class="o"&gt;*******&lt;/span&gt;
    &lt;span class="nx"&gt;proxy&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;localhost&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;7777&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;header_upstream&lt;/span&gt; &lt;span class="nx"&gt;Host&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;header_upstream&lt;/span&gt; &lt;span class="nx"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Real&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;IP&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;header_upstream&lt;/span&gt; &lt;span class="nx"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Forwarded&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;For&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;header_upstream&lt;/span&gt; &lt;span class="nx"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Forwarded&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Proto&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To this:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;tvc&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;16.&lt;/span&gt;&lt;span class="nx"&gt;science&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;tls&lt;/span&gt; &lt;span class="o"&gt;*******&lt;/span&gt;
    &lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="err"&gt;/var/www/html&lt;/span&gt;
    &lt;span class="nx"&gt;file_server&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;www&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tvc&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;16.&lt;/span&gt;&lt;span class="nx"&gt;science&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;redir&lt;/span&gt; &lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//tvc-16.science{uri}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;lrr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tvc&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;16.&lt;/span&gt;&lt;span class="nx"&gt;science&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;tls&lt;/span&gt; &lt;span class="o"&gt;*******&lt;/span&gt;
    &lt;span class="nx"&gt;reverse_proxy&lt;/span&gt; &lt;span class="nx"&gt;localhost&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;diy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tvc&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;16.&lt;/span&gt;&lt;span class="nx"&gt;science&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;tls&lt;/span&gt; &lt;span class="o"&gt;*******&lt;/span&gt;
    &lt;span class="nx"&gt;reverse_proxy&lt;/span&gt; &lt;span class="nx"&gt;localhost&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;dingus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tvc&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;16.&lt;/span&gt;&lt;span class="nx"&gt;science&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;tls&lt;/span&gt; &lt;span class="o"&gt;*******&lt;/span&gt;
    &lt;span class="nx"&gt;reverse_proxy&lt;/span&gt; &lt;span class="nx"&gt;localhost&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;7777&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;header_up&lt;/span&gt; &lt;span class="nx"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Real&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;IP&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;header_up&lt;/span&gt; &lt;span class="nx"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Forwarded&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;For&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The reverse proxies map as-is, and the configuration to make my &lt;a href="https://github.com/zgoat/goatcounter"&gt;GoatCounter&lt;/a&gt; instance work correctly is actually even simpler, since Caddy v2 passes &lt;a href="https://github.com/caddyserver/caddy/issues/2873"&gt;most headers through now.&lt;/a&gt;  &lt;/p&gt;
&lt;p&gt;But as you might've noticed, the &lt;code&gt;git&lt;/code&gt; section which automatically updated the static pages you're reading right now...is gone! 👻&lt;/p&gt;
&lt;h1&gt;Swapping out my Webhook Implementation&lt;/h1&gt;
&lt;p&gt;As a quick refresher for how the &lt;code&gt;git&lt;/code&gt; integration worked (read the Caddy 1 post for more details!):  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When the TVC-16 &lt;a href="https://github.com/Difegue/TVC-16"&gt;Git repo&lt;/a&gt; is updated, GitHub sends a &lt;code&gt;POST&lt;/code&gt; request to a &lt;code&gt;tvc-16.science&lt;/code&gt; subdomain, triggering a pull of the updated repo's &lt;code&gt;gh-pages&lt;/code&gt; branch, which contains the static HTML files for the blog, built with &lt;a href="https://blog.getpelican.com/"&gt;Pelican&lt;/a&gt;.  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With Caddy 1, it was super easy to use the built-in &lt;a href="https://web.archive.org/web/20190131203258/https://caddyserver.com/docs/http.git"&gt;git plugin&lt;/a&gt; to setup a built-in webhook endpoint that Github could then hit. Sadly, Caddy 2 doesn't bundle a git plugin anymore! 😢  &lt;/p&gt;
&lt;p&gt;You can use a &lt;a href="https://caddy.community/t/v2-git-webhooks/10207"&gt;community one&lt;/a&gt; if you build your Caddy yourself (&lt;code&gt;xcaddy&lt;/code&gt; is a very nice tool to do that), but I don't really fancy rebuilding my webserver myself whenever I want to update.  &lt;/p&gt;
&lt;p&gt;So, I switched to a &lt;strong&gt;standalone server&lt;/strong&gt; to handle webhooks!&lt;br&gt;
To keep with the theme of &lt;em&gt;"I guess I'm using stuff written in Go now"&lt;/em&gt;, I went with &lt;a href="https://github.com/adnanh/webhook"&gt;webhook.&lt;/a&gt; &lt;sub&gt;very original name and not confusing at all thanks&lt;/sub&gt;&lt;br&gt;
&lt;code&gt;webhook&lt;/code&gt; is sadly a bit more verbose to setup than the ole Caddy integration, but the overall concept is the same:  &lt;/p&gt;
&lt;p&gt;1️⃣ Setup your hook's ID and rules in a &lt;code&gt;hooks.json&lt;/code&gt; file, to run a git pull in &lt;code&gt;/var/www/html&lt;/code&gt; when it's hit:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;mikon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;execute-command&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/usr/local/bin/update_static.sh&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;command-working-directory&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/var/www/html&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;trigger-rule&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;and&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="s2"&gt;&amp;quot;match&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="s2"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;payload-hash-sha1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="s2"&gt;&amp;quot;secret&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;TheWebHookSecret&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="s2"&gt;&amp;quot;parameter&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="s2"&gt;&amp;quot;source&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;header&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;X-Hub-Signature&amp;quot;&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="s2"&gt;&amp;quot;match&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="s2"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;value&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="s2"&gt;&amp;quot;value&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;refs/heads/gh-pages&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="s2"&gt;&amp;quot;parameter&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="s2"&gt;&amp;quot;source&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;payload&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;ref&amp;quot;&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;It's easier to use a bash script as the &lt;code&gt;execute-command&lt;/code&gt; here since &lt;code&gt;webhook&lt;/code&gt; doesn't accept inline arguments, but the script itself is just a one-liner:  &lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;1
2
3
4&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="c1"&gt;# This runs in /var/www/html, which already contains an initialized copy of the git repo.&lt;/span&gt;
git pull --allow-unrelated-histories -s recursive -X theirs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;

&lt;p&gt;2️⃣ Start the &lt;code&gt;webhook&lt;/code&gt; server:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;webhook -hooks hooks.json -port 4000 -verbose&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;3️⃣ Add a subdomain to your website and your Caddyfile to reverse-proxy it:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;tamamo.tvc-16.science {&lt;/span&gt;
&lt;span class="err"&gt;    tls *******&lt;/span&gt;
&lt;span class="err"&gt;    reverse_proxy localhost:4000&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;4️⃣ Add the webhook to your GitHub repo, and you're done!  &lt;/p&gt;
&lt;p&gt;Since it goes through Caddy, you get SSL verification that &lt;em&gt;just works&lt;/em&gt; out of the box.&lt;br&gt;
&lt;img alt="🦊 mikon!" src="https://tvc-16.science/images/webhook.png"&gt;&lt;br&gt;
GitHub actually sends &lt;em&gt;two&lt;/em&gt; POST requests (one when &lt;code&gt;master&lt;/code&gt; is pushed, and one when &lt;code&gt;gh-pages&lt;/code&gt; is updated by GitHub Actions), but &lt;code&gt;webhook&lt;/code&gt; will filter the first one out since it doesn't match the &lt;code&gt;refs/heads/gh-pages&lt;/code&gt; rule.  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;webhook&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2021&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;07&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;05&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;8a4b2a&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;finished&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handling&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mikon&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;webhook&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2021&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;07&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;05&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;39&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Started&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mikon&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;webhook&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2021&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;07&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;05&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;39&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;b556e5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;incoming&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HTTP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;webhook&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2021&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;07&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;05&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;39&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;b556e5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mikon&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;matched&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;webhook&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2021&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;07&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;05&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;39&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;b556e5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mikon&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;didn&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;triggered&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;because&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;trigger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;were&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;satisfied&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;webhook&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2021&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;07&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;05&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;39&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Completed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OK&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;11.868297&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h1&gt;Closing thoughts&lt;/h1&gt;
&lt;p&gt;I was kinda worried about redoing the autodeploy setup as Caddy 2 doesn't support it out of the box, but &lt;code&gt;webhook&lt;/code&gt; seems to be a solid alternative.&lt;br&gt;
Not having to replace it again whenever I end up re-switching HTTP servers is also a bonus!  &lt;/p&gt;
&lt;p&gt;The usual way of deploying this static blog stuff is to do the Pelican build on the host machine directly instead of using CI, but I prefer putting the grunt work outside of this woefully underpowered 3$ VPS that's already running about 5 services too many.  &lt;/p&gt;
&lt;p&gt;Here's a Tamamo for having made it to the end.&lt;br&gt;
&lt;img alt="UNIXCHADS win yet again!" src="https://tvc-16.science/images/tamamo.jpg"&gt;  &lt;/p&gt;</content><category term="Software"></category><category term="docker"></category><category term="goatcounter"></category><category term="caddy"></category><category term="webhook"></category></entry><entry><title>That one time I wrote a memory patcher for an Android Emulator to keep playing FGO</title><link href="https://tvc-16.science/memu-patcher.html" rel="alternate"></link><published>2021-04-23T00:00:00+02:00</published><updated>2021-04-23T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2021-04-23:/memu-patcher.html</id><summary type="html">&lt;p&gt;Why yes, I was the hacker known as 4chan this entire time!&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've been playing Fate/Grand Order for a pretty long time now, and have irredeemably fallen into &lt;a href="https://twitter.com/Difegue/status/1289636218578378752"&gt;deep gambling addiction&lt;/a&gt;.&lt;br&gt;
Besides the whole "paying for coomer JPEGs" thing, FGO often relies on heavy farming to get a bunch of resources.&lt;sup&gt;&lt;sub&gt;Regular mobage trite really&lt;/sup&gt;&lt;/sub&gt;  &lt;/p&gt;
&lt;p&gt;&lt;img alt="I haven't actually paid money for most of this it's just been already 5 years good lord save me type-moon release something else already" src="https://tvc-16.science/images/memupatcher/fgo.jpg"&gt;&lt;/p&gt;
&lt;p&gt;While I do enjoy the gameplay to a point, repeating a battle 300 times is not fun no matter how you try to spin it...&lt;br&gt;
For a while, the easiest way to automate farming in FGO was to throw the game on an &lt;strong&gt;Android emulator&lt;/strong&gt; and run a &lt;a href="https://github.com/29988122/Fate-Grand-Order_Lua"&gt;Lua script&lt;/a&gt; on it.&lt;br&gt;
Alas, FGO is also quite known for its hate of &lt;strong&gt;&lt;a href="https://topjohnwu.medium.com/from-anime-game-to-android-system-security-vulnerability-9b955a182f20"&gt;any form of game/phone modification.&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
&lt;sup&gt;(I very much recommend reading this article if you haven't already done so, it'll be more interesting than anything written here.)&lt;/sup&gt;  &lt;/p&gt;
&lt;p&gt;At some point during the sweet summer of 2017, FGO stopped working on most famous emulator used to run it, &lt;a href="https://www.memuplay.com"&gt;Memu&lt;/a&gt;.&lt;br&gt;
The developers had implemented some extra checks for it when the game launches.&lt;br&gt;
Back then I was temporarily without a decent phone as well, so the incentive to get the bloody thing working again was quite high. &lt;sup&gt;Never underestimate what someone can do for his vidyagames and login streaks...&lt;/sup&gt;  &lt;/p&gt;
&lt;p&gt;Surprisingly, the checks were quite easy to circumvent, as they were only looking for &lt;strong&gt;specific identifiers&lt;/strong&gt;, which could be replaced in the emulator's memory directly with a hex editor:&lt;br&gt;
&lt;script src="https://emgithub.com/embed.js?target=https%3A%2F%2Fgithub.com%2FDifegue%2FChaotic-Realm%2Fblob%2Fmaster%2FMemuPatcher%2FUnlimitedMemuWorks%2FForm1.cs%23L20-L24&amp;style=github&amp;showBorder=on&amp;showLineNumbers=on&amp;showFileMeta=on&amp;showCopy=on"&gt;&lt;/script&gt;  &lt;sub&gt;&lt;sup&gt;Somehow doing a grand-scale string replacement in-memory like that didn't completely crash the thing, which is kinda impressive I guess?&lt;/sub&gt;&lt;/sup&gt;  &lt;/p&gt;
&lt;p&gt;I take no credit for figuring that one out, it came out of some &lt;a href="https://github.com/wDCat/ANRC/issues/98"&gt;dudes on github&lt;/a&gt;, who promptly decided to hide/delete it, thinking it'd make the trick last longer.&lt;br&gt;
&lt;img alt="Imagine thinking that essentially performing brain surgery on a live emulator is a reliable and permanent fix" src="https://tvc-16.science/images/memupatcher/sikritclub.png"&gt;&lt;br&gt;
Oh, those &lt;em&gt;absolute, utter dinguses.&lt;/em&gt;&lt;br&gt;
&lt;img alt="get a load of this freakin guy" src="https://tvc-16.science/images/memupatcher/lmao.gif"&gt;&lt;br&gt;
The method was obviously screenshotted and spread around on various boards.&lt;br&gt;
It worked, but busting out HxD everyime you wanted to run your farming script? 100% pain.  &lt;/p&gt;
&lt;p&gt;The Github thread was starting to look like a mid-2000's warez forum with everyone begging for the method through Facebook by this point(seriously go flip through it it's unreal), which made me think things could be improved somewhat.  &lt;/p&gt;
&lt;p&gt;So with the following goals in mind:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Destroying the secret club for the sole purpose of spreading chaos over the land  &lt;/li&gt;
&lt;li&gt;Making my own life easier so I could farm the upcoming Nerofest lottery in peace  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I wrote an automated memory patcher in .NET/WinForms in a few days:&lt;br&gt;
&lt;img alt="I know there's a bunch of fucks but what can I say this was released anonymously" src="https://tvc-16.science/images/memupatcher/patcher.png"&gt;&lt;br&gt;
I say .NET, but this is almost more P/Invoke than interpreted considering all the patching code goes through Win32's &lt;code&gt;VirtualQueryEx/ReadProcessMemory/WriteProcessMemory&lt;/code&gt; calls. ¯\_(ツ)_/¯  &lt;/p&gt;
&lt;p&gt;Releasing this thing anonymously into the wild was especially funny, with various reactions such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FUD about the patcher being malware despite being uploaded with the source code attached  &lt;/li&gt;
&lt;li&gt;Reddit tutorials being written referring to the program as the &lt;a href="https://www.reddit.com/r/grandorder/comments/6wi08i/episode_xiii_doom_or_be_doomed/dm89feh/"&gt;"Patchouli Patcher"&lt;/a&gt; since I had &lt;a href="https://tvc-16.science/images/memupatcher/suofile.png"&gt;accidentally left my .vs folder in the uploaded code&lt;/a&gt; like an idiot&lt;/li&gt;
&lt;li&gt;FUD about the patcher &lt;a href="https://pastebin.com/U7qb6Jbh"&gt;getting your FGO account potentially banned&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;People begging for support screaming that they did buy Sonic Mania (sega should pay me for all this free advertising I gave them)  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Real evidence of said begging not staged" src="images/memupatcher/mania.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;I never got it to reliably work on Windows 7/8, but nonetheless it seemed to be quite popular! (Looking back at the source, I suspect there's something wrong with my usage of &lt;code&gt;SYSTEM_INFO&lt;/code&gt;.)&lt;br&gt;
Most importantly, it spread the method around so other people could implement it in Cheat Engine or similar.  &lt;/p&gt;
&lt;h2&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;As a result, I got my share of anonymous hacker fame, saved some login streaks (including the guy &lt;a href="https://github.com/wDCat/ANRC/issues/110"&gt;making the Lua farming script&lt;/a&gt; so yknow, what goes around comes around ✨), and autofarmed to my hearts' content until &lt;a href="https://www.bignox.com/"&gt;Nox&lt;/a&gt; got reliable FGO support, at which point I switched and never looked back. 👏  &lt;/p&gt;
&lt;p&gt;I've uploaded the latest version of the code I had here: &lt;a href="https://github.com/Difegue/Chaotic-Realm/tree/master/MemuPatcher"&gt;https://github.com/Difegue/Chaotic-Realm/tree/master/MemuPatcher&lt;/a&gt;&lt;br&gt;
I don't think this will be useful to anyone considering it's just a half-baked memory scanner and Cheat Engine literally does the same thing, but you might get a laugh out of it.  &lt;/p&gt;
&lt;p&gt;I think FGO still manages to block emulators every now and then, but I've entirely moved to just running &lt;a href="https://github.com/Fate-Grand-Automata/FGA"&gt;FGA&lt;/a&gt; on my phone instead of the Lua script these days, so I don't really care anymore.  &lt;/p&gt;</content><category term="Software"></category><category term="memu"></category><category term="android"></category><category term="patch"></category><category term="winforms"></category><category term="fgo"></category></entry><entry><title>LRR goes neumorphism (well, the icon at least)</title><link href="https://tvc-16.science/lrr-icon-study.html" rel="alternate"></link><published>2021-03-19T00:00:00+01:00</published><updated>2021-03-19T00:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2021-03-19:/lrr-icon-study.html</id><summary type="html">&lt;p&gt;haha gloss and shadows go brrrrrrrrrrrrrr&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="old logo and favicon" src="https://tvc-16.science/images/lrr-nuicon/old.png"&gt;  &lt;/p&gt;
&lt;p&gt;The favicon you've probably already seen a thousand times if you have a &lt;a href="https://github.com/Difegue/LANraragi"&gt;LANraragi&lt;/a&gt; install was ripped off from a Japanese Monogatari website sometime in late 2014. (get it b/c it's araragi and you see the ahoge and everything haha)&lt;br&gt;
The logo came a bit later(late 2016), but it's essentially just an edit of the ubiquitous &lt;a href="https://fontawesome.com/icons/cubes?style=solid"&gt;fa-cubes&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;The favicon served me well enough, but it's starting to feel really low-res, and doesn't really work well on dark backgrounds.&lt;br&gt;
As for the logo, it doesn't look very nice at low resolutions either if I were to make it the new favicon.  &lt;/p&gt;
&lt;p&gt;Therefore, I've been wanting to update the LRR logo in order to make it:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Legible/Recognizable enough at low resolution&lt;/li&gt;
&lt;li&gt;Work on both light and dark backgrounds&lt;/li&gt;
&lt;li&gt;Not a ripoff of existing art (don't want to risk a lawsuit from aniplex and fontawesome if i ever make it big i swear on me mum they'd do it we live in a society)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;while not losing what makes it &lt;strong&gt;unique&lt;/strong&gt;, by which I mean keeping the ahoge motif.  &lt;/p&gt;
&lt;p&gt;&lt;img width="300" alt="can you believe this image is 9 years old already oh my god where does the time go" src="/images/lrr-nuicon/rrg.jpg"/&gt;  &lt;/p&gt;
&lt;h2&gt;Thoughts and process&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.justinmind.com/blog/neumorphism-ui/"&gt;Neumorphism&lt;/a&gt; is the new design meme, but outside of outlandish dribble mockups it seems to just translate to softer shadows and more transparency, with a little bit of 3D thrown in.&lt;br&gt;
&lt;sup&gt;&lt;sub&gt;Not a design expert please don't scream at me for not getting it&lt;/sup&gt;&lt;/sub&gt;&lt;/p&gt;
&lt;p&gt;So, let's make a 3D icon! Using the one and only free 3D tool all grand designers use, &lt;s&gt;Blender&lt;/s&gt; Paint 3D by Microsoft:  &lt;/p&gt;
&lt;p&gt;&lt;img alt="it aint stupid if it works" src="https://tvc-16.science/images/lrr-nuicon/p3D.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;I took the existing 3D logo I'd already made for the &lt;a href="./hacktoberfest-lrr-2.html"&gt;5 years hacktoberfest&lt;/a&gt;, and started iterating from there.&lt;/p&gt;
&lt;p&gt;To help with recognizing the icon at low-res, the best thing to do was to bring in some &lt;strong&gt;color&lt;/strong&gt; to the blocks.&lt;br&gt;
The color scheme was inspired by the WinRAR icon, since LRR deals with archive/compressed files basically all the time!  &lt;/p&gt;
&lt;p&gt;&lt;img alt="the finger tool in gimp has been my best ally for over 8 years now and it aint stopping anytime soon" src="https://tvc-16.science/images/lrr-nuicon/study.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Fully coloring them looked kinda garbo, so I went &lt;strong&gt;arthouse&lt;/strong&gt; and just scribbled some lines.&lt;br&gt;
Paint 3D did a surprisingly good job at handling the breadth of the lines depending on the depth, and it reminds me of the NeXTSTEP blocks so I like it:  &lt;/p&gt;
&lt;p&gt;&lt;img alt="damn its like i'm steve jobs" src="https://tvc-16.science/images/lrr-nuicon/OIP.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;I was kinda scared of the 3D ahoge looking like a piece of poop straight outta &lt;a href="https://www.youtube.com/watch?v=Lk2hzFC5Zok"&gt;Duke Nukem Forever&lt;/a&gt; with the glossy lighting and low polycount, but thankfully it looks quite good after some post-processing in &lt;s&gt;Photoshop&lt;/s&gt; GIMP to smooth it out.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="new favicon" src="/images/lrr-nuicon/new.png" width="550"/&gt; &lt;img alt="hmu in the comments if you actually read those alt texts" src="/images/lrr-nuicon/splash.jpg" width="250"/&gt;  &lt;/p&gt;
&lt;p&gt;I really dig the way it's positioned though! It pops out &lt;strong&gt;way&lt;/strong&gt; more than in the previous icon, and having it rotated to the side brings a lot of extra depth.&lt;br&gt;
Having it burrowed a bit more into the blocks also makes it recognizable even at low resolutions. &lt;sup&gt;&lt;sub&gt;and it kinda looks like a parasitic slug so that's cool &lt;/sup&gt;&lt;/sub&gt;  &lt;/p&gt;
&lt;p&gt;The design doesn't fare as well as I'd hoped on dark backgrounds however, so I slapped a squircle straight out of the Apple playbook for the icon variant.&lt;br&gt;
It looks a bit weird on Windows since squircles aren't really a thing in MS iconography, but the Windows app is just a launcher anyways so eh ¯\_(ツ)_/¯  &lt;/p&gt;
&lt;h2&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;m8 this icon thing is easy who needs designers rite just slap some 3D blocks and ur good&lt;/em&gt;  &lt;/p&gt;
&lt;p&gt;I wanted to name 0.7.7 Video Crime at first since it's basically the only Tin Machine song I like, but since I pick the Bowie song names proportional to the quality of the release, and 0.7.7 was &lt;strong&gt;super&lt;/strong&gt; huge, I went for Pallas Athena instead.&lt;br&gt;
Probably my favorite song from &lt;em&gt;Black Tie White Noise&lt;/em&gt;, although I love most of the album it's real darn good  &lt;/p&gt;
&lt;p&gt;I took the opportunity to remake my OpenGraph Github splash as well, using Inter as the typography:&lt;br&gt;
&lt;img alt="now in the iOS app store! what have I become good lord forgive me for I have sinned" src="https://tvc-16.science/images/lrr-nuicon/opengraph.jpg"&gt;&lt;br&gt;
I'll kinda miss the low-res GIMP pepper from the previous version.  &lt;/p&gt;</content><category term="LANraragi"></category><category term="lanraragi"></category><category term="icon"></category><category term="design"></category><category term="neumorphism"></category></entry><entry><title>Replacing the PRAM Battery in a PowerBook G3 Wallstreet</title><link href="https://tvc-16.science/wallstreet-pram-replacement.html" rel="alternate"></link><published>2021-03-16T00:00:00+01:00</published><updated>2021-03-16T00:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2021-03-16:/wallstreet-pram-replacement.html</id><summary type="html">&lt;p&gt;Ah yes, DMX's 2006 hit track, "Lord Give Me a Chime".&lt;/p&gt;</summary><content type="html">&lt;p&gt;I got my hands on a &lt;a href="https://en.wikipedia.org/wiki/PowerBook_G3#PowerBook_G3_Series_(Wallstreet_Series_I)"&gt;Powerbook G3&lt;/a&gt; recently. Lovely machine!  &lt;/p&gt;
&lt;p&gt;&lt;img alt="dingusbook screen" src="https://tvc-16.science/images/powerbook/powerbook_screen.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;But it had a few issues I quickly noticed once the machine booted without the &lt;a href="https://www.youtube.com/watch?v=7G3SXTg7gDI"&gt;usual Mac chime.&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;Mostly, &lt;strong&gt;no sound&lt;/strong&gt;, and a screen with some flickering and purpleish artifacts.&lt;br&gt;
Kinda weak hinges too, but considering that those were apparently a &lt;a href="https://web.archive.org/web/20021003091053/http://www.ocf.berkeley.edu/%7Ekenao/unhinged/"&gt;terrible mess&lt;/a&gt; during the service life of the laptop, I'm gonna consider myself lucky.  &lt;/p&gt;
&lt;p&gt;Previous experiences with Macs have shown me that a missing chime usually happens when the PRAM(fancy apple name for CMOS) battery in the machine dies out.&lt;br&gt;
Replacing it usually goes a long way towards getting the computer back on track to healthy town.  &lt;/p&gt;
&lt;p&gt;It probably wouldn't fix the sound missing in Mac OS, but since diagnosing that issue involves taking the laptop apart, we might as well replace the battery!  &lt;/p&gt;
&lt;h2&gt;Building a replacement PRAM&lt;/h2&gt;
&lt;p&gt;Due to this being a laptop however, it doesn't use the &lt;a href="https://www.newertech.com/products/pram_3_6v.php"&gt;chonkster 3.6v&lt;/a&gt; batteries you'd commonly find on other old Macs.&lt;br&gt;
Instead, after taking apart the entire machine (thanks &lt;a href="https://www.ifixit.com/Guide/PowerBook+G3+Wallstreet+PRAM+Battery+Replacement/11?revisionid=HEAD"&gt;ifixit&lt;/a&gt;), you get to pull out a weird mini-rechargeable battery composed of 6 coin cells.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="what in the goddamn" src="https://tvc-16.science/images/powerbook/powerbook_pram.jpg"&gt;&lt;br&gt;
(PSA: Some PowerBook models seem to only have &lt;a href="https://www.mac-forums.com/threads/pram-battery-replacement-for-powerbook-g3-400mhz-lombard-alternatives.341150/"&gt;4 cells&lt;/a&gt; instead of 6.)&lt;/p&gt;
&lt;p&gt;Luckily, those cells are Panasonic VL2330 3V rechargeable coin cells, and are still produced today!&lt;br&gt;
Although it does mean you need to basically re-build a PRAM set yourself.  &lt;/p&gt;
&lt;p&gt;You'll see this for yourself upon opening up the set, but the cells are connected to each other on both ends matching the following schema:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;  &lt;span class="n"&gt;___&lt;/span&gt;    &lt;span class="n"&gt;___&lt;/span&gt;    &lt;span class="n"&gt;___&lt;/span&gt;      
 &lt;span class="o"&gt;/&lt;/span&gt;   &lt;span class="err"&gt;\&lt;/span&gt;  &lt;span class="o"&gt;/&lt;/span&gt;   &lt;span class="err"&gt;\&lt;/span&gt;  &lt;span class="o"&gt;/&lt;/span&gt;   &lt;span class="err"&gt;\&lt;/span&gt;    
&lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="c1"&gt;---  - ---  -  |   --red wire &lt;/span&gt;
 &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;___&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;  &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;___&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;  &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
                 &lt;span class="o"&gt;|&lt;/span&gt;
                 &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="c1"&gt;--------white wire &lt;/span&gt;
  &lt;span class="n"&gt;___&lt;/span&gt;    &lt;span class="n"&gt;___&lt;/span&gt;    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;      
 &lt;span class="o"&gt;/&lt;/span&gt;   &lt;span class="err"&gt;\&lt;/span&gt;  &lt;span class="o"&gt;/&lt;/span&gt;   &lt;span class="err"&gt;\&lt;/span&gt;  &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;    
&lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="c1"&gt;---  + ---  +  |   --black wire&lt;/span&gt;
 &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;___&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;  &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;___&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;  &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;___&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;               

  &lt;span class="n"&gt;___&lt;/span&gt;    &lt;span class="n"&gt;___&lt;/span&gt;    &lt;span class="n"&gt;___&lt;/span&gt;      
 &lt;span class="o"&gt;/&lt;/span&gt;   &lt;span class="err"&gt;\&lt;/span&gt;  &lt;span class="o"&gt;/&lt;/span&gt;   &lt;span class="err"&gt;\&lt;/span&gt;  &lt;span class="o"&gt;/&lt;/span&gt;   &lt;span class="err"&gt;\&lt;/span&gt;    
&lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="c1"&gt;---  + ---  + -------red wire &lt;/span&gt;
 &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;___&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;  &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;___&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;  &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;___&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;

                 &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="c1"&gt;--------white wire                &lt;/span&gt;
  &lt;span class="n"&gt;___&lt;/span&gt;    &lt;span class="n"&gt;___&lt;/span&gt;    &lt;span class="n"&gt;___&lt;/span&gt;      
 &lt;span class="o"&gt;/&lt;/span&gt;   &lt;span class="err"&gt;\&lt;/span&gt;  &lt;span class="o"&gt;/&lt;/span&gt;   &lt;span class="err"&gt;\&lt;/span&gt;  &lt;span class="o"&gt;/&lt;/span&gt;   &lt;span class="err"&gt;\&lt;/span&gt;    
&lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="c1"&gt;---  - ---  - -------black wire&lt;/span&gt;
 &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;___&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;  &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;___&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;  &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;___&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;    
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;sub&gt;&lt;sup&gt;Tried to throw this schema into &lt;a href="https://github.com/ivanceras/svgbob"&gt;svgbob&lt;/a&gt; but it didn't look too hot so you'll have to deal with the OG ASCII&lt;/sub&gt;&lt;/sup&gt;&lt;br&gt;
VL2330 cells are pretty cheap at your local &lt;a href="https://aliexpress.com/item/4001264470979.html"&gt;Chinese overlord&lt;/a&gt;, so you can build yourself a brand new PRAM for like $20.  &lt;/p&gt;
&lt;p&gt;For assembly, I simply cut off the leads from the old PRAM and re-attached them to the new cells with ye ole' mates duct tape. 🩹 The stock PRAM is &lt;a href="https://www.youtube.com/watch?v=GGTGIlT6JvM"&gt;spot welded&lt;/a&gt; together, so you can't simply unsolder the leads like you'd do with a dumb circuit board.  &lt;/p&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;Once the battery assembled, tested for continuity and put back in the laptop, did we get a chime on boot?&lt;br&gt;
Well, I did get a form of &lt;em&gt;noise&lt;/em&gt; out of the laptop, but it sounded more like some radio screeching than an actual chime.  &lt;/p&gt;
&lt;p&gt;Before putting the machine back together I had taken a look at the other pieces, and it's likely that the Sound/Power Board is dying and being what causes the remaining problems.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="dingusbook power board" src="https://tvc-16.science/images/powerbook/powerbook_board.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;That &lt;a href="https://en.wikipedia.org/wiki/Ceramic_capacitor#Multi-layer_ceramic_capacitors_(MLCC)"&gt;MLCC&lt;/a&gt; capacitor in front of the main power jack has certainly seen better days.&lt;br&gt;
&lt;sub&gt;It's always the fucking caps isn't it&lt;/sub&gt;  &lt;/p&gt;
&lt;p&gt;The screen still flickers, probably due to power draw issues coming from said board, but hey at least it doesn't artifact out anymore!  &lt;/p&gt;</content><category term="Hardware"></category><category term="apple"></category><category term="mac"></category><category term="powerbook"></category><category term="repair"></category><category term="pram"></category></entry><entry><title>The 2020 LRR Hacktoberfest Bonanza</title><link href="https://tvc-16.science/hacktoberfest-lrr-2.html" rel="alternate"></link><published>2020-10-01T00:00:00+02:00</published><updated>2020-10-01T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2020-10-01:/hacktoberfest-lrr-2.html</id><summary type="html">&lt;p&gt;I don't think there's a market for shirts yet but I can make some stickers, sure.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="Fiiiiiiiiiiiiiiiiiiiiiiiive Years" src="https://tvc-16.science/images/lrr_5.png"&gt;&lt;br&gt;
It's that time of the year again!  &lt;/p&gt;
&lt;p&gt;&lt;a href="https://hacktoberfest.digitalocean.com/"&gt;Hacktoberfest 2020&lt;/a&gt; is starting and I am once again aiming to get free shit™ by doing what I already do the entire year: Pushing jank code to GitHub.  &lt;/p&gt;
&lt;p&gt;This year, I'm putting out a hot new &lt;a href="https://github.com/Difegue/LANraragi/releases/tag/v.0.7.3"&gt;LANraragi release&lt;/a&gt; for you to hack on all month. &lt;sub&gt;&lt;sup&gt;(It's got downloaders!)&lt;/sub&gt;&lt;/sup&gt;  &lt;/p&gt;
&lt;h2&gt;Give me them sweet rewards&lt;/h2&gt;
&lt;p&gt;Any PR merged to the LRR repo (or Karen or even &lt;a href="/stylophone"&gt;Stylophone&lt;/a&gt; if you're weird I don't really care) is eligible to receive the now classic LRR sticker set:&lt;/p&gt;
&lt;p&gt;&lt;img alt="rare photography of LRR stickers in their natural habitat" src="https://tvc-16.science/images/stickers_rl.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;And until stocks run out(probably sometime in 2025), I'll also throw in this new cool holographic sticker to celebrate 5 years of LANraragi.&lt;br&gt;
&lt;sub&gt;&lt;sup&gt;The repo started in 2014 so we're already a bit past the 5-year mark I want my time back&lt;/sub&gt;&lt;/sup&gt;  &lt;/p&gt;
&lt;p&gt;&lt;img alt="three trillion hours in paint 3D" src="https://tvc-16.science/images/holofive.png"&gt;  &lt;/p&gt;
&lt;h2&gt;What to do tho&lt;/h2&gt;
&lt;p&gt;Using feedback from the &lt;a href="/lrr-survey-2-results"&gt;last user survey&lt;/a&gt;, I've opened a fair bit of &lt;a href="https://github.com/Difegue/LANraragi/issues?q=is%3Aissue+is%3Aopen+label%3Ahacktoberfest"&gt;Hacktoberfest issues&lt;/a&gt; to give would-be contributors ideas.&lt;br&gt;
Although you can really contribute on anything you want! Even something as simple as documentation improvements would be welcome.  &lt;/p&gt;
&lt;p&gt;Just like last year, the stickers are also available to anyone who pledges $5+ on &lt;a href="https://github.com/users/Difegue/sponsorship"&gt;GitHub Sponsors&lt;/a&gt; or &lt;a href="https://ko-fi.com/lanraragi"&gt;Ko-Fi.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you did one of the above, you can go to the &lt;a href="https://forms.office.com/Pages/ResponsePage.aspx?id=DQSIkWdsW0yxEjajBLZtrQAAAAAAAAAAAAN__osxt25URTdTUTVBVFRCTjlYWFJLMlEzRTJPUEhEVy4u"&gt;following form&lt;/a&gt; to give me a mail address to ship the stuff to.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="I can't believe you're still using a nonfree solution like office forms my dude it's 2020" src="https://tvc-16.science/images/rmshacking.png"&gt;  &lt;/p&gt;</content><category term="LANraragi"></category><category term="hacktoberfest"></category><category term="swag"></category><category term="lanraragi"></category><category term="github sponsors"></category><category term="lods of emone"></category><category term="cheap stickers"></category></entry><entry><title>Introducing Stylophone</title><link href="https://tvc-16.science/stylophone.html" rel="alternate"></link><published>2020-09-13T00:00:00+02:00</published><updated>2020-09-13T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2020-09-13:/stylophone.html</id><summary type="html">&lt;p&gt;A modern, native MPD client for the Universal Windows Platform.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://www.musicpd.org/"&gt;Music Player Daemon&lt;/a&gt; clients don't get much love on Windows.&lt;br&gt;
You're usually left with ports of whatever clients people have created for Linux, which usually involves GTK, Qt or other cross-platform control libraries which look like they're coming straight out of the Windows 7 era.  &lt;/p&gt;
&lt;p&gt;The only MPD client offering on the Microsoft Store is &lt;a href="https://www.microsoft.com/en-us/p/chimney/9wzdncrfj6jx"&gt;Chimney&lt;/a&gt;, which I'm pretty sure nobody remembers anymore due to the Store being utterly unsearchable. It works, but the Windows 8 aesthetic feels just as out of place nowadays...
&lt;img alt="Circled buttons? What *is* this, Windows Phone 7?" src="https://tvc-16.science/images/stylophone/chimney.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;I've used &lt;a href="https://github.com/CDrummond/cantata"&gt;Cantata&lt;/a&gt; for a long while as it was the only client left that was sorta-up-to-date, but it recently slipped into maintenance mode.  &lt;/p&gt;
&lt;p&gt;&lt;a href="https://persephone.fm/"&gt;Even macOS&lt;/a&gt; was getting a brand new native MPD client with Swift 'n bells 'n whistles! Enough!&lt;br&gt;
Making a new MPD client running on UWP/WinRT was a good way to check out how the platform's evolved since I last made RSS Live Tiles in 2017.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="Soul Hackers is underrated soundtrack work from Shoji Meguro" src="https://tvc-16.science/images/stylophone/stylophone.png"&gt;  &lt;/p&gt;
&lt;p&gt;Stylophone is now &lt;strong&gt;live&lt;/strong&gt; on &lt;a href="https://github.com/Difegue/Stylophone"&gt;GitHub&lt;/a&gt; and on the &lt;a href="https://www.microsoft.com/store/apps/9NCB693428T8"&gt;Microsoft Store&lt;/a&gt;, so if you don't really care how the app was made you can stop reading and click on 👆👀☝  &lt;/p&gt;
&lt;h1&gt;Building the app&lt;/h1&gt;
&lt;p&gt;A .NET base for the client section thankfully already existed through &lt;a href="https://archive.codeplex.com/?p=libmpc"&gt;LibMpc.net&lt;/a&gt;, an ole' codeplex library which got reworked and improved upon through the years &lt;a href="https://github.com/glucaci/MpcNET"&gt;on Github.&lt;/a&gt;&lt;br&gt;
So Stylophone technically contains code from 2008, which is...not very important but nice!  &lt;/p&gt;
&lt;p&gt;To bootstrap the UWP app portion, I've been really impressed with the &lt;a href="https://docs.microsoft.com/en-us/windows/uwp/design/windows-template-studio/"&gt;Windows Template Studio&lt;/a&gt;, which allows you to prop up a skeleton and start coding app functionality straight away without having to reimplement &lt;code&gt;INotifyPropertyChanged&lt;/code&gt; and &lt;code&gt;BoolToVisibilityConverter&lt;/code&gt;s for the 9847563th time. A+ stuff.  &lt;/p&gt;
&lt;p&gt;I've also used the &lt;a href="https://github.com/windows-toolkit/WindowsCommunityToolkit"&gt;Windows Community Toolkit&lt;/a&gt; for a few real nice-to-haves, such as &lt;a href="https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI/Extensions/ScrollViewer"&gt;middle-mouse scrolling&lt;/a&gt; and listview headers.  &lt;/p&gt;
&lt;p&gt;The architecture is standard MVVM, with a few services for essential features such as navigation and listening to the MPD server through an idle connection.  &lt;/p&gt;
&lt;h1&gt;Design and name&lt;/h1&gt;
&lt;p&gt;Music players often appear in designer concepts, so I had &lt;a href="https://twitter.com/zeealeid/status/1262382516591345674"&gt;a lot&lt;/a&gt; of &lt;a href="https://twitter.com/ImShashankDogra/status/1144380971485057024"&gt;inspiration&lt;/a&gt; to &lt;a href="https://twitter.com/define_studio/status/1297163374812266496"&gt;choose from&lt;/a&gt;.&lt;br&gt;
Even on the &lt;a href="https://twitter.com/jsngr/status/1280619068794470402"&gt;macOS side,&lt;/a&gt; since they're also jumping on the "transparent sidebar" bandwagon now.&lt;br&gt;
&lt;sup&gt;&lt;sub&gt;Microsoft always does it first m8s&lt;/sub&gt;&lt;/sup&gt;  &lt;/p&gt;
&lt;p&gt;For existing apps, I mostly looked at Groove Music, but also some existing UWP players like &lt;a href="https://github.com/DominicMaas/SoundByteOSS"&gt;SoundByte&lt;/a&gt; or &lt;a href="https://github.com/thecodrr/BreadPlayer"&gt;Bread Player.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Truth is, the compact view is entirely ripped off from SoundByte! God bless open source" src="https://tvc-16.science/images/stylophone/stylophone2.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;The app uses WinUI 2 by default, although there are a few parts where I still needed stock Windows.UI.Xaml. (Mostly for background Acrylic)  &lt;/p&gt;
&lt;p&gt;The name was "FluentMPC" for the longest time, briefly became "Moroder" before I settled on "&lt;a href="https://en.wikipedia.org/wiki/Stylophone"&gt;Stylophone&lt;/a&gt;", referencing the lil' dingus toy instrument David Bowie used in &lt;em&gt;Space Oddity&lt;/em&gt;.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="It also served in Heathen(The Rays) which in my opinion is a better Bowie track but I'm probably alone on that" src="https://tvc-16.science/images/stylophone/styloicon.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;The look of the instrument made for good inspiration as well when it came to making an icon.&lt;br&gt;
Starting from the same proportions as the modern office icons, I used the look of the metal latches as the pattern and heavily simplified the "S" from the original logo.  &lt;/p&gt;
&lt;p&gt;The resulting icon doesn't indicate whatsoever that this is a music-related application. 🤐&lt;br&gt;
But as someone who can't design, I like it!  &lt;/p&gt;
&lt;h1&gt;Essential features and tailoring the app&lt;/h1&gt;
&lt;p&gt;What I wanted most from an MPD client besides the obvious playback/playlist features was &lt;strong&gt;good album art handling.&lt;/strong&gt;&lt;br&gt;
Traditionally, MPD clients used to pull album art from outside sources like search engines or last.fm, as the server had no capacity to provide art to clients. This usually led to a bunch of false positives, and allowed said search engines to track what you listened to.  &lt;/p&gt;
&lt;p&gt;This changed &lt;a href="https://github.com/MusicPlayerDaemon/MPD/issues/42"&gt;recently&lt;/a&gt; with the &lt;code&gt;albumart&lt;/code&gt; command, which allows clients to pull binary data for cover art from the server.&lt;br&gt;
It only handles cover.jpg/png files in the same folder as the tracks for now, but a second command, &lt;code&gt;readpicture&lt;/code&gt;, is stated to come in MPD 0.22 to handle embedded art.  &lt;/p&gt;
&lt;p&gt;Stylophone handles both commands to build its album art cache, so it is future-proofed in that sense.  &lt;/p&gt;
&lt;p&gt;My second most-wanted "feature", if it can be called that, is a good &lt;em&gt;Now Playing&lt;/em&gt; view.&lt;br&gt;
I'd like to use Stylophone in a smart speaker setup later down the line, where it could show the current/next tracks on a touch screen alongside the basic playback controls.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="Waiting for the Deadly Premonition 2 soundtrack..." src="https://tvc-16.science/images/stylophone/stylophone3.jpg"&gt;  &lt;/p&gt;
&lt;h1&gt;Closing words&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;Actually, we encourage people who redistribute free software to charge as much as they wish or can.&lt;/em&gt;&lt;br&gt;
- &lt;a href="https://www.gnu.org/philosophy/selling.en.html"&gt;Free Software Foundation&lt;/a&gt;  &lt;/p&gt;
&lt;p&gt;Stylophone currently costs 5 USD on the &lt;a href="https://www.microsoft.com/store/apps/9NCB693428T8"&gt;Microsoft Store&lt;/a&gt;.&lt;br&gt;
The entire app is free software under the MIT license so you can just compile it yourself if you don't want to pay. 👌&lt;br&gt;
I won't provide binaries in any other fashion at the moment since distributing UWP apps is just &lt;a href="https://github.com/microsoft/ProjectReunion/issues/128"&gt;annoying.&lt;/a&gt;&lt;br&gt;
&lt;sup&gt;&lt;sub&gt;&lt;strike&gt;Although if you've read the article all the way to the end, you might just deserve a &lt;a href="http://go.microsoft.com/fwlink/?LinkId=532540&amp;amp;mstoken=CK394-4WW3G-DDJP6-2MC92-WP49Z"&gt;freebie!&lt;/a&gt;&lt;/strike&gt; It's over chief the promo code expired might make a new one someday&lt;/sup&gt;&lt;/sub&gt;  &lt;/p&gt;
&lt;p&gt;Maybe in a year or so, I'll be able to port the app to Mac, Linux and mobile throught the &lt;a href="https://platform.uno/blog/announncing-uno-platform-3-0-linux-support-fluent-material-and-more/"&gt;Uno Platform&lt;/a&gt;, giving me total dominion over the MPD client space.&lt;br&gt;
And endless anguish from having to support users from so many different platforms.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="John Carmack's inauguration speech has some great thoughts about the current state of computing you should go watch it" src="https://tvc-16.science/images/coolmeme.jpg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;late 2020 edit&lt;/strong&gt;&lt;br&gt;
I guess the total dominion thing will have to wait a bit considering how Uno fares at the moment.  &lt;/p&gt;
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;Tried cramming the Stylophone codebase into Uno Platform and it&amp;#39;s uhhhhhhhhhhhhhhhhhhhhhhhhhhh it can play songs I guess? &lt;a href="https://t.co/ZAmbOaN21E"&gt;pic.twitter.com/ZAmbOaN21E&lt;/a&gt;&lt;/p&gt;&amp;mdash; ＫＩＬＬＥＲ ＱＵＥＥＮ (@Difegue) &lt;a href="https://twitter.com/Difegue/status/1329221609652105217?ref_src=twsrc%5Etfw"&gt;November 19, 2020&lt;/a&gt;&lt;/blockquote&gt;

&lt;p&gt;&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;  &lt;/p&gt;</content><category term="Software"></category><category term="mpd"></category><category term="winui"></category><category term="c#"></category><category term="uwp"></category><category term="windows"></category><category term="windows 10"></category><category term="music"></category><category term="client"></category><category term="stylophone"></category></entry><entry><title>LANraragi User Survey 2 Results</title><link href="https://tvc-16.science/lrr-survey-2-results.html" rel="alternate"></link><published>2020-07-25T00:00:00+02:00</published><updated>2020-07-25T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2020-07-25:/lrr-survey-2-results.html</id><summary type="html">&lt;p&gt;The featurebowl has arrived! Did &lt;strong&gt;your&lt;/strong&gt; suggestion make the cut?&lt;/p&gt;</summary><content type="html">&lt;p&gt;3 months later, it's time to close up &lt;a href="./lrr-survey-2"&gt;Survey 2&lt;/a&gt; and look at the results.&lt;br&gt;
No pie charts this time! Pasting the tables directly from Excel saves me from uploading a bunch of images. 😌😌  &lt;/p&gt;
&lt;h1&gt;Users and platforms&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operating System&lt;/th&gt;
&lt;th&gt;Users&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;macOS&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BusyBox&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DSM&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unraid&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Doesn't actually use LRR&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;I only got 70 replies to the survey this time, which I'm putting on account of the sadpanda death craze having died down. Way less "I didn't stick with your program because it &lt;strong&gt;sucks&lt;/strong&gt;" replies, after all.&lt;br&gt;
Github ⭐ count flew past 250 as well! I still like watching the counter go up even if it &lt;a href="https://kitze.io/posts/github-stars-wont-pay-your-rent"&gt;doesn't amount to anything.&lt;/a&gt;  &lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Install method and location&lt;/th&gt;
&lt;th&gt;Users&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Windows Installer - Server&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows Installer - Local&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker - Server&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker - Local&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Built it from source - Server&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Built it from source - Local&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Homebrew - Server&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Homebrew - Local&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total&lt;/td&gt;
&lt;td&gt;66&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The Windows/Linux users are now basically 50/50, which is unexpected! And validates the hours spent making a proper Windows installer, I guess.&lt;br&gt;
&lt;sub&gt;I don't feel validated for the Homebrew port yet but I'm sure it'll come&lt;/sub&gt;  &lt;/p&gt;
&lt;p&gt;Windows users mostly install on their local machine compared to Linux users jamming the thing into a secondary machine/server, which makes sense I suppose. (&lt;span style="color:green"&gt;&amp;gt;using windows server&lt;/span&gt;)  &lt;/p&gt;
&lt;h1&gt;Usage and external readers&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Preferred view mode&lt;/th&gt;
&lt;th&gt;Users&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Table view&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thumbnail view&lt;/td&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total&lt;/td&gt;
&lt;td&gt;66&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Yeah, not surprising. I still love you, table view.  &lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;External Readers used&lt;/th&gt;
&lt;th&gt;Users&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No, I only use the Web Reader&lt;/td&gt;
&lt;td&gt;49&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tachiyomi Extension&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ichaival (Android)&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LRReader (Windows)&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OPDS&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The crowd clamoring for external reader support was only a vocal minority after all!&lt;br&gt;
&lt;img alt="i've been bamboozled so freakin hard" src="images/1-2-hurts-just-a-little-bit.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;I'll just have to delude myself into thinking that the OPDS support is actually used.&lt;/p&gt;
&lt;h1&gt;Feature requests&lt;/h1&gt;
&lt;p&gt;ayyy here we go it's the featurebowl  &lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Users&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Batch Tagging improvements&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Script to attempt better duplicate detection&lt;/td&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deeper Category integration&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Support for image folders alongside archives&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser Extension to directly call the server's Downloaders&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Better Out of Box Experience&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Easier way to contribute to the source code&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;I keep underestimating how much people use Batch Tagging considering I personally don't use it at all. 😥 &lt;sub&gt;autoplugin on archive uploads covers my needs i am not a smart man&lt;/sub&gt;&lt;br&gt;
The improvements(default timeout values for plugins, fallback plugins) are quite easy but I'd like to extend the timeout functionality to autoplugin as well.  &lt;/p&gt;
&lt;p&gt;The duplicate script is a fun side-experiment and I wasn't expecting it to overtake the category stuff, so uh I'll do it I guess?  &lt;/p&gt;
&lt;p&gt;The one thing I personally want most is the browser extension as I already use Hydrus Companion a &lt;strong&gt;lot&lt;/strong&gt; for single images, but that relies on downloaders and all the backend work they imply, which will probably take a while. Sorry!  &lt;/p&gt;
&lt;p&gt;I still want to do the OoB experience thing at some point, it's logical that existing users wouldn't care too much about it.&lt;br&gt;
Image folders can die. 🙃  &lt;/p&gt;
&lt;h1&gt;Suggestion Box&lt;/h1&gt;
&lt;p&gt;Here are a few interesting messages I got from the feature suggestion box.  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;Sort by date added&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;Option to customize the columns of the table view.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;sorting on thumbs view. remember sorting. sort by artists than by titles&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;Custom sortable columns in table view. Added Date&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;There is a long-standing issue on adding customizable sorting, but it's true that right now you can't sort at all using only the thumbnail view. I'll keep a note for that!  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;Manga bookmarks(i.e volumes with bookmarks for chapters)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This would be nice, but I'm not quite sure how to implement it. Specific tags would work but that's a bit of a slippery slope.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;Improved built in front-end user interface&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;As soon as AI gets &lt;a href="https://twitter.com/sharifshameem/status/1284095222939451393"&gt;good enough at creating React applications&lt;/a&gt;, I promise I'll use LRR as a testbed for it.  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;Category view. Category thumbnails are 1st manga thumbnail&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;the ability to link archives in a series together that doesnt require creating a category button on the UI&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Category buttons are a bit unwieldy and I'd like to have a dedicated category view with thumbnails as well.&lt;br&gt;
The Javascript on the Index is a bit spaghettish and I dread having to work on it though 😶  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;EPUB support, images could be generated dynamically. Probably a huge pain to handle all the different EPUB formats though :/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I'm actually adding this in 0.7.1 after looking it up a bit - No text handling tho so support is quite limited unless you use an external client.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;An add/edit/remove tag API on an arcid basis so that external clients can set/unset tags.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;That's also in 0.7.1!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;Mao themes plz&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This one suggestion drives me crazy. Which Mao is this guy talking about? Mao Zedong, or the chairman from Futabu? Did he just heavily typo "More themes"? The possibilities are endless.  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;Server side read status/progress to sync between devices  &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;Plugins&amp;amp;scripts that execute On-read/After-reading a manga i.e update extrernal manga trackers when a volume/chapter is read&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Syncing reading progression is a good feature, I left it client-side originally so that it made sense with multiple users on the server, but I think this is a very minor use-case compared to one user accessing his stuff on multiple devices.&lt;br&gt;
Guess I'll do it!  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;An easy way to edit (mainly crop) archive thumbnail, currently i dupe the cover-crop-insert into zip arch usually as file 000 for preservation&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This one is more of a support thing, but I think you'd be better off editing the generated thumbnails directly instead of modifying the archive, since that leads to the ID changing.&lt;br&gt;
Cropping from the server would be possible with magick but that's really way too niche to implement.  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;An option for when opening an archive to, instead of opening the last read or first image, to open a page showing all of the archive&amp;#39;s thumbnails allowing you to go directly to a specific image, sort of like EHentai and similar sites.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;Integrate the tags and thumbnail view when selecting an archive like eH has it.  &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;When selecting an archive, it would be very nice to first be presented with a summery of the tags and the thumbnail images, just like how E-H does it. Would make it much easier to manage and also jump to the page you want with much less clicks.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Just hit Ctrl when the reader loads to see all the pages at once.&lt;br&gt;
I'll agree that the reader currently doesn't have metadata shown anywhere, but I don't think that'd be very useful without a way to proc searches by clicking on tags. (See below)  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;Middle click on tag :&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;Should encode query into the url bar&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Those kinda go together in that implementing 2 makes 1 possible.  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;An Opening script, or a config file, since remembering the full Docker command to start can be hard sometimes, specially when your machine didn&amp;#39;t save it.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Wait, that's just &lt;a href="https://docs.docker.com/compose/"&gt;docker-compose&lt;/a&gt;! You can also consider using Portainer if you want an easier GUI interface.  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;Bugfixes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I will try my best. 🙇‍♂️  &lt;/p&gt;
&lt;h1&gt;Finishing up&lt;/h1&gt;
&lt;p&gt;I can't put all the replies up in one article, but all your thank-yous are very much appreciated!&lt;br&gt;
I'm currently working on other things so LRR is a bit on the backburner for the time being, but I'll certainly try to get some dev juice flowing for the next GH Hacktoberfest. (gotta get them stickers fam)  &lt;/p&gt;
&lt;p&gt;Until then, please enjoy the new &lt;a href="https://github.com/Difegue/LANraragi/releases/tag/v.0.7.1"&gt;0.7.1&lt;/a&gt; release.&lt;br&gt;
*mic drop*  &lt;/p&gt;</content><category term="LANraragi"></category><category term="lanraragi"></category><category term="survey"></category><category term="philosophical software architecture ramblings"></category></entry><entry><title>Hacking your way around both Homebrew and macOS to use libarchive</title><link href="https://tvc-16.science/homebrew-libarchive.html" rel="alternate"></link><published>2020-05-25T00:00:00+02:00</published><updated>2020-05-25T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2020-05-25:/homebrew-libarchive.html</id><summary type="html">&lt;p&gt;I also learned more about how to build a Perl module than I actually wanted to.&lt;/p&gt;</summary><content type="html">&lt;p&gt;The LANraragi &lt;a href="https://formulae.brew.sh/formula/lanraragi"&gt;Homebrew Port&lt;/a&gt; was recently merged into the core repository, making access to the world's only(&lt;em&gt;and therefore best&lt;/em&gt;) Perl manga manager easy to all Macs.  &lt;/p&gt;
&lt;p&gt;Getting the port to the acceptable level of standards for the core repository wasn't easy and needed one cool trick.  &lt;/p&gt;
&lt;h1&gt;The libarchive conundrum&lt;/h1&gt;
&lt;p&gt;LRR depends on the well-known &lt;a href="https://www.libarchive.org/"&gt;libarchive&lt;/a&gt; project to handle archives.&lt;br&gt;
It's so well-known that Apple themselves use and bundle it in releases of macOS.  &lt;/p&gt;
&lt;p&gt;Homebrew tries to be a good citizen by not installing dependencies which are already bundled by macOS if possible.&lt;br&gt;
This makes sense! No need having &lt;code&gt;libarchive&lt;/code&gt; in triplicate if the system version does the job.  &lt;/p&gt;
&lt;p&gt;Auditing the Homebrew formula henceforth gives you the following error:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Dependency &lt;span class="s1"&gt;&amp;#39;libarchive&amp;#39;&lt;/span&gt; is provided by macOS&lt;span class="p"&gt;;&lt;/span&gt; please replace &lt;span class="s1"&gt;&amp;#39;depends_on&amp;#39;&lt;/span&gt; with &lt;span class="s1"&gt;&amp;#39;uses_from_macos&amp;#39;&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;So the Homebrew formula for LRR should &lt;code&gt;uses_from_macos "libarchive"&lt;/code&gt;. Sounds easy! Except not.  &lt;/p&gt;
&lt;p&gt;In their infinite wisdom, Apple made the decision that their libarchive should be &lt;a href="https://stackoverflow.com/a/3167763"&gt;private API.&lt;/a&gt;&lt;br&gt;
As such, they only include a &lt;strong&gt;compiled&lt;/strong&gt; version of libarchive, without the function headers.  &lt;/p&gt;
&lt;p&gt;This would be fine if I was shipping precompiled executables that could call this compiled libarchive directly, but for better or worse, this is Perl! 🐫  &lt;/p&gt;
&lt;p&gt;The way I use the lib is through a pair of &lt;a href="https://en.wikipedia.org/wiki/XS_%28Perl%29"&gt;XS Modules&lt;/a&gt;, which are basically C programs compiled on the user's machine when the module is installed.&lt;br&gt;
&lt;img alt="wish I had an archive.h" src="https://tvc-16.science/images/libarchivexs.png"&gt;&lt;br&gt;
And so, we're hosed. Without the headers, the modules can't compile, making the Homebrew formula impossible to build on a stock Mac.  &lt;/p&gt;
&lt;p&gt;This makes Homebrew's order a tall one:  &lt;/p&gt;
&lt;h3&gt;"Use the bundled libarchive, except you can't compile anything that relies on it."&lt;/h3&gt;
&lt;h1&gt;Sideloading headers&lt;/h1&gt;
&lt;p&gt;The easiest solution here is providing the libarchive headers ourselves.&lt;br&gt;
Apple's version might be modified in some fashion from the original source code, so it's better to grab the headers directly &lt;a href="https://opensource.apple.com/source/libarchive/"&gt;from them:&lt;/a&gt;  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# libarchive headers from macOS 10.15 source&lt;/span&gt;
  &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;libarchive-headers-10.15&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://opensource.apple.com/tarballs/libarchive/libarchive-72.11.2.tar.gz&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;sha256&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;655b9270db794ba0b27052fd37b1750514b06769213656ab81e30727322e401f&amp;quot;&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;(And you probably get nerd cred for jamming apple.com as a dependency in your formula. 😎)  &lt;/p&gt;
&lt;p&gt;Homebrew decompresses resource tarballs on its own, so all we have to do to use the headers is move them in a custom &lt;code&gt;include&lt;/code&gt; folder in our install:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;  &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;libarchive-headers-10.15&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stage&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;libexec&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;include&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;libarchive/libarchive/archive.h&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;libexec&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;include&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;libarchive/libarchive/archive_entry.h&amp;quot;&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And just add said folder using good ol' &lt;a href="https://en.wikipedia.org/wiki/CFLAGS"&gt;CFLAGS&lt;/a&gt;, so that the Perl Modules will automatically pick up the folder when compiling the XS scripts.  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;CFLAGS&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;-I&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;libexec&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;include&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;code&gt;libexec&lt;/code&gt; is automatically mapped by brew to a directory like &lt;code&gt;/usr/local/Cellar/foo/0.1/libexec&lt;/code&gt;.&lt;br&gt;
And we're done!  &lt;/p&gt;
&lt;p&gt;&lt;img alt="oh no" src="https://tvc-16.science/images/brew-fail.png"&gt;&lt;br&gt;
&lt;img alt="YOU GAIN CPAN" src="https://tvc-16.science/images/bullshit.jpg"&gt;&lt;/p&gt;
&lt;h1&gt;Fixing the CPAN modules&lt;/h1&gt;
&lt;p&gt;Astute observers have probably noticed the error in the previous screenshot:&lt;br&gt;
The &lt;code&gt;cc&lt;/code&gt; command called to compile the XS script does not include the &lt;code&gt;libexec&lt;/code&gt; folder as an include. Why?&lt;br&gt;
Some background first.  &lt;/p&gt;
&lt;p&gt;There exist a variety of ways to handle building/installing a CPAN module in Perl, and this article &lt;a href="https://metacpan.org/pod/Module::Install"&gt;wouldn't&lt;/a&gt; be &lt;a href="https://metacpan.org/pod/Module::Build"&gt;enough&lt;/a&gt; to &lt;a href="https://metacpan.org/pod/Dist::Zilla"&gt;talk&lt;/a&gt; about &lt;a href="https://metacpan.org/pod/ExtUtils::MakeMaker"&gt;all of them&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;As said earlier, I use two XS Modules that both consume libarchive:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Archive::Extract::Libarchive&lt;/code&gt; (✅ builds fine with the new CFLAGS)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Archive::Peek::Libarchive&lt;/code&gt; (❌ doesn't work yet)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You'd think that with such similar namespaces, both would use the same installer... Wrong! 😱&lt;br&gt;
Peek uses &lt;code&gt;ExtUtils::MakeMaker&lt;/code&gt;, and Extract uses &lt;code&gt;Module::Build&lt;/code&gt;.  &lt;/p&gt;
&lt;p&gt;So let's focus on MakeMaker for a bit.&lt;br&gt;
You basically use it by writing a Perl-style Makefile, &lt;code&gt;Makefile.PL&lt;/code&gt;, which when interpreted by Perl will spit out a &lt;em&gt;regular&lt;/em&gt; Makefile to use with your &lt;em&gt;regular&lt;/em&gt; make/make install combo.  &lt;/p&gt;
&lt;p&gt;When building this Makefile, MakeMaker will &lt;strong&gt;not&lt;/strong&gt; take into account your custom CFLAGS environment variable, instead opting to use the one that was used &lt;a href="http://coding.derkeiler.com/Archive/Perl/comp.lang.perl.misc/2005-11/msg01613.html"&gt;when your version of Perl was built.&lt;/a&gt;&lt;br&gt;
In our case, the Perl we use comes from homebrew, and is built with esoteric include paths such as &lt;code&gt;/usr/local/Cellar/perl/5.30.2_1/lib/perl5/5.20.2/darwin-thread-multi-2level/CORE&lt;/code&gt;. The horror.  &lt;/p&gt;
&lt;p&gt;Now of course, Makefile.PL writers can add extra include paths, and our failing module certainly &lt;a href="https://metacpan.org/source/REHSACK/Archive-Peek-Libarchive-0.38/Makefile.PL#L95"&gt;does so&lt;/a&gt;, using another package called Config::AutoConf.&lt;br&gt;
Said module's documentation is a bit &lt;a href="https://metacpan.org/pod/Config::AutoConf#_get_extra_compiler_flags"&gt;empty&lt;/a&gt; as to how it figures out the extra compiler flags, and this rabbit hole has gone on long enough, so let's just &lt;strong&gt;patch the Makefile directly in Homebrew.&lt;/strong&gt;  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Archive::Peek::Libarchive&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stage&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;inreplace&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Makefile.PL&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gsub!&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;$autoconf-&amp;gt;_get_extra_compiler_flags&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
                &lt;span class="s2"&gt;&amp;quot;$autoconf-&amp;gt;_get_extra_compiler_flags .$ENV{CFLAGS}&amp;quot;&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="o"&gt;[...]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;By adding the environment variable ourselves, the cumbersome module finally builds, and we're out of Perl module building hell.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="took me three releases to get a working homebrew version" src="https://tvc-16.science/images/over.jpg"&gt;  &lt;/p&gt;
&lt;h1&gt;Spare thoughts&lt;/h1&gt;
&lt;p&gt;I use the 10.15 headers from Apple, as they're the only ones that actually include all the functions used by LANraragi.&lt;br&gt;
This of course means that some functions are not available for both High Sierra and Mojave.&lt;br&gt;
Luckily all this stuff fails rather gracefully, with no stability issues.  &lt;/p&gt;
&lt;p&gt;LRR is the &lt;a href="https://github.com/Homebrew/homebrew-core/search?q=uses_from_macos+%22libarchive%22&amp;amp;unscoped_q=uses_from_macos+%22libarchive%22"&gt;only formula&lt;/a&gt; in homebrew/core using this trick with libarchive.&lt;br&gt;
The other are &lt;strong&gt;weaksauce&lt;/strong&gt;, using a duplicate version instead. Plebeians!  &lt;/p&gt;
&lt;p&gt;Jokes aside, it is a bit weird for the homebrew audit bot to consider libarchive to be bundled by the OS, where it is clearly unusable out of the box for something as simple as a &lt;code&gt;make&lt;/code&gt; scenario. 😐  &lt;/p&gt;</content><category term="Cool Tricks"></category><category term="macos"></category><category term="homebrew"></category><category term="libarchive"></category><category term="headers"></category><category term="apple"></category><category term="perl"></category><category term="makefile"></category><category term="makemaker"></category><category term="cflags"></category></entry><entry><title>LANraragi User Survey 2 - Electric Boogaloo</title><link href="https://tvc-16.science/lrr-survey-2.html" rel="alternate"></link><published>2020-05-22T00:00:00+02:00</published><updated>2020-05-22T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2020-05-22:/lrr-survey-2.html</id><summary type="html">&lt;p&gt;A quick lookback at the progress made since the last survey, and a new one!&lt;/p&gt;</summary><content type="html">&lt;p&gt;It's been less than a year since &lt;a href="https://tvc-16.science/lrr-survey.html"&gt;the last user survey results&lt;/a&gt; for LANraragi -- I went and reread them and it's actually pretty cool to see everything that has been released since.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="hey look pdf support" src="https://tvc-16.science/images/lrr-survey/survey2-image.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Here's a quick rundown of everything that landed between this last post and today's &lt;a href="https://github.com/Difegue/LANraragi/releases/tag/v.0.7.0"&gt;0.7.0 release&lt;/a&gt;:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Categories&lt;/strong&gt;/Archive Sets, so you can finally bundle multiple archives in a named thingamabob, to track series, favorites, etc.  &lt;/li&gt;
&lt;li&gt;The Docker image is now available on &lt;a href="https://hub.docker.com/r/difegue/lanraragi/tags"&gt;a ton&lt;/a&gt; of architectures, including of course ARM. It's smaller too!  &lt;/li&gt;
&lt;li&gt;Reading progress and total page count is now shown on the main page.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;External reader&lt;/strong&gt; support has &lt;a href="https://sugoi.gitbook.io/lanraragi/advanced-usage/external-readers"&gt;grown consequently&lt;/a&gt;, with dedicated Windows/Android clients, a Tachiyomi extension and support for OPDS readers.  &lt;/li&gt;
&lt;li&gt;Full support for &lt;strong&gt;PDF files&lt;/strong&gt;.  &lt;/li&gt;
&lt;li&gt;Support for webp and more image files, alongside the ability to dynamically resize images output by the server to save on bandwidth.  &lt;/li&gt;
&lt;li&gt;A &lt;a href="https://sugoi.gitbook.io/lanraragi/installing-lanraragi/windows"&gt;true Windows Installer&lt;/a&gt; instead of the ol' PowerShell script.  &lt;/li&gt;
&lt;li&gt;A &lt;a href="https://sugoi.gitbook.io/lanraragi/installing-lanraragi/macos"&gt;Homebrew version&lt;/a&gt; now exists to install the server on macOS and Linux with a simple command.&lt;/li&gt;
&lt;li&gt;High-resolution Thumbnails.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Server-Side&lt;/strong&gt; searching, using the same modifiers as E*H.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tag suggestions&lt;/strong&gt; when searching.  &lt;/li&gt;
&lt;li&gt;A much beefier Client API (although work on that side never quite ends..)  &lt;/li&gt;
&lt;li&gt;Preview support for directly &lt;strong&gt;downloading&lt;/strong&gt; E*H links to LRR.  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And probably even more stuff I've forgotten about. 🎊&lt;br&gt;
I think 0.7.0 is a &lt;em&gt;bretty&lt;/em&gt; good release and hopefully, you'll think the same.  &lt;/p&gt;
&lt;p&gt;The next big ordeal is Downloader support since I finally got tired of downloading my zips manually and dropping them into the Web Uploader, but after that, I might be running out of ideas.  &lt;/p&gt;
&lt;p&gt;There's always work to be done, but now is a good time to refresh the User Survey!&lt;br&gt;
I think the first one yielded some good results, so I'm opening it up once again in order to receive all your hatemail.  &lt;/p&gt;
&lt;h2&gt;&lt;a href="https://forms.office.com/Pages/ResponsePage.aspx?id=DQSIkWdsW0yxEjajBLZtrQAAAAAAAAAAAAN__osxt25URDlOU0JFV0xISENaVjlFTEVOQUlHMDJZWi4u"&gt;Please check it out here.&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Thanks for reading! Be sure to like, subscribe, and &lt;a href="https://ko-fi.com/T6T2UP5N"&gt;give me all your money&lt;/a&gt;.&lt;br&gt;
&lt;sub&gt;&lt;sup&gt;Please don't actually give me all your money&lt;/sub&gt;&lt;/sup&gt;&lt;/p&gt;</content><category term="LANraragi"></category><category term="lanraragi"></category><category term="survey"></category><category term="lookback"></category></entry><entry><title>Using PS/2 keyboard/mice on old Macs with Arduino</title><link href="https://tvc-16.science/adbuino-ps2.html" rel="alternate"></link><published>2020-02-12T00:00:00+01:00</published><updated>2020-02-12T00:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2020-02-12:/adbuino-ps2.html</id><summary type="html">&lt;p&gt;It's time to deal with ADB. Not the Android one!&lt;/p&gt;</summary><content type="html">&lt;p&gt;My Mac fleet has been progressing nicely with all the &lt;a href="https://tvc-16.science/emac-lcd-mod.html"&gt;previous&lt;/a&gt; &lt;a href="https://tvc-16.science/performa-plus-display.html"&gt;restorations&lt;/a&gt;, but I still didn't have a way to control the pre-OSX machines. Those machines came to me keyboardless, and I initially thought it'd be no problem at all!&lt;br&gt;
Alas, I did not know about the terrifying Apple Desktop Bus.&lt;br&gt;
&lt;img alt="noooooooo" src="https://tvc-16.science/images/adb/pinout.jpg"&gt;&lt;br&gt;
&lt;a href="https://en.wikipedia.org/wiki/Apple_Desktop_Bus"&gt;ADB for short&lt;/a&gt;, this connector was used to plug keyboards, mice, joysticks and what have you into Macintoshes until USB took over.  Since I won't be able to use my old PS/2 stuff, might as well invest into some ADB devic--
&lt;img alt="the apple tax is fucking real" src="https://tvc-16.science/images/adb/ebay.jpg"&gt;  &lt;/p&gt;
&lt;h2&gt;😥 😥 😥&lt;/h2&gt;
&lt;p&gt;I shouldn't be surprised really, but I feel bad shelling out 40€ for a yellowed keyboard/mouse combo when I already have tons of PC ones laying around.  &lt;/p&gt;
&lt;h1&gt;A quick tour of ADB replacement solutions&lt;/h1&gt;
&lt;p&gt;There aren't many solutions to convert other devices to ADB on the market.&lt;br&gt;
The best one seems to be the &lt;a href="https://www.bigmessowires.com/usb-wombat"&gt;USB-ADB Wombat&lt;/a&gt;, but that doesn't exactly come cheap either!&lt;br&gt;
Although it basically offers the full shebang and works with more modern USB keyboards.&lt;br&gt;
&lt;img alt="wombat" src="https://tvc-16.science/images/adb/wombat.jpg"&gt;&lt;br&gt;
You also have &lt;a href="http://www.geethree.com/adb/"&gt;this thing&lt;/a&gt;, which &lt;em&gt;a contrario&lt;/em&gt; is rather old and only works for mice. Meh.  &lt;/p&gt;
&lt;p&gt;Turning to DIY solutions, most of the stuff out there is meant to use ADB keyboards on modern machines, using code from &lt;a href="https://github.com/tmk/tmk_keyboard/tree/master/converter/adb_usb"&gt;TMK&lt;/a&gt;. People really love their Apple Extended Keyboards!&lt;br&gt;
Which is nice and all, but the opposite of what I need.  &lt;/p&gt;
&lt;p&gt;After a bit more searching I did end up finding &lt;a href="http://thinkclassic.org/viewtopic.php?id=630"&gt;this spare forum post.&lt;/a&gt;&lt;br&gt;
PS/2 to ADB, both keyboard and mouse running off an off-the-shelf Arduino? Sounds great!  &lt;/p&gt;
&lt;p&gt;The code is available &lt;a href="http://synack.net/svn/adbduino/"&gt;on bbraun's SVN&lt;/a&gt;, so I grabbed an Arduino off my dead project pile and got to work.&lt;/p&gt;
&lt;h1&gt;Making the adbduino adapter&lt;/h1&gt;
&lt;p&gt;The provided .osm file for the circuit board needs &lt;a href="https://www.osmondpcb.com/"&gt;Osmond&lt;/a&gt; to be opened.&lt;br&gt;
Opening it, it's all pretty simple however, no need for extra resistors or anything:  &lt;/p&gt;
&lt;p&gt;&lt;img alt="adbduino circuit board" src="https://tvc-16.science/images/adb/osmond.png"&gt;&lt;br&gt;
The PCB has spots for two ADB connectors, but the second one is really just meant to enable passthrough to other ADB devices if you have any.&lt;br&gt;
Since I wasn't sure this stuff would actually still work, I breadboarded an abomination with just one connector.&lt;br&gt;
&lt;img alt="end my life" src="https://tvc-16.science/images/adb/adapter.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;The original project used a Pro Mini, but the Nano I had on hand is essentially the same thing, so no problem on that front.  &lt;/p&gt;
&lt;p&gt;Hooked it up to the ol' IIvx, and heck, it works! With some caveats.  &lt;/p&gt;
&lt;h1&gt;Using n' troubleshooting&lt;/h1&gt;
&lt;p&gt;My first major issue came with the mouse I was using -- It was struggling to synchronize itself with the ADB bus, essentially wrecking havoc on the Mac with random movements and clicks when moved.&lt;br&gt;
&lt;img alt="end my life" src="https://tvc-16.science/images/adb/adbduino-early.jpg"&gt;&lt;br&gt;
When eventually synced however(which could be helped by arcane methods such as &lt;sup&gt;&lt;sub&gt; flipping the mouse over and repeatedly pressing caps lock&lt;/sup&gt;&lt;/sub&gt;), it ran fine.  &lt;/p&gt;
&lt;p&gt;Thankfully, just switching out the mouse for an old USB model with a USB-&amp;gt;PS/2 adapter fixed everything.&lt;br&gt;
I don't recommend buying new PS/2 mice from AliExpress if you really need them!  &lt;/p&gt;
&lt;p&gt;The other big issue I had came from the keyboard skipping keys and occasionally staying "stuck" on the last keypress, resulting in a text a bit like &lt;code&gt;thissssssssss&lt;/code&gt;.&lt;br&gt;
Some debugging with the Serial monitor later, there were essentially three potential cases going on when releasing a key:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;A) 0x3C (key pressed) -&amp;gt; 0x3D (key released)  &lt;/span&gt;
&lt;span class="err"&gt;B) 0x3C (key pressed) -&amp;gt; 0xFF (keyboard error)  &lt;/span&gt;
&lt;span class="err"&gt;C) 0x3C (key pressed) -&amp;gt; 0xFF (keyboard error) -&amp;gt; 0x3C (key pressed)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Case A is the normal one, B skipped the key, and C was causing the stuck key problem.&lt;br&gt;
This is &lt;strong&gt;totally&lt;/strong&gt; the keyboard's fault (heck, I've had this one since Windows 98), but since this solution is about being as cheap as I can possibly be, I'd rather try to fix this through software. &lt;em&gt;aww yeah it's arduino hacking time&lt;/em&gt;  &lt;/p&gt;
&lt;iframe width="516" height="290" src="https://www.youtube.com/embed/1ZsETdpmvR0" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;...Well that's what I'd like to say, but I'm far from being good at hardware programming and dropped my bitwise operators, so I only managed a half-fix.  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;By &lt;a href="https://github.com/Difegue/Chaotic-Realm/blob/8c8afceb654f921774d88ac99e36613b679c1b9f/adbduino/sketch/adbuino.ino#L313"&gt;storing the last pressed keycode&lt;/a&gt;, we can recalculate its keyup variant and send that to ADB instead when we encounter a &lt;code&gt;0xFF&lt;/code&gt; error code. This fixes case B!  &lt;/li&gt;
&lt;li&gt;Case C is fixed by adding a &lt;a href="https://github.com/Difegue/Chaotic-Realm/blob/8c8afceb654f921774d88ac99e36613b679c1b9f/adbduino/sketch/adbuino.ino#L455"&gt;timer check&lt;/a&gt; so that a further keypress is dropped if it comes too soon for human hands.  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Adding timers is pretty sloppy and nearly breaks the ADB implementation since the protocol is super dependent on timing, but commenting out all the &lt;code&gt;Serial.print&lt;/code&gt; debug calls makes it fast enough to pass! &lt;sup&gt;&lt;sub&gt; damn I'm good at programming&lt;/sup&gt;&lt;/sub&gt;&lt;/p&gt;
&lt;p&gt;Although just dropping this bugged keypress fucks with the keyboard's internal state, so the next time said key is pressed for real, it'll be &lt;strong&gt;skipped&lt;/strong&gt;.&lt;br&gt;
This could be solved by sending some extra stuff on the PS/2 side of things but ehhhhhh, good enough. This is much less annoying for casual use than the stuck keys.  &lt;/p&gt;
&lt;h1&gt;Spare thoughts&lt;/h1&gt;
&lt;p&gt;&lt;img alt="I retrobright'd this powermac and it looks pretty darn good now" src="https://tvc-16.science/images/adb/adbduino-final.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;Building this thing cost me around &lt;strong&gt;5€&lt;/strong&gt; to get an ADB cable, some PS/2 connectors and the PS/2 mouse, which eventually proved itself to be useless.&lt;br&gt;
If you don't own spare PS/2 devices or an Arduino from a dead project, the total cost probably rises to 10€ or so.&lt;br&gt;
The ADB connector itself was salvaged from an old destroyed PowerBook dock, but any female S-video connector will do, as they both use DIN-4.  &lt;/p&gt;
&lt;p&gt;All around, this is much cheaper than buying a real ADB keyboard or a USB adapter! Although it certainly isn't flawless.&lt;br&gt;
The issues I had seem more related to my PS/2 hardware being old than the adapter itself, however.  &lt;/p&gt;
&lt;p&gt;You can find my hacked-up version of the adbduino sketch &lt;a href="https://github.com/Difegue/Chaotic-Realm/tree/master/adbduino"&gt;here&lt;/a&gt;.  &lt;/p&gt;</content><category term="Hardware"></category><category term="apple"></category><category term="macintosh"></category><category term="mac"></category><category term="arduino"></category><category term="adb"></category><category term="ps/2"></category><category term="diy"></category><category term="retrocomputing"></category></entry><entry><title>Fixing an Apple Performa Plus CRT Display</title><link href="https://tvc-16.science/performa-plus-display.html" rel="alternate"></link><published>2019-12-27T00:00:00+01:00</published><updated>2019-12-27T00:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2019-12-27:/performa-plus-display.html</id><summary type="html">&lt;p&gt;I ain't afraid of no CRT.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="domo watashi wa performa-san desu" src="https://tvc-16.science/images/performa/performa.jpg"&gt;&lt;br&gt;
This is an Apple Performa Plus Display. It's...a completely standard Shadow Mask CRT.  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;The Apple Performa Plus Display was a low-end Goldstar-built 14-inch monitor designed and fabricated for the Macintosh Performa series. Apple slightly modified this device to create the Apple Color Plus monitor, which was essentially the Performa Plus Display in a nicer case. The Apple Performa Plus Display also had a tilt &amp;amp; swivel stand.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I liked the size though, so I grabbed it! Only issue is that its cable was cut off.&lt;br&gt;
&lt;img alt="this is fucking barbaric but what do you expect out of a screen with no detachable cable" src="https://tvc-16.science/images/performa/performa2.jpg"&gt;&lt;/p&gt;
&lt;p&gt;The tube seemed to work fine when powered on, although there's no real way to make sure without feeding it a proper video signal! What the hell, let's take it apart.&lt;br&gt;
&lt;img alt="dude it's alright CRTs can't kill yo-*dies in flyback transformer*" src="https://tvc-16.science/images/performa/performa3.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;The inside circuit looked pretty much perfect -- No blown caps or anything weird that'd have made me give up on the spot.&lt;br&gt;
What I'm interested in is the internal video connector here, which is at the other end of the cut-off cable:  &lt;/p&gt;
&lt;p&gt;&lt;img alt="wish I had a time machine to '94 to ask apple/goldstar engineers why the ground isn't in the connector" src="https://tvc-16.science/images/performa/performa4.jpg"&gt;&lt;br&gt;
Of course, there's no detailed information on the net about the pinout of an internal video connector for a one-off CRT monitor.&lt;br&gt;
The Service Manual certainly doesn't help either:&lt;br&gt;
&lt;img alt="haha yeah fuck you apple" src="https://tvc-16.science/images/performa/performaservice.png"&gt;&lt;br&gt;
So, how am I going to remake the cable for this thing?  &lt;/p&gt;
&lt;h1&gt;Technotes to the rescue&lt;/h1&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;Technical Notes provide late breaking information about new Apple technologies and supplementary documentation discussing some of the more complex issues related to programming for the Mac OS.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I was surprised to see that old Mac hardware is actually quite well documented thanks to Apple's old &lt;a href="https://developer.apple.com/library/archive/navigation/#"&gt;technotes&lt;/a&gt;.&lt;br&gt;
I used a &lt;a href="https://www.fenestrated.net/mac/mirrors/Apple%20Technotes%20(As%20of%202002)/"&gt;mirror from 2002&lt;/a&gt; to shorten a bit the amount of articles I had to search/read through, but you can certainly pull all the info you need from the official site.  &lt;/p&gt;
&lt;p&gt;Looking at the &lt;a href="https://www.fenestrated.net/mac/mirrors/Apple%20Technotes%20(As%20of%202002)/hw/hw_08.html"&gt;Color Monitor Connections&lt;/a&gt; article basically gave me all the info I needed.&lt;br&gt;
Since this is Apple, Macintosh screens don't use classic VGA but DA-15 connectors with a custom pinout:&lt;br&gt;
&lt;img alt="Macintosh II Video Card and Macintosh IIci Built-in Video" src="https://tvc-16.science/images/performa/mac_video.gif"&gt;&lt;br&gt;
Looking at this pinout and the way the cables are laid out/colored in the internal video connector, you can theorize a mapping. Let's look back at what is in the CRT:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The red/blue/white wires are each paired with a matching ground(black) wire.&lt;/li&gt;
&lt;li&gt;After that, there is only one brown wire and two black wires left.&lt;/li&gt;
&lt;li&gt;The chassis (global) ground isn't even on the connector, making it rather obvious.  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It's easy to guess that the pairs of red/blue/white match the color signals (&lt;code&gt;RED/RED.GND&lt;/code&gt;, &lt;code&gt;BLU/BLU.GND&lt;/code&gt;, etc.). &lt;sup&gt;&lt;sub&gt;It's a bit weird that Green is represented by a white wire, I guess they didn't have green ? &lt;/sup&gt;&lt;/sub&gt;&lt;br&gt;
What about the brown wire, though?  &lt;/p&gt;
&lt;p&gt;The last piece of the puzzle is that Mac screens don't use Horizontal/Vertical Sync like VGA, instead preferring to use &lt;a href="https://en.wikipedia.org/wiki/Component_video_sync"&gt;Composite Sync&lt;/a&gt;.&lt;br&gt;
The DA-15 pinout above has pins for VGA-style &lt;code&gt;HSYNC/VSYNC&lt;/code&gt;, but those are meant for those not-at-all common cases where you'd hook up a VGA screen to your Mac with an adapter.  &lt;/p&gt;
&lt;p&gt;Therefore, there are only &lt;strong&gt;two&lt;/strong&gt; wires to hook up for the Sync signal (&lt;code&gt;CSYNC&lt;/code&gt; and &lt;code&gt;CSYNC.GND&lt;/code&gt;), instead of the four for VGA.(&lt;code&gt;HSYNC/VSYNC&lt;/code&gt; and their matching grounds)&lt;br&gt;
It seems then likely that the brown wire is the &lt;code&gt;CSYNC&lt;/code&gt; signal!  &lt;/p&gt;
&lt;h3&gt;But aren't you missing some pins from the DA-15 picture?&lt;/h3&gt;
&lt;p&gt;I am! I didn't talk about &lt;code&gt;SENSE0/1/2&lt;/code&gt; yet.&lt;br&gt;
Those pins are meant to be grounded in specific ways to tell the Mac what kind of screen we're plugging in. For example:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;The Macintosh LC requires that pin 4 (SENSE0) be connected to Ground to signal the connection of a 640 x 480 monitor. The Macintosh LC requires that pin 4 and 10 (SENSE0 and SENSE2) be connected to Ground to signal the connection of a 512 x 384 monitor (i.e., the Macintosh 12&amp;quot; RGB Display). The Macintosh LC requires that pin 10 (SENSE2) be connected to Ground to signal the connection of a VGA monitor. Pin 7 (SENSE1) is grounded in the Macintosh LC.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The way those pins are interpreted depends on the Macintosh you're plugging the screen into, but the one we're interested in never seems to change much: &lt;code&gt;Grounding SENSE0 means the monitor is 640x480.&lt;/code&gt;&lt;br&gt;
That's the resolution of the Performa Plus, so I'll be leaving the other two pins alone.  &lt;/p&gt;
&lt;h1&gt;Time to connect&lt;/h1&gt;
&lt;p&gt;I grabbed a cheap DA-15 cable off AliExpress and made an atrocious Frankencable out of it:&lt;br&gt;
&lt;img alt="end my life" src="https://tvc-16.science/images/performa/performa5.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;The connections mostly match what I wrote above, except some yellow/white cables magically appeared at the end that weren't in the video connector.&lt;br&gt;
I guessed those were probably only there for DA-15 compliance and led nowhere, so I left 'em unconnected.  &lt;/p&gt;
&lt;p&gt;I hooked up the other end of the cable to a &lt;a href="https://twitter.com/Difegue/status/1206696379483148289?s=20"&gt;freshly-salvaged&lt;/a&gt; Macintosh IIvx, powered everything on expecting to get a horribly mangled image and:  &lt;/p&gt;
&lt;p&gt;&lt;img alt="haha this is a disaste-wait what" src="https://tvc-16.science/images/performa/performa6.jpg"&gt;&lt;br&gt;
&lt;sup&gt;&lt;sub&gt;Wow this actually works &lt;/sup&gt;&lt;/sub&gt; I mean &lt;strong&gt;of course it works!&lt;/strong&gt;&lt;br&gt;
&lt;img alt="based" src="https://tvc-16.science/images/performa/izutsumi.gif"&gt;&lt;/p&gt;
&lt;h1&gt;Screen size and final observations&lt;/h1&gt;
&lt;p&gt;&lt;img alt="Macintosh IIvx" src="https://tvc-16.science/images/performa/performa7.jpg"&gt;&lt;br&gt;
The image quality is surprisingly good for a makeshift cable here -- No parasites or blurry picture.&lt;br&gt;
Although I found it odd that the image doesn't fill the screen entirely.&lt;br&gt;
Since the IIvx is a pretty old Mac(running System 7), I thought its video card might not have the horsepower to handle 640x480.  &lt;/p&gt;
&lt;p&gt;No worries! Time to upgrade a bit and try the cable out on a Power Mac G3:&lt;br&gt;
&lt;img alt="Power Mac G3" src="https://tvc-16.science/images/performa/performa8.jpg"&gt;&lt;br&gt;
This is &lt;strong&gt;slightly&lt;/strong&gt; better! But not by much.&lt;br&gt;
The resolution utility in Mac OS 9 does say the screen resolution is at 640x480, so there are a few possible reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The CRT itself is configured to display the image like this  &lt;/li&gt;
&lt;li&gt;The screen actually supports resolutions higher than 640x480 and I fucked up forcing the resolution through &lt;code&gt;SENSE0&lt;/code&gt;  &lt;/li&gt;
&lt;li&gt;Those white/yellow cables aren't there just for show pardner 🤠&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For option 1 the only way out is aligning the CRT manually.&lt;br&gt;
While I'm less afraid of being zapped by CRTs after working on the eMac, this is next level and messing with factory settings is probably a terrible idea.&lt;/p&gt;
&lt;p&gt;Option 2 can be tested on the Power Mac G3 by overriding the resolution with &lt;a href="https://www.madrau.com/SRXv3/html/SR2/indexSR2.html"&gt;SwitchRes2&lt;/a&gt;, which I'll try later.&lt;br&gt;
And if it's option 3, I probably won't even bother since the cable is already &lt;em&gt;pretty good™&lt;/em&gt; as it stands.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="Can't believe I bothered that much just for a dumb Apple sticker" src="https://tvc-16.science/images/performa/performa9.jpg"&gt;&lt;br&gt;
I thank the gods of NASA in these festive times for the gift that is duct tape. Happy 2020!  &lt;/p&gt;</content><category term="Hardware"></category><category term="apple"></category><category term="macintosh"></category><category term="mac"></category><category term="performa"></category><category term="crt"></category><category term="diy"></category><category term="retrocomputing"></category></entry><entry><title>Some notes on restoring a 2003 1GHz eMac</title><link href="https://tvc-16.science/emac-lcd-mod.html" rel="alternate"></link><published>2019-11-28T00:00:00+01:00</published><updated>2019-11-28T00:00:00+01:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2019-11-28:/emac-lcd-mod.html</id><summary type="html">&lt;p&gt;I guess this is an Apple blog now.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I recently found an old &lt;a href="https://en.wikipedia.org/wiki/EMac"&gt;eMac&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="yeah rip" src="https://tvc-16.science/images/emac/emac (1).jpg"&gt;  &lt;/p&gt;
&lt;p&gt;It was not in good shape.&lt;br&gt;
I never really dabbled in Apple hardware before this (in fact my first real hands-on with Mac OS as a whole was what, 8 months ago ?), so I quickly tried booting it and it seemed to work! Except the CRT coil was glowing red. Neat.  &lt;/p&gt;
&lt;p&gt;There's a surprising amount of documentation online about restoring eMacs -- Probably due to a mix of the failing analog boards and the low pricepoint of the machine. As a result, I tried fixing the thing. Here are a few notes.  &lt;/p&gt;
&lt;h2&gt;Getting some reading done&lt;/h2&gt;
&lt;p&gt;The essential reference for eMac modification is &lt;a href="https://web.archive.org/web/20180220163441/http://www.lbodnar.dsl.pipex.com/eServer/"&gt;Leo Bodnar's site&lt;/a&gt;, or at least the archive.org snapshots of it, as it has since then died a brutal death.  &lt;/p&gt;
&lt;p&gt;Other helpful links were &lt;a href="https://fr.ifixit.com/Device/eMac"&gt;iFixit&lt;/a&gt; for figuring out how to dismantle the beast, and the &lt;a href="http://gmcotton.com/Ham_Radio/MISC%20Manuals/Mac/emac%20service%20manual.pdf"&gt;Original Apple service manual.&lt;/a&gt; Basic stuff.  &lt;/p&gt;
&lt;p&gt;My initial reference and guide for most of the process was this &lt;a href="https://ierna.com/2006/07/02/emac-lcd-conversion/"&gt;2006 article&lt;/a&gt; on an eMac LCD conversion. Quite detailed but it misses a few details, which I'll go over here.  &lt;/p&gt;
&lt;h2&gt;Connecting the logic board to a working screen and power supply&lt;/h2&gt;
&lt;p&gt;Once the machine taken apart, I've recovered the original power and video cables that were connected to the CRT board, and modified them to connect to Molex/VGA respectively.&lt;br&gt;
The pinouts can be found on the &lt;a href="https://web.archive.org/web/20180220163441/http://www.lbodnar.dsl.pipex.com/eServer/"&gt;Leo Bodnar archive&lt;/a&gt;, alongside a more detailed breakout of the different boards present in the machine.&lt;/p&gt;
&lt;p&gt;As far as the power cable goes, it can really connect to anything that outputs both 12 and 5V -- As mentioned in the archive, the eMac actually expects 20 and 19V on a few of its pins for Firewire support, but using 12V for those works fine enough.  &lt;/p&gt;
&lt;p&gt;I did redirect the hard drive and DVD player to run off the PSU directly instead of the eMac's integrated down-converter board, in order to make sure those have the voltage they need.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="damn that's a lot of icons" src="https://tvc-16.science/images/emac/emac (2).jpg"&gt;  &lt;/p&gt;
&lt;p&gt;Well whaddaya know, this actually works. Time to drop all this good stuff into a case.  &lt;/p&gt;
&lt;h2&gt;Dropping the LCD screen&lt;/h2&gt;
&lt;p&gt;Any well-proportioned 17" LCD fits the eMac front panel, but if you're like me and blindly followed the ierna blog thinking you could just slam it in and call it a day, you'll probably be surprised.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="lcd dropped in emac front panel" src="https://tvc-16.science/images/emac/emac (3).jpg"&gt;  &lt;/p&gt;
&lt;p&gt;My screen had bigger bezels than the one he used, which required me to cut off the microphone support stand and use tape to fix it instead.&lt;br&gt;
&lt;img alt="microphone support(pic from ierna)" src="https://tvc-16.science/images/emac/micro.jpg"&gt;&lt;br&gt;
(pic from ierna, as I didn't take a picture of this part.)  &lt;/p&gt;
&lt;p&gt;This made the screen fit fine in the panel, but the CRT cowl which comes on top of it was too small as well!&lt;br&gt;
The cowl is essential to the structure of the machine, but luckily you can get away with just cutting the corners.&lt;br&gt;
&lt;img alt="damn that's a lot of icons" src="https://tvc-16.science/images/emac/emac (4).jpg"&gt;  &lt;/p&gt;
&lt;p&gt;As a tradeoff though, I didn't cut the outside of the case like ierna, so all those modifications are invisible.&lt;br&gt;
Sure, I can't turn off the screen and the OSX Menu is slightly hidden by the front panel but ehh, I'll fix it in software.  &lt;/p&gt;
&lt;h2&gt;Mounting the power supply&lt;/h2&gt;
&lt;p&gt;Mounting a full-sized ATX PSU in the original case was challenging, and is frankly overkill considering the eMac doesn't draw that much power.&lt;br&gt;
Sadly it's all I had on hand so I rolled with it.  &lt;/p&gt;
&lt;p&gt;There are mounting holes on the plate since it originally housed the analog board, but of course they don't line up whatsoever with ATX, so I had to make some custom mounting brackets to get it to stay fixed. I really hope you guys don't do this.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="screwing those bottom screws with the plastic parts blocking your paths was a real joy" src="https://tvc-16.science/images/emac/emac (5).jpg"&gt;  &lt;/p&gt;
&lt;p&gt;One last hurdle caused by the ATX supply was that it was blocking the pins of the plastic case when putting the eMac back together.&lt;br&gt;
The pins go in the two large holes on top you can see there, so I had to sand them down a bit to make them smaller.  &lt;/p&gt;
&lt;p&gt;And it's done! Well, almost.  &lt;/p&gt;
&lt;h2&gt;Display refresh rate hell&lt;/h2&gt;
&lt;p&gt;&lt;img alt="pictures taken moments before disaster" src="https://tvc-16.science/images/emac/emac (6).jpg"&gt;  &lt;/p&gt;
&lt;p&gt;So, I got 10.14 Tiger booted up, listened to some music through iTunes and it seems fine!&lt;br&gt;
Then I went to toy a bit in the settings and everything fell apart.  &lt;/p&gt;
&lt;p&gt;As mentioned by lbodnard, the eMac's screen resolutions use &lt;strong&gt;very high&lt;/strong&gt; refresh rates (up to 100Hz), which go far beyond what old TFT LCDs can handle.&lt;br&gt;
OSX seemed to know about this at first and set a 75Hz rate so my LCD was usable, but a misstep on my part in System Settings set the resolution to an unusable rate.  &lt;/p&gt;
&lt;p&gt;Therefore, my eMac is left without a screen again. 😐&lt;br&gt;
Resetting the resolution settings &lt;a href="http://hints.macworld.com/article.php?story=2001100114532165"&gt;seems rather easy&lt;/a&gt; if you have filesystem access, but I don't, so I'll probably have to take the machine apart again and plug in a newer screen that can handle the high rates.  &lt;/p&gt;
&lt;p&gt;If there's one warning to take from this, it's that you should probably enable Mac OS's built-in SSH/VNC servers so you can run the eMac headless -- Screen lockout happens easily!  &lt;/p&gt;
&lt;h2&gt;Update: Getting out of display rate hell&lt;/h2&gt;
&lt;p&gt;The solution to the above conundrum was indeed plugging in another screen. Luckily OSX wasn't stuck at some super high rate, so a more recent LCD did the trick.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="all's right with the world thanks to switchresx" src="https://tvc-16.science/images/emac/emac_end.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;I promptly installed an old version of &lt;a href="http://www.madrau.com/index.html"&gt;SwitchResX&lt;/a&gt; in order to disable all the extra resolutions the eMac adds to account for its normal CRT screen.&lt;br&gt;
This means no more surprises when starting a game that goes to fullscreen and tries to guess the best resolution out of what the machine offers.  &lt;/p&gt;
&lt;p&gt;And I guess I'm finally done with this machine! It took quite a while.&lt;br&gt;
&lt;sub&gt;&lt;sup&gt;The hard drive is starting to fail though so I'll probably need to clone it and drop a new IDE drive in this sucker...&lt;/sup&gt;&lt;/sub&gt;&lt;/p&gt;
&lt;h2&gt;Final notes&lt;/h2&gt;
&lt;p&gt;My first experience with OSX was with 10.14 Mojave a few months back -- Using 10.4 Tiger on this machine, it's pretty funny how 98% of the UI has essentially stayed the same in 13 years.&lt;br&gt;
Guess I'm too used to Windows switching everything around every couple of releases.  &lt;/p&gt;
&lt;p&gt;At least I don't have to learn how to use the Finder again!  &lt;/p&gt;</content><category term="Hardware"></category><category term="apple"></category><category term="macos"></category><category term="emac"></category><category term="lcd"></category><category term="mod"></category><category term="diy"></category></entry><entry><title>Deploying an ephemeral macOS environment through Github Actions</title><link href="https://tvc-16.science/mac-github-actions.html" rel="alternate"></link><published>2019-10-25T00:00:00+02:00</published><updated>2019-10-25T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2019-10-25:/mac-github-actions.html</id><summary type="html">&lt;p&gt;I don't think they meant this when they said "Think Different".&lt;/p&gt;</summary><content type="html">&lt;p&gt;Oh no! Someone's giving me &lt;a href="https://github.com/Difegue/LANraragi/pull/221"&gt;macOS stuff to test&lt;/a&gt; and I don't have access to any Mac whatsoever this weekend!&lt;br&gt;
&lt;em&gt;What in the doushio can I do?&lt;/em&gt;  &lt;/p&gt;
&lt;p&gt;Most solutions for getting a virtualized Mac running involve downloading the 7GB-something setup from Apple, installing it in &lt;a href="https://github.com/foxlet/macOS-Simple-KVM"&gt;some form of virtualization&lt;/a&gt; software, setting up a bunch of bullshit magical variables to masquerade your VM as an iMac and then waiting through the entire installation process.  &lt;/p&gt;
&lt;p&gt;Past that, you'll also need to add an Apple Account, download Xcode and its glorious 6 GBs of awful IDE design, etc etc &lt;em&gt;jesus this is going to eat my entire weekend&lt;/em&gt;&lt;br&gt;
&lt;sub&gt;&lt;sup&gt;you can technically only download the xcode command line tools and they're like 200MBs but I'm trying to overdramatize a bit here&lt;/sup&gt;&lt;/sub&gt;  &lt;/p&gt;
&lt;p&gt;The other solution is of course to rent a Mac VPS, but as this is a super niche market, prices are &lt;a href="https://www.macstadium.com/pricing"&gt;atrociously expensive&lt;/a&gt; for what amounts to me running &lt;code&gt;homebrew&lt;/code&gt; for about 30 minutes.  &lt;/p&gt;
&lt;h2&gt;Jumping into Actions&lt;/h2&gt;
&lt;p&gt;Since GitHub Actions introduced matrix builds recently, there very conveniently are &lt;a href="https://help.github.com/en/github/automating-your-workflow-with-github-actions/software-in-virtual-environments-for-github-actions#macos-1014"&gt;macOS runners&lt;/a&gt; for me to run code on. And they come with a &lt;strong&gt;lot&lt;/strong&gt; of devtools preinstalled, making for a rather comfy experience.  &lt;/p&gt;
&lt;p&gt;At this point I could just write an Actions script running my &lt;code&gt;homebrew&lt;/code&gt; code and be done with it, but I thought I could have something a bit more flexible.&lt;br&gt;
Hot off the trail of &lt;a href="./lcl-pebble.html"&gt;my last Actions venture&lt;/a&gt;, I thought about using the &lt;a href="https://github.com/marketplace/actions/debugging-with-tmate"&gt;tmate&lt;/a&gt; action to spawn a SSH session on the Mac runner for me. It'd basically give me an &lt;strong&gt;on-demand, pre-provisioned&lt;/strong&gt; macOS command line whenever I'd want!  &lt;/p&gt;
&lt;p&gt;&lt;img alt="ANYTHING" src="https://tvc-16.science/images/anything.jpg"&gt;&lt;br&gt;
&lt;sub&gt;&lt;sup&gt;For up to 6 hours of execution time but honestly that's already way too much &lt;/sup&gt;&lt;/sub&gt;  &lt;/p&gt;
&lt;h2&gt;Writing the Action&lt;/h2&gt;
&lt;p&gt;Using the tmate action as-is would basically make this setup dead simple, but it &lt;a href="https://github.com/mxschmitt/action-tmate/issues/3"&gt;doesn't work on Mac runners as-is.&lt;/a&gt; Darn.  &lt;/p&gt;
&lt;p&gt;Luckily the entire action is really easy to replicate in bash, so I just wrote my own:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;Summon Steve Jobs&lt;/span&gt;

&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;

&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;macOS-10.14&lt;/span&gt;

    &lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;actions/checkout@v1&lt;/span&gt;
    &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;Install tmate&lt;/span&gt;
      &lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p p-Indicator"&gt;|&lt;/span&gt;
        &lt;span class="no"&gt;brew install tmate openssh screenfetch&lt;/span&gt;
        &lt;span class="no"&gt;echo -e &amp;#39;y\n&amp;#39;|ssh-keygen -q -t rsa -N &amp;quot;&amp;quot; -f ~/.ssh/id_rsa&lt;/span&gt;
        &lt;span class="no"&gt;tmate -S /tmp/tmate.sock new-session -d&lt;/span&gt;
        &lt;span class="no"&gt;tmate -S /tmp/tmate.sock wait tmate-ready&lt;/span&gt;
    &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;It just works&lt;/span&gt;
      &lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p p-Indicator"&gt;|&lt;/span&gt;
        &lt;span class="no"&gt;screenfetch&lt;/span&gt;
        &lt;span class="no"&gt;SSH=&amp;quot;$(tmate -S /tmp/tmate.sock display -p &amp;#39;#{tmate_ssh}&amp;#39;)&amp;quot;&lt;/span&gt;
        &lt;span class="no"&gt;WEB=&amp;quot;$(tmate -S /tmp/tmate.sock display -p &amp;#39;#{tmate_web}&amp;#39;)&amp;quot;&lt;/span&gt;
        &lt;span class="no"&gt;echo &amp;quot;SSH: ${SSH}&amp;quot;&lt;/span&gt;
        &lt;span class="no"&gt;echo &amp;quot;Web: ${WEB}&amp;quot;&lt;/span&gt;
        &lt;span class="no"&gt;echo &amp;quot;You can now connect to the tmate session.&amp;quot;&lt;/span&gt;
        &lt;span class="no"&gt;while true; do &lt;/span&gt;
          &lt;span class="no"&gt;# loop infinitely&lt;/span&gt;
          &lt;span class="no"&gt;sleep 10&lt;/span&gt;
        &lt;span class="no"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The only issue here is that quitting the session doesn't automatically end the run like the original action does, but it's good enough.™  &lt;/p&gt;
&lt;p&gt;&lt;img alt="rebel rebel how could they know" src="https://tvc-16.science/images/macactions.png"&gt;&lt;/p&gt;
&lt;h2&gt;Spare Thoughts&lt;/h2&gt;
&lt;p&gt;I was curious about whether you could get VNC output on this, but while the command to enable VNC works, the runners don't expose anything for you to connect to.&lt;/p&gt;
&lt;p&gt;Since I wanked this to debug the upcoming Homebrew support for LRR, it's totally legal and not breaking the &lt;a href="https://help.github.com/en/github/automating-your-workflow-with-github-actions/about-github-actions#usage-limits"&gt;Actions TOS&lt;/a&gt; in any way. 😇  &lt;/p&gt;</content><category term="Cool Tricks"></category><category term="github actions"></category><category term="apple"></category><category term="macos"></category><category term="vm"></category><category term="homebrew"></category></entry><entry><title>LANraragi Hacktoberfest Update and GitHub Sponsors</title><link href="https://tvc-16.science/hacktoberfest-lrr.html" rel="alternate"></link><published>2019-10-12T00:00:00+02:00</published><updated>2019-10-12T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2019-10-12:/hacktoberfest-lrr.html</id><summary type="html">&lt;p&gt;Begging in style.&lt;/p&gt;</summary><content type="html">&lt;h2&gt;Hacktoberfest&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://hacktoberfest.digitalocean.com/"&gt;Hacktoberfest&lt;/a&gt; is well underway!&lt;br&gt;
I personally got my four PRs in and I suggest looking at &lt;a href="https://hacktoberfestswaglist.com/"&gt;this handy list&lt;/a&gt; to maximize the amount of free shit™ you can get from companies desperate for contributions.  &lt;/p&gt;
&lt;p&gt;It's also about time I made my yearly sticker order for Christmas, so I thought I'd use the opportunity to provide a bonus incentive.&lt;br&gt;
&lt;img alt="three billion hours in inkscape" src="https://tvc-16.science/images/stickers.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;I'll ship out this &lt;code&gt;very cool&lt;/code&gt; sticker set to anyone who makes a (not-crap) PR to &lt;a href="https://github.com/Difegue/LANraragi"&gt;the LRR repo.&lt;/a&gt;&lt;br&gt;
(Hacktoberfest PRs from the start of the month count too so hit me up the two of you who already contributed)  &lt;/p&gt;
&lt;p&gt;I have some issues available under the &lt;a href="https://github.com/Difegue/LANraragi/issues?q=is%3Aissue+is%3Aopen+label%3Ahacktoberfest"&gt;Hacktoberfest&lt;/a&gt; tag for would-be volunteers.&lt;br&gt;
There are quite a few of them that &lt;a href="https://github.com/Difegue/LANraragi/issues/196"&gt;don't require&lt;/a&gt; &lt;a href="https://github.com/Difegue/LANraragi/issues/195"&gt;writing a single line&lt;/a&gt; &lt;a href="https://github.com/Difegue/LANraragi/issues/189"&gt;of server code&lt;/a&gt;, so I better not hear the excuse that Perl is a hindrance this time 'round.  &lt;/p&gt;
&lt;h2&gt;GitHub Sponsors&lt;/h2&gt;
&lt;p&gt;If you're not good at programming but really &lt;strong&gt;really&lt;/strong&gt; want this dumb sticker pack (god bless your twisted soul), I'm also sending one to anyone who pledges $5+ on my newly-minted GitHub Sponsors page &lt;a href="https://github.com/users/Difegue/sponsorship"&gt;here.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;GitHub matches whatever you put as your pledge, so there's never been a better time to help me get filthy rich through open source.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="BUY IT" src="https://tvc-16.science/images/buyit.png"&gt;  &lt;/p&gt;
&lt;p&gt;(If you don't want to steal Microsoft's money, &lt;a href="https://ko-fi.com/lanraragi"&gt;Ko-Fi&lt;/a&gt; is still available too.)&lt;/p&gt;
&lt;h2&gt;Claiming your stickers&lt;/h2&gt;
&lt;p&gt;If you did one of the above, you can go to the &lt;a href="https://forms.office.com/Pages/ResponsePage.aspx?id=DQSIkWdsW0yxEjajBLZtrQAAAAAAAAAAAAN__osxt25URTdTUTVBVFRCTjlYWFJLMlEzRTJPUEhEVy4u"&gt;following form&lt;/a&gt; to give me a mail address to ship the stuff to.&lt;br&gt;
Happy Hacking!&lt;/p&gt;</content><category term="LANraragi"></category><category term="hacktoberfest"></category><category term="swag"></category><category term="lanraragi"></category><category term="github sponsors"></category><category term="lods of emone"></category><category term="cheap stickers"></category></entry><entry><title>Webscraping my bank with GitHub Actions and displaying it on a Pebble SmartWatch</title><link href="https://tvc-16.science/lcl-pebble.html" rel="alternate"></link><published>2019-08-31T00:00:00+02:00</published><updated>2019-08-31T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2019-08-31:/lcl-pebble.html</id><summary type="html">&lt;p&gt;Giving my bank account to GitHub so they can tell my watch how much money is in it? Sure!&lt;/p&gt;</summary><content type="html">&lt;p&gt;I had some &lt;a href="https://github.com/Difegue/Chaotic-Realm/tree/master/lcl-hack"&gt;old code&lt;/a&gt; laying around to scrape the &lt;a href="https://www.lcl.fr/"&gt;Crédit Lyonnais&lt;/a&gt; account pages and convert my account balance/details to JSON.  &lt;/p&gt;
&lt;p&gt;I used this for a few years to quickly have my account balance on my phone, then gave up on it once the bank's mobile app caught up.&lt;br&gt;
Recently though, &lt;a href="http://rebble.io/2019/07/24/its-timeline-time.html"&gt;Rebble revived Timeline&lt;/a&gt; and I do love my Pebble Watches more than my phone, so I thought I'd put the ol' lcl.js scraper back on the saddle.  &lt;/p&gt;
&lt;h1&gt;Wait! What the fuck even &lt;strong&gt;is&lt;/strong&gt; Timeline ?&lt;/h1&gt;
&lt;p&gt;&lt;img alt="timeline" src="images/timeline.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;Pebble smartwatches feature a scrollable timeline of events the user can reach/read in one button press. The events that end up in Timeline can come from the phone's calendar as well as third-party applications.  &lt;/p&gt;
&lt;p&gt;If you want more details, I recommend looking up the &lt;a href="https://pebble-help-legacy.rebble.io/help.getpebble.com/customer/en/portal/articles/2541544-timelinee140.html?b_id=12263"&gt;Pebble documentation archive&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;What interests us here is the third-party application part, brought back from the ashes by &lt;a href="http://rebble.io"&gt;Rebble&lt;/a&gt;.&lt;br&gt;
By using a token generated by the watch, we can send data to the timeline through a webhook. And combined with the LCL bank account scraper, the results are pretty easy to figure out:  &lt;/p&gt;
&lt;p&gt;&lt;img alt="fuck this" src="images/dosh.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Moneybills, right on the wrist!&lt;/p&gt;
&lt;h1&gt;Unearthing the LCL scraper and making it work again&lt;/h1&gt;
&lt;p&gt;The bad thing about online banking is that the web services that come with it are usually unpractical and severely outdated. The &lt;strong&gt;good&lt;/strong&gt; thing in that though, is that when you're scraping a bank website for data, your implementation will last years without bugs caused by redesigns. ✌  &lt;/p&gt;
&lt;p&gt;When I pulled out my old scraping code, the only thing I had to rework was the login sequence. (Alongside a quick port to &lt;a href="https://github.com/GoogleChrome/puppeteer"&gt;Puppeteer&lt;/a&gt; since PhantomJS is basically dead now)&lt;br&gt;
I used the mobile version of the website to login back in 2015, but alas it is no more.&lt;br&gt;
Which means I'm stuck dealing with this abomination coming straight from every French banking website:  &lt;/p&gt;
&lt;p&gt;&lt;img alt="fuck this" src="images/lcl_keyboard.png"&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;"click on the jpegs to enter your keycode"&lt;/code&gt; login page.&lt;br&gt;
I hate this thing for a myriad of reasons, and the implementation is actually quite solid here:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;You can't just enter the keycode straight into the HTML input on the right: Clicks on the keyboard enter a &lt;em&gt;scrambled&lt;/em&gt; number into the input, which is then decoded later down the login sequence by some JavaScript bullshit dark magic.  &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The order of the numbers is &lt;em&gt;randomized&lt;/em&gt; on each load of the login page, so you can't just record clicks at a specific position either.  &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The keyboard is rendered as a single image, with the areas you have to click defined by a HTML &lt;a href="https://www.w3schools.com/tags/tag_map.asp"&gt;imagemap&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since I'm already down the rabbit hole of webscraping/JavaScript automation, the solution I used here is a suitable waste of processing power: I load up the &lt;a href="https://github.com/tesseract-ocr"&gt;Tesseract engine&lt;/a&gt; and &lt;strong&gt;scan the keyboard image&lt;/strong&gt; to find out the order of the numbers in the page.  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;keyboard.png&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Just OCR the damn keyboard&lt;/span&gt;
&lt;span class="nx"&gt;ocrResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Tesseract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recognize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;keyboard.png&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Get something like 34980 15267&lt;/span&gt;
&lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ocrResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/ /&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/[^\d]/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 

&lt;span class="c1"&gt;// Click on the areas matching the numbers we received, according to the code in env&lt;/span&gt;
&lt;span class="c1"&gt;// DOM IDs are idImageClavier_01-10, going by column:&lt;/span&gt;
&lt;span class="c1"&gt;// 1 3 5 7 9&lt;/span&gt;
&lt;span class="c1"&gt;// 2 4 6 8 10&lt;/span&gt;
&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LCL_CODE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Rearrange the keyboard map to match the DOM IDs:&lt;/span&gt;
&lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;charAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;charAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;charAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;[...]&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;charAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;charAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Go through code character by character, find its location on the map and hit the matching area&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kr"&gt;char&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;charAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;posInMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;char&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;posInMap&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;//DOM IDs start at 1&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;posInMap&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;posInMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;0&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;posInMap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;//console.log(&amp;quot;Clicking on &amp;quot;+ char +&amp;quot; at #idImageClavier_&amp;quot;+posInMap);&lt;/span&gt;
&lt;span class="nx"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="s1"&gt;&amp;#39;#idImageClavier_&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;posInMap&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I hate this. But one click on the login button later and &lt;em&gt;hacker voice I'm in&lt;/em&gt;&lt;br&gt;
The rest of the code consists on mangling the bank's internal API to get readable JSON.  &lt;/p&gt;
&lt;h1&gt;Pushing the account balance to Timeline&lt;/h1&gt;
&lt;p&gt;Once I have recovered the data from the generated JSON, pushing it to the watch is much easier:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;curl -X PUT https://timeline-api.rebble.io/v1/user/pins/cool-pin-id &lt;/span&gt;
&lt;span class="err"&gt;    --header &amp;quot;Content-Type: application/json&amp;quot; &lt;/span&gt;
&lt;span class="err"&gt;    --header &amp;quot;X-User-Token: watch_token&amp;quot; &lt;/span&gt;
&lt;span class="err"&gt;    -d &amp;#39;{ &lt;/span&gt;
&lt;span class="err"&gt;        &amp;quot;id&amp;quot;: &amp;quot;cool-pin-id&amp;quot;, &lt;/span&gt;
&lt;span class="err"&gt;        &amp;quot;time&amp;quot;: &amp;quot;2019-08-30T22:43:56Z&amp;quot;, &lt;/span&gt;
&lt;span class="err"&gt;        &amp;quot;layout&amp;quot;: { &lt;/span&gt;
&lt;span class="err"&gt;                  &amp;quot;type&amp;quot;: &amp;quot;genericPin&amp;quot;, &lt;/span&gt;
&lt;span class="err"&gt;                  &amp;quot;title&amp;quot;: &amp;quot;Account name&amp;quot;, &lt;/span&gt;
&lt;span class="err"&gt;                  &amp;quot;body&amp;quot;: &amp;quot;Account code&amp;quot;, &lt;/span&gt;
&lt;span class="err"&gt;                  &amp;quot;subtitle&amp;quot;: &amp;quot;+ 9 999,99 €&amp;quot;, &lt;/span&gt;
&lt;span class="err"&gt;                  &amp;quot;tinyIcon&amp;quot;: &amp;quot;system://images/STOCKS_EVENT&amp;quot; &lt;/span&gt;
&lt;span class="err"&gt;                  } &lt;/span&gt;
&lt;span class="err"&gt;        }&amp;#39; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;a href="https://developer.rebble.io/developer.pebble.com/guides/pebble-timeline/pin-structure/index.html"&gt;Pebble developer documentation&lt;/a&gt; is a godsend here!&lt;/p&gt;
&lt;p&gt;&lt;code&gt;cool-pin-id&lt;/code&gt; must be a different ID for each new Timeline pin pushed. I just get my OS to generate UUIDs for that but you might want to do it another way.  &lt;/p&gt;
&lt;p&gt;The watch token can be generated using this &lt;a href="https://github.com/Willow-Systems/pebble-generate-token"&gt;handy Pebble app.&lt;/a&gt;  &lt;/p&gt;
&lt;h1&gt;Jamming this whole thing in Github Actions&lt;/h1&gt;
&lt;p&gt;While this is all cool and good, I was wary of running this script on my server regularly.&lt;br&gt;
I mean, it &lt;strong&gt;does&lt;/strong&gt; use your bank credentials and you can't be paranoid enough.  &lt;/p&gt;
&lt;p&gt;Back in 2015, I was running it on a computer that didn't accept incoming connections; Said computer would just upload the JSON generated by the script every few hours to the server.&lt;br&gt;
But this is 2019 and caring about security is &lt;em&gt;so passé&lt;/em&gt;, so I'm just gonna give my bank details to GitHub Actions.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="hot dog" src="images/dangerzone.png"&gt;  &lt;/p&gt;
&lt;p&gt;&lt;em&gt;Yeehaw!&lt;/em&gt; The Actions script itself isn't anything to write about, but hey, it's on &lt;a href="https://twitter.com/github/status/1159511691480260608"&gt;Actions v2&lt;/a&gt;!  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;Get bank details and push to Pebble&lt;/span&gt;

&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nt"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;0 9 * * *&lt;/span&gt;

&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;actions/checkout@v1&lt;/span&gt;
    &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;Use Node.js&lt;/span&gt;
      &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;actions/setup-node@v1&lt;/span&gt;
      &lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nt"&gt;node-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;12.x&lt;/span&gt;
    &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;NPM install&lt;/span&gt;
      &lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p p-Indicator"&gt;|&lt;/span&gt;
        &lt;span class="no"&gt;npm install&lt;/span&gt;
    &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;Epic lcl hack&lt;/span&gt;
      &lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nt"&gt;LCL_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;${{ secrets.LCL_ID }}&lt;/span&gt;
        &lt;span class="nt"&gt;LCL_CODE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;${{ secrets.LCL_CODE }}&lt;/span&gt;
      &lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p p-Indicator"&gt;|&lt;/span&gt;
        &lt;span class="no"&gt;node lclscraper.js  &lt;/span&gt;
    &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;Push pebble timeline pin&lt;/span&gt;
      &lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nt"&gt;TIMELINE_TOKEN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;${{ secrets.TIMELINE_TOKEN }}&lt;/span&gt;
      &lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p p-Indicator"&gt;|&lt;/span&gt; 
        &lt;span class="no"&gt;LAB1=$(cat lcl.json | jq -r &amp;#39;.[0].label&amp;#39;)&lt;/span&gt;
        &lt;span class="no"&gt;TOT1=$(cat lcl.json | jq -r &amp;#39;.[0].total&amp;#39;)&lt;/span&gt;
        &lt;span class="no"&gt;CODE1=$(cat lcl.json | jq -r &amp;#39;.[0].code&amp;#39;)&lt;/span&gt;
        &lt;span class="no"&gt;DATE=$(date +%s | jq &amp;#39;todate&amp;#39;)&lt;/span&gt;
        &lt;span class="no"&gt;UUID=$(cat /proc/sys/kernel/random/uuid)&lt;/span&gt;
        &lt;span class="no"&gt;TIMELINE_JSON=&amp;quot;{\&amp;quot;id\&amp;quot;: \&amp;quot;$UUID\&amp;quot;,\&amp;quot;time\&amp;quot;: $DATE,\&amp;quot;layout\&amp;quot;: {\&amp;quot;type\&amp;quot;: \&amp;quot;genericPin\&amp;quot;,\&amp;quot;title\&amp;quot;: \&amp;quot;$LAB1\&amp;quot;,\&amp;quot;body\&amp;quot;: \&amp;quot;$CODE1\&amp;quot;,\&amp;quot;subtitle\&amp;quot;: \&amp;quot;$TOT1 €\&amp;quot;,\&amp;quot;tinyIcon\&amp;quot;: \&amp;quot;system://images/STOCKS_EVENT\&amp;quot;}}&amp;quot;&lt;/span&gt;
        &lt;span class="no"&gt;echo $TIMELINE_JSON&lt;/span&gt;
        &lt;span class="no"&gt;curl -X PUT https://timeline-api.rebble.io/v1/user/pins/$UUID --header &amp;quot;Content-Type: application/json&amp;quot; --header &amp;quot;X-User-Token: $TIMELINE_TOKEN&amp;quot; -d &amp;quot;$TIMELINE_JSON&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The job installs the required node.js dependencies (Tesseract and Puppeteer), runs the scraper, and pushes a timeline JSON to Rebble.&lt;br&gt;
Pretty easy, isn't it?&lt;/p&gt;
&lt;h1&gt;Closing thoughts&lt;/h1&gt;
&lt;p&gt;The inspiration and info for this came from &lt;a href="https://willow.systems/integrate-pebble-with-ifttt-once-again/"&gt;Will0's blog&lt;/a&gt;, although I couldn't use his IFTTT proxy as-is due to errors with non-ASCII characters. 😅  &lt;/p&gt;
&lt;p&gt;I used &lt;a href="https://github.com/marketplace/actions/debugging-with-tmate"&gt;tmate&lt;/a&gt; to debug on the GitHub Actions side this time around, and it was quite convenient! Would be better if the web terminal supported pasting, however.  &lt;/p&gt;
&lt;p&gt;&lt;a href="https://ec.europa.eu/info/law/payment-services-psd-2-directive-eu-2015-2366_en"&gt;Payment Services Directive 2&lt;/a&gt; will start being enforced in a few days: It forces European banks to add generic APIs for bank aggregators.&lt;br&gt;
Sadly that doesn't seem to mean proper APIs being made available for consumers as well. 🙃  &lt;/p&gt;
&lt;p&gt;It also mandates strong authentication, which hopefully means those stupid image keyboards will go away and be replaced by proper 2FA.&lt;/p&gt;
&lt;p&gt;I guess now that I wrote this article my bank will add a captcha or some shit to the login window.  &lt;/p&gt;</content><category term="Cool Tricks"></category><category term="github actions"></category><category term="puppeteer"></category><category term="tesseract"></category><category term="pebble"></category><category term="rebble"></category><category term="timeline"></category><category term="epic bank hacks"></category></entry><entry><title>LANraragi User Survey Results</title><link href="https://tvc-16.science/lrr-survey.html" rel="alternate"></link><published>2019-08-02T00:00:00+02:00</published><updated>2019-08-02T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2019-08-02:/lrr-survey.html</id><summary type="html">&lt;p&gt;I sure got a spike of new users from the death of Sadpanda.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I opened a small user survey a few months ago to figure out what kind of features LANraragi users would want next.&lt;br&gt;
There's no "&lt;em&gt;telemetry but it's ok because you can turn it off and then everyone turns it off&lt;/em&gt;" in the software, so it was interesting to figure out how many people use my ol' manager and what they get out of it.  &lt;/p&gt;
&lt;p&gt;I planned to release the results alongside 0.6.0 but &lt;a href="./doujinsoft-2.html"&gt;got sidetracked&lt;/a&gt; and other stuff happened and uhhh we've been on beta 2 for like two months 🙃  &lt;/p&gt;
&lt;p&gt;So without further ado, the results! And my &lt;strong&gt;hot takes and opinions&lt;/strong&gt; on some of the most frequent feature requests.  &lt;/p&gt;
&lt;h1&gt;Total users and reasons to not install the software&lt;/h1&gt;
&lt;p&gt;&lt;img alt="users" src="images/lrr-survey/users.png"&gt;&lt;br&gt;
I still thought I was being kinda niche, so it's super unexpected to see the number of concurrent users reach 70+.&lt;br&gt;
Github ⭐ count is at 130-something, which draws me ever closer to 4th place &lt;a href="https://github.com/topics/mojolicious"&gt;in the mojolicious&lt;/a&gt; list for e-bragging rights.  &lt;/p&gt;
&lt;p&gt;Users that tried and didn't stick with LANraragi did so for the reasons I expected:&lt;br&gt;
&lt;code&gt;Ease of installation, and the fact it's not a desktop client.&lt;/code&gt;&lt;br&gt;
The survey was made before I introduced the &lt;a href="./karen-1.html"&gt;new Windows installer&lt;/a&gt;, which I hope alleviates the pain of installing the server for Windows users.  &lt;/p&gt;
&lt;p&gt;As for the Desktop client, I could go the &lt;a href="https://github.com/happypandax/desktop"&gt;HPX route&lt;/a&gt; and cram my front-end in Electron or something, but I hardly see the capital gain from doing that.&lt;br&gt;
In my opinion, a Desktop app should have the matching UX (right-click context menus, higher information density), and add features that aren't possible within a browser's sandbox. (External reader support, for instance.)&lt;br&gt;
&lt;sup&gt;Also the current JavaScript code powering the front-end is a &lt;em&gt;mess&lt;/em&gt; and I'd rather not twist it further to make it fit into a Desktop app 😶 &lt;/sup&gt;  &lt;/p&gt;
&lt;p&gt;I toyed with building a proof-of-concept client on top of the current &lt;a href="https://sugoi.gitbook.io/lanraragi/extending-lanraragi/client-api"&gt;API&lt;/a&gt; using &lt;a href="https://github.com/AvaloniaUI/Avalonia"&gt;Avalonia&lt;/a&gt;, but kinda stopped due to a lack of time. It's certainly possible, though.  &lt;/p&gt;
&lt;p&gt;Among the "other" reasons people had, not being able to easily import metadata from the OG happypanda(not X) came up a few times.&lt;br&gt;
HP's original database is SQLite if I recall, it'd be possible to write a plugin that uses &lt;a href="https://metacpan.org/pod/DBD::SQLite"&gt;a Perl SQLite client&lt;/a&gt; to pull the metadata from it on a file-to-file basis. Not trivial, however. HPX can consume the original HP's database as-is, so it's probably a better fit here.  &lt;/p&gt;
&lt;h1&gt;Platforms&lt;/h1&gt;
&lt;p&gt;&lt;img alt="platforms" src="images/lrr-survey/platforms.png"&gt; &lt;br&gt;
Zero surprise here, the software is built with Linux+Docker in mind. There's been an uptick in Windows+WSL usage with the fall of the panda as well.  &lt;/p&gt;
&lt;p&gt;The "other" platforms are fun stuff like QNAP QTS NASes, ASUSTOR, Ploxmox, etc.&lt;br&gt;
I'd like to apologize to Raspberry Pi users for not providing an ARM container image for the time being.&lt;br&gt;
Hopefully as &lt;a href="https://engineering.docker.com/2019/04/multi-arch-images/"&gt;buildx&lt;/a&gt; becomes a thing, an easy way to build ARM containers on Github Actions will surface.  &lt;/p&gt;
&lt;h1&gt;Storage&lt;/h1&gt;
&lt;p&gt;&lt;img alt="storage" src="images/lrr-survey/storage.png"&gt;   &lt;/p&gt;
&lt;p&gt;I admittedly was expecting more NAS users as that's the primary use case for LRR in my opinion, but that is fine too.&lt;br&gt;
Thumbnail view is near-ubiquituous, so I'll probably make it the default view in a later release.  &lt;/p&gt;
&lt;h1&gt;Feature Requests&lt;/h1&gt;
&lt;p&gt;&lt;img alt="features" src="images/lrr-survey/features.png"&gt;  &lt;/p&gt;
&lt;p&gt;&lt;em&gt;oh boy here we go&lt;/em&gt;   &lt;/p&gt;
&lt;p&gt;Categories/Archive Sets is the clear winner here, which is great as I'll be able to ignore cries for image folders for a while longer. ✌&lt;br&gt;
&lt;sup&gt;It's a pain I really don't want to do it reeeeeee &lt;/sup&gt;&lt;br&gt;
This feature is pretty generic and allows people to shape their collection however they want (chapters of a tank, favorites, etc.)&lt;/p&gt;
&lt;p&gt;Regex searching and Tag suggestions are both pretty high up here, and will probably be done when I revamp the current index to use server-side processing instead of loading a big clunky JSON cache. I got a request for adding search to the API too so it all fits!  &lt;/p&gt;
&lt;p&gt;The "Other" category was full of suggestions as expected; I'll be listing the biggest ones below:&lt;/p&gt;
&lt;h3&gt;👉 Mobile Client&lt;/h3&gt;
&lt;p&gt;That's easy, that one &lt;a href="https://github.com/Utazukin/Ichaival"&gt;already exists.&lt;/a&gt; &lt;a href="http://youtube.com/watch?v=39cZap0XcH8"&gt;Next!&lt;/a&gt;  &lt;/p&gt;
&lt;h3&gt;👉 Sorting archives by Date Added&lt;/h3&gt;
&lt;p&gt;This is a feature in Ichaival that isn't actually in the main client.&lt;br&gt;
I plan to modify the current list view to allow customizing the headers based on tag namespaces. So for example, you could replace the "Artist" column by "date_added", and sort that way.&lt;/p&gt;
&lt;h3&gt;👉 Show reading progress and total page count&lt;/h3&gt;
&lt;p&gt;This got a &lt;strong&gt;ton&lt;/strong&gt; of requests! It'll probably show up alongside the New! icon. Not too hard to add.  &lt;/p&gt;
&lt;h3&gt;👉 Add a Favorites system&lt;/h3&gt;
&lt;p&gt;For all the sadpanda expatriates out there. Categories will probably fit that need for most people.  &lt;/p&gt;
&lt;p&gt;&lt;a href="https://sugoi.gitbook.io/lanraragi/advanced-usage/favorite-tags"&gt;Favorite tags&lt;/a&gt; are already a thing and can probably replace Favorites if all you need is a one-click shortcut to your favorite artist or fetish. &lt;/p&gt;
&lt;h3&gt;👉 Add a Ratings system&lt;/h3&gt;
&lt;p&gt;I always felt like rating systems were bloat, so I won't implement this explicitly.&lt;br&gt;
Nothing's stopping you from making a &lt;code&gt;rating:⭐⭐⭐&lt;/code&gt; tag, though. It might even look pretty cool once I implement customizing the list view as explained above.  &lt;/p&gt;
&lt;h3&gt;👉 External viewer support&lt;/h3&gt;
&lt;p&gt;This request comes up every now and then; Sadly I can't do anything about it without a Desktop client first.&lt;br&gt;
HPX offers this option, but it just opens up the viewer program on the server. I still keep to my mantra of LRR being designed for NAS and storage servers first, so I won't implement viewers that way.&lt;/p&gt;
&lt;h3&gt;👉 Downloaders&lt;/h3&gt;
&lt;p&gt;This comes up very often as well! And I don't want to do it! (╯°□°）╯︵ ┻━┻&lt;br&gt;
Trying to have one program do everything by itself gets you something like Hydrus, which is incredibly impressive but also boasts a complex codebase as a result. This makes maintenance exponentially difficult.  &lt;/p&gt;
&lt;p&gt;Adding downloader support is a huge time investment, and would probably yield a result inferior to what dedicated downloader programs can currently do. I'd rather spend time making sure what's already in place is fast and functional.&lt;/p&gt;
&lt;h3&gt;👉 Support for PDF archives&lt;/h3&gt;
&lt;p&gt;This is actually not that hard! If we're considering the PDFs only have images and no text of any kind.&lt;br&gt;
Maybe someday.&lt;/p&gt;
&lt;h3&gt;👉 Duplicate detection&lt;/h3&gt;
&lt;p&gt;Currently, exact duplicate files are detected and marked as such. I don't plan to go further for the time being.&lt;/p&gt;
&lt;h3&gt;👉 Auto-tag support for esoteric filenames from esoteric programs&lt;/h3&gt;
&lt;p&gt;I've had some requests for being able to get metadata from files named &lt;code&gt;[[EBA]_Dutch_Wife_Ooyasan__Landlady_Dutch_Wife_(Action_Pizazz_HB_2013-10)_[English]_TV]&lt;/code&gt;, or archives downloaded using software like https://github.com/RicterZ/nhentai and https://github.com/seven332/EhViewer.&lt;/p&gt;
&lt;p&gt;LRR tries to infer tags from archive filenames when they follow the E-H naming standard.&lt;br&gt;
Past that, support for other naming standards, or metadata embedded in archives falls under the plugin system.  &lt;/p&gt;
&lt;h3&gt;👉 HD Thumbnails&lt;/h3&gt;
&lt;p&gt;This is coming from one of the only macOS users, with their fancy schmanzy retina screens. Makes sense.&lt;br&gt;
Currently thumbnails are indeed a bit too small for hi-res displays. I'm considering just doubling their resolution as-is.&lt;br&gt;
I don't think this will impact storage too much! Otherwise it'd probably have to be a togglable option.&lt;/p&gt;
&lt;h3&gt;👉 Rolling automatic backups&lt;/h3&gt;
&lt;p&gt;I got an issue in about adding an API for making/restoring backups, so I'll probably do that instead so people can automate their own backup strategy.&lt;br&gt;
It does remind me I still haven't implemented log rotation, though.&lt;/p&gt;
&lt;h3&gt;👉 Easier way to update the WSL/Karen installation&lt;/h3&gt;
&lt;p&gt;I initially planned to keep the bootloader/Karen installation static, and allow sideloading new LRR releases as they come(with an update button and everything). That was technically a bit harder so I gave up on it.  &lt;/p&gt;
&lt;p&gt;PowerShell scripts for installing/uninstalling aren't convenient, and ideally I'd like to move to a proper Windows installer package further down the line.&lt;br&gt;
Making .msis is a pain in the ass though, so don't expect that soon. ¯_(ツ)_/¯&lt;/p&gt;
&lt;h3&gt;👉 Miscellaneous reader UX woes&lt;/h3&gt;
&lt;p&gt;Everyone has their own favorite way to read, and as such it's really hard to make a one-size fits all reader.&lt;br&gt;
(Which is why desktop client + external reader would certainly alleviate that pain.)  &lt;/p&gt;
&lt;p&gt;Here are most of the remarks I received. The &lt;a href="https://github.com/Difegue/LANraragi/issues/114"&gt;UX improvements&lt;/a&gt; issue also keeps growing with everyone adding in their own remarks, which makes this all rather hard to track.  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Automatic slideshow mode with configurable timer  &lt;/li&gt;
&lt;li&gt;"Long strip" view from Mangadex  &lt;/li&gt;
&lt;li&gt;Random next/previous page  &lt;/li&gt;
&lt;li&gt;Easier way to copy the current image from the reader  &lt;/li&gt;
&lt;li&gt;Display archive tags in reader  &lt;/li&gt;
&lt;li&gt;Proper full-size image link for two-page mode  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I'll tackle some of those every now and then, probably.&lt;br&gt;
The reader itself is in pretty good shape as far as JavaScript goes (unlike the archive index), so it's easy to work on.  &lt;/p&gt;
&lt;h3&gt;👉 Provide a better discussion place for support and plugin sharing&lt;/h3&gt;
&lt;p&gt;This is interesting so I'm keeping it for last: It's true that discussion is a bit hard to track in the non-specific management threads.  &lt;/p&gt;
&lt;p&gt;There are a number of options available, like 8chan, gitter, discord(ew), IRC... I'm not sure what would be best so if you have any ideas it'd be cool to drop a comment below.  &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;EDIT&lt;/strong&gt;: I went ahead and wanked out a Discord server you can find &lt;a href="https://discord.gg/aRQxtbg"&gt;here.&lt;/a&gt; Please enjoy!  &lt;/p&gt;</content><category term="LANraragi"></category><category term="lanraragi"></category><category term="survey"></category><category term="rip sadpanda"></category><category term="philosophical software architecture ramblings"></category></entry><entry><title>More Wii Mail Madness</title><link href="https://tvc-16.science/more-wii-mail.html" rel="alternate"></link><published>2019-07-27T00:00:00+02:00</published><updated>2019-07-27T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2019-07-27:/more-wii-mail.html</id><summary type="html">&lt;p&gt;Exploring a few more weird usages of Wii Mail.&lt;/p&gt;</summary><content type="html">&lt;p&gt;After launching &lt;a href="./doujinsoft-2.html"&gt;RiiConnect24 integration for DoujinSoft&lt;/a&gt; last month, I started receiving &lt;strong&gt;tons&lt;/strong&gt; of mail from Wii consoles over the world using the service. Some of it intended, some not at all!  &lt;/p&gt;
&lt;p&gt;&lt;img alt="some fanmail" src="images/doujinsoft/fanmail.jpg"&gt;&lt;/p&gt;
&lt;p&gt;In order to better handle messages that don't map to what I've described in my &lt;a href="./doujinsoft-rc24.html"&gt;previous breakdown&lt;/a&gt;, I have set up DoujinSoft to automatically forward them to my own Wii. This is a quick fly-by of the most interesting messages I've encountered.  &lt;/p&gt;
&lt;h1&gt;DIY Showcase Surveys&lt;/h1&gt;
&lt;p&gt;I briefly talked about the Surveys before. As they're related to WarioWare DIY, it makes sense that they'd be the messages I receive the most.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="surveyin" src="/images/survey.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;Survey mail is dead simple(maybe even a bit too much):  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;From&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;w2939127336970027&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;com&lt;/span&gt;
&lt;span class="n"&gt;To&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;w5552226006146758&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;com&lt;/span&gt;
&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;0006&lt;/span&gt;&lt;span class="n"&gt;D000A711EE8DEA72B03BEF8CF&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;com&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;Subject&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;QUESTION&lt;/span&gt;
&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Wii&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;AppId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;57413445&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;3031&lt;/span&gt;
&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Wii&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;IconNew&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;57413445&lt;/span&gt;
&lt;span class="n"&gt;MIME&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;multipart&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mixed&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
 &lt;span class="n"&gt;boundary&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Boundary-NWC24-03BEF8CF0006D&amp;quot;&lt;/span&gt;

&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;Boundary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;NWC24&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;03&lt;/span&gt;&lt;span class="n"&gt;BEF8CF0006D&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;plain&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;us&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;ascii&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Transfer&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="n"&gt;bit&lt;/span&gt;


&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;Boundary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;NWC24&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;03&lt;/span&gt;&lt;span class="n"&gt;BEF8CF0006D&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;octet&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
 &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;a0000109&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dat&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Transfer&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Disposition&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;attachment&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
 &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;a0000109&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dat&lt;/span&gt;

&lt;span class="n"&gt;TXVzdHkgTWVsb24AAAAAAAAAAAAAAAAAAAEDBQ&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;


&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;Boundary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;NWC24&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;03&lt;/span&gt;&lt;span class="n"&gt;BEF8CF0006D&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Once base64-decoded, the contents of the mail are as follow:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;4d6f 6e73 7465 7220 4d61 7468 0000 0000  Monster Math....&lt;/span&gt;
&lt;span class="err"&gt;0000 0000 0000 0000 0000 0507            ............```&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The title of the game/manga/comic takes 25 bytes, followed by a byte for the type, a byte for the amount of stars it got, and a final byte for the id of the comment.  &lt;/p&gt;
&lt;p&gt;Since all the info you get about the initial item is its name and type, that makes it rather hard to map back to DoujinSoft's database -- There are a &lt;strong&gt;ton&lt;/strong&gt; of games named &lt;em&gt;Wario Quest&lt;/em&gt;, for example.&lt;br&gt;
For the time being, I'm just storing and showing survey data &lt;a href="https://diy.tvc-16.science/surveys"&gt;as is&lt;/a&gt; in DoujinSoft.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="surveyin on the information superhighway" src="/images/doujinsoft/survey-dsoft.png"&gt;  &lt;/p&gt;
&lt;h1&gt;Mii Parade&lt;/h1&gt;
&lt;p&gt;Remember the X-WiiFace header I used last time to add a Chuck Norris face to my messages?  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;09&lt;/span&gt; &lt;span class="n"&gt;Jul&lt;/span&gt; &lt;span class="mi"&gt;2019&lt;/span&gt; &lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;09&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;
&lt;span class="n"&gt;From&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;w7720650706766587&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;com&lt;/span&gt;
&lt;span class="n"&gt;To&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;w1657786287988553&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;com&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
 &lt;span class="n"&gt;w6330930957365086&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;com&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
 &lt;span class="n"&gt;w1300677307397579&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;com&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
 &lt;span class="n"&gt;w4440069290024607&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;com&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
 &lt;span class="n"&gt;w7450179939585945&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;com&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
 &lt;span class="n"&gt;w4358193120538293&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;com&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
 &lt;span class="n"&gt;w7457852362831041&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;com&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
 &lt;span class="n"&gt;w2227537699606042&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;com&lt;/span&gt;
&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt;&lt;span class="n"&gt;EB001B6DE412CFCAFB03BF2C29&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;com&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Wii&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;AppId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;48414341&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0001&lt;/span&gt;
&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;WiiFace&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;gADwfPB68GIAUABvAGsAZQAAAAAAAH80gScGhVFr2gMAAGZgAb0qggyL&lt;/span&gt;
 &lt;span class="n"&gt;ikAXIbSNAIoAiCzEAAAAAAAAAAAAAAAAAAAAAAAAAAA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="n"&gt;MIME&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;plain&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;us&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;ascii&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Transfer&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="n"&gt;bit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I was receiving mails of the sort very often - sometimes moreso than DIY Surveys.&lt;br&gt;
I initially thought it was just one dude sending his Mii to a lot of his friends at once...Except that makes no sense! There's no way for users to send mails to multiple people within the Wii Message Board.  &lt;/p&gt;
&lt;p&gt;So then, what could possibly prompt a Wii to broadcast Mii data like this to multiple consoles at once?&lt;br&gt;
My top-notch investigation skills led me to realize that it could only be the &lt;em&gt;Mii Parade&lt;/em&gt;.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="lotta miis" src="/images/MiiParade_01_en.gif"&gt;  &lt;/p&gt;
&lt;p&gt;Mii Parade was a feature of the Mii Channel which allowed you to automatically send your Miis to your friends, which in return would send you theirs.&lt;br&gt;
Miis received that way ended up in the Mii Parade, which allows up to apparently &lt;a href="https://youtube.com/watch?v=3iQi1LPntZg"&gt;&lt;strong&gt;10000 Miis.&lt;/strong&gt;&lt;/a&gt;  &lt;/p&gt;
&lt;p&gt;...I certainly never got that many back in 2008. Right now, the pool from all DoujinSoft users got me to about 250 Miis.  &lt;/p&gt;
&lt;h1&gt;Wii Speak Channel&lt;/h1&gt;
&lt;p&gt;This is my favorite one. The Wii Speak microphone was a USB accessory for the Wii, enabling voice chat in select games such as Monster Hunter Tri, The Conduit, or UNO. (Seriously, is there anything the Wii version of UNO &lt;em&gt;couldn't&lt;/em&gt; do?)  &lt;/p&gt;
&lt;p&gt;&lt;img alt="wii speakero" src="/images/WiiSpeakproduct.PNG"&gt;&lt;/p&gt;
&lt;p&gt;The accessory came bundled with a Wii Channel, appropriately dubbed... Wii Speak Channel.&lt;br&gt;
A feature of this channel I wasn't aware of was the possibility to send &lt;a href="https://www.youtube.com/watch?v=VWC1xyJqrtA"&gt;&lt;em&gt;voice messages&lt;/em&gt;&lt;/a&gt; to other Wiis. How cool is that? And more importantly, how does it abuse e-mails to make it happen?  &lt;/p&gt;
&lt;p&gt;&lt;img alt="aw shit here we go again" src="/images/wiispeak.jpg"&gt;&lt;/p&gt;
&lt;p&gt;You might remember from the previous breakdown that Wii E-Mails can embed custom stationery as an attachment, which in turn is interpreted and displayed by the Message Board.  &lt;/p&gt;
&lt;p&gt;This stationery can contain a custom envelope, a custom letterhead, and a custom &lt;strong&gt;sound&lt;/strong&gt; that plays when you open the message.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="wii speak message" src="/images/wiispeak_msg.jpg"&gt;&lt;/p&gt;
&lt;p&gt;You've probably already guessed how this works.&lt;br&gt;
This sound is usually just used for quick SFX, but here the Wii Speak Channel just crams the &lt;strong&gt;entire voice message&lt;/strong&gt; inside the stationery.  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt; &lt;span class="n"&gt;Jul&lt;/span&gt; &lt;span class="mi"&gt;2019&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;
&lt;span class="n"&gt;From&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;w7475328617225276&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;com&lt;/span&gt;
&lt;span class="n"&gt;To&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;w2227537699606042&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;com&lt;/span&gt;
&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt;&lt;span class="n"&gt;A4001A8EC592CF1C3C03BF99EB&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;com&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Wii&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;AppId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;48434650&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;3031&lt;/span&gt;
&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Wii&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Cmd&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;00042019&lt;/span&gt;
&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;WiiFace&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;gAAASQB3AGEAdABhAAAAAAAAAAAAAGVUhl8KkW9KhAjlwFggSb0Kwlxu&lt;/span&gt;
 &lt;span class="n"&gt;CGBzWUCuSowAiiUEAE0AYQB0AHkAegB1AHoAdQAAAAA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="n"&gt;MIME&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;multipart&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mixed&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
 &lt;span class="n"&gt;boundary&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Boundary-NWC24-03BF99EB000A4&amp;quot;&lt;/span&gt;

&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;Boundary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;NWC24&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;03&lt;/span&gt;&lt;span class="n"&gt;BF99EB000A4&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;plain&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;utf&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="n"&gt;be&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Transfer&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;

&lt;span class="n"&gt;AFYAbwB0AHIAZQAgAGEAbQBpACAAVwBpAGkAIABhACAAYwBvAG0AcABvAHMA6QAgAAoAYwBlACAA&lt;/span&gt;
&lt;span class="n"&gt;bQBlAHMAcwBhAGcAZQAgAGQAZQBwAHUAaQBzAAoAbABhACAAQwBoAGEA7gBuAGUAIABXAGkAaQAg&lt;/span&gt;
&lt;span class="n"&gt;AFMAcABlAGEAawAuAAoAUwBpACAAdgBvAHUAcwAgAHAAbwBzAHMA6QBkAGUAegAgAHYAbwB1AHMA&lt;/span&gt;
&lt;span class="n"&gt;IABhAHUAcwBzAGkAIABjAGUAdAB0AGUACgBjAGgAYQDuAG4AZQAsACAAcwDpAGwAZQBjAHQAaQBv&lt;/span&gt;
&lt;span class="n"&gt;AG4AbgBlAHoAIAAiAEQA6QBtAGEAcgByAGUAcgAiAAoAZQBuACAAYgBhAHMAIABkAGUAIABsACcA&lt;/span&gt;
&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="n"&gt;QBjAHIAYQBuACAAcABvAHUAcgAgAHIA6QBwAG8AbgBkAHIAZQAgAOAACgB2AG8AdAByAGUAIABh&lt;/span&gt;
&lt;span class="n"&gt;AG0AaQAgAHAAYQByACAAdQBuACAAbQBlAHMAcwBhAGcAZQAgAGEAdQBkAGkAbwAuAAoAUwBpACAA&lt;/span&gt;
&lt;span class="n"&gt;dgBvAHUAcwAgAG4AZQAgAHAAbwBzAHMA6QBkAGUAegAgAHAAYQBzAAoAbABhACAAYwBoAGEA7gBu&lt;/span&gt;
&lt;span class="n"&gt;AGUAIABXAGkAaQAgAFMAcABlAGEAawAsAAoAcwDpAGwAZQBjAHQAaQBvAG4AbgBlAHoAIAAiAFIA&lt;/span&gt;
&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="n"&gt;QBwAG8AbgBkAHIAZQAiAAoAcABvAHUAcgAgAOkAYwByAGkAcgBlACAAdQBuAGUAIAByAOkAcABv&lt;/span&gt;
&lt;span class="n"&gt;AG4AcwBlAAoA4AAgAHYAbwB0AHIAZQAgAGEAbQBpAC4&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;


&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;Boundary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;NWC24&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;03&lt;/span&gt;&lt;span class="n"&gt;BF99EB000A4&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;msgboard&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
 &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;a0000164&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;wii&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Transfer&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Disposition&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;attachment&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
 &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;a0000164&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;wii&lt;/span&gt;

&lt;span class="n"&gt;Vao4LQAAACAAAABxAAAAoAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAUAAAABAAAAoAAAAJgA&lt;/span&gt;
&lt;span class="n"&gt;AAAMAAABQAAATbAAAAAaAABPAAAACMQAAAArAABX4AAATMAAY2hqdW1wLmJpbgBsZXR0ZXJfTFou&lt;/span&gt;
&lt;span class="o"&gt;[...&lt;/span&gt; &lt;span class="n"&gt;about&lt;/span&gt; &lt;span class="mi"&gt;740&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;stationery&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;U2XzPk&lt;/span&gt;&lt;span class="sr"&gt;/R9lROUs/k/k8fJTIAEPUQMBQ03H9eJKHicARD/g&lt;/span&gt;&lt;span class="n"&gt;rujgAAJGECDvTbmwFEBi4hLRrG8GQA&lt;/span&gt;
&lt;span class="n"&gt;vc358RMxQwKuABDf8Cw0Y5RbL&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;XBMlQq1r1VHk8PJBDk&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;yQPDFJ01LQ78sIeMxMfcSDv3x4OIyTd&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;cwO3u4S3c8OvSzR4AHv6Kzf&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;wAA&lt;/span&gt;


&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;Boundary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;NWC24&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;03&lt;/span&gt;&lt;span class="n"&gt;BF99EB000A4&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h2&gt;👀👀👀&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Gunpei_Yokoi#Lateral_Thinking_with_Withered_Technology"&gt;Lateral Thinking with Withered Technology&lt;/a&gt; applies to Wii Mail as a whole, but this is taking it to the next level.  &lt;/p&gt;
&lt;p&gt;The Wii Speak Channel allows a maximum of 10 seconds for voice messages, likely to prevent mails growing to outrageous sizes.&lt;br&gt;
I'm not sure if there's any limit on the Wii Message Board itself; Maybe I could update the DoujinSoft mail templates so that they play &lt;a href="https://www.youtube.com/watch?v=PX7zPlQjAr8"&gt;sanic.mp3&lt;/a&gt; whenever users open them.  &lt;/p&gt;</content><category term="Software"></category><category term="nintendo"></category><category term="wii"></category><category term="wiiconnect24"></category><category term="riiconnect24"></category><category term="mail"></category><category term="mii"></category></entry><entry><title>DoujinSoft x RiiConnect24 - A technical breakdown</title><link href="https://tvc-16.science/doujinsoft-rc24.html" rel="alternate"></link><published>2019-07-08T00:00:00+02:00</published><updated>2019-07-08T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2019-07-08:/doujinsoft-rc24.html</id><summary type="html">&lt;p&gt;Exploiting old Nintendo APIs for fun and profit.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've recently dug out the old DoujinSoft codebase to &lt;a href="/doujinsoft-2.html"&gt;interop with RiiConnect24&lt;/a&gt;.&lt;br&gt;
It's made in Java because I &lt;a href="https://github.com/Difegue/DoujinSoft/tree/master/libs-ext/diyedit/diyedit-jar/1.0"&gt;use a Swing GUI application as a dependency&lt;/a&gt; to handle all the WarioWare proprietary file-format decoding. You expecting me to learn reverse-engineering or something? 🐱‍👓  &lt;/p&gt;
&lt;p&gt;Thanks to the RiiConnect24 guys giving me the keys to the castle (a fake Wii friend number and some debug tools), I set out to finally give people an easy way to get games from DoujinSoft &lt;strong&gt;directly&lt;/strong&gt; to their console.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="d i r e c t l y" src="images/direct.gif"&gt;&lt;br&gt;
papa iwata this one's for you  &lt;/p&gt;
&lt;h2&gt;📧 A quick primer on WiiConnect24 📧&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;The flowing light on the Wii is timed with the bird call of the Japanese bush warbler.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;WiiConnect24 was the cool name given by Nintendo to half of the Wii's networking features. (The other half being Nintendo Wi-Fi Connection) According to marketing, all it does is download data when the Wii is in standby mode, but it actually handles a bit more than that, namely:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The entire friend system  &lt;/li&gt;
&lt;li&gt;The &lt;a href="https://www.youtube.com/watch?v=afhwHfG0enY"&gt;Wii Message Board&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Receiving data from games or channels&lt;/li&gt;
&lt;li&gt;Executing a weird variant of JavaScript to handle said data&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wiibrew.org/wiki/WiiConnect24"&gt;And some more junk&lt;/a&gt;  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The funny thing about WC24 is how it handles messaging: If you ever had a Wii back in 2006 you might have noticed you could add non-Wii devices as friends through their email address, and subsequently send and receive e-mail to/from them.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="has anyone ever added a cellphone to their friends tho" src="images/wiifriend.gif"&gt;&lt;br&gt;
PC users would see your Wii represented by a wxxxxxxxxxxxxxxxx@wii.com email address.&lt;br&gt;
I always thought it was really cool of Nintendo to implement this extra layer of communication to the console! I mean, they had to do some extra work for this whole email integration to work, right?  &lt;/p&gt;
&lt;p&gt;&lt;sup&gt;Right?&lt;/sup&gt;  &lt;/p&gt;
&lt;p&gt;The truth is a bit more grounded and the opposite of my assumptions:&lt;br&gt;
All of WiiConnect24's messaging is handled by emails. &lt;strong&gt;All of it.&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;02&lt;/span&gt; &lt;span class="n"&gt;Jul&lt;/span&gt; &lt;span class="mi"&gt;2019&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;
&lt;span class="n"&gt;From&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;w7475328617225276&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;com&lt;/span&gt;
&lt;span class="n"&gt;To&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;w2227537699606042&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;com&lt;/span&gt;
&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="n"&gt;A001A8EC592CF1C3C03BF08FA&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;com&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Wii&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;AppId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;48414541&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0001&lt;/span&gt;
&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Wii&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Cmd&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;00044001&lt;/span&gt;
&lt;span class="n"&gt;MIME&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;plain&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;utf&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="n"&gt;be&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Transfer&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;

&lt;span class="n"&gt;AEIAYQB6AGkAbgBnAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decodes&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Bazinga&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;A message between two Wiis really is just an email with a few custom headers, as seen above.&lt;br&gt;
The upside is that this makes it extremely easy to understand how a game sends data, and how to fake it.&lt;/p&gt;
&lt;h2&gt;🤜 Being friends 🤛&lt;/h2&gt;
&lt;p&gt;For DoujinSoft to be able to send emails/content to other Wiis, said consoles must add our Wii number as a friend.&lt;br&gt;
Surprisingly and (probably)unlike Nintendo's future implementations, the server doesn't track who's friend with who; All negotiation is done directly between the consoles.  &lt;/p&gt;
&lt;p&gt;A Wii sending a friend request to another will send an email looking a bit like this:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;From&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;w7475328617225276&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;com&lt;/span&gt;
&lt;span class="n"&gt;To&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;w2227537699606042&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;com&lt;/span&gt;
&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;00066000&lt;/span&gt;&lt;span class="n"&gt;A711EE8DEA72B03BEF879&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;com&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 
&lt;span class="n"&gt;Subject&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;WC24&lt;/span&gt; &lt;span class="n"&gt;Cmd&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt; 
&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Wii&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;AppId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;00000001&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0001&lt;/span&gt; 
&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Wii&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Cmd&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80010001&lt;/span&gt; 
&lt;span class="n"&gt;MIME&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; 
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;multipart&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mixed&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; 
 &lt;span class="n"&gt;boundary&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Boundary-NWC24-03BEF87900066&amp;quot;&lt;/span&gt; 

&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;Boundary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;NWC24&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;03&lt;/span&gt;&lt;span class="n"&gt;BEF87900066&lt;/span&gt; 

&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;plain&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;us&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;ascii&lt;/span&gt; 
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Transfer&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="n"&gt;bit&lt;/span&gt; 

&lt;span class="n"&gt;WC24&lt;/span&gt; &lt;span class="n"&gt;Cmd&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt; 

&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;Boundary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;NWC24&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;03&lt;/span&gt;&lt;span class="n"&gt;BEF87900066&lt;/span&gt; 

&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;octet&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; 
 &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;a0000102&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dat&lt;/span&gt; 
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Transfer&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt; 
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Disposition&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;attachment&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; 
 &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;a0000102&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dat&lt;/span&gt; 

&lt;span class="n"&gt;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&lt;/span&gt; 
&lt;span class="n"&gt;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&lt;/span&gt; 
&lt;span class="n"&gt;AAAAAAAAAAAAAAAAAAA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; 

&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;Boundary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;NWC24&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;03&lt;/span&gt;&lt;span class="n"&gt;BEF87900066&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Here, Wii number 7475 3286 1722 5276 sent a friend request to Wii number 2227 5376 9960 6042.&lt;br&gt;
&lt;code&gt;X-Wii-AppId&lt;/code&gt; and &lt;code&gt;X-Wii-Cmd&lt;/code&gt; are changed from the defaults used in regular messages to inform WiiConnect24 this message is a Friend Request. (And therefore, it's not shown to the user on his console)  &lt;/p&gt;
&lt;p&gt;If the other Wii sends a similar message the other way then presto! The consoles are friends.&lt;br&gt;
&lt;img src="/images/utsuho-friends.jpg" style="width:300px" /&gt;&lt;br&gt;
As a result, this is pretty easy to replicate for DoujinSoft: When the user gives us the Wii number to send content to, we send this mail beforehand to act as the friend request.&lt;br&gt;
This approach requires the user to add our number to their console beforehand, which isn't too bothersome.  &lt;/p&gt;
&lt;h2&gt;🚚 Sending the goods over 🚛&lt;/h2&gt;
&lt;p&gt;If you've read the other article, you know that our final objective is to send WarioWare DIY content to other Wiis through WiiConnect24.&lt;br&gt;
You've probably guessed then that when sending content to your friends from the game, it actually sends... emails.  &lt;/p&gt;
&lt;p&gt;The example below is for a Record, the lightest content in DIY, weighing at 8KB. Makes for a shorter email here:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;From&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;w7475328617225276&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;com&lt;/span&gt;
&lt;span class="n"&gt;To&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;w2227537699606042&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;com&lt;/span&gt;
&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;00069000&lt;/span&gt;&lt;span class="n"&gt;A711EE8DEA72B03BEF889&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;com&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;Subject&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RR&lt;/span&gt;
&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Wii&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;AppId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;57413445&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;3031&lt;/span&gt;
&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Wii&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;IconNew&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;57413445&lt;/span&gt;
&lt;span class="n"&gt;MIME&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;multipart&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mixed&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
 &lt;span class="n"&gt;boundary&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Boundary-NWC24-03BEF88900069&amp;quot;&lt;/span&gt;

&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;Boundary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;NWC24&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;03&lt;/span&gt;&lt;span class="n"&gt;BEF88900069&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;plain&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;us&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;ascii&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Transfer&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="n"&gt;bit&lt;/span&gt;


&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;Boundary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;NWC24&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;03&lt;/span&gt;&lt;span class="n"&gt;BEF88900069&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;octet&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
 &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;a0000105&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dat&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Transfer&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Disposition&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;attachment&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
 &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;a0000105&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dat&lt;/span&gt;

&lt;span class="n"&gt;EAAgAAQRABEAAAABRFMATUlPX1MABxcAAAAHVxIA61cAU3tOaWdodCAMV2FsayAicARBSwMgR2Ft&lt;/span&gt;
&lt;span class="n"&gt;ZXOgEsAjBwAAd2904BPwEPAigOBIAQACBwEBABgAnxjwT7CZXGhpCHRuAFMgM6TA4QB2NuCREAAA&lt;/span&gt;
&lt;span class="n"&gt;dRkvnPAwwfBBAAYA8AAAGBEQDgwKCSAHBVAHAAIEBwwDDhATGP&lt;/span&gt;&lt;span class="c1"&gt;//oAHwH/DAIPAP8CHwM/8C/wMw&lt;/span&gt;
&lt;span class="sr"&gt;/wBAA5ALAP//AT4AAQD8ICMwA5ALMBMD2fCFgLcB8KPgtQMCEAFCAgABDgAALjHuERj//&lt;/span&gt;&lt;span class="n"&gt;wkQAjAH&lt;/span&gt;
&lt;span class="n"&gt;Ef8QrAELDgEPDAETABj&lt;/span&gt;&lt;span class="sr"&gt;/BbrAAwTAAxAjkAMHwAMF3QBZkAMAAFmQAxETAUEP/&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="n"&gt;AHcSOxD5Ej8PPx&lt;/span&gt;
&lt;span class="n"&gt;qPG6sewQBAMBQRMqDAwv7TMCAP8A9gURBwD&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;DiELuHIiCcGX8QPwZAkABAAJDBAVEAwJBLEB7AIB&lt;/span&gt;
&lt;span class="n"&gt;YZADCf8VoAP&lt;/span&gt;&lt;span class="sr"&gt;/8RPxE/ET8RPytvLI8toRE10CYRMOQRMB1yACBwIufwoyGyI3EA0SKxATEBcSN/&lt;/span&gt;&lt;span class="n"&gt;yQ&lt;/span&gt;
&lt;span class="n"&gt;AxJLEk9S19I3Ul8H&lt;/span&gt;&lt;span class="sr"&gt;/38TIANSH3ELUSvyJ/In8ifz8ifzzPPe8/AEAvInAyf/&lt;/span&gt;&lt;span class="n"&gt;IRYwB7M7ESNQ&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;wNj&lt;/span&gt;
&lt;span class="n"&gt;IAPQH&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;PUVtA&lt;/span&gt;&lt;span class="sr"&gt;/URML/xcgA/M7//M7czt0T/M7FC/0T/&lt;/span&gt;&lt;span class="n"&gt;Tm9PjpsztUT3InEiIKCv8wB1USIPsPBV8N&lt;/span&gt;
&lt;span class="n"&gt;QiMGwAPr1F9SR1JPCMADBgBZkANvAQBZkAME9E&lt;/span&gt;&lt;span class="sr"&gt;/0T/RPZE//FX/&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="n"&gt;vYA9hL0T1RPAP8A9m0GEQcA&lt;/span&gt;
&lt;span class="sr"&gt;/g8hC3ZyCiXZ4JAD0QPmfgUKAQUKAQ0RFhENCgUF+GMDAWGQAwr/FqAD9WP/9WP1Y/&lt;/span&gt;&lt;span class="n"&gt;Vj9wb3GPcq&lt;/span&gt;
&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="n"&gt;E8Yi9cB1yACCARNCzQ&lt;/span&gt;&lt;span class="sr"&gt;/IjcQDf8SKxATEBcSN5ADEksST1Ezx9I3Ul8I/xQgA1IfURP/USv2d/&lt;/span&gt;&lt;span class="n"&gt;Z3&lt;/span&gt;
&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="n"&gt;nf2d&lt;/span&gt;&lt;span class="sr"&gt;/ga+Cz4Pv/0T0InIRYwB7M7ESNQ+wNj/yAD0B/YptA/URNXg9M794v/9E/3i/&lt;/span&gt;&lt;span class="n"&gt;RP&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;S75QPlS&lt;/span&gt;
&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="n"&gt;E8as1sTIgoLBh0SJhMg&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;ij&lt;/span&gt;&lt;span class="sr"&gt;/xI32G/UX1JHUk/4rzZ3Vn//15v0T/if+J/0T/&lt;/span&gt;&lt;span class="n"&gt;pC&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;lT6Zvv4nxvH&lt;/span&gt;
&lt;span class="n"&gt;CaMnjjAHECmvesJwC&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;mj0QPqzgYLAgYACw4SFxIOCwa&lt;/span&gt;&lt;span class="sr"&gt;/CoAECgWQA1Z/Vof5s/mz//&lt;/span&gt;&lt;span class="n"&gt;mz&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;bP7Vvto&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;r4nxzbeI9&lt;/span&gt;&lt;span class="sr"&gt;/Cyq7IjQHORANWs+Yj1iX/xrvGvNa19I3Ws/Yj1eTUSv/+sf6x/rH+sf8avx8/&lt;/span&gt;&lt;span class="n"&gt;I74&lt;/span&gt;
&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="sr"&gt;/9CJyvbMAezOxvfUPsDYyAD/1RXVF/c9tA/Wrd0P7rX+9v/+9v72/vb/&lt;/span&gt;&lt;span class="n"&gt;X79kP2i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;J8fA&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;D5c3D7&lt;/span&gt;
&lt;span class="n"&gt;HPsGAgYJDmEJAAQABhIOEhXc799SMxWDFf4X9D&lt;/span&gt;&lt;span class="sr"&gt;/DS4zvDfE/AQFYr/zvXi/8775T/pz//&lt;/span&gt;&lt;span class="n"&gt;q75s7ET&lt;/span&gt;
&lt;span class="n"&gt;EMk1W1qHFmMSF&lt;/span&gt;&lt;span class="sr"&gt;/wQ49mz3ePVY9wD1D8M/38YoAP+A/4D/gP+A/+m/7jv/8q8714DF8/&lt;/span&gt;&lt;span class="n"&gt;zHOk0Wx8d&lt;/span&gt;
&lt;span class="sr"&gt;/xRl9lcfL94bn88f33FDtnf//O//F/8X/xf/F/8k/zb/SJfxEwAAFU0LDxUc5x96/x&lt;/span&gt;&lt;span class="n"&gt;Q3nLtXfx3j&lt;/span&gt;
&lt;span class="n"&gt;H7PyJ1dzPAf7URNRGx4DG981Tw78768XPwEB&lt;/span&gt;&lt;span class="sr"&gt;/xfzO/8X/6D/sv/E//InbO+v8dQT2G/&lt;/span&gt;&lt;span class="n"&gt;Wf9&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;uFFP0&lt;/span&gt;
&lt;span class="n"&gt;kAP1U&lt;/span&gt;&lt;span class="sr"&gt;/VjnxcJMAMACf8kXf4DHycfD19L9L9/qx+j6v86/gNfFwlPFxQv/g&lt;/span&gt;&lt;span class="n"&gt;zfC4EfChQmX14HHxsA&lt;/span&gt;
&lt;span class="n"&gt;H6ADfxaAA1AjlEP&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;EzvbW&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;Pc&lt;/span&gt;&lt;span class="c1"&gt;//996z8b3gOd6x3z3gMZP/+e/v+w/8L+A14DD/8s8jAHEd8vE3//&lt;/span&gt;
&lt;span class="n"&gt;DC8nkANXkxebsCuAT7YHDAMHDA8TBRgTDwwHD8oFD2n&lt;/span&gt;&lt;span class="sr"&gt;/kAP1Y/8X/xf/F/4D/yD/Msv/&lt;/span&gt;&lt;span class="n"&gt;RP4DAAB9&lt;/span&gt;
&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="n"&gt;wwvEyI0&lt;/span&gt;&lt;span class="sr"&gt;/wybEA1c953zXfvxKzjDUkf/Ul/d81Z3USv/F/8X/xf/F///ov+0/&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="n"&gt;YeA3zvgicvCjAH&lt;/span&gt;
&lt;span class="sr"&gt;/7M7HxeTK1MvHxNZw9+u0D//Xwdc59M7/xf/F/8X/xf/Iv7/NP9G/gNET/9xj4MPNP8/&lt;/span&gt;&lt;span class="n"&gt;CAgQAv&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;Y&lt;/span&gt;
&lt;span class="n"&gt;H6oUb&lt;/span&gt;&lt;span class="sr"&gt;/AfwEP/8D9v5SUzFnD/9z/L//////H/n/+x/8Mv1QMCBAABvy8XDlVj/+7///+Q/6L/tP//&lt;/span&gt;
&lt;span class="n"&gt;xv&lt;/span&gt;&lt;span class="sr"&gt;/Y/+r//P//////HP8u7/9Au9s/EwE/GvAF8BfwKf/wO/BN8F/wcfCD8JXwp/C5//DL8N3w7/&lt;/span&gt;&lt;span class="n"&gt;EB&lt;/span&gt;
&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="n"&gt;RPxJfE38Un&lt;/span&gt;&lt;span class="sr"&gt;/8VvxbfF/8ZHxo/G18cfx2f/x6/H98g/yIfIz8kXyV/Jp//J78o3yn/&lt;/span&gt;&lt;span class="n"&gt;Kx8sPy1fLn&lt;/span&gt;
&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="n"&gt;vn&lt;/span&gt;&lt;span class="sr"&gt;/8wvzHfMv80HzU/Nl83fzif/zm/Ot87/z0fPj8/X0B/QZ//Qr9D30T/&lt;/span&gt;&lt;span class="n"&gt;Rh9HP0hfSX9Kn89Lv0&lt;/span&gt;
&lt;span class="n"&gt;zfTf9PH1Az&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;


&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;Boundary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;NWC24&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;03&lt;/span&gt;&lt;span class="n"&gt;BEF88900069&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;You'll notice &lt;code&gt;X-Wii-AppId&lt;/code&gt; changed once again alongside a new header called &lt;code&gt;X-Wii-IconNew&lt;/code&gt;, this time to match the title ID of the Wii game, DIY Showcase.  &lt;/p&gt;
&lt;p&gt;&lt;code&gt;X-Wii-IconNew&lt;/code&gt; tells the Wii System Menu that the game's channel icon has to be updated. Here, this is used to show a small envelope when you have content incoming.  &lt;/p&gt;
&lt;p&gt;The Game/Record/Comic itself is just added as an attachment to the mail. It's compressed using LZ10, a variant of the &lt;a href="https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Storer%E2%80%93Szymanski"&gt;LZSS compression algorithm&lt;/a&gt; Nintendo likes to use in GBA/NDS games.&lt;br&gt;
Past that, it's base64-encoded, as specified in the mail's Content-Transfer-Encoding header. (base64 encoding is a classic when doing stuff with WC24)  &lt;/p&gt;
&lt;p&gt;Once again, doing this from DoujinSoft is as easy as sending mail over to the Wii console.&lt;br&gt;
&lt;img alt="gotem" src="/images/doujinsoft/dj_result.jpg"&gt;&lt;br&gt;
Man, that was a bit too easy in hindsight, wasn't it? Let's push this a bit further.  &lt;/p&gt;
&lt;h2&gt;🎁 Bonus Round: Custom Wii Message Board letters 🎁&lt;/h2&gt;
&lt;p&gt;Remember receiving cute customized letters from Nintendo or other games?&lt;br&gt;
&lt;img alt="letters" src="/images/letters.jpg"&gt;&lt;br&gt;
Games or channels, when pushing email to the Message Board, had the option to add custom data to change the envelope, the stationery used, and add a sound effect when opening the message.  &lt;/p&gt;
&lt;p&gt;This was a bit of an underused feature even back when the Wii was an active console, with most of Nintendo's own big games (Mario Galaxy, Smash Bros. Brawl) sending stuff with the default envelopes.&lt;br&gt;
Some games &lt;a href="http://www.studiousoctopus.com/?p=1229"&gt;did use it to great effect&lt;/a&gt;, and it always felt a bit special to receive a custom letter.  &lt;/p&gt;
&lt;p&gt;There's little documentation about the custom stationery, but studying &lt;a href="https://github.com/giantpune/mailboxbomb/blob/master/source/main.cpp#L173"&gt;message board exploits&lt;/a&gt; quickly shows they're just email attachments as well, similar to DIY's games. 🧐  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;From&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;w2227537699606042&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;rc24&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;xyz&lt;/span&gt;
&lt;span class="n"&gt;To&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;w7475328617225276&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;rc24&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;xyz&lt;/span&gt;
&lt;span class="n"&gt;Subject&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;=?&lt;/span&gt;&lt;span class="n"&gt;UTF&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="n"&gt;BE&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="n"&gt;AFkAbwB1AHIAIABnAGEAbQBlAHMAIABhAHIAZQAgAHIAZQBhAGQAeQAh&lt;/span&gt;&lt;span class="o"&gt;?=&lt;/span&gt;
&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Wii&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;AppId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;354&lt;/span&gt;&lt;span class="n"&gt;E4541&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4142&lt;/span&gt;
&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Wii&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Cmd&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;00042019&lt;/span&gt;
&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Wii&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;AltName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AEQAbwB1AGoAaQBuAFMAbwBmAHQAIABTAHQAbwByAGU&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;WiiFace&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;gBYAQwAuAE4AbwByAHIAaQBzAAAAAFtAgZX3KDb4KCzG7AuQaKuokFxM&lt;/span&gt;
 &lt;span class="n"&gt;BmloSrjSAIr9DiUEAFcAbwBsAHYAZQByAGkAbgBlAAA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Wii&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;NoReply&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;
&lt;span class="n"&gt;MIME&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;multipart&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mixed&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
 &lt;span class="n"&gt;boundary&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Boundary-NWC24-03BF040F00005&amp;quot;&lt;/span&gt;

&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;Boundary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;NWC24&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;03&lt;/span&gt;&lt;span class="n"&gt;BF040F00005&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;plain&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;utf&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="n"&gt;be&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Transfer&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;

&lt;span class="n"&gt;AFQAaABhAG4AawAgAHkAbwB1ACAAZgBvAHI&lt;/span&gt;&lt;span class="o"&gt;[...]&lt;/span&gt;&lt;span class="n"&gt;ACAAIAB&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;AH4AfgB&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;AH4&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;

&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;Boundary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;NWC24&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;03&lt;/span&gt;&lt;span class="n"&gt;BF040F00005&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;wii&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;msgboard&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
 &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;a0000009&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;wii&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Transfer&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Disposition&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;attachment&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
 &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;a0000009&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;wii&lt;/span&gt;

&lt;span class="n"&gt;Vao4LQAAACAAAABxAAAAoAAAAAAAAAA&lt;/span&gt;&lt;span class="o"&gt;[...]&lt;/span&gt;&lt;span class="n"&gt;AAAAAAAAAAAAAAAAAAAA&lt;/span&gt;

&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;Boundary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;NWC24&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;03&lt;/span&gt;&lt;span class="n"&gt;BF040F00005&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I've cut the actual data here as this stuff is getting &lt;strong&gt;real damn long&lt;/strong&gt;, but I've added a few fun extras to demonstrate what can be done with WiiConnect24 mail:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Yet another &lt;code&gt;X-Wii-AppId/X-Wii-Cmd&lt;/code&gt; combo: At this point I'll admit I have no idea what this one does and just wholelifted it from messages sent by the Wii.  &lt;/li&gt;
&lt;li&gt;&lt;code&gt;X-Wii-AltName&lt;/code&gt; allows you to show a name of your choice on the envelope instead of the name the Wii gave your friend number; Useful for us as DoujinSoft is a &lt;em&gt;household name&lt;/em&gt;, god dang it!&lt;/li&gt;
&lt;li&gt;&lt;code&gt;X-WiiFace&lt;/code&gt; is a base64 encoded Mii, that appears on the upper-left side of the envelope. This works for regular and custom envelopes without a hitch: In fact the Wii gives exactly &lt;strong&gt;zero fucks&lt;/strong&gt; what your custom graphics are and will draw on top of it relentlessly.&lt;br&gt;
&lt;img alt="lookin cool" src="images/doujinsoft/wiimb_glitch.jpg"&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;X-Wii-MB-NoReply&lt;/code&gt; disables the "Reply" button for this message.&lt;/li&gt;
&lt;li&gt;And the final piece, the custom stationery, is stored as an attachment with Content-Type &lt;code&gt;x-wii-msgboard&lt;/code&gt;. The graphics themselves are wrapped in a &lt;a href="http://wiibrew.org/wiki/U8_archive"&gt;U8 archive&lt;/a&gt;(crazy how many file formats you only ever encounter when dealing with Nintendo stuff), and base64-encoded as per the norm.  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The result looks like this!&lt;br&gt;
&lt;img alt="letter" src="/images/doujinsoft/wiimb_custom1.jpg"&gt;&lt;br&gt;
&lt;img alt="stationery" src="/images/doujinsoft/wiimb_custom2.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;Really gives us that fun, almost-official flair.  &lt;/p&gt;
&lt;h2&gt;Final spare thoughts&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The envelope notifications from &lt;code&gt;X-Wii-IconNew&lt;/code&gt; take region into account, unlike(thankfully) everything else: DoujinSoft's emails are sent using the US title ID, so other regions sadly miss out on the envelope icon for their channel.&lt;br&gt;
Luckily, the bonus custom letters kinda mitigate that by acting as a notification.  &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;LZ10 compression turned out to be a bitch to find proper implementations for; I used &lt;a href="http://www.romhacking.net/utilities/826/"&gt;a C library&lt;/a&gt; which I compiled and bound to the Java code using JNA, turning the DoujinSoft server into an even bigger Frankenstein's monster. 👍  &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I might write a second, clean server to collect emails from users when I get around to implementing survey box responses and games sent to the store from users' Wiis.  &lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://wiibrew.org/wiki//shared2/wc24/mbox"&gt;Wiibrew's breakdown of WC24 email&lt;/a&gt;&lt;br&gt;
&lt;a href="https://wiibrew.org/wiki/WiiConnect24/WC24_Content"&gt;More WC24 details from Wiibrew&lt;/a&gt;&lt;br&gt;
&lt;a href="https://bigredpimp.wordpress.com/2008/01/22/more-wii-message-header-fun/"&gt;Wii mail headers&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/RiiConnect24/WMB-Letterhead"&gt;All the official stationery, ripped by RiiConnect24.&lt;/a&gt;&lt;br&gt;
&lt;a href="https://drive.google.com/drive/folders/0B7vPmuZfN3sndnRVNmJ4amw5Mms"&gt;Said stationery in decoded image form if you just want to take a look&lt;/a&gt;&lt;/p&gt;</content><category term="Software"></category><category term="java"></category><category term="nintendo"></category><category term="wii"></category><category term="wiiconnect24"></category><category term="riiconnect24"></category><category term="warioware"></category><category term="doujinsoft"></category><category term="mio"></category></entry><entry><title>DoujinSoft 2.0 - now with extra RiiConnecting</title><link href="https://tvc-16.science/doujinsoft-2.html" rel="alternate"></link><published>2019-07-07T00:00:00+02:00</published><updated>2019-07-07T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2019-07-07:/doujinsoft-2.html</id><summary type="html">&lt;p&gt;We have to go back... to WiiConnect24!&lt;/p&gt;</summary><content type="html">&lt;p&gt;Remember &lt;a href="https://en.wikipedia.org/wiki/WarioWare_D.I.Y."&gt;WarioWare D.I.Y&lt;/a&gt;? The Nintendo DS game for making WarioWare-style microgames.&lt;br&gt;
It's turning 10 this year and stands as, in my opinion, the best in Nintendo's "&lt;em&gt;wacky user-generated content with that weird &lt;a href="https://www.youtube.com/watch?v=q_Yd2gn37AM"&gt;baby face good lord what is that&lt;/a&gt;&lt;/em&gt;" series.  (Alongside other grands like Mario Paint and both Mario Maker games)  &lt;/p&gt;
&lt;p&gt;I launched &lt;a href="https://diy.tvc-16.science/"&gt;the DoujinSoft Store/Archiver&lt;/a&gt; two years ago as a way to easily catalogue all the content created with the game. It's a pretty obvious rip-off of the &lt;a href="https://supermariomakerbookmark.nintendo.net"&gt;Mario Maker Bookmark Site&lt;/a&gt;, to stay in-theme.  &lt;/p&gt;
&lt;p&gt;As-is, it's a good archive of all the stuff people made back in the day, but there wasn't an easy way to play the games, save from injecting them yourself into a savefile for the DS Game. Ditto for sharing newly made games with other people.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="pretty tongue-in-cheek there game" src="images/doujinsoft/showcase.png"&gt;&lt;br&gt;
DIY had a matching game on the Wii, WarioWare DIY Showcase, which acted as extra storage for your DS Games, alongside a few other things. Namely, the possibility to &lt;em&gt;send/receive games&lt;/em&gt; from Wii friends.  &lt;/p&gt;
&lt;p&gt;I was approached recently by the good folks at &lt;a href="https://rc24.xyz/"&gt;RiiConnect24&lt;/a&gt;, who'd figured out how this part worked.&lt;br&gt;
Cue a bit of jolly co-operation, and I'm happy to present &lt;strong&gt;DoujinSoft 2.0&lt;/strong&gt;, now featuring:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A complete dump of the content that was available on the Nintendo Wi-Fi Connection before its closure:&lt;br&gt;
That's around &lt;h2&gt;&lt;em&gt;185 233‬&lt;/em&gt;&lt;/h2&gt; Games/Music/Comics you can peruse! They're all pretty small so thankfully it compresses well 👀💦  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RiiConnect24&lt;/strong&gt; integration, allowing you to choose games on the storefront and get them &lt;strong&gt;directly&lt;/strong&gt; shipped to your Wii system.  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="look at that swanky modal" src="images/doujinsoft/djsoft2.jpg"&gt;&lt;/p&gt;
&lt;p&gt;You'll also get nice custom letters on your Wii Message Board if you use the feature!&lt;br&gt;
(Well technically they're the old ones from the Wii Shop Channel &lt;sup&gt;but personally I was only gifted a game once so it's not like I saw them that often&lt;/sup&gt;)&lt;/p&gt;
&lt;p&gt;&lt;img alt="stationery" src="/images/doujinsoft/wiimb_custom2.jpg"&gt;  &lt;/p&gt;
&lt;h2&gt;What's next for DoujinSoft 3.0&lt;/h2&gt;
&lt;p&gt;I'm doing the giving, so I might as well start doing the receiving!&lt;br&gt;
DIY Showcase allows players to rate the games they receive, so there's a fun integration to be made here.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="surveyin" src="/images/doujinsoft/survey.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;Past that, it only makes sense that users would be able to send/upload their own games to DoujinSoft using their Wii consoles. It'd almost make us look like a proper online service!  &lt;/p&gt;
&lt;h2&gt;The technical breakdown&lt;/h2&gt;
&lt;p&gt;&lt;img alt="cool pic" src="/images/doujinsoft/rc24.png"&gt;&lt;/p&gt;
&lt;p&gt;Since this is a tech blog, if you're interested in how WiiConnect24 ticks I've written &lt;a href="./doujinsoft-rc24.html"&gt;a fair bit about what I learned here.&lt;/a&gt; Thanks for reading, and have fun using DoujinSoft!&lt;/p&gt;</content><category term="Software"></category><category term="nintendo"></category><category term="wii"></category><category term="wiiconnect24"></category><category term="riiconnect24"></category><category term="warioware"></category><category term="doujinsoft"></category><category term="mio"></category></entry><entry><title>Masquerading a WSL Distro as a Windows Port, Part 2</title><link href="https://tvc-16.science/wsl-karen-2.html" rel="alternate"></link><published>2019-06-06T00:00:00+02:00</published><updated>2019-06-06T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2019-06-06:/wsl-karen-2.html</id><summary type="html">&lt;p&gt;Interacting with WSL from managed code isn't as easy as I'd thought.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://tvc-16.science/wsl-karen-1.html"&gt;Previously,&lt;/a&gt; we got to the point where we had a WSL distro installed seamlessly to the user's computer, alongside all the shortcuts, bells and whistles of a proper Windows application.&lt;br&gt;
Let's talk a bit about the Windows GUI part of the magic trick.  &lt;/p&gt;
&lt;h2&gt;A simple, taskbar-based GUI&lt;/h2&gt;
&lt;p&gt;Since the bulk of the interaction with LANraragi won't be made from this GUI, I wanted to make it as unobtrusive as possible, while still featuring some &lt;a href="https://www.microsoft.com/design/fluent/"&gt;Fluent Design&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;I got some inspiration from Docker for Windows' &lt;a href="https://docs.docker.com/docker-for-windows/images/docker-app-welcome.png"&gt;welcome screen&lt;/a&gt; and the OneDrive app in W10, who both pop-up small windows from their tray icons.&lt;/p&gt;
&lt;p&gt;&lt;img alt="light themin'" src="https://tvc-16.science/images/karen-light.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;I rolled &lt;a href="https://www.nuget.org/packages/Hardcodet.NotifyIcon.Wpf/"&gt;this handy&lt;/a&gt; NuGet package to get the tray icon part out of the way and got to work designin'.  &lt;/p&gt;
&lt;p&gt;Fluent Design would've been almighty easy to pull off had I used &lt;a href="https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/xaml-islands"&gt;XAML Islands&lt;/a&gt;, but I didn't want to be &lt;strong&gt;too&lt;/strong&gt; bleeding edge and restrict the app to 1903 and up purely for cool visual effects. Maybe in a few years. 🏝   &lt;/p&gt;
&lt;p&gt;Instead, I rolled Acrylic the &lt;a href="https://withinrafael.com/2018/02/01/adding-acrylic-blur-to-your-windows-10-apps-redstone-4-desktop-apps/"&gt;DIY way&lt;/a&gt;, and Dark Theme by &lt;a href="https://engy.us/blog/2018/10/20/dark-theme-in-wpf/"&gt;looking up the registry.&lt;/a&gt; The buttons are just some custom XAML &lt;a href="https://github.com/Difegue/Karen/blob/master/Karen/App.xaml#L69"&gt;theming&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;The final result is probably overkill for an interface most people will only look at for 5 seconds, but I couldn't help it. &lt;/p&gt;
&lt;h2&gt;WSL interop from WPF&lt;/h2&gt;
&lt;p&gt;The GUI interops with WSL in four different ways.&lt;br&gt;
I expected to use the &lt;a href="https://docs.microsoft.com/en-us/windows/desktop/api/_wsl/"&gt;WSL API&lt;/a&gt; to do all of these with P/Invokes galore, but encountered a few hitches where I had to run &lt;code&gt;wslconfig.exe&lt;/code&gt; and &lt;code&gt;wsl.exe&lt;/code&gt; instead through NET's Process API.&lt;/p&gt;
&lt;h3&gt;🐧 Check that the Linux Distro is actually installed&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;WslIsDistributionRegistered&lt;/code&gt; flat out &lt;a href="https://stackoverflow.com/questions/55681500/why-did-wslapi-suddenly-stop-working-in-wpf-applications"&gt;doesn't work&lt;/a&gt; in a GUI application for now. I had to make due with running &lt;code&gt;wslconfig.exe /l&lt;/code&gt; and parsing its output to find my distro name.  &lt;/p&gt;
&lt;p&gt;(I initially toyed with the idea of being able to uninstall/install the Distro from the GUI using &lt;code&gt;WslRegisterDistribution&lt;/code&gt;, which would allow for easy updates, but not being able to specify the folder the distro lands in kinda put a stop to that.)&lt;/p&gt;
&lt;h3&gt;🐱‍💻 Execute a Perl one-liner to fetch the LANraragi Version number installed in it&lt;/h3&gt;
&lt;p&gt;Using some &lt;a href="https://mojolicious.org/perldoc/ojo"&gt;ojo&lt;/a&gt; magic ⚡⚡⚡ :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;perl -Mojo -E &lt;span class="s2"&gt;&amp;quot;my &lt;/span&gt;&lt;span class="nv"&gt;$conf&lt;/span&gt;&lt;span class="s2"&gt; = eval(f(qw(/home/koyomi/lanraragi/lrr.conf))-&amp;gt;slurp); say %&lt;/span&gt;&lt;span class="nv"&gt;$conf&lt;/span&gt;&lt;span class="s2"&gt;{version}.q/ - &amp;#39;/.%&lt;/span&gt;&lt;span class="nv"&gt;$conf&lt;/span&gt;&lt;span class="s2"&gt;{version_name}.q/&amp;#39;/ &amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This version is then displayed on the GUI.&lt;br&gt;
I &lt;strong&gt;could've&lt;/strong&gt; used &lt;code&gt;WslLaunch&lt;/code&gt; here to run my one-liner, but the function requires a rather hefty baggage to get started(more on that later). The &lt;code&gt;WslLaunchInteractive&lt;/code&gt; variant just works, but it pops up a console and we don't want any of that.  &lt;/p&gt;
&lt;p&gt;Instead, I simply run my one-liner in &lt;code&gt;wsl.exe&lt;/code&gt; and fetch the text result.&lt;/p&gt;
&lt;h3&gt;📂 Set up a symbolic link to the content folder specified in the GUI&lt;/h3&gt;
&lt;p&gt;When the user gives us his content folder, it's a pure Windows path. &lt;br&gt;
I quickly convert it to its WSL equivalent and setup a symlink to a designated folder in the Linux filesystem:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// Map the user&amp;#39;s content folder to its WSL equivalent&lt;/span&gt;
&lt;span class="c1"&gt;// This means lowercasing the drive letter, removing the : and replacing every \ by a /.&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;winPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentFolder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;contentFolder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;/mnt/&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Char&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToLowerInvariant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;winPath&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;winPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;\\&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;🆙 Start and stop the Linux server&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;WslLaunch&lt;/code&gt; is used to start the server. I use the same customized init system as in my Docker images, so all the setup save for the symlinks done above is already there. The function requires opened STDIN/STDOUT/STDERR streams to pipe the process into, so I &lt;a href="https://docs.microsoft.com/en-us/windows/console/allocconsole"&gt;create a hidden console&lt;/a&gt; to magically give my GUI usable console streams.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There's no API function available to terminate/stop a currently running WSL distro. While &lt;code&gt;WslLaunch&lt;/code&gt; gives you the PID of the process you start in WSL, Linux processes aren't hard-tethered to their parents. I always seemed to get a bunch of child processes laying around even after killing said PID, so I added a call to &lt;code&gt;wslconfig.exe /terminate&lt;/code&gt; to the mix. This is the one thing that doesn't work in 1803, since &lt;a href="https://github.com/microsoft/WSL/issues/3253"&gt;/terminate was sneakily added in an Insider build.&lt;/a&gt; ¯_(ツ)_/¯&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using an extra &lt;a href="https://www.nuget.org/packages/HideConsoleOnClose/"&gt;NuGet&lt;/a&gt; package, I got a user-friendly Log Console without even really trying at all. 🥤😎&lt;/p&gt;
&lt;p&gt;&lt;img alt="it's not WSL without a terminal hidden somewhere is it" src="https://tvc-16.science/images/karen-dark.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;This is just the console created from &lt;code&gt;AllocConsole&lt;/code&gt;, shown and hidden when the user clicks the "Log Console" button. The NuGet package adds some extra sorcery to make sure closing the console simply hides it again.&lt;/p&gt;
&lt;h2&gt;Final thoughts&lt;/h2&gt;
&lt;p&gt;Packing up an entire WSL distribution seemed way too clumsy to pull off at first, but Microsoft's tooling actually makes disposable distros pretty easy to do! I could almost put this on the store if it wasn't for the fact I'm horribly twisting what WSL was originally made for. &lt;em&gt;Please forgive me Rich Turner for I have sinned&lt;/em&gt;  &lt;/p&gt;
&lt;p&gt;My Docker images are pretty lightweight since I went all in on Alpine a few months back, so the full downloadable Windows package clocks in at &lt;strong&gt;85 MBs&lt;/strong&gt;, with a few more optimizations possible in the future.&lt;br&gt;
That's less than your average Electron application. 😏  &lt;/p&gt;
&lt;p&gt;You can check the full source code for this crazy thing &lt;a href="https://github.com/Difegue/Karen"&gt;here.&lt;/a&gt; Thanks for reading!&lt;/p&gt;</content><category term="LANraragi"></category><category term="wsl"></category><category term="wpf"></category><category term="c#"></category></entry><entry><title>Masquerading a WSL Distro as a Windows Port, Part 1</title><link href="https://tvc-16.science/wsl-karen-1.html" rel="alternate"></link><published>2019-06-05T00:00:00+02:00</published><updated>2019-06-05T00:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2019-06-05:/wsl-karen-1.html</id><summary type="html">&lt;p&gt;That's cheating! But is it really seamless?&lt;/p&gt;</summary><content type="html">&lt;p&gt;One of the major complaints I often get with &lt;a href="https://github.com/Difegue/LANraragi"&gt;LANraragi&lt;/a&gt; is that it's a major pain to install. And I tend to agree!&lt;br&gt;
The software runs as a web server through &lt;a href="http://mojolicious.org"&gt;Mojolicious&lt;/a&gt;, paired with a Redis database.  &lt;/p&gt;
&lt;p&gt;Thanks to the power of Docker images, I've made it much easier for Linux users, but Windows users were still stuck dealing with Virtual Machines or the handy-but-not-really-stable &lt;a href="https://docs.docker.com/docker-for-windows/"&gt;Docker for Windows.&lt;/a&gt; (Which is pretty much a VM too, all things considered.)  &lt;/p&gt;
&lt;p&gt;I got requests for a good Windows version often enough that at one brief point I hacked up a full source port of the app:&lt;br&gt;
&lt;img alt="peak 2016 here lads" src="https://tvc-16.science/images/quickstarter.jpg"&gt;&lt;br&gt;
It relied on a Redis port &lt;a href="https://github.com/microsoftarchive/redis"&gt;made and subsequently abandoned by Microsoft&lt;/a&gt;, and a &lt;strong&gt;lot&lt;/strong&gt; of batch file glue.&lt;br&gt;
Lovely, but hey it worked! Until some dude strolled along with his Shift-JIS encoded Windows OS and subsequently broke the entire archive detection code.&lt;br&gt;
&lt;img alt="i fucking hate codepages" src="https://tvc-16.science/images/coolmeme.jpg"&gt;&lt;br&gt;
Then Mojolicious itself dropped Windows support, things happened, and I pretty much had to kill the thing because it wasn't even standing on its own anymore.  &lt;/p&gt;
&lt;p&gt;Meanwhile, &lt;a href="https://docs.microsoft.com/en-us/windows/wsl/about"&gt;WSL&lt;/a&gt; was making headlines. I've been using it for a while already to do dev work on LANraragi and it's solid. I read &lt;a href="https://medium.com/@hoxunn/wsl-docker-custom-distro-2-0-730fd97fe72e"&gt;this blogpost&lt;/a&gt; recently, which put a crazy and certainly unintended spin on the concept in my head:  &lt;/p&gt;
&lt;h2&gt;Why not just ship WSL as a Windows port?&lt;/h2&gt;
&lt;p&gt;Generating lightweight WSL distros out of Docker images seemed easy with my current workflow, and past that it'd just be a matter of providing users an easy, Windows-like interface to the server stowed away in the distro.  &lt;/p&gt;
&lt;p&gt;I'd basically have three big parts to work on for this:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;👉 &lt;strong&gt;Build&lt;/strong&gt; a WSL distro (basically just a Linux rootfs) out of my existing Docker image  &lt;/li&gt;
&lt;li&gt;👉 &lt;strong&gt;Register&lt;/strong&gt; said distro on the user's computer through the WSL API, and install a basic GUI tool  &lt;/li&gt;
&lt;li&gt;👉 Use the GUI tool to &lt;strong&gt;interop&lt;/strong&gt; with the distro, mapping Windows directories and settings to the Linux world and starting the webserver  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I'll spoil you the results by saying it &lt;em&gt;totally heckin' works&lt;/em&gt; and is currently out in beta:&lt;br&gt;
&lt;img alt="bazinger z" src="https://tvc-16.science/images/karen.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;One codebase, putting out Docker images used by both Linux and Windows users in "slightly" different ways.&lt;br&gt;
I'll dedicate the rest of this blogpost - and its followup - to depicting the major hiccups I encountered building this thing out.&lt;/p&gt;
&lt;h1&gt;Building the WSL distro in Github Actions&lt;/h1&gt;
&lt;p&gt;I do all the continuous integration for LANraragi in &lt;a href="https://github.com/features/actions"&gt;Github Actions&lt;/a&gt; these days, so it seemed an easy choice to add the WSL distro build to it.&lt;br&gt;
According to Nunix's blogpost, it's really just a matter of running &lt;a href="https://docs.docker.com/engine/reference/commandline/export/"&gt;&lt;code&gt;docker export&lt;/code&gt;&lt;/a&gt; on the images I'm already building.&lt;br&gt;
Said command gives out a .tar containing the full Linux filesystem, for readymade import into WSL. Should be easy, righ-&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;Error response from daemon: This Docker operation is forbidden by GitHub Actions,&lt;/span&gt;
&lt;span class="err"&gt;you can find documentation at https://developer.github.com/actions/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;There's actually no warning about this anywhere in the Actions documentation, but since Actions run in premade Docker environments, a few Docker commands are &lt;a href="https://github.com/actions/docker/issues/7#issuecomment-459808907"&gt;locked out&lt;/a&gt; to prevent potential sandbox escapes.  &lt;/p&gt;
&lt;p&gt;And of course, the all-essential &lt;code&gt;export&lt;/code&gt; is part of those. I had to switch to using &lt;a href="https://docs.docker.com/engine/reference/commandline/save/"&gt;&lt;code&gt;docker save&lt;/code&gt;&lt;/a&gt;, which also exports your image as a .tar archive, but &lt;strong&gt;not&lt;/strong&gt; as a ready-to-use filesystem.&lt;br&gt;
Instead, it exports each layer of the Docker image as a separate .tar, then bundles up all of those.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="bazinger z" src="https://tvc-16.science/images/export_vs_save.png"&gt;&lt;br&gt;
This is convenient for reimporting into Docker itself, but not at all for what I want here.  &lt;/p&gt;
&lt;p&gt;The hackjob solution I ended up using was to manually squash all the layers post-save:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;#&lt;/span&gt; &lt;span class="n"&gt;Export&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="k"&gt;extract&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;resulting&lt;/span&gt; &lt;span class="n"&gt;tarball&lt;/span&gt;
&lt;span class="n"&gt;docker&lt;/span&gt; &lt;span class="n"&gt;save&lt;/span&gt; &lt;span class="c1"&gt;--output save.tar difegue/lanraragi&lt;/span&gt;
&lt;span class="n"&gt;tar&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;xf&lt;/span&gt; &lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tar&lt;/span&gt; &lt;span class="c1"&gt;--wildcards &amp;quot;*.tar&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;mkdir&lt;/span&gt; &lt;span class="n"&gt;squashed&lt;/span&gt;

&lt;span class="o"&gt;#&lt;/span&gt; &lt;span class="n"&gt;Find&lt;/span&gt; &lt;span class="k"&gt;all&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tar&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;them&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;same&lt;/span&gt; &lt;span class="n"&gt;squashed&lt;/span&gt; &lt;span class="n"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="n"&gt;repack&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tar&lt;/span&gt;
&lt;span class="n"&gt;find&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;mindepth&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;iname&lt;/span&gt; &lt;span class="ss"&gt;&amp;quot;*.tar&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;print0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;exec&lt;/span&gt; &lt;span class="n"&gt;tar&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;xf&lt;/span&gt; &lt;span class="err"&gt;{}&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;C&lt;/span&gt; &lt;span class="n"&gt;squashed&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="n"&gt;find&lt;/span&gt; &lt;span class="n"&gt;squashed&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;printf&lt;/span&gt; &lt;span class="ss"&gt;&amp;quot;%P\n&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;tar&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;cf&lt;/span&gt; &lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tar&lt;/span&gt; &lt;span class="c1"&gt;--no-recursion -C squashed -T -&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Well, got my distro. What now?&lt;/p&gt;
&lt;h1&gt;Installing a WSL distro seamlessly to the enduser's machine&lt;/h1&gt;
&lt;p&gt;Unlike with my previous foray, I didn't(couldn't) bother supporting Windows 7 or 8 here.&lt;br&gt;
It's straight bleedin' edge Windows 10, which means &lt;strong&gt;PowerShell&lt;/strong&gt; is free game instead of wrangling old .bat scripts.  &lt;/p&gt;
&lt;p&gt;You can check the full source for the install/uninstall scripts &lt;a href="https://github.com/Difegue/Karen/blob/master/Karen/Karen-Installer.ps1"&gt;here&lt;/a&gt;  and &lt;a href="https://github.com/Difegue/Karen/blob/master/Karen/Karen-Uninstaller.ps1"&gt;here.&lt;/a&gt;&lt;br&gt;
I haven't bothered wrapping those in proper executable installers yet: Writing .MSIs is a pain!&lt;br&gt;
(I don't envy macOS devs often, but I sure could use something like &lt;a href="https://sveinbjorn.org/platypus"&gt;Platypus&lt;/a&gt; to quickly wrap scripts in nice-looking executables.)&lt;/p&gt;
&lt;p&gt;I initially wrote the scripts fully using &lt;code&gt;wsl.exe&lt;/code&gt; to unregister/register/terminate the LANraragi distro, but quickly realized that as nice as &lt;code&gt;wsl.exe&lt;/code&gt; was, it's completely useless in Windows 10 versions under 1903, the April 2019 Update.&lt;br&gt;
Here's a quick breakdown of available WSL command tools in Win10 and their featureset alongside versions.  &lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Win10 version/Tool&lt;/th&gt;
&lt;th&gt;wsl.exe&lt;/th&gt;
&lt;th&gt;wslconfig.exe&lt;/th&gt;
&lt;th&gt;wslapi.h (direct API call)&lt;/th&gt;
&lt;th&gt;lxrun&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1709 (Fall CU)&lt;/td&gt;
&lt;td&gt;Execute commands in a distro&lt;/td&gt;
&lt;td&gt;List distros, unregister, set as default&lt;/td&gt;
&lt;td&gt;List, register/unregister, execute commands&lt;/td&gt;
&lt;td&gt;(Ubuntu only) Install/Uninstall, Update&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1803 (RS4)&lt;/td&gt;
&lt;td&gt;Nothing new&lt;/td&gt;
&lt;td&gt;Nothing new&lt;/td&gt;
&lt;td&gt;Nothing new&lt;/td&gt;
&lt;td&gt;Dead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1809 (RS5)&lt;/td&gt;
&lt;td&gt;Nothing new&lt;/td&gt;
&lt;td&gt;Terminate a running distro&lt;/td&gt;
&lt;td&gt;Nothing new&lt;/td&gt;
&lt;td&gt;Dead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1903 (You are here)&lt;/td&gt;
&lt;td&gt;List distros, register/unregister, terminate, set as default&lt;/td&gt;
&lt;td&gt;Nothing new&lt;/td&gt;
&lt;td&gt;Nothing new&lt;/td&gt;
&lt;td&gt;Dead&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;"Registering" a distro in WSL terms means basically unpacking the Linux filesystem somewhere and registering the resulting folder as containing a Linux distribution.  &lt;/p&gt;
&lt;p&gt;&lt;code&gt;wslconfig.exe&lt;/code&gt; is a more capable alternative for older Windows versions, but it doesn't allow registering a distro.&lt;br&gt;
As for direct API calls, the &lt;a href="https://docs.microsoft.com/en-us/windows/desktop/api/wslapi/nf-wslapi-wslregisterdistribution"&gt;registration function&lt;/a&gt; doesn't allow you to pick a folder to unpack the distro to.  &lt;/p&gt;
&lt;p&gt;Since I wanted to target at least 1803, the latest LTSC release, I ended up bundling &lt;a href="https://github.com/DDoSolitary/LxRunOffline"&gt;LxRunOffline&lt;/a&gt; alongside the installer to perform the distro registration easily. It also features a nice progress bar &lt;code&gt;wsl.exe&lt;/code&gt; doesn't have, so at least it looks good. 👀  &lt;/p&gt;
&lt;p&gt;&lt;img alt="lookin cool unspecified open source software to use on wsl" src="https://tvc-16.science/images/ps.png"&gt;  &lt;/p&gt;
&lt;p&gt;Past this, I'm basically just doing usual Installer-y things: Copying the GUI executable to a designated folder in AppData, adding a Start Menu shortcut, checking WSL is installed and that we're on a 64-bit OS, the works.&lt;/p&gt;
&lt;p&gt;More on the GUI tool itself in the &lt;a href="https://tvc-16.science/wsl-karen-2.html"&gt;next blog post!&lt;/a&gt;&lt;/p&gt;</content><category term="LANraragi"></category><category term="wsl"></category><category term="github actions"></category></entry><entry><title>Building this blog with Pelican, Github Actions and Caddy</title><link href="https://tvc-16.science/blogopolis-docker.html" rel="alternate"></link><published>2019-04-25T22:00:00+02:00</published><updated>2019-04-25T22:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2019-04-25:/blogopolis-docker.html</id><summary type="html">&lt;p&gt;Writing this also allows me to check that this continuous integration thingamabob is working as expected.&lt;/p&gt;</summary><content type="html">&lt;p&gt;The original, blogless iteration of TVC-16 was hosted on a bog-standard Apache2 server.&lt;br&gt;
&lt;a href="https://lrr.tvc-16.science"&gt;My&lt;/a&gt; &lt;a href="https://diy.tvc-16.science"&gt;subdomains&lt;/a&gt; are all directly linked to Docker containers, so that very basic configuration was enough.  &lt;/p&gt;
&lt;p&gt;For blog articles though, I wanted to have something easier on the server maintenance than having to copy my html by hand. I originally considered using &lt;a href="https://ghost.org/"&gt;Ghost&lt;/a&gt; in yet another Docker container and doing all my writing in it, but it'd eventually be a pain to export/reimport all the data in case of a server switch.  &lt;/p&gt;
&lt;p&gt;I decided to use a static site generator like all the nerds instead and went with &lt;a href="https://blog.getpelican.com/"&gt;Pelican&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Classic stuff so far, but I don't think many people are using &lt;a href="https://github.com/features/actions/"&gt;Github Actions&lt;/a&gt; for their deploying yet despite the fact that it's &lt;strong&gt;rad&lt;/strong&gt;! &lt;sub&gt;&lt;sup&gt;well the fact it's in beta might be a blocker too for some people&lt;/sup&gt;&lt;/sub&gt;&lt;/p&gt;
&lt;h1&gt;Building in Pelican&lt;/h1&gt;
&lt;p&gt;I didn't do anything out of the ordinary in the Pelican side -- this is a hella standard blog if you ignore the theme. (Which I wholelifted from the previous nonblog version, so I can thank 2018 myself for putting in all the CSS work).  &lt;/p&gt;
&lt;p&gt;The uptime counter at the end is still generated by a crontab on the server itself and dumped to a textfile, which is just read by the JS of the website:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;* * * * * uptime | awk -F&amp;#39;( |,|:)+&amp;#39; &amp;#39;{print $6,$7}&amp;#39; &amp;gt; /var/www/html/uptime&lt;/span&gt;
&lt;span class="err"&gt;* * * * * uptime | awk -F&amp;#39;( |,|:)+&amp;#39; &amp;#39;{print $6,$7&amp;quot;,&amp;quot;,$8,&amp;quot;hours,&amp;quot;,$9,&amp;quot;minutes.&amp;quot;}&amp;#39; &amp;gt; /var/www/html/uptimedetail&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Nothing wrong with a bit of Unix philosophy every now and then.  &lt;/p&gt;
&lt;p&gt;The only thing I did with Pelican that's kinda obscure unless you read the configuration file specs is having a custom template render to a custom page (the Projects):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;# custom page generated with a jinja2 template&lt;/span&gt;
&lt;span class="err"&gt;TEMPLATE_PAGES = {&amp;#39;projects.html&amp;#39;: &amp;#39;projects.html&amp;#39;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I dropped the entire Pelican sources into a &lt;a href="https://github.com/Difegue/TVC-16"&gt;Github repo&lt;/a&gt;, and now it's on to the fun part:&lt;/p&gt;
&lt;h1&gt;Hot Github integrations&lt;/h1&gt;
&lt;p&gt;I already have some experience in GH Actions from writing the test suite for &lt;a href="https://github.com/Difegue/LANraragi"&gt;LANraragi&lt;/a&gt;, so I was expecting the setup time for this to be fairly quick. Turns out it went faster than expected!  &lt;/p&gt;
&lt;p&gt;&lt;img alt="hot github actions near your town" src="https://tvc-16.science/images/tvc-16-actions.png"&gt;  &lt;/p&gt;
&lt;p&gt;This building process is triggered on every push to &lt;em&gt;master&lt;/em&gt; and is as simple as it looks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Step 1&lt;/strong&gt;: Run Pelican on the repo to build the website. This is &lt;a href="https://github.com/Difegue/TVC-16/tree/master/.github/action-pelican"&gt;super basic&lt;/a&gt;, I'm just grabbing a pelican docker image and building away.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Step 2&lt;/strong&gt;: Force-push the built &lt;em&gt;output&lt;/em&gt; folder to the &lt;em&gt;gh-pages&lt;/em&gt; branch of the repo.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The output branch is named &lt;em&gt;gh-pages&lt;/em&gt; because I conveniently re-used an &lt;a href="https://github.com/JasonEtco/push-to-gh-pages"&gt;existing Action&lt;/a&gt; to go faster. I don't actually use Github Pages but hey, no harm done!&lt;/p&gt;
&lt;p&gt;🔮 Another integration I made is with the comment system: The way Microsoft made its MSDN comment system &lt;a href="https://docs.microsoft.com/en-us/teamblog/a-new-feedback-system-is-coming-to-docs"&gt;basically just Github Issues&lt;/a&gt; is pretty inspiring.&lt;br&gt;
&lt;a href="https://utteranc.es/"&gt;Utterances&lt;/a&gt; is an open source variant I added to my Pelican template in about 5 seconds.  &lt;/p&gt;
&lt;p&gt;As a result, both articles and comments are backed up on Github. If I ever have to switch to another Git repo provider I'd likely lose the comments, but I'm banking on those not being too important.&lt;/p&gt;
&lt;h1&gt;Deploying on TVC-16 through Caddy&lt;/h1&gt;
&lt;p&gt;A friend recently told me that I was being an old fart by still using Apache2 in &lt;em&gt;the current year&lt;/em&gt;, so I swapped it out for &lt;a href="https://caddyserver.com/"&gt;Caddy&lt;/a&gt;.&lt;br&gt;
Caddy's big advantage here lies in its &lt;a href="https://caddyserver.com/docs/http.git"&gt;&lt;code&gt;http.git&lt;/code&gt;&lt;/a&gt; plugin, which automagically pulls the latest built version of the website from Github.  &lt;/p&gt;
&lt;p&gt;Here's the Caddyfile I use:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;tvc-16.science, www.tvc-16.science {&lt;/span&gt;
&lt;span class="err"&gt;    root /var/www/html&lt;/span&gt;
&lt;span class="err"&gt;    git {&lt;/span&gt;
&lt;span class="err"&gt;        repo     github.com/difegue/TVC-16&lt;/span&gt;
&lt;span class="err"&gt;        branch   gh-pages&lt;/span&gt;
&lt;span class="err"&gt;        pull-args --allow-unrelated-histories -s recursive -X theirs&lt;/span&gt;
&lt;span class="err"&gt;        hook /webhook mywebhooksecret&lt;/span&gt;
&lt;span class="err"&gt;    }&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;When adding in the webhook on the Github side, make sure to set the Content-Type to &lt;code&gt;application/json&lt;/code&gt;, your hooks will fail otherwise.&lt;/p&gt;
&lt;p&gt;The biggest issue I encountered here was that http.git does a &lt;code&gt;git clone&lt;/code&gt; when deploying your website for the first time, then only does &lt;code&gt;git pull&lt;/code&gt; to update it. Which is very sane if you're pulling the sources, then building the website on the server itself.  &lt;/p&gt;
&lt;p&gt;What I'm pulling however is the already-built website, which is force-pushed by the Github Actions bot every time. As such, the Git history is getting continuously broken, making regular Git pulls fail instantly.&lt;/p&gt;
&lt;p&gt;Adding the pull arguments you see above fixes this, by telling Git to disregard history and always use remote files in case of conflict.&lt;/p&gt;
&lt;h1&gt;Final thoughts&lt;/h1&gt;
&lt;p&gt;I found out about the &lt;a href="https://api.github.com/zen"&gt;Github Zen endpoint&lt;/a&gt; when adding in webhooks. It just spits out fortune cookie sentences on each call.  &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;12/06 Edit:&lt;/strong&gt; While it's not very well documented, you can add custom metadata to your markdown pages and put it to work in Pelican's templates.&lt;br&gt;
I wanted to change the OpenGraph image for each blogpost to a specific one for better &lt;code&gt;social media presence&lt;/code&gt;, so I easily added a &lt;code&gt;HeroImage&lt;/code&gt; property to my articles:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Running&lt;/span&gt; &lt;span class="n"&gt;cool&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;retro&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Windows&lt;/span&gt; &lt;span class="n"&gt;through&lt;/span&gt; &lt;span class="n"&gt;WSL&lt;/span&gt;
&lt;span class="o"&gt;[...]&lt;/span&gt;
&lt;span class="n"&gt;Authors&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Difegue&lt;/span&gt;
&lt;span class="n"&gt;HeroImage&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;crt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;win&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jpg&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And used it in my template.  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;og:image&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{{ SITEURL }}/{{ article.heroimage }}&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Good stuff all around!&lt;/p&gt;</content><category term="Software"></category><category term="docker"></category><category term="pelican"></category><category term="caddy"></category><category term="github actions"></category></entry><entry><title>Running cool-retro-term in Windows through WSL1</title><link href="https://tvc-16.science/cool-retro-term-wsl.html" rel="alternate"></link><published>2019-04-21T20:00:00+02:00</published><updated>2019-04-21T20:00:00+02:00</updated><author><name>Difegue</name></author><id>tag:tvc-16.science,2019-04-21:/cool-retro-term-wsl.html</id><summary type="html">&lt;p&gt;Javascript terminal emulators have nothing on this.&lt;/p&gt;</summary><content type="html">&lt;h1&gt;2022 Update&lt;/h1&gt;
&lt;p&gt;Check &lt;a href="./cool-retro-term-wsl2.html"&gt;this article&lt;/a&gt; for updated and much simpler instructions using WSL2+WSLg. 🥳&lt;br&gt;
Or you can keep reading below if you're stuck with WSL1 for some reason.  &lt;/p&gt;
&lt;p&gt;I often work with Linux environments through SSH on my Windows machine. As most people who use the command line on a semi-regular basis, I like looking at available alternatives/customizations to the stock terminal emulator.  &lt;/p&gt;
&lt;p&gt;On Windows, you've got a lot of solid alternatives such as cmder or the myriad of cool-looking emulators using web technologies under the hood. (I especially like &lt;a href="https://github.com/felixse/FluentTerminal"&gt;Fluent Terminal&lt;/a&gt;, which really gets my Win10 design language boner going!)  &lt;/p&gt;
&lt;p&gt;But this stuff man, it doesn't have the &lt;em&gt;bling&lt;/em&gt;. The &lt;em&gt;hipster cred&lt;/em&gt;.&lt;br&gt;
&lt;img alt="whoa dude now this is good stuff" src="https://tvc-16.science/images/crt-intro.png"&gt;  &lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/Swordfish90/cool-retro-term"&gt;cool-retro-term&lt;/a&gt; is a terminal emulator I often wished I had on Windows, for no other reason than to look cool. Nobody really ever bothered to port it to run on Windows however(and I wouldn't do it either tbh), so I swallowed my dreams of being an &lt;em&gt;epic hacker&lt;/em&gt; and just opened up PuTTY for the umpteenth time.  &lt;/p&gt;
&lt;p&gt;Until now.&lt;br&gt;
&lt;img alt="bazinga" src="https://tvc-16.science/images/crt-win.jpg"&gt;  &lt;/p&gt;
&lt;p&gt;The Windows Subsystem for Linux nowadays just allows one to run the original emulator as-is with a simple X server port for Windows. I'll be giving a short how-to list here, tested on Windows 10 1809.&lt;/p&gt;
&lt;h1&gt;Activate WSL and download the required Linux dependencies&lt;/h1&gt;
&lt;p&gt;I'll let &lt;a href="https://docs.microsoft.com/en-us/windows/wsl/install-win10"&gt;Microsoft speak for me&lt;/a&gt; on the first point. I used Ubuntu as a base distro, but you can probably manage with another one.&lt;br&gt;
Once in your WSL terminal, install the few dependencies you'll need:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;sudo apt-get update&lt;/span&gt;
&lt;span class="err"&gt;sudo apt-get install libgl1 xfce4-terminal&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;xfce4-terminal is a hefty download, but without it I was unable to get keyboard input to work in CRT. The errors were linked to XKB, so you might be able to manage by only installing xkb-data or similar packages.  &lt;/p&gt;
&lt;h1&gt;Download the cool-retro-term AppImage and extract it&lt;/h1&gt;
&lt;p&gt;The AppImage conveniently bundles up Qt and everything needed. WSL doesn't have fuse support however, so you'll have to extract it instead of running it as-is. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;wget https://github.com/Swordfish90/cool-retro-term/releases/download/1.1.1/Cool-Retro-Term-1.1.1-x86_64.AppImage&lt;/span&gt;
&lt;span class="err"&gt;chmod a+x Cool-Retro-Term-1.1.1-x86_64.AppImage&lt;/span&gt;
&lt;span class="err"&gt;./Cool-Retro-Term-1.1.1-x86_64.AppImage --appimage-extract&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Close your Linux CLI for now.&lt;/p&gt;
&lt;h1&gt;Boot 'er up with Xming&lt;/h1&gt;
&lt;p&gt;👉 Download and install &lt;a href="https://sourceforge.net/projects/xming/"&gt;Xming&lt;/a&gt; on the Windows side.  &lt;/p&gt;
&lt;p&gt;vcXsrv is another port of the X server to Windows that's often recommended in &lt;a href="https://www.ctrl.blog/entry/how-to-x-on-wsl"&gt;X-on-WSL blogposts&lt;/a&gt;, but it didn't work with CRT during my experiments. You can also try &lt;a href="https://sourceforge.net/projects/freexer/"&gt;FreeXer&lt;/a&gt;, or Cygwin's X server.  &lt;/p&gt;
&lt;p&gt;Once installed, just start it up and go back to Linux:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;export DISPLAY=:0&lt;/span&gt;
&lt;span class="err"&gt;cd squashfs-root&lt;/span&gt;
&lt;span class="err"&gt;./AppRun&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img alt="lookin' hefty on cpu joker" src="https://tvc-16.science/images/crt-cpu.jpg"&gt;&lt;br&gt;
From here on, you can right-click on the terminal to load a different graphical profile or make a custom one yourself.&lt;br&gt;
Basically everything works as-is -- even urxvt mouse tracking!&lt;/p&gt;
&lt;h1&gt;Automating it all with some scripting&lt;/h1&gt;
&lt;p&gt;I jerry-rigged a pair of .bat/.sh scripts to start both Xming on the Windows side and CRT on the WSL side.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;cool-retro-term.bat&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;start &amp;quot;&amp;quot; &amp;quot;C:\Program Files (x86)\Xming\Xming.exe&amp;quot; :0 -clipboard -multiwindow&lt;/span&gt;
&lt;span class="err"&gt;start /min wsl -d ubuntu ./crt.sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;crt.sh&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;cd ~/squashfs-root &amp;amp;&amp;amp; export DISPLAY=:0.0 &amp;amp;&amp;amp; ./AppRun &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h1&gt;Caveats&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;WSL &lt;a href="https://github.com/Microsoft/WSL/issues/829"&gt;doesn't have GPU acceleration yet&lt;/a&gt;. This is entirely running in software and as you can see in the &lt;code&gt;htop&lt;/code&gt; above, easily eats more CPU than those newfangled javascript terminals. 😐 &lt;/li&gt;
&lt;li&gt;Pasting text into cool-retro-term works, but copying text from it doesn't. &lt;/li&gt;
&lt;li&gt;You can encounter a segfault or two if you mess with the graphical settings too much in one session. Once a profile is saved however, I've encountered no crashes in the few hours I spent playing with this.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All things considered, this is basically a party trick at the moment and nothing else.&lt;br&gt;
It's pretty cool with &lt;code&gt;ncmpcpp&lt;/code&gt; though!&lt;/p&gt;</content><category term="Cool Tricks"></category><category term="wsl"></category><category term="cool-retro-term"></category><category term="xming"></category><category term="terminal"></category><category term="crt"></category></entry></feed>