Home

2024

Worklog

LETSGO Game

Quietly Going Insane With Tools & Automation
šŸŒ‹

Quietly Going Insane With Tools & Automation

Tags
UnrealToolsAutomationEngineeringZigC
Owner
J
Justin Nearing
šŸ’”
In the long dark of Winter 2024, I went down a programming rabbit hole. It wasnā€™t at optimal mental health, I donā€™t think this particularly helped, but I learned a lot.

This is the story of one programmer Quietly Going Insane With Tools & Automation:

šŸŒ‹Quietly Going Insane With Tools & AutomationšŸ§µStrings, Actually, Do Not Existāš”Suffering: The First Two Weeks Of Zig

As a programmer, I sometimes go a bit off the deep end.

This is a story about me doing just that, this week.

The Triggering Event

Unreal has this awesome feature called Live Coding which will hot reload C++ compilation changes into the Unreal Editor without having to restart the editor.

Anyone who dislikes this feature has not considered the alternative.

But it is a bit janky, as I describe in āŒØļøActually Writing Some Damn Code ļ»æ:

ā€¼ļø
I create a variable using one of my data structures:
image

Compiles green, I save all, all good.

Restart Unreal:

image

With the helpful compiler message:

image

Cool.

Real Cool.

Thatā€™s cool.

Modern Solutions for Modern Problems

My understanding is this is caused by the Live Coding compiler adding layers and layers of my garbage changes until something breaks.

Like a true developer, my first thought is to nuke the problem from orbit.

Such is the recommended fix found on the Unreal Dev Wiki:

Delete a few folders, and we are SO back?

This is the way.

But hold on.

You expect me to manually navigate to my Unreal Project folder and remember which subfolders to delete?

Nah, naaaah.

We gonā€™ automate this.

So Begins the Madness of ā€œDoing it Rightā€

Ok so if I were in a sane development environment Iā€™d whip up a bash script that ignorantly nukes the paths in a directory.

But Iā€™m in Windows, because itā€™s a better OS for Unreal. Fight me.

Windows, making poor decisions like using \ (not /) for folder paths, doesnā€™t do Bash.

It does Powershell.

I ainā€™t shook.

I have a pretty clear, and simple, use case for this Powershell script.

So easy that Chat Jippity can help convert my echo to Write-Host , etc.

But before I can actually write the Powershell script that will delete the folders, I ask myself how Iā€™ll be calling this script.

In OSX/Unix, I have terminal open at all times, ls'ing through life like a god damn wizard.

This is not something I do in Windows.

I use the GUI like a chump.

Thatā€™s when I have myself an idea:

image

This is the absolute shambles that is my Windows right-click menu.

The idea is simple:

Further shambolism by adding my script to this right-click menu.

This is actually super easy, it turns out.

We just need to start playing with the Windows Registry.

A Totally Normal Thing To Do: Hacking Registry Keys

šŸ˜…
Itā€™s not ā€œhackingā€ per se, but if you break reg keys you have the ability to kill your Windows. I think. I havenā€™t tested the theory but I assume you donā€™t want to just start nuking things in the Registry Editor.

As a Windows admin, you should have access to the Registry Editor.

Opening that you should see something like:

image

Apparently, if you add registry keys here, things show up when you right click in File Explorer:

My script is called CleanBuild
My script is called CleanBuild

And presto:

image

The value for the key command leads to the path the actual CleanBuild script is in.

But waaaait a minute.

How do I reliably know where the script is?

If I wanted to do this ā€œrightā€- as in something I could replicate across multiple computers for some ungodly reason, I need a reliable script path.

Thatā€™s easy, I just need to make an installer script that will:

  • Add the registry key
  • Put the actual CleanBuild script in a reliable path the registry value will resolve to.

Things Are Starting To Run Away From Me: Making a Script to Install a Script

I ainā€™t shook.

This is fine.

Iā€™m learning Powershell. Thatā€™s useful, theoretically.

Iā€™m learning how to interact with the Windows operating system on a deeper level.

This exercise has value, surely.

But Iā€™m also fully cogent that Iā€™m several hours deep into the 15-second manual task of deleting 4 directories in my Unreal Project.

This is fine. This is a totally normal thing to be doing:

## Setup local path to install CleanBuild
$AppData = Get-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders' -Name 'Local AppData' | Select-Object -ExpandProperty 'Local AppData'
$ToolsPath = 'Tools\CleanBuild\CleanBuild.ps1'

$Path = Join-Path -Path $AppData -ChildPath $ToolsPath

Write-Host "Creating path: [$Path]"
New-Item -Path $Path -ItemType File -Force | Out-Null

if (Test-Path -Path $Path) {
    Write-Host "*** [OK]"
}

## Download CleanBuild
Write-Host "Download CleanBuild to [$ToolsPath]"
$CleanBuildUrl = "https://raw.githubusercontent.com/JerkyTreats/tools/main/CleanBuild/CleanBuild.ps1"
$Req = Invoke-WebRequest -Uri $CleanBuildUrl

Set-Content -Path $Path -Value $Req.Content

So I whip together a quick installer script that finds my local windows users AppData folder and copypasta the contents of my CleanBuild script to {AppData}\Tools\CleanBuild\CleanBuild.ps1

We then add the registry key that will let me run this script from the right-click context menu:

## Add Script to Right-Click Explorer Menu
$RegPath = 
    'Registry::HKEY_CLASSES_ROOT\Directory\shell\CleanBuild\command', # Right Click on a folder
    'Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\CleanBuild\command' #Right Click in explorer window
$RegName = "(Default)"
$RegValue = $Path
$IconPath = Join-Path -Path $AppData -ChildPath "Tools\CleanBuild\icon.ico"

Write-Host "Adding Command to Right-Click File Context Menu"
Write-Host "Writing [$RegPath]"

foreach ( $rp in $RegPath ) {
    New-Item -Path $rp -Force 
    New-ItemProperty -Path $rp $RegName -Value $RegValue -PropertyType String -Force #| Out-Null
    New-ItemProperty -Path $rp Icon -Value $IconPath -PropertyType String -Force 

    if (Test-Path -Path $RegPath){
        Write-Host "*** OK"
    }
}

Losing the Plot: Itā€™s OK Iā€™m Learning

Thereā€™s a problem.

Itā€™s small.

Insignificant really:

image

My script doesnā€™t have an icon.

It doesnā€™t matter. Right?

Ainā€™t no one using this but me.

I think it matters.

As the only user of this tool, I think it matters.

Literally 100% of users are upset that the icon is missing.

We must go deeper:

image

Apparently, if you add a reg key called Icon with a path to an .ico file, an icon will appear.

So I went to Midjourney, got it to generate me an icon:

Apologies to the artists whose artwork was stolen in the creation of this image. I would pay for a service with 100% licensed training data. But for a non-commercial prototype tool built on a whim, my moral compass feels OK about using this generated asset in this context. I understand if this means we can't be friends.
Apologies to the artists whose artwork was stolen in the creation of this image. I would pay for a service with 100% licensed training data. But for a non-commercial prototype tool built on a whim, my moral compass feels OK about using this generated asset in this context. I understand if this means we can't be friends.

That gives me a .png, but I need an .ico.

Icoā€™s are weird, as each image has layers ranging from 16x16 px to 512x512 or something like that.

I just used some randomly googled png ā†’ ico converter and used that.

Now I feel like we got an absolute banger on our hands:

image

But this presents us with a new problem.

Now there are multiple files that this installer needs to download- the script and the ico.

I could just loop through a list of files to download from GitHub.

But letā€™s be real, this whole copypasta-raw-github-data is kinda Fā€™d in the first place.

Yes, it works, but damn dude this isnā€™t how people do things.

Zipping Multiple Files: Now I Need a GitHub Actions Workflow.

But hey, I still ainā€™t shook.

This is fine.

Weā€™re just going to zip the ico and script together, download the single file in the installer and unzip to the defined path.

Easy.

Buuuuut now I have a build step for my CleanBuild.

I need to zip the files together.

I need to zip the files every time any file changes.

I could do this manually and just add to git.

But we got into this whole mess by avoiding manual labor.

The whole point of this exercise is to do it right.

The definition of which means no manual work ever again.

Fine. This is fine.

I havenā€™t done any real work in a week, but this is fine, Iā€™m learning.

I add the .github/workflows folder and add a build yaml file.

name: CleanBuild
run-name: CleanBuild
on: 
  push:
    branches: 
      - main
jobs:
  GenerateRelease:
    permissions: write-all
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository code
        uses: actions/checkout@v4
      - run: |
          echo "šŸ’” The ${{ github.repository }} repository has been cloned to the runner."
          echo "PWD is [ ${{ github.workspace }} ]"
          ls -al .

      ### Create Build Files
      - name: Zip CleanBuild 
        uses: montudor/action-zip@v1
        with: 
          args: zip -qq -r cleanbuild.zip CleanBuild/CleanBuild.ps1 CleanBuild/icon.ico    

      ### Create Tag and Release 
      - name: Bump version and push tag
        uses: anothrNick/[email protected] 
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 
          WITH_V: true
          PRERELEASE: false      
      - uses: "marvinpinto/[email protected]"
        with:
          repo_token: "${{ secrets.GITHUB_TOKEN }}"
          automatic_release_tag: "latest"
          files: | 
            cleanbuild.zip
I sure hope all these rando devs donā€™t have malicious code in their Actions. This is fine, right?

Weā€™re doing this ā€œrightā€, which means setting up semantic versioning for this damned tool, creating a full Release on push to main, and adding our .zip artifact as a release file.

Which is all cool, and works, buuuut I still feel like mucking with a zip in the first place is jank as hell.

Letā€™s look at this from a prospective users POV:

  • I am asking the user to download a random Powershell script
  • That script requires elevated privileges as its blowing up the registry and downloading random internet files.
  • This is total nonsense.

Thatā€™s not how Windows users do things.

No one is going to download and run random .ps1 files they find on the internet.

However, Windows users will absolutely run a random setup.exe they find off the internet.

Theyā€™ll click through an install wizard faster than they accept the Terms and Conditions for every service theyā€™ve ever used ever.

Can I wrap my jank ass Powershell script into an .exe?

Youā€™re god damned right I can.

Figuring out how to make literal windows executables because Iā€™ve gone totally insane.

At this point Iā€™ve accepted the madness.

Iā€™ve become the hatter, and out of my hat Iā€™m going to pull literal .exe files for CleanBuild.

Itā€™s time to go all the way down, Alice.

The easiest solution is to find some random internet dev who has built a powershell-to-exe converter so that I donā€™t have to do any real work.

Thereā€™s a few options it seems:

  • ps2exe
  • NSIS
  • iexpress

ps2exe is the exact thing Iā€™m looking for, but executables built with ps2exe are routinely flagged by anti-virus software as malicious actors have used it to build bad things.

NSIS is a whole scripting thing, hosted through SourceForge.

Buuuut SourceForge has a bit of a smell.

Smells like warez, a relic of a long forgotten internet, an internet probably best left forgotten.

iExpress is apparently available on all Windows machines by default. Itā€™s just a wizard to make arbitrary installer wizards.

Iā€™m not sure how Iā€™d automate this as a build step though.

In all, Iā€™m not thrilled with any of these options.

It feels like weā€™ve gone past the point of reasonable jankiness.

The non-jank way to do this is to use a real programming language, build a real executable, like a real programmer would.

This is fine.

Itā€™s not like the Powershell script is doing anything too fancy here.

Basic CRUD work on Windows paths.

Surely there is a maximally degenerate language I can use to build my stupid script.

Zig: The Maximally Degenerate Language to Build Your Stupid Scripts

Gentlemen persons, it is time to learn Zig.

I am choosing Zig after a solid 30 seconds of thought.

Why?

I was able to successfully Bing zig build for windows and get results for building an exe and adding an ico.

šŸ§½
Yes. Bing. Iā€™m trying it OK. Google web search is legit garbage. Like it had never been great at the job, but it feels in the past 2~ years it fundamentally lost the ability to retrieve relevant information for day-to-day programming use cases, which is the last straw for me. I know Iā€™m supposed to use DuckDuckGo, but my previous attempts honestly felt underwhelming. And FWIW Bing does a decent job at mixing classic google-fu websearch with the semantic language chatbot conversations of Chat Jippity. This isnā€™t Ballmerā€™s Bing, this is Satyaā€™s. They are not the same.

I considered using C++ for this, as it allows me to practice the C++ work I have to do as part of learning Unreal.

But Biden said to avoid C++ because itā€™s not memory safe.

You can tell it didnā€™t take much to convince me not to use C++.

So why Zig?

Well, you know those insufferable devs on your team constantly proselytizing the virtues of Rust?

Those devs find the Zig devs insufferable.

In other words, if I am to fully lose my mind, I may as well chose the language of maximal degeneracy.

Zig is the way.

Literally Two Weeks Later: I've Done No Work, God is Dead, There Are No Strings

Sooo Iā€™m writing this part a full 2 weeks later, conceding that I may have been a bit hasty with my 30 second decision to learn Zig.

Those four damned folders in my Unreal Project have still not been deleted.

Instead, Iā€™ve invested dozens of hours, amortized over the last 2 weeks, trying to grok basic Zig usage.

It turns out, Zig devs are insufferable because they have all suffered.

Suffered, at some point in their lives, the unforgiving initial learning curve of low-level programming.

There are no strings here, just arbitrary slices of null terminated bytes.

WHAT DOES THAT EVEN MEAN?

ā‰ļø
Regardless, I'm pressing on with Zig. I wanted to wrap this all into this single article, give the impression Iā€™m so damned smart I can learn any language and do any task with speed and grace and seniority.

Instead Iā€™m stuck trying to coerce [_][] const u8 { "foo", "bar" }; to u16 because windows calls wonā€™t accept u8 because Microsoft is the one that made bad decisions. Besides, itā€™s gotten to the point where I figure I can farm additional content out of a dedicated ā€œFirst Impressions of Zigā€ post.

I get the feeling Zig users will consume any content about Zig.

Gotta farm the engagement where you can get it, folks. The Story Continues:

šŸ§µStrings, Actually, Do Not Exist