Playing Wacky Wheels (DosBOX via WebAssembly) with Friends: A Guide to Multiplayer Mode

Everything started as a joke between old friends: Do you remember 28 years ago, when we used to play Wacky Wheels in a split screen in your old PC?

Playing Wacky Wheels with friends can be an incredibly fun and exciting experience. Wacky Wheels is a classic DOS game that has been ported to modern systems through the help of DOSBox.

While the game can be enjoyed alone, it reaches its full potential when shared with friends. A while ago, we used to play the game in the same keyboard and with split screen (no internet for us back then), but the fun was guaranteed. This guide will explain how to set up a multiplayer game of Wacky Wheels with friends in a modern environment.

Playing old DOS games comes with a series of challenges, like ensuring all parties have the same binary distribution, configuration of emulator and networking capabilities. In the case of Wacky Wheels, IPX is not supported for the multiplayer mode and the alternatives are connectivity through modem or serial connection.

To solve most of the problems above, I’ll be using the most common resource in today’s computer, a web browser. This solution implies compiling our binaries to be executed via Web Assembly and let the browser distribute the software and enable communications with other players.

Executing your DOS binaries in the browser

Getting DOSBox running in the browser

Compiling DOSBox for being executed in the browser is a well solved problem. In fact, there are plenty of web pages that already allow you to play your favourite games, including Wacky Wheels.

We are going to compile to Web Assembly our own version of DOSBox with network capabilities and package our favourite game with a custom DOSBox configuration.

Luckly we have a DOSBox port to emscripten a toolchain to compile to Web Assembly: https://github.com/dreamlayers/em-dosbox. So before starting ensure you have emscripten installed, follow these instructions.

Once we have all the tooling installed, it’s time to compile our own Web Assembly version of DOSBox, follow the steps:

  • Clone the repository
git clone https://github.com/dreamlayers/em-dosbox.git
cd em-dosbox
  • Configure and build em-dosbox to use SDL2 and SDL-NET
./autogen.sh
emconfigure ./configure CPPFLAGS="-s USE_SDL=2 -s USE_SDL_NET=2" LDFLAGS="-s USE_SDL_NET=2"
make -j

And that’s it, after executing that command you should find the file src/dosbox.html that you can serve from a http server and will execute DOSBox.

Executing the game

That’s cool, but we want to play Wacky Wheels. Fortunately em-dosbox allows you to package binaries and execute them directly.

Get a legal copy of the software that you want to emulate, Wacky Wheels even have a shareware version. Place it in your src directory, under the folder ww.

And execute the following command from the src folder:

cd src

./packager.py ww ww WW.EXE

We will asking the packager to create a new file, ww.html, include all the files in the directory ww and once DOSBox is loaded, to execute the WW.EXE file, our game!

Wacky Wheels networking

As mentioned before, the networking capabilities for this game are limited. We will be using the serial connection, concretelly a null modem.

If we were playing as we did back in 1994, we would have two computers connected to each other by a serial cable. Now we don’t have that cable, but TCP/IP connections and in top of that, we are in the world of Web Assembly, so our connections are not real sockets (as we would have with native DOSBox), but WebSockets.

Here is a diagram of the networking jumps and transformations:

And here are the components:

  • A tcp relay server that will act as a null modem, forwarding the information from one socket to another. I built a simple nodejs version of this using ChatGPT and you can downloaded here.
  • A service that translates WebSockets traffic to normal socket traffic. In my case I used the Websockify project also built in nodejs.

Both services can be run in the same machine, the only restriction is that the Websockify service needs to have a public interface so browsers can connect to it.

Once you have a public machine, launch first your tcp relay server:

git clone https://github.com/arcturus/pair-relayer.git
cd pair-relayer
npm install
node pair-relayer.js

Once that service running (by default in port 3000 unless specified), it’s time to launch the Websockify service and forward any incoming web sockets to connnections to the previous service:

git clone https://github.com/novnc/websockify.git
cd websockify/websockify
npm install
./websockify.js <PUBLIC_HOSTNAME>:<WS_PORT> <TCP_RELAY_HOST>:<TCP_RELAY_PORT>

Finally we need to tell our DosBOX instance that can use a nullmodem. In your Wacky Wheels directory where binaries are distributed place a file called dosbox.conf with the following content:

[serial]
serial1=nullmodem server:<PUBLIC_HOSTNAME> port:<WS_PORT> rxdelay:1000`

Repackage your game and enjoy the game in the browser.

How to build a Firebase Extension

Disclaimer: As of today, Firebase Extensions are an experimental feature in the platform and the way they work might change or even get removed from Firebase.

What are Firebase Extensions?

Firebase Extensions are the easiest way of pack and reuse Firebase Functions. That’s it. An extension is a packaged function that get installed in your Firebase project (one or multiple times) and that can be configured from the command line or the admin interface.

Firebase offers a set of extensions that have been growing on time, from sending emails, to process payments or translate text. Someone already created those functions for you and you just need to install and configure them in your projects.

You can request to become a publisher of extensions, this post is not about that, especially since it’s not clear to me how you apply to become a publisher. This post is about how to build one and test it.

Let’s build a Firebase Extension: Reverse geocoding

Let’s build a extension that given a document from your Firestore database with latitude and longitude, will reverse geocode those coordinates into a human readable address.

Firstly, mention that I wasn’t able to find much documentation about how to build them. So what I did is simple, use the power of Open Source and read the code:

You can find the codebase for this extension in this github repo.

First thing if you are going to work with extensions, tell firebase you want to play around with them, remember, is still a feature in preview mode:

$ firebase --open-sesame extdev

Let’s use the extension initializer that is, so far, hidden in the command line:

$ firebase ext:dev:init

After running that command and following some questions, you have an extension that is an example ready to modify:

Extension template created with firebase ext:dev:init

Your project structure will be:

  • CHANGELOG.md: Record your changes and different versions. Add release notes and those will appear in the Firebase Admin panel.
  • PREINSTALL.md: Documentation in markdown format that will be presented to the customer before installing the extension. The content of this file will be displayed by the command firebase ext:info. Useful if you plan to publish your extension.
  • POSTINSTALL.md: This file provides your users an overview of how to use your extension after they’ve installed it. All content is optional, but this is the recommended format. Your users will see the contents of this file in the Firebase console after they install the extension.
  • extension.yaml: This is the most important file. It describes the resources that your extension is exposing, like function or multiple functions. Also, declares the most important part of an extension, the parameters that you can use to configure the extension and adapt it to your needs. Unfortunately, all links related to documentation in the template are broken, so learning more about this file, and the correct format was a reading exercise from the extensions already created.
  • functions: This directory will contain your Firebase functions.

Our extension.yaml

Our extension transform latitude and longitude into a human readable address using Google Maps SDK. If we want to make it reusable we need to make it flexible.

To achieve that we will do the following:

  • We will define a function that will be triggered every time an document is created or updated in an specific Firebase path. This path must be configurable so users of our extension can adapt it to their needs. We will define a variable, COLLECTION_PATH, to configure this.
  • We should read latitude and longitude from fields in that document. Those fields should be configurable too (for example, they could be plain fields like ‘latitude’ or could be part of a Map ‘geo.latitude’). We will define two variables, LATITUDE_FIELD_NAME and LONGITUDE_FIELD_NAME, to let our extension know where to get latitude and longitude values.
  • The result should be stored in an configurable field of the original document. We will define the variable OUTPUT_FIELD_NAME to define where to store the address information.
  • Finally we will be using the Google Maps SDK to perform the reverse geocoding operation. An extra configurable parameter will be used here to each developer using your extension will need to provide their own. For this purpose we will define the variable GOOGLE_MAPS_API_KEY.

Here is our extension.yaml file:

name: firestore-extension-reverse-geocoding  # Identifier for your extension
version: 0.0.1  # Follow semver versioning
specVersion: v1beta  # Version of the Firebase Extensions specification

# Friendly display name for your extension (~3-5 words)
displayName: Reverse Geocoding

# Brief description of the task your extension performs (~1 sentence)
description: >-
  Given a document with a latitude and longitude, add an attribute to the document with the address for that point.

license: Apache-2.0  # https://spdx.org/licenses/

# Public URL for the source code of your extension
sourceUrl: https://github.com/firebase/firebase-tools/tree/master/templates/extensions

# Specify whether a paid-tier billing plan is required to use your extension.
# As we will performing calls to an external service the paid-tier will be necessary for this extension.
billingRequired: true

# In a `roles` field, list any IAM access roles required for your extension to operate.
roles:
  - role: datastore.user
    reason: Allows the extension to write to your Firestore Database instance.

# In the `resources` field, list each of your extension's functions, including the trigger for each function.
resources:
  - name: fsreversegeocoding
    type: firebaseextensions.v1beta.function
    description:
      Listens for new documents on the path specified via configuration, will look for latitude and longitude
      parameters and resolve them to a concrete address.
    properties:
      location: ${LOCATION}
      runtime: nodejs12
      eventTrigger:
        eventType: providers/cloud.firestore/eventTypes/document.write
        resource: projects/${PROJECT_ID}/databases/(default)/documents/${COLLECTION_PATH}/{messageId}

# In the `params` field, set up your extension's user-configured parameters.
params:
  - param: LOCATION
    label: Cloud Functions location
    description: >-
      Where do you want to deploy the functions created for this extension?
      You usually want a location close to your database. For help selecting a
      location, refer to the [location selection
      guide](https://firebase.google.com/docs/functions/locations).
    type: select
    options:
      - label: Iowa (us-central1)
        value: us-central1
      - label: South Carolina (us-east1)
        value: us-east1
      - label: Northern Virginia (us-east4)
        value: us-east4
      - label: Belgium (europe-west1)
        value: europe-west1
      - label: London (europe-west2)
        value: europe-west2
      - label: Frankfurt (europe-west3)
        value: europe-west3
      - label: Hong Kong (asia-east2)
        value: asia-east2
      - label: Tokyo (asia-northeast1)
        value: asia-northeast1
    default: us-central1
    required: true
    immutable: true

  - param: COLLECTION_PATH
    label: Collection path
    description: >
      What is the path to the collection that contains the latitude and longitued that you want to analyze?
    example: items
    validationRegex: "^[^/]+(/[^/]+/[^/]+)*$"
    validationErrorMessage: Must be a valid Cloud Firestore Collection
    default: items
    required: true

  - param: LATITUDE_FIELD_NAME
    label: Latitude field name
    description: >
      What is the name of the field that contains latitude value. Accepts dot notation.
    example: lat
    default: lat
    required: true

  - param: LONGITUDE_FIELD_NAME
    label: Longitude field name
    description: >
      What is the name of the field that contains the longitude value. Accepts dot notation.
    example: lon
    default: lon
    required: true
  
  - param: OUTPUT_FIELD_NAME
    label: Output field
    description: >
      What is the name of the field where you want to store your reverse geocoding components.
    example: reverse_geo
    default: reverse_geo
    required: true

  - param: GOOGLE_MAPS_API_KEY
    label: Google Maps Key
    description: >
      Google Maps Api Key used to request the reverse geocoding.
    example: XXX
    default: null
    required: true

Fetching our configuration

Once we install the extension we will need to configure and give value to those parameters. How do we fetch that data? Easy, through environment variables: process.env.

You can request this values at will, we will store them in a simple object in a file called config.js:

"use strict";

module.exports.default = {
  location: process.env.LOCATION,
  latitudeFieldName: process.env.LATITUDE_FIELD_NAME,
  longitudeFieldName: process.env.LONGITUDE_FIELD_NAME,
  outputFieldName: process.env.OUTPUT_FIELD_NAME,
  apiKey: process.env.GOOGLE_MAPS_API_KEY,
};

Building our main function

In our extension.yaml file we defined which resources our extension will be providing, and we name them. In our case we named a function called fsreversegeocoding, so that will be the function exported in our index.js file.

As well, for those familiar with Firestore, there are specific handlers for dealing with changes in documents, so our function will look like:

module.exports.fsreversegeocoding =
    functions.handler.firestore.document.onWrite(async (change) => {
        // do your stuff here
    }
);

We will use node-geocoder for calling the Google Maps API and Firebase SDK to store the results.

Take a look to the final result here.

Installing your extension

Let’s try our extension in one of our projects. We will do that from command line, from the directory containing the extension. Remember we are not distributing this extension, so if you want to install it download the source code and:

  • Remember to enable the firebase beta features if you didn’t do it before:
$ firebase --open-sesame extdev

Install the extension and provide the values for the configurable variables:

$ firebase ext:install . --project <your_project_name>

When you execute the install command you will get prompt for all the configurable attributes that you defined in the extension.

And from here, you’ll be able to interact and configure your extension from the Firebase Console.

Replacing irccloud with open web technologies

I’ve been a very happy irccloud user for the last 3 years, I think they offer a great service, but my passion for open web technologies and self-hosting solutions made this service the first candidate to be part of my revolution.

For me, the most important use cases for irccloud are to be always online, keep the logs of all channels I’m in and being notified when someone ping me or an interesting topic arises.

The search for self-hosting starts

With those as primary needs, I started searching for different open source projects but quickly I realised that hosting weechat is the best option for me:

  • It’s open source and well maintained.
  • Super extensible with several scripting languages.
  • Really well documented.
  • Has several opens source clients.

From those clients, I’m specially interested in the web ones, don’t need another app in my phone.

Glowing Bear is for me the best of the web clients:

  • Again well maintained and supported by the community.
  • Works well in desktop and mobile.
  • On the way to be a progressive web app, with manifest and serviceworker support!

What’s missing?

The only part missing in my setup is the ability to this combination of weechat + glowing-bear to use web push to perform notifications. Right now it uses normal desktop notifications to notify about private messages, but that’s not a push from the server running weechat. When we are not running glowing-bear we won’t know. And that part is important!

So what do we need to build? Spoiler alert, I already did, as a proof of concept, so I end up building the following:

  • A weechat plugin that uses web push when you receive a private message to send the notification to your browser.
  • Modify Glowing Bear to use web push and display notifications. Also reopen the client when user clicks on the notifcation.

A webpush Weechat plugin

Based on notifo plugin by Alex Poirot, modified the original python script to use webpush libraries and variables coming from the client.

You can find the weechat script in this github repo.

When you load the script via:
/python load webpush.py


You will be ask to provide a set of variables to make the script work. Some of those variables you will find them in the Glowing Bear client (scroll down for more details), other variables as the VAPID information needed for performing push can be generated by your own or via a curl request doing:
$bash> curl https://webpushproxy.progressiveweb.pw/generatevapid

While setting the variables needed you will see something like:

/set plugins.var.python.webpush.endpoint "https://updates.push.services.mozilla.com/wpush/v1/gAAAAABZC3AKX33Pwsof7y0Q2zppDCJliANyTZIe_UqkYPAKnFmz1dqO-j7YlBpFJOdqMFHwfnJ0gWHDB7bGH1EZ-7MhVHa6lflC82dffJGVXL_vv-5vpWL8AZ0g14ee_kRvWCFof1cA"

Variables settings for webpush plugin

Modifications to the Glowing Bear client

You can find all the modifications done to the client in this github fork (in the branch push_notifications).

Just two modifications. Once you accept to receive notifications from the glowing bear client, you will have in the settings screen the extra variables needed to configure the weechat plugin variables, see the image below:

push settings

The other modification happens directly in the file serviceworker.js where I only had to add the logic to process push notifications, open a new tab is there is no tab already open.

Demo

So I recorded a little video showing how you can receive the notifications even if your tab is closed, hope you enjoy (better to make the video bigger to check the text :O)

 

Playing around with The Physical Web

It’s been a while since I heard the first time from The Physical Web Project, but was not until last Over the Air where I had the chance with @wilsonpage and others to try out my self a beacon, and be surrounded by multiple of this tiny URL emitters.

The concept is quite simple, but at the same time adds so many new paths, questions and possibilities that after some time, that is after restarting my work at Mozilla decided to give it a spin.

My first approach was pretty simple, I spend a lot of time in front of a browser. Firefox for Android and Firefox OS are my choices, when I’m in a mobile device. They take care of my privacy, synchronize tabs with my desktop, also sync passwords, bookmarks, history and are damn fast. So, with all the love that I have for the browser, what could be a better place than the browser to interact with the Physical Web?

I started coding on my spare time, first with the android version, since it’s been a while that I don’t contribute to that project and wanted to refresh my skills.

And just a couple of weeks ago I finished my hack, at the same time that Google Ubiquity conference was happening. I didn’t have the opportunity to attend but while watching it offline came to the Physical Web introduction video that you can watch below:

Scott Jenson talk was stunning and pretty inspirational. So many possibilities! But the thing that make me open my eyes the most was the announcement of Opera and Chrome considering to add the concept of the Physical Web to their browsers! \o/

Seems I was not that crazy! So after another couple of nights finally I managed to polish the hack. Integration of the Physical Web into Firefox for Android, still a lot to do, but it does work beautifully.

No better place to find physical urls than your browser ?

After talking with some people from the Firefox for Android team they commented that will be amazing to have it as an add-on, for that we still need to wait until Gecko catch up with the implementation of the W3C Web Bluethooth api, but that will be really soon!