thedave.dev

hax0r theme? really Dave?

Console.WriteLine("Hello, World!");
//Console.WriteLine("I'm hosted with GitHub Pages.");
Console.WriteLine("I'm hosted with Cloudflare Pages.");

/about:me/ or /about:contact/ or since you’re here, why not go see a bird1?


MineSweeper... In batch?

I wrote a version of MineSweeper in batch. It’s not the most efficient or the most user-friendly, but it works. It’s a fun little project that I thought I’d share.

Where to get it?

thedaveCA/stupid-batch-tricks. Just grab everything, it requires some stuff from the _helpers directory as well, so either clone the whole repo or download the .zip.

It has only been tested on Windows 11, under both Windows Terminal and the Command Prompt. An early prototype worked fine on Windows 10 as well, but I have not tested the final release. It should work on Windows 10 >= 1909, but I make no guarantees.

Controls

Command Action Command Action
w s Move around the board a d Move around the board
x Flag as safe/mine z Reveal if we found a mine!
r Draw the board R Clear and refresh the screen
0 New game with: No mines 1 New game with: ALL THE MINES! 100% mines
2 New game with: A lot of mines 3 New game with: A few mines
c Clear the board q Quit
shift-C DEBUG: Enable cheat mode shift-V DEBUG: Display variables

The controls are… A bit janky, but they work. I can’t trivially use arrow keys batch, so I had to use w, a, s, and d for movement.

  • Cheat mode reveals three separate boards: The mines, the flags, the pre-calculated numbers.
  • Display variables shows the gamestate varibles, excluding the actual board.

Weird/fun implementation details

  • It mainly runs internally in cmd.exe, but it does require choice.exe and findstr.exe, both of which are included in Windows. All other dependencies are just other .cmd batch files.
  • Batch extensions are heavily used, so it will not work in MS-DOS or other non-Windows environments. Sorry, FreeDOS user.

  • Game state was a unique challenge.
    • Implemented 100% in memory, no temporary files are used for tracking game state.
    • MineSweeper is a textbook 2D array project, but…
  • Batch doesn’t have arrays.
    • Please, I cannot stress this enough, do not tell my batch files that it doesn’t have arrays, because it just pretends that arrays are real and runs with it.
    • While not used here, my Helpers\CleanEnvironmentVariables.cmd script can be used to selectively clear varibles matching patterns. It could be used to clear a one dimensional not-really-an-array, or the first dimension of a two-dimensional not-really-an-array, but I wouldn’t recommend it so that version of the script is not published anywhere.
  • Batch has no concept of objects or classes.
    • I store three versions of each board, and determine the game state by comparing them.
    • game_board_mines: The mines are prepresented by 0 or 1.
    • game_board_state: The location of the flags are stored using the the visible flag characters to speed up rendering.
    • game_board_count: The number of nearby cells with mines is pre-calculated into a table.
  • The way the boards are stored has some interesting side effects.
    • game_board_state is directly displayed to the user, to speed up rendering.
    • To check if a cell has been flagged, check if game_board_state[x][y] == %game_visual_flag%.
    • To determine if a cell has been revealed, check if game_board_state[x][y] == game_board_count[x][y].
    • Checking if a cell has a mine is easy, game_board_mines[x][y] == 1.
    • To reveal a cell, check game_board_mines[x][y] to see if it is a mine, if not, set game_board_state[x][y] = game_board_count[x][y].
    • The visible flags are all variables, and can be emojis or other characters, but alignment on the console is hard and emoji support cannot be reliably detected.
    • I said you don’t have arrays, so you can’t actually do game_board_mines[x][y], it actually looks like if "!game_board_state[%game_position_x%][%game_position_y%]!" == "%game_visual_flag%".
  • The board is variable size, with no fixed limits. That doesn’t mean no limits, just no fixed limits. It is slow, because batch.
  • I did implement a flood-fill algorthim that will automatically reveal nearby safe cells as a timesaver. I guess it should be optional, it’s a pig on large boards. Since I don’t have proper arrays, I just keep scanning the board over and over until it doesn’t find any new cells to reveal, there’s probably a better way, but the complexity goes up, fast.
  • There is a live display of the time and game timer which updates every second.
    • Except batch has no timers, so it’s just a loop that sleeps for a second.
    • Batch has no sleep command either.
    • The “timer” ends early when the user presses a key.
    • The timer can actually trigger more than once a second if the user is fast enough.
    • The timer is therefore limited to incrementing no more than once per second, but it is not guaranteed to count every second.
    • This is fine, we time the amount of time we are waiting for the user, not the time the game is processing.
    • It would be possible to store the start time and calculate the elapsed time, but that would make it impossible to implement a pause button.
    • I didn’t actually implement a pause button, but it is enough that I could. If you don’t care what it looks like, literally just pause would be fine, this would stop the timer, and the game would redraw the screen without further action.
  • The whole board is actually re-drawn every second, but you won’t see the screen flicker.
    • … and not because it draws to the screen amazingly fast, either.
    • Turn on the two debug options and you’ll see the variables flicker, but not the board or menu.
    • I didn’t bother to implement the anti-flicker technique for the debug stuff, because it’s debug stuff.
    • Also the debug stuff flickering is actually like a heatbeat so I can see when it crashes. It’s a feature.
    • Calling echo is expensive, so when possible I assemble as much as I can into memory first and dump it at once.
    • Extra large boards will probably break the way the board is echoed long before you encounter any other limits.
    • Technically the entire screen including the menu is redrawn, which allows for variable sized elements and popover menus and messages.
    • I didn’t implement variable sized elements or popover menus.
    • Board size is hardcoded, the menu does let you start a new game with a selected number of mines. Both are set as variables near the top of the script if you want to tweak. I set y=x by default, but the game should play with non-square boards.
    • Game messages are displayed briefly at the top, and if you miss them, you miss them.
    • It will handle console resizes, and resize the display accordingly. The main game board will not resize, but the clock and timer will as a proof of concept.
    • When possible, it will restore your scrollback buffer, if you exit cleanly.
    • If you decide to do any coding, run cmd /c minesweeper.cmd rather than calling it directly, this is the only way to guarantee that variables do not persist after the game crashes. setlocal usually works, but it is not foolproof.

Should you play this game?

No.

Should you look at the code?

Also no.

Why does it exist?

I guess I didn’t have a headache one day and said to myself, wouldn’t I like to have a headache?

And I was doing some other stuff in batch that would benefit from some column alignment and this seemed like one way to play around. I have some basic functions in _helpers and _snippets that will pad strings, align columns, and other goodies.

Colours and other ANSI-escape codes are used extensively, and require a _helper script to load. This is entirely essential, there is no way to display the board without it. Okay, you could rewrite it, but it would mean clearing the screen which causes an unacceptable amount of flicker.

It is “batch” but not MS-DOS compatible batch, it uses various Windows-specific features that were added to more modern versions of Windows.

What’s next?

Hopefully not batch.

Okay, but what else could be added?

  • You could make the first-click always safe. I didn’t bother, but it would be easy enough to just destroy the mine. But right now the cell counts are pre-computed so that would need to be deferred.
  • Some MineSweeper games apparently make the edges somewhat safer for beginners. I didn’t bother.
  • Timed games, high scores, etc. I didn’t bother. But there is a timer, so it wouldn’t be hard to add.
  • A pause button. I didn’t bother, but it would be easy enough. It probably should intentionally cover the board.
  • I started writing a version that would play itself visibly, but it wasn’t worth the time. If you want a challenge, why not give it a shot?
  • I didn’t bother with a win/lose condition, but it would be easy enough to check if all non-mine cells are revealed.

Repasting Lenovo Laptop -- It actually helped

I’ve been building computers since the 90s, and I’ve never before seen a performance boost from repasting a CPU.

I fired up an Ubuntu from a non-persistent USB stick and ran stress to test, but since it was non-persistent, and I didn’t want to bother with unnecessary networking, I was forced to use the very technical method of recording data: Taking pictures of the screen with my phone.

Lenovo P1 gen 2, i7-9850H, 64 GB RAM, 1 TB NVMe SSD, Quadro T1000 GPU.

I actually have two of these machines, the first I needed to disassemble to clean out a fan, but the fans and everything were fine on the second unit and the only reason to remove the heat sink was to repaste. After the success of the first, I decided to give it a shot, and here we go:

Before repasting, the CPU would throttle and settle in at, 3050MHz. After repasting, it stabilized at 3400MHz. This is a significant improvement.

core before after
core 0,0 95 89
core 1,0 93 89
core 2,0 98 92
core 3,0 96 93
core 4,0 94 87
core 5,0 94 90

Well worth the hassle, and I don’t think I even broke anything!

beforeafter

Not bad for a few minutes of work.

Fastmail sieve script to create labels for each mailbox, subdomain and domain

It’s weird, I supported a product that implemented sieve some years ago, I use an e-mail platform that uses sieve today, but I’ve never actually written a sieve script. I’ve modified a few in very basic ways, but I didn’t really “get” the syntax intuitively.

Now I made a thing. It’s not a great thing, but it’s a thing.

Fastmail has a neat trick that creates a subdomain per-user automatically, so if your address there is dave@example.com then *@dave.example.com will also work. This is great for filtering, but it’s a bit of a pain to maintain. I wanted to create a script that would automatically create a label for each domain, subdomain, mailbox, etc.

It turned into a bit of a beast but it works.

One quirk of Fastmail, internally they use . as the folder separator and this is exposed in a few places, one of them being sieve rules. If you try to create a folder of “example.com/dave” it will actually create a folder of “example/com/dave”. You can do a literal dot by using ^ instead (this is not an escape sequence, it is an alternate character). So, to create a folder of “example.com/dave” you would use “example^com/dave”. Sounds easy enough, just loop through the mailbox and domain to replace . with ^, right?

sieve has no loops. No functions, no recursion. It does have regex, and Fastmail at least does support variables. It turns out nobody can stop me from nesting!

I went up to 6 levels “deep” (6 periods per mailbox name, 6 periods per domain name) and it works. Let’s be honest, if either your domain or your address has more than 6 “.” characters, you’re probably doing something wrong. And I can truncate at that point anyway.

So here it is, a way to label inbound messages automatically based on the mailbox, subdomain (when present) and domain.

And since I am insane, it has a little configuration section at the top so that you can select if you want only the deepest label or all sub-labels as well.

Only the deepest

address label
dave@example.com example.com/dave
test@dave.example.com example.com/dave/test

All sub-labels:

address label
dave@example.com example.com
dave@example.com example.com/dave
test@dave.example.com example.com
test@dave.example.com example.com/dave
test@dave.example.com example.com/dave/test

But wait, what if I had dave@example.co.uk? Wouldn’t this create a label of co.uk/example/dave? Yes, yes it would. Given the language limitations I’m not importing the full public suffix list or anything silly, but I did add a couple TLDs as example and you can add more if you like. It only supports up to three levels (2 . total) as a “root” domain, but that is probably enough and the concept could be extended further. Maybe we could strip the subdomain, domain and suffix separately and make it more generic, but I don’t actually care. If you happen to want that and can’t figure it out, let me know and I’ll bang my head against sieve a bit more. And so with that, here we are:

It is a work in progress, in that I keep tweaking it a bit, but it’s working well enough for me to share it.

Edge, Edge, Edge, the browser that tried

Almost to the Edge

Dammit Microsoft. I tried to use Edge, I really did. The old Edge was pretty meh and while it would be great to have competition in the browser space, I fully understand why it was ended.

The good

The new Edge though, the one based on Chromium? It’s great! It’s got the benefits of Google Chrome without reporting everything you do back to Google, and while there are valid concerns about what it might be reporting to Microsoft the reality is that I’m running Microsoft Windows already, if I can trust a company with my OS then in my opinion the further risk of the browser is… Acceptable.

And it’s a great browser! Synchronization works reliably, it’s fast, sleeping tabs and performance mode both significantly reduce energy (aka, extend battery life when mobile). Tab Groups are genuinely useful and intuitive. SmartScreen is still annoying, but I bet it saves inexperienced users from some malware attempts.

And installing any webpage you like as an app is awesome, it makes great web interfaces like Fastmail and Toodledo feel almost like native apps (hiding the “browser” part of the experience), and it’s more seamless than even Chrome itself.

The bad

Sure, it’s not perfect. Microsoft is desperate to get people to Bing and for some reason your search engine choice isn’t synchronized between browsers but DuckDuckGo is listed by default and switching the default search is no big deal.

We can’t have nice things

But recently Microsoft has decided to shoot themselves in the proverbial foot in the browser space. Again. Edge has started begging me to reset my settings to Bing, the New Tab page mysteriously re-adds all the junk linking to Microsoft News, Bing AI Chatbot appears and seems to re-appear after the settings to remove it were changed.

And we can’t forget the coupon malwareShopping features service which may optionally scan your connected email.

I’m done. Under the hood it is a good browser, maybe even a great browser, but I’m tired of hunting down all the crap that needs to be disabled just to use it. And for some unknown reason the settings for all the most annoying (an invasive) stuff don’t synchronize, so you get to disable it over, and over, and over, on every machine, every user profile, every browser profile.

There are choices

I’m done. I like to try out Firefox every once in a while but Mozilla is doing a bit of the same (coughPocketcough), so I’m trying LibreWolf instead.

Firefox based, but privacy focused, extension copmatible, Firefox Sync compatible (Bookmarks, Extensions and (some) settings synchronize). It has some dumb defaults, like resetting cookies for every new session but I respect the privacy-first approach and I’m willing to fiddle with a few things to make the web borderline tolerable.

I’d be open to a Chromium-based privacy-focused browser too, but I couldn’t find one that synchronizes bookmarks and settings between my devices, and life is too short to reconfigure every last thing over and over.

Installing Windows 11 via USB stick

Installing Windows, made easy, but harder again

Remember when you could just create a Windows installer USB stick and install Windows? Microsoft makes this trivially easy for Windows 11 and even recent older versions of Windows, and of course Windows Insider builds as well.

The problem

In current Windows Insider releases install.wim is now over 4GB, so it cannot be copied to the FAT32 partition needed to boot a USB stick. This isn’t a new problem, in the past you could work around it by breaking up install.wim into just the SKU of Windows that you require, but this is no longer the case.

The solution

There are two approaches:

  1. Split your install.wim into multiple files by following this guide.

  2. Format your USB stick into two partitions, a FAT32 boot partition and an exFAT data partition as described in this guide.

Update Windows 11 USB stick, maintaining the split between partitions

What you want to test different builds? I went with the option to format the USB stick into two partitions, and I can update the partitions using rclone (or you can use your favourite tool, of course).

  • Download the ISO and mount it as (E:).
  • Mount your USB stick.
  • Update the files on the bootable partition (R:):
rclone sync E:\ R:\ --filter "- ei.cfg" --filter "+ sources/boot.wim" --filter "- sources/**" --progress -v --modify-window 2s --order-by size,mixed,75 --delete-before --transfers 4
  • Update the install.wim partition (T:):
rclone sync E:\ T:\ --filter "- ei.cfg" --filter "+ sources/**" --filter "- *" --progress -v --modify-window 2s

Of course your paths may vary, use the appropriate paths for your system.

Blocking Wi-Fi calling

What is Wi-fi Calling

Mobile Wi-Fi calling is a feature that allows users to make and receive phone calls over a Wi-Fi network instead of relying solely on a cellular network. Depending on your network it may be available when you have mobile connectivity, or only as a backup, if offered at all.

Why block it?

Wi-Fi calling is great, when it is great. But what if your local Wi-Fi is unreliable, or you expect to be interrupting it and want to block mobile Wi-Fi calling? Of course you could turn it off on your device, but I wanted to block it at the network level while I was dealing with some intermittent connectivity issues.

Carriers don’t seem to publish the IP ranges, but if you don’t mind blocking IPsec entirely, this article on optimizing your network for Wi-Fi Calling1 has all the details you need:

Proto Port Number Description
UDP 500 Phase one
UDP 4500 IPsec encrypted packets

Alternative approach

Seamless Transition is well supported on the carriers and devices I use, which allows a user to leave Wi-Fi range and transition to the mobile network nearly seamlessly, although the reverse is not supported without disconnecting and re-dialing your call. But as a user you can take advantage of this, if you’re on a Wi-Fi call and suspect the network is causing problems you can turn Wi-Fi off and back on again to force the voice traffic over to the mobile network.

  1. Is Your Network Optimized for Wi-Fi Calling? 

Learning to code with ChatGPT

A couple days ago I wrote up a quote post about ChatGPT’s improvements and had an online discussion or two about the ChatGPT and programming in general. At least one opinion si that having ChatGPT write code for me is a terrible way to learn, and it will be the end of society and such.

Maybe they’re right. But I thought it was worth putting together my thoughts on how it has been useful to me.

I started programming in GWBasic probably somewhere before my 10th birthday, moved on to MSDOS batch, 4DOS’s scripting enhancements (although to be honest I liked the version that Norton whitelabelled NDOS a bit better), moved on to Pascal and C as a teenager, and since that time I mostly switched to casual “solve a problem” scripting rather than actually trying to comprehensively learn a language.

I’ve recently been getting into C# with the goal of actually learning the language rather than just solving a specific problem of the hour. It’s been fun and frustrating and the frustrating times are the ones where ChatGPT has been helpful.

I get the concerns about programmers copy/pasting code from StackOverflowChatGPT without really understanding it, and that’s a very real part of why I’m not especially proud of the PHP I’ve written over the years (you’ll often see some very mixed styles of coding clearly indicating the parts I ripped wholesale and modified a tiny bit, wiithout necessarily understanding – A habit that I am leaving behind because the point is to learn).

But where it is crazy useful is when there are multiple APIs or packages and I need a starting point or summary. Do I want System.Timer, System.Threading.Timer, Stopwatch, DispatchTimer, or to go down the rabbithole of UWP apps and start digesting Windows.System.Threading.ThreadPoolTimer? ChatGPT gave me a summary, and I asked a couple follow-up questions to confirm my understanding. Awesome. Could I have figured it out on my own? Sure, absolutely… But having a pointer of where to start and then digging into the documentation from there is invaluable.

It’s an interesting world.

ChatGPT vs reality from a programming perspective

One thing I haven’t seen talked about ChatGPT yet is just how quickly it is improving. Learning?

A couple weeks ago I asked it a question: In C# how can I be notified when the console size changes?

It came up with an answer suggesting to subscribe to an event Console.WindowSizeChanged along with some example code of how to do it. Sounds great, right? Except for one little detail: The event doesn’t exist. Never has.

There is a single mention of it in a feature request on GitHub but that’s it, the answer was otherwise completely imaginary. And it’s not the only time I got an answer that just didn’t work, code that didn’t compile, or didn’t do what it said. To it’s credit, when pointed out it apologized (!), described the bug and a potential solution.

I went back today to pull my chat history to grab some screenshots, the history doesn’t go far enough back so I asked a similar question and it’s fixed! It came up with the same solution I’m already using, essentially polling and generating the event myself.

The improvements are neat. But also a bit unsettling just how quickly it is moving forward.

  1. The bird is the word! 


Site built: 2024-12-09 23:43:57 -0700