Posted in Uncategorized

After an unfortunate incident involving a crash and a lost save file, I decided to write a batch script to automatically back up my Subnautica 2 saves every x number of minutes. You’ll need 7-Zip and PowerShell installed.

This script will check if Subnautica 2 is running, and if so, will zip the Subnautica save folder to whatever folder the script is located.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@echo off
 
:loop
 
rem Get HH:MM:SS for log prefix
FOR /F "tokens=* USEBACKQ" %%F IN (`powershell get-date -format ^"{HH:mm:ss}^"`) DO (
	SET logtime=%%F
)
 
rem Determine if subnautica is running
tasklist /fi "ImageName eq SubnauticaZero.exe" /fo csv 2>NUL | find /I "SubnauticaZero.exe">NUL
 
rem If subnautica is running...
if "%ERRORLEVEL%"=="0" (
 
	rem Get current datetime https://stackoverflow.com/a/2854857
	FOR /F "tokens=* USEBACKQ" %%F IN (`powershell get-date -format ^"{yyyy_MM_dd_HH_mm_ss}^"`) DO (
		SET mydatetime=%%F
	)
 
	rem Set up a folder name for our new backup
	SET stamp=slot0000-%mydatetime%.zip
 
	rem Back up with 7zip
	"C:\Program Files\7-Zip\7z.exe" a -tzip "%stamp%" "%USERPROFILE%\AppData\LocalLow\Unknown Worlds\Subnautica Below Zero\SubnauticaZero\SavedGames\slot0000" > NUL
 
	echo [%logtime%] Backed up to %stamp%
) else (
	echo [%logtime%] Not running...
)
 
rem sleep for 30 minutes https://stackoverflow.com/a/16803409
powershell -command "Start-Sleep -s 1800"
 
goto loop

To run the script simply save the above code into a file with a filename like backup.bat and double click it. Run it whenever you’re playing and close it when you’re done.

Read More »

Posted in Javascript

While playing around with Frontity (a react framework for WordPress) I noticed the compiled bundle size for the default Mars theme was pretty huge clocking in at an unacceptable 390KB of JavaScript. That’s a lot to download before my site will appear. Below I detail how I knocked over 100KB off that size with no discernible difference in functionality simply by swapping out React with its lightweight drop in replacement Preact.

Extending WebPack

To replace React we need to modify Frontity’s webpack configuration. At the time of writing it’s not documented how to do that however a forum post by SantosGuillamot contains a link to a CodeSandbox showing how to do it.

In your root folder’s package.json add the following dependencies:

1
2
3
4
5
6
"dependencies": {
	...
	"change-config": "./packages/change-config",
	"preact": "^10.5.14",
	"preact-render-to-string": "^5.1.19"
}

Create the following files:

  1. /packages/change-config/package.json
  2. /packages/change-config/frontity.config.js

Here’s the contents of package.json:

1
2
3
4
5
6
7
8
9
10
11
{
  "name": "change-config",
  "version": "1.0.0",
  "description": "Frontity package created using the Frontity CLI.",
  "keywords": [
    "frontity",
    "frontity-theme"
  ],
  "license": "Apache-2.0",
  "dependencies": {}
}

And here is frontity.config.js:

1
2
3
4
5
6
export const webpack = ({ config, mode }) => {
    config.resolve.alias["react"] = "preact/compat";
    config.resolve.alias["react-dom/test-utils"] = "preact/test-utils";
    config.resolve.alias["react-dom"] = "preact/compat";
    config.resolve.alias["react/jsx-runtime"] = "preact/jsx-runtime";
};

Now back in your root folder open frontity.settings.js and set the following at the end of the packages section:

1
2
3
4
5
6
7
8
const settings = {
  ...
  "packages": [
    ...,
    "@frontity/tiny-router",
    "@frontity/html2react",
    "change-config"
  ]

That should be it. Run

1
2
npx frontity build
npx frontity serve

Here are the before and after filesizes:

Before:

Frontity base Mars theme bundle size

After:

Frontity Mars theme bundle size with React replaced with Preact

A saving of 110KB! Not too shabby.

Read More »

Posted in Uncategorized

For something that should be simple, importing a model from VRoid Studio into Unreal Engine 4 as of the time of writing is extremely convoluted and time consuming – even with the great tools that have been created to speed up the process.

Below I’ll attempt to walk through every step I took to get things looking and working correctly as well as documenting my struggles along the way. Our basic workflow will be VRoid -> Blender -> Unreal.

I should note that as of the time of writing, the latest version of each of the pieces of software I’ll be using are as follows:

Read More »

Posted in Javascript

Custom button added to Gutenberg top panel

Gutenberg may be the shiny new toy available for WordPress admin but it still feels very limiting at times – especially when trying to do things the developers don’t anticipate. I wanted to add an extra button at the top of the post edit page next to the Publish button before finding out there is no SlotFill for this but once again, React has us covered with a handy little helper createPortal. createPortal allows you to place a component anywhere in the DOM of your choosing – perfect for what I wanted to do here.

The Component

Without further adieu, component code below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/**
 * WordPress dependencies
 */
import { Button } from '@wordpress/components';
import { compose } from '@wordpress/compose';
import { createPortal, useEffect, useState } from '@wordpress/element';
import { withSelect } from '@wordpress/data';
 
const TopBtn = ( { postPreviewButton } ) => {
	// Empty div element our button will be placed into
	// This div will be inserted before the Publish button with useEffect()
	const [ container ] = useState( document.createElement( 'div' ) );
 
	// The button submit handler
	const onSubmit = () => {
		console.log( 'Button pressed!' );
	};
 
	// Do the actual placing/removing of the button in the DOM
	useEffect( () => {
		// Ensure we have a Preview button to place next to
		if ( ! postPreviewButton ) {
			return;
		}
 
		// Insert our button immediately after the post preview button.
		postPreviewButton.parentNode.insertBefore(
			container,
			postPreviewButton.nextSibling
		);
		// Remove our button on component unload
		return () => {
			postPreviewButton.parentNode.removeChild( container );
		};
	}, [ postPreviewButton ] );
 
	// Use createPortal to place our component into the <div/> container
	return createPortal(
		<>
			<Button
				isSecondary
				showTooltip={ true }
				label="My Custom Button"
				onClick={ onSubmit }
			>
				My Btn
			</Button>
			<span>&nbsp;</span>
		</>,
		container
	);
};
 
export default compose( [
	withSelect( () => {
		return {
			// Provide the Post preview button element so we'll know where to
			// place our component
			postPreviewButton: document.querySelector( '.editor-post-preview' ),
		};
	} ),
] )( TopBtn );

This setup is quite flexible allowing you to add components pretty much anywhere on the page you like. I’ve commented the component as best I could. If you have any questions feel free to ask in the comments section below.

Read More »

Posted in Uncategorized

For the longest time now I’ve been having issues with certbot not being able to create a certificate for my domain, returning the error

Attempting to renew cert (mydomain.com) from /home/ubuntu/.certbot/config/renewal/mydomain.com.conf produced an unexpected error: Failed authorization procedure. mydomain.com (http-01): urn:ietf:params:acme:error:unauthorized :: The client lacks sufficient authorization :: Invalid response from https://mydomain.com/.well-known/acme-challenge/-jYlHtpK6x6LZ8B4KjHeY7RgchNFPoouXADS_XQtowc [2606:4700:3035::681c:1e6e]: “<!DOCTYPE html>\n<!–[if lt IE 7]> <html class=\”no-js ie6 oldie\” lang=\”en-US\”> <html class=\”no-js “. Skipping.

I think the reason for this is because I’m using Full (Strict) encryption mode in CloudFlare dashboard which requires a valid SSL certificate be present when communicating between my web server and CloudFlare.

Full (strict) SSL/TLS encryption mode with CloudFlare

The solution for this is instead of using certbot’s default authentication method, we instead make use of the certbot-dns-cloudflare plugin that will handle the Lets Encrypt challenge through DNS. This works by automatically creating and deleting our CloudFlare DNS TXT record for us during the certbot renew. Let’s set this up now.

Read More »

Posted in Database

While trying to import a database dump on the homebrew version of MariaDB on OSX, I was getting the error

SQL ERROR [mysql4] Out of resources when opening file `./mydatabase/table.MYD´ (Errcode: 24 – Too many open files)

This is caused by the open_files_limit setting being too low. To check your open_files_limit in MariaDB run

1
SHOW GLOBAL VARIABLES LIKE 'open_files_limit';

For me this value was 256 – it should be much higher. Increasing the value isn’t as simple as it would seem, however, as the limit is actually coming from OSX’s maxfiles value. You can see this value by running

1
launchctl limit

1
2
3
4
5
6
7
8
9
cpu         unlimited      unlimited
filesize    unlimited      unlimited
data        unlimited      unlimited
stack       8388608        67104768
core        0              unlimited
rss         unlimited      unlimited
memlock     unlimited      unlimited
maxproc     2784           4176
maxfiles    256            unlimited

Thankfully increasing maxfiles is relatively simple:

Adjusting Open File Limits in Yosemite

To adjust open files limits on a system-wide basis in Mac OS X Yosemite, you must create two configuration files. The first is a property list (aka plist) file in /Library/LaunchDaemons/limit.maxfiles.plist that contains the following XML configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
  <plist version="1.0">
    <dict>
      <key>Label</key>
        <string>limit.maxfiles</string>
      <key>ProgramArguments</key>
        <array>
          <string>launchctl</string>
          <string>limit</string>
          <string>maxfiles</string>
          <string>200000</string>
          <string>200000</string>
        </array>
      <key>RunAtLoad</key>
        <true/>
      <key>ServiceIPC</key>
        <false/>
    </dict>

This will set the open files limit to 200000. The second plist configuration file should be stored in /Library/LaunchDaemons/limit.maxproc.plist with the following contents:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple/DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
  <plist version="1.0">
    <dict>
      <key>Label</key>
        <string>limit.maxproc</string>
      <key>ProgramArguments</key>
        <array>
          <string>launchctl</string>
          <string>limit</string>
          <string>maxproc</string>
          <string>2048</string>
          <string>2048</string>
        </array>
      <key>RunAtLoad</key>
        <true />
      <key>ServiceIPC</key>
        <false />
    </dict>
  </plist>

Both plist files must be owned by root:wheel and have permissions -rw-r–r–. This permissions should be in place by default, but you can ensure that they are in place by running sudo chmod 644 . While the steps explained above will cause system-wide open file limits to be correctly set upon restart, you can apply them manually by running launchctl limit.

In addition to setting these limits at the system level, we recommend setting the at the session level as well by appending the following lines to your bashrc, bashprofile, or analogous file:

1
2
ulimit -n 200000
ulimit -u 2048

At this point, you can restart your computer and enter ulimit -n into your terminal. If your system is configured correctly, you should see that maxfiles has been set to 200000.

Thanks to tombigel for his very informative gist.

Read More »

Posted (Updated ) in PHP

With the rise of Gutenberg and it’s million tiny JavaScript files loaded every time you go to write a post, it’s become more important than ever to utilise a CDN for static assets loaded through the WordPress admin.

W3 Total Cache is a great plugin for speeding up your site and it’s CDN feature is top notch. One seemingly missing feature however is serving static assets from the configured CDN when browsing around your sites admin. By default no CDN rewriting at all is done on your sites backend and if you have a lot of logged in users writing posts, this can cause a bunch of unnecessary load on your server as they each download all the files.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// Automatically rewrite assets to CDN URLs in the backend
if (class_exists('W3TC\Dispatcher')) {
    add_action('admin_init', function () {
        $config = new W3TC\Config();
 
        if ($config->get('cdn.enabled')) {
            // CDN domain with trailing slash
            $cdn_domain = W3TC\Dispatcher::component('Cdn_Core')->get_cdn()->format_url('');
 
            /**
             * Filters the HTML link tag of an enqueued style.
             *
             * @since 2.6.0
             * @since 4.3.0 Introduced the `$href` parameter.
             * @since 4.5.0 Introduced the `$media` parameter.
             *
             * @param string $html   The link tag for the enqueued style.
             * @param string $handle The style's registered handle.
             * @param string $href   The stylesheet's source URL.
             * @param string $media  The stylesheet's media attribute.
             */
            add_filter('style_loader_tag', function ($tag, $handle, $href, $media) use ($cdn_domain) {
                return str_replace(site_url('/'), $cdn_domain, $tag);
            }, 10, 4);
 
            /**
             * Filters the HTML script tag of an enqueued script.
             *
             * @since 4.1.0
             *
             * @param string $tag    The script tag for the enqueued script.
             * @param string $handle The script's registered handle.
             * @param string $src    The script's source URL.
             */
            add_filter('script_loader_tag', function ($tag, $handle, $src) use ($cdn_domain) {
                return str_replace(site_url('/'), $cdn_domain, $tag);
            }, 10, 3);
        }
    });
}

I’d only recommend using this code with an Origin Pull CDN, as it doesn’t handle pushing any of these assets to the CDN – only serving from what’s already in there (which if using an Origin Pull CDN will be everything).

Read More »

Posted (Updated ) in Uncategorized

With the official How to Transfer Data Between microSD Cards for Use on Nintendo Switch documentation being for Windows only, and the Reddit thread on the topic not coming up with anything that works on the latest firmware, I thought I’d write up a quick post on how I moved from a 128GB to 512GB card successfully using OS-X.

  1. Turn off your Switch by holding the power button for 3 seconds and selecting the relevant option.
  2. Remove your old SD card from your Switch
  3. Insert it into your Mac
  4. Run the following in your terminal
    1
    2
    
    mkdir ~/Desktop/sdcard
    cp -r /Volumes/Untitled/Nintendo ~/Desktop/sdcard
  5. Insert your new SD card into your Switch and turn it on
  6. If an error message about your SD card not being readable pops up, close it
  7. Go to Settings – System – Formatting Options – Format microSD Card and format your card
  8. Once the files have finished copying on your Mac eject your old SD card and store it away for safe keeping
  9. Turn your Switch off, take your new SD Card out and insert it into your Mac
  10. Run the following in your terminal
    1
    
    cp -r ~/Desktop/sdcard/Nintendo/* /Volumes/Untitled/Nintendo
  11. Once the files have finished copying eject your new SD Card, insert it into your Switch and turn your Switch on
  12. If there is no error message, you’re all done!

Read More »

Posted (Updated ) in PHP

In a recent project I had to download and process a bunch of CSVs. Initially I had an extremely ugly exec() call to linux’s wget command for reasons I won’t go into but obviously a better, PHP-based solution was required. I had previous experience with Guzzle and its pooled requests so it was the obvious place to go.

Below is the script I ended up with. It takes an array of files and downloads them all to a __FILE__.’/downloads/’ directory. Not the cleanest thing in the world but it did the trick and you should be able to adapt it as you need.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?php
 
ini_set('display_errors', true);
require __DIR__.'/vendor/autoload.php';
 
use GuzzleHttp\Client;
use GuzzleHttp\Pool;
use GuzzleHttp\Psr7\Response;
 
$files = array(
	'http://mysite.com/1.csv',
	'http://mysite.com/2.csv',
	'http://mysite.com/3.csv',
	'http://mysite.com/4.csv',
);
 
// Track redirects so our Pool's fulfilled closure knows which URL the current
// download is for.
$client = new Client([
	'allow_redirects' => ['track_redirects' => true]
]);
 
$requests = function($total) use ($client, $files, $import_path) {
    foreach ( $files as $file )
    {
        yield function($poolOpts) use ($client, $file) {
            $reqOpts = array_merge($poolOpts, [
            	// Sink option specifies the download path for this file
                'sink' => __DIR__.'/downloads/' . basename($file)
            ]);
 
            return $client->getAsync($file, $reqOpts);
        };
    }
};
 
$pool = new Pool($client, $requests(100), [
    'concurrency' => 3,
    'fulfilled' => function(Response $response, $index) use ($files) {
        // Grab the URLs this file redirected through to download in chronological order.
        $urls = $response->getHeader(\GuzzleHttp\RedirectMiddleware::HISTORY_HEADER);
 
        echo "Downloaded ", end($urls), "<br/>\n";
    },
    'rejected' => function(Exception $reason, $index) use (&$import_errors) {
        $url = (string)$reason->getRequest()->getUri();
 
        echo "Failed to download ", $url, ": ", $reason->getMessage(), "<br/>\n";
    },
]);
 
$pool->promise()->wait();
 
echo "Finished downloading.<br/>";

Read More »