Fix a Nabi Big 20 HD Tablet that cannot connect to FUHU servers!

I recently got a Nabi Big 20 HD tablet to use as a camera viewer in a nursery at our church. The only problem was, when I turned it on, it kept giving me an error, stating that I had to hook up to the wifi, and that the FUHU servers had a problem. Essentially, since Fuhu went out of business, you can’t connect to the fuhu servers.

Since you can’t connect to the servers, setup can’t be completed, and this 20″ tablet became a 20″ paperweight. Obviously, that would not do, so I figured out how to use fastboot mode, TWRP, and the advanced file manager to delete the unneeded junk to make the tablet functional again. Below are my instructions, performed from a Linux computer. You could do this from Windows as well, if you download the right tools.

Before you begin, you will need the TWRP recovery image, which you can get from here: or from

  1. Power off the tablet.
  2. Hold the volume up and power on buttons, and release them when you see the menu options.
    NOTE: This is actually fastboot mode!
  3. Perform an OEM unlock
alaskalinuxuser@alaskalinuxuser-OptiPlex-7010:~$ fastboot oem unlock
(bootloader) Showing Options on Display.
(bootloader) Use device keys for selection.
(bootloader) erasing userdata...
(bootloader) erasing userdata done
(bootloader) erasing cache...
(bootloader) erasing cache done
(bootloader) unlocking...
(bootloader) Bootloader is unlocked now.
OKAY [ 13.625s]
finished. total time: 13.625s
  1. After this it reboots and starts up again, so power off the tablet.
  2. Hold the volume up and power on buttons again, release them when you see the menu options.
    NOTE: Once again, this is actually fastboot mode!
  3. Flash the recovery image.
alaskalinuxuser@alaskalinuxuser-OptiPlex-7010:~/Downloads$ fastboot flash recovery recovery.img
target reported max download size of 643825664 bytes
sending 'recovery' (8646 KB)...
OKAY [  0.313s]
writing 'recovery'...
OKAY [  0.314s]
finished. total time: 0.627s
  1. From the still open menu, use the volume keys to scroll down to “recovery mode” and press the power button once to choose it. NOTE: it will show the NABI screen, then reboot into TWRP. Unfortunately, ADB does not work in this version of TWRP.
  2. Go to “Mount” and check “System” and then click to disable MTP.
  3. Press the home key or back key to get back to the main menu.
  4. Click “Advanced”.
  5. Click “file manager”.
  6. Scroll to “priv-app” and select it.
    Click on each of these items and choose to delete them:
    –Personally, I just deleted all “fuhu” apps in this folder, but I think you only need those ones.
    Then in the “app” folder, delete all the fuhu apps. Yes, I’m pretty sure you need to delete all of these ones.
  7. Select the home or back button to get to the main TWRP screen.
  8. Reboot to system.
  9. Enjoy!
    NOTE: It should start up, and may go through the Google setup (if you never started it before), and then will drop you off in “parent mode”.

At this point, I recommend installing a regular launcher, such as Trebuchet, Apex launcher, Nova launcher, etc. I used Apex launcher personally, because you can “hide” unwanted apps, and I use it to hide the unwanted Nabi apps. You should be able to see the Chrome browser in the parent mode window, use that to download the apk for the launcher you want (or use the Google account if you set up an account).

Once a launcher is installed, press the home key, and choose to always use the launcher you installed. You can now use this tablet as a regular Android tablet.

TWRP does have the option to install SuperSU and root the device. That’s completely up to you. Kingo Root also works incredibly well on this tablet. It will be stuck on Android 4.4.2, so it is a little outdated, but seems to work rather well. It was designed for gaming, so it is pretty powerful for as old as it is.

Linux – keep it simple.

Let’s Encrypt with DDNS on CentOS 7

My new A+ rating for my personal web server, with certificates from Let’s Encrypt!

A while back, I started using CentOS, with Apache, to host my own website. As I talked about here on this blog, the website is for my Piwigo server, which is a Google Photo’s alternative. My pictures from my phone are backed up to my home server automatically, and the Piwigo server acts as an interface where people with appropriate passwords can log in and see the photos. Typically, just me and my wife.

One problem that I had, however, was difficulty getting a certificate from a CA (Certificate Authority), and I had to use a self signed certificate. This worked great, to be honest, except that some browsers have a pesky “this is not secure” message that you had to accept alot. It got old if I was showing some one, either client or friend, the setup but had to acknowledge a big security warning.

So, I set out once again to try to get that fixed. I heard a lot of good things about Let’s Encrypt, the free, open source encryption method, and that they now support DDNS, so I thought I’d give it a try. So, logging into the terminal, I followed the instructions, and got this in the terminal:

[root@localhost alaskalinuxuser]# certbot --apache
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator apache, Installer apache
Starting new HTTPS connection (1):

Which names would you like to activate HTTPS for?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel): 1
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for
Cleaning up challenges
Unable to find a virtual host listening on port 80 which is currently needed for Certbot to prove to the CA that you control your domain. Please add a virtual host for port 80.

This was a bit confusing to me, since I could browse to my own website on port 80. But, fortunately, I found the answer here:

So, I made a new file at /etc/httpd/conf.d/alaskalinuxuser.conf and filed it in with this:

<VirtualHost *:80>  
    DocumentRoot /var/www/html 

After that, I exited nano and restarted the httpd daemon, and was able to re-run certbot:

[root@localhost conf.d]# certbot --apache -d
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator apache, Installer apache
Starting new HTTPS connection (1):
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for
Waiting for verification...
Cleaning up challenges
Deploying Certificate to VirtualHost /etc/httpd/conf.d/ssl.conf
Redirecting vhost in /etc/httpd/conf.d/alaskalinuxuser.conf to ssl vhost in /etc/httpd/conf.d/ssl.conf

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations! You have successfully enabled

And now I have a CA vouching for my web server!

Linux – keep it simple.

Putting LineageOS on a Samsung Galaxy S9 (SM-G960F/DS)

LineageOS on the Samsung Galaxy S9

Recently, a friend of mine wanted to get away from the stock software of their phone, and asked for my help to install LineageOS. There were several write-ups about how to do this, so I’m just sharing my experience of making this process work, in case it is useful to someone else.

First things first, LineageOS says that you need to be on the latest Android 10 firmware for this to work. So, after getting the phone, I turned on automatic updates under software update and had to go through 4 rounds of downloading and installing the updates. This took a while on a 10 Mbps internet connection, about 3 hours. But finally, it was up to date from Android 9 to now be Android 10 with the latest firmware.

During this time, I also downloaded the latest LineageOS nightly and recovery for the S9. As seen from their site.

Now with the latest firmware, I enabled developer options by clicking on about phone, and multiple taps of the build number to enable developer options. I looked in the developer options for the OEM unlock option, but it was no-where to be found. Interesting…. But, a quick search of XDA revealed that you have to wait 7 days after enabling the developer options before you can see the OEM unlock option. However, a user, named altai1963 had a neat post about disabling automatic time updates and changing the date to be a month ago and tricking the phone that more than 7 days had passed. So, I used it, and sure enough, it worked like a charm.

I did notice though, that I didn’t need to follow all of the steps they had done. All I did was uncheck auto date/time, and set the date back one month. Then the OEM unlock option became available under developer options. After I clicked on it, it asks if you want to wipe all of your data and proceed, to which I said yes. The phone rebooted to the Samsung logo for a while with a blue flashing notification light. After about 4 minutes, it said “Android wirt restarten” and then rebooted in (I believe) German.

Fortunately, Australian English was an option from the drop-down, as was Canadian English, so I went with my old friends the Aussies and clicked through the startup screens, choosing to skip setup of anything, since I was about to wipe it anyways to install LineageOS.

Now the fun begins….

I powered off the phone from the menu, and then held Volume down, the Bixby button, and Power button to power it on. Within a second, the screen was green with a download icon and lots of text, so I released all of the buttons, and per the on screen instructions, pressed volume up to allow changes.

Per LineageOS’s instructions, I tested that Heimdall (which was already installed on my Linux computer) was working and compatible with the phone:

$ heimdall print-pit
Heimdall v1.4.1

Copyright (c) 2010-2014 Benjamin Dobell, Glass Echidna

This software is provided free of charge. Copying and redistribution is

If you appreciate this software and you would like to support future
development please consider donating:

Initialising connection...
Detecting device...
Claiming interface...
Setting up interface...

Initialising protocol...
Protocol initialisation successful.

Beginning session...

Some devices may take up to 2 minutes to respond.
Please be patient!

Session begun.

Downloading device's PIT file...
PIT file download successful.

Entry Count: 32
Unknown 1: 1598902083
Unknown 2: 844251476
Unknown 3: 21324
Unknown 4: 14665
Unknown 5: 12600
Unknown 6: 48
Unknown 7: 5
Unknown 8: 0

--- Entry #0 ---
Binary Type: 0 (AP)
Device Type: 8 (Unknown)
Identifier: 80
Attributes: 2 (STL Read-Only)
Update Attributes: 1 (FOTA)
Partition Block Size/Offset: 0
Partition Block Count: 1024
File Offset (Obsolete): 1
File Size (Obsolete): 0
Partition Name: BOOTLOADER
Flash Filename: sboot.bin
FOTA Filename: 
----- Edited for breivety ------
--- Entry #31 ---
Binary Type: 0 (AP)
Device Type: 8 (Unknown)
Identifier: 25
Attributes: 5 (Read/Write)
Update Attributes: 5 (FOTA)
Partition Block Size/Offset: 1697152
Partition Block Count: 0
File Offset (Obsolete): 0
File Size (Obsolete): 0
Partition Name: USERDATA
Flash Filename: userdata.img
FOTA Filename: remained

Ending session...
Rebooting device...
Releasing device interface...

And then the phone rebooted. So I shut down the phone again, and again went into download mode by pressing Volume down, the Bixby button, and Power button to power it on. Again, the screen was green with a download icon and lots of text, so I released all of the buttons, and per the on screen instructions, pressed volume up to allow changes.

Now from my computer terminal, I typed:

$ heimdall flash --RECOVERY ./lineage-17.1-20200615-recovery-starlte.img --no-reboot
Heimdall v1.4.1

Copyright (c) 2010-2014 Benjamin Dobell, Glass Echidna

This software is provided free of charge. Copying and redistribution is

If you appreciate this software and you would like to support future
development please consider donating:

Initialising connection...
Detecting device...
Claiming interface...
Setting up interface...

Initialising protocol...
Protocol initialisation successful.

Beginning session...

Some devices may take up to 2 minutes to respond.
Please be patient!

Session begun.

Downloading device's PIT file...
PIT file download successful.

Uploading RECOVERY
RECOVERY upload successful

Ending session...
Releasing device interface...

Now the phone showed a load bar across the screen. It was finished as soon as I saw it, so it was pretty fast. Then, I unplugged the USB cable, and per the instructions on the screen, held Volume Down and the Power button for 7 seconds to get out of download mode. As soon as it switched off, I held the Volume Up button with the Bixby button, and the Power button to go into recovery mode. This is important, because if you let it boot all the way into the stock ROM, it will wipe the recovery you just loaded. After a second or two, I could see the pink LineageOS recovery screen, and I plugged back in the USB cable to the computer.

On the phone, I used the volume keys and power button to select “Apply update” and “ADB sideload”. Then, on the computer terminal, I typed:

$ adb sideload ./
serving: ‘./’ (~17%)

To which it proceeded to 47%, and the phone screen had small white text at the bottom with status, eventually stating “script succeeded: result was [1.000000]” and giving me a prompt back on the phone screen and computer terminal.

At this point, I could install Magisk or Gapps, but in this case the owner of the phone wanted to install only LineageOS. So, I pressed the back arrow at the top of the phone screen, and tapped “Reboot System Now”. The phone rebooted and immediately showed me the LineageOS boot logo, and then a pop up asking me to wipe all encrypted data. I said yes, the phone rebooted to wipe the data, then rebooted back to LineageOS. Success!

One final note, when I started this process, the battery was at 100%, but by the time I finished, the battery was down to 67%, so be sure to fully charge the phone before you start this process, if you intend to follow along.

Linux – keep it simple.

Convert Nero’s NRG to standard ISO file format

While searching for inspiration for 3D programming ideas, I decided to play a few old games for a few minutes to get the feel for the classics. In so doing, I had one game that the CD was burnt using Nero, which saved the CD as an NRG file. Unfortunately, Linux can’t natively use NRG files without some tweaking or special programs.

What Linux can do, however, is convert the NRG files to standard ISO file formats which can be used in Windows. A quick duckduckgo search lead me here:

In the article, it suggests using nrg2iso, which should be available in the Ubuntu repository. While it was available, and seemed to work, the end result was an unusable file that took up over 600 mb of space and couldn’t be mounted as a CD. So, I had to keep digging. A quick apt-cache search for nrg gave me some clues:

alaskalinuxuser@alaskalinuxuser-OptiPlex-7010:~/Downloads$ apt-cache search nrg
libcdio-dev - library to read and control CD-ROM (development files)
libcdio17 - library to read and control CD-ROM
acetoneiso - feature-rich application to mount and manage CD and DVD images
bashburn - simplify cd/dvd burning at the command line
ecm - prepares CD image files so they compress better
furiusisomount - ISO, IMG, BIN, MDF and NRG image management utility
fuseiso - FUSE module to mount ISO filesystem images
golang-github-disintegration-imaging-dev - Simple Go image processing package
iat - Converts many CD-ROM image formats to iso9660
nrg2iso - Extracts ISO9660 data from Nero ".nrg" files

Iat looked interesting, online I could only find a page in German, stating it is an ISO9660 Analyzer Tool, but other searching online revealed it could be used for converting nrg files to iso’s as well. So, I gave it a try:

alaskalinuxuser@alaskalinuxuser-OptiPlex-7010:~/Downloads$ sudo apt-get install iat
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following NEW packages will be installed:
0 upgraded, 1 newly installed, 0 to remove and 28 not upgraded.
Need to get 7,056 B of archives.
After this operation, 24.6 kB of additional disk space will be used.
Get:1 bionic/universe amd64 iat amd64 0.1.3-7build1 [7,056 B]
Fetched 7,056 B in 0s (21.2 kB/s)
Selecting previously unselected package iat.
(Reading database ... 280573 files and directories currently installed.)
Preparing to unpack .../iat_0.1.3-7build1_amd64.deb ...
Unpacking iat (0.1.3-7build1) ...
Setting up iat (0.1.3-7build1) ...
Processing triggers for man-db (2.8.3-2ubuntu0.1) ...
alaskalinuxuser@alaskalinuxuser-OptiPlex-7010:~/Downloads$ iat ./Mechcom_gold.nrg ./Mechcom_gold.iso
Iso9660 Analyzer Tool v0.1.3 by Salvatore Santagati
Licensed under GPL v2 or later

Detect Signature ISO9660 START at 339968
Detect Signature ISO9660 START at 342016
Detect Signature ISO9660 at 344064

 Image offset start at 307200
 Sector header 0 bit
 Sector ECC 0 bit
 Block 2048

And the result was a success! Not only did it complete properly, but the generated ISO file was usable in Linux. So now I can play some games… er, conduct my research.

Linux – keep it simple.

Install μlogger on CentOS 7 Server

If you are unfamiliar with μlogger, it is a handy Android application paired with a self hosted server application to keep track of your location. Or, in the author’s own words:

μlogger (‘micro-logger’) is an application for continuous logging of location coordinates, designed to record hiking, biking tracks and other outdoor activities. Application works in background. Track points are saved at chosen intervals and may be uploaded to dedicated server in real time. This client works with μlogger web server. Together they make a complete self owned and controlled client–server solution.

You can actually just use the app on your phone to record yourself walking, biking, flying, etc., and then save your track as a gpx file for editing or viewing on any supporting app on your phone. But, if you wanted to save your adventures easily, or keep your adventures updating live for someone you know to view, then you can use the web server application as well. It works incredibly well, and if you are out of cell/wifi range, it will update with your personal server once you return, which is very useful.

The only problem was, while it had clear instructions for installation, they were written for someone smarter than me, so it took a bit of work to get it set up on my home CentOS 7 server. Hopefully, by writing this down, others can save themselves a little bit of a headache.

First, I would like to mention that this assumes you already are running a web server on your machine. In my case, I am running the Apache web server. So, I wont cover web server installing and setup here, but there are some great tutorials for that out there, like this one.

The instructions look like this, per the

  • Download zipped archive or clone the repository on your computer
  • Move it to your web server directory (unzip if needed)
  • Fix folder permissions: uploads folder (for uploaded images) should be writeable by PHP scripts
  • In case of development version it is necessary to build javascript bundle from source files. You will need to install npm and run npm install and npm run build in root folder
  • Create database and database user (at least SELECT, INSERT, UPDATE, DELETE privileges, CREATE, DROP for setup script, SEQUENCES for postgreSQL)
  • Create a copy of config.default.php and rename it to config.php. Customize it and add database credentials
  • Edit scripts/setup.php script, enable it by setting $enabled value to true
  • Make sure you have a web server running with PHP and chosen database
  • Open http://YOUR_HOST/ulogger-server/scripts/setup.php page in your browser
  • Follow instructions in setup script. It will add database tables and set up your μlogger user
  • Remember to remove or disable scripts/setup.php script
  • Log in with your new user on http://YOUR_HOST/ulogger-server/
  • You may also want to set your new user as an admin in config file.
  • Folders .docker/ and .tests/ as well as composer files are needed only for development. May be safely removed.

The biggest headache that I ran into was that the minimum requirement was for PHP 5.5, and CentOS 7 only comes with PHP 5.4 by default. However, that was easily fixed as root:

yum update
yum install
yum install
yum install yum-utils
yum-config-manager --enable remi-php55 [Install PHP 5.5]
yum-config-manager --enable remi-php56 [Install PHP 5.6]
yum-config-manager --enable remi-php72 [Install PHP 7.2]
yum install php php-mcrypt php-cli php-gd php-curl php-mysql php-ldap php-zip php-fileinfo

This updated PHP to version 7.2, so you could skip that line if you didn’t want it, but I figured it might save me from having to update it in the future, so I went with it. Note that I did have to test out my other server functions that use PHP to make sure they were compatible.

After getting PHP up to date, I then downloaded the μlogger server repository from GitHub. It is pretty small, and only took a few seconds, even on my slow 10 mb internet to download it. I extracted in place, and went to work in the terminal, moving it to my web server location and giving it the proper ownership:

cd Downloads/
cd ulogger-server-master/
mkdir /var/www/html/ulogger
cp -Rav ./* /var/www/html/ulogger/
cd /var/www/html/
chown -R apache:apache ./ulogger
cd ulogger/

Now that it is in the right place, I needed a database for it to work with….

mysql -u root -p
enter password:
MariaDB> create database uloggerdb;
MariaDB> grant all privileges on uloggerdb.* TO 'EDITED_USERNAME'@'localhost' identified by 'EDITED_PASSWORD';
MariaDB> flush privileges;
MariaDB> exit

Keep in mind, the author of μlogger only suggests a few privileges, which he states in his read me as “(at least SELECT, INSERT, UPDATE, DELETE privileges, CREATE, DROP for setup script, SEQUENCES for postgreSQL)” however, as I monkeyed around with this, the script only seemed to run when I gave my user all privileges. Not sure if that’s just me, but here’s what I did, and praise God, it worked, because I was getting a little frustrated at this point. This write-up is the end result, not the “how many times I failed setting this up” story….

Now I needed to proceed and make a copy of the config file and edit it per the instructions:

cp config.default.php config.php
nano config.php
cat config.php 

/* μlogger
 * Copyright(C) 2017 Bartek Fabiszewski (
 * This is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <>.

// This is default configuration file.
// Copy it to config.php and customize

// Database config

// PDO data source name, eg.:
//$dbdsn = "mysql:host=localhost;port=3306;dbname=uloggerdb;charset=utf8"
//$dbdsn = "mysql:unix_socket=/tmp/mysql.sock;dbname=uloggerdb;charset=utf8"
// pgsql:host=localhost;port=5432;dbname=uloggerdb
// sqlite:/tmp/uloggerdb.db
//$dbdsn = "uloggerdb";
$dbdsn = 'mysql:dbname=uloggerdb;host=';

// Database user name
$dbuser = "EDITED_USERNAME";

// Database user password
$dbpass = "EDITED_PASSWORD";

// Optional table names prefix, eg. "ulogger_"
$dbprefix = "";


The key part that went wrong here, was that there were several examples of how to set the “$dbdsn” or database name and location. I tried the ones included in the file, but none of them worked, so I web searched and found this line worked: $dbdsn = ‘mysql:dbname=uloggerdb;host=’; so that is what I used in the end, instead of any of the included examples.

Now I needed to enable the setup script:

nano scripts/setup.php 

.... edited for space ....
// This script is disabled by default. Change below to true before running.
$enabled = true;
.... edited for space ....

With the setup script enabled, it was now time to get started! I opened https://EDITED_HOST/ulogger/scripts/setup.php page in my browser, and followed the on screen instructions, logging in with the username and password I made earlier, and everything went smoothly! Keep in mind, once the setup is done, you need to delete or disable the setup script to prevent it’s future use. So I just deleted it. Here is the end result when you bring up the web page https://EDITED_HOST/ulogger/

Sorry, I blurred everything out because I didn’t want any uninvited house guests! 🙂 If you want to see a great demo of it from the author, though, you can check it out from his web server on Heroku. Here’s a screen shot of it:

The mobile app is very simple to setup. Under settings, you simply input the username, password, and URL for your web server. You can then choose if you want it to auto upload as you go, or you can update it manually by pressing an upload button on the main screen.

I thought it best to mention that the original user you make is an administrator, and can edit several settings. I recommend that you make a new user with lesser credentials (non-admin) for use with the phone app. When you are logged in as the administrator (the first account you made) you can choose to make new users. This allows you to have lesser accounts for daily logging of activity. It also is handy if you are using this for your company as a way to track drivers/workers or if you have multiple family members, because everyone can have their own username and password.

Hopefully, this will save others using CentOS 7 a little bit of time to set this up!

Linux – keep it simple.

Ship, Captain, and Crew: Ready to set sail!

Well, for better or worse, it’s finally finished! That’s right, my first, from scratch, no tutorial, 3D dice game, “Ship, Captain, and Crew” is now in Beta stage! Be sure to give it a try!

You can find all the source code here:

And you can download the Windows, Linux, and Android versions here:

Be sure to let me know in the comments if you find any bugs or have any suggestions. The game is not really for playing by oneself, although there is a player vs computer setting. However, the key point of this game is a sort of paper/rock/scissors game with your friends when you need to decide something, like who’s buying, or who gets to pick where/what/etc. next.

You can also read the official rules on Wikipedia, which my game follows. I mention this, because some play where you must roll a 4,5, then 6 for ship, captain, and crew, and others play 6, 5, then 4 instead. Either way, the mechanics of the game are the same, just which order you must roll the dice. In this game, I set it to follow the official 6, 5, and 4 rule-set.

Let me know if you tried it out!

Linux – keep it simple.

Ship, Captain, and Crew: Cross Platform Differences?

Hopefully, the above pictures came through for you, or this wont make much sense. I’ve been working on my dice game, and it is nearly completed. However, as I test it out on different platforms, I notice that it doesn’t look the same. In the above screen shots, the left is from my Linux desktop, and the right is from my Android phone.

What hopefully is obvious is that the Android phone has an abnormal pixelation/black dots on it. Also notice that the table and dice are brighter on the Linux machine. Both screens are using a 1920×1080 resolution, so it isn’t a stretch/scale problem. The darkness of the Android screen is not a display brightness problem, as these are screen shots and not pictures of the screen. So what is causing this?

Well, after a bit of web searching, I came up with nothing. Thus, I must be using the wrong key words. But, after some trail and error, I found that the culprit was the texture normals for the table. Interesting. As far as I can tell, the normals are used to calculate light beams reflecting/refracting off of the surface of the object. For some odd reason, the normals texture overlay work great on the computer, but fail miserably on the phone. But both phone and computer support openGL 2 and 3, and this game is only openGL 2.

So, I don’t know why it happens. If you happen to know, please feel free to let me know in the comments. What I did to fix it was remove the normals texture overlay, and just use the textured wood overlay for the table. Now both the phone and the computer look the same. As always, you can check out the full project on my GitLab.

Linux – keep it simple.

Ship, Captain, and Crew: Exports and keys


So, as I near completion of my 3D dice game, I ran into several small issues that I thought I would share here, since they all apply to exporting your game. Of course, as you go along, if you are making a cross platform game, you occasionally need to export it and test various things on the different platforms (more on that in another post). And when you export them, you have to use this export function and templates.

So, that said, a few interesting tid-bits:

First, I found it interesting that when you make a release key for your Android version of the game it saves your key password in a plain text file in your directory folder. Ordinarily, this wouldn’t be a terrible issue, except that as a maker of open source games, when I use git to sync my work, it then would save the release key in plain text on GitLab! So, if you are to the point where you stop using debugging, and start using release keys that you made or signed, you will have to do the following:

  • Open you project folder
  • Find the file called: export_presets.cfg
  • Find the line called: keystore/release_password=
  • and delete your password.

Or, you can delete your password from inside Godot, in the exports screen by scrolling down to it and deleting it and saving the project. I find this to be a bit annoying. I would think Godot would at least hash it, or something. Or keep passwords in another file, specific for Godot, and not in your project folder. But that’s just me, I guess.

Second, if you are making an Android version of the game, you will need an icon. By default, it uses the icon you already have for your game in the main project folder, called “icon.png”. But if you don’t specify an icon for the Android game, your main icon will come out looking wrong (way zoomed in) on your Android device.

After some reading, I found that you need to supply one 192×192 pixel icon, which Godot will then translate into the various other icon sizes. But, this didn’t work. I found out later that is because in Android 8+ you need 432x pixel icons. So, in all, you will need several icons, each used for different launchers. Or, carefully edit your main icon and it will be used in a fallback chain and edited by Godot to make your icon for the Android application.

Third, I ran into a lot of problems with key signing. I’d like to point out that in the guide it says that you set the debug key in the editor settings. This is true, and without that it will not work at all. That said, with that, it will not work at all either. At least for me. I had to also specify in the project export template for the Android app the paths to the debug keys again. Otherwise I got an error about “jarsigner” failing. Overall, this is only a minor inconvenience, but it was a big hang up for me until I figured it out. You then have to remove all of that and enter you release keys if you want to make a release version.

Just a few thoughts from a guy who doesn’t really know what he’s doing, so take it with a grain of salt. But, if you run into the above issues, be sure to try these out. Also, you can check out the full code for my game here on my GitLab.

Linux – keep it simple.

Ship, Captain, and Crew: Give it a spin!

Well, I suppose “roll” would be a more popular dice game term, but spin is actually more accurate in this case! I’m actually rotating the dice in a spin maneuver, and having them fall on the table, rather than rolling them across it from an angle or the side.


The first question I had to tackle was what type of object to use for the game. The three types are Static Body, Kinematic Body, and Rigid Body. Each have their own uses, and here I put into words my limited understanding. Be sure to comment if you can clarify any of these.

Static Body is an object that is not supposed to react to any force. It should instead stay right where it is when it is struck by another object. In this game, I used a static body for the table top. This way the force of being struck by objects, such as the dice, and physics powers, like gravity, have no affect on it. In fact, I didn’t even give the table legs, because nothing is needed to “hold” the table up, since it is a static body.

Kinematic Body is an object that is often used for making the main player of a game, or something like that. It is not affected by physics unless you program it too. Gravity and such have no affect on it until you tell it to through code. So, if I had a kinematic ball roll across the table, when it gets to the edge, it would keep rolling on air, as if the table were still under it. This is what we used in BornCG’s great video game tutorial for the main player. It is very easy to control movement of this object.

Rigid Body is a type of object that by nature is fully affected by physics. Gravity and inertia all come into play on this type of object. If the table were not there, a rigid body object would fall indefinitely. These objects are difficult to move in one sense, because you can’t just say, “go that way” like you do with kinematic bodies, instead you have to exert force against them in a direction to cause them to move that way.

To make the dice game as realistic as possible, I used rigid bodies for the dice. When they fall to the table, they can bounce, tumble, or bump and shove other dice. The big question was how to move them. Of course, once in the air, they fall to the table and tumble, etc. That was the easy movement, because it is not programmed by me at all, it is simply the engine physics taking place. The tough movement was what to do with them after they hit the table.

Originally, I was planning to move them aside, and set them out of the way so I could roll the remaining dice. Not having any 3D experience, I thought this wouldn’t be too hard, but it became a nightmare for rigid bodies. Here are several methods I tried after reading hours of documentation and QA forums:

var oldPosition = get_node("LowPolyDiceC").get("dicePosition")
var targetPostion = get_node("pirateShip").get("shipPosition")
var newDirection = targetPostion-oldPosition
# This one only moves a tiny bit, and not in the right direction.   
# This one doesn't move at all.   
# This one rockets it towards the ship, then it falls off the table.   
# This one does not go to the ship, but goes up in the sky near it.

I found several ways to translate, or “teleport” the dice somewhere else, but not where I wanted them. And, once transported, they didn’t work with physics anymore. I also found ways to whack them with a force, but they arced up through the air and fell off the table (think golf club hitting golf ball). None of those would do. I tried asking my issue as a question on Godot’s QA forums, but I didn’t get a response from anyone, so I had to give this idea up.

Instead, I used the impulse (force) to send the dice straight up into the air again. Then, as they go up into the air, I perform several “spins” of the dice to randomize the result. Here’s my code:

func _make_dice_random (diceName):
	var x = randi() % 365 + 1
	var y = randi() % 365 + 1
	var z = randi() % 365 + 1

func _spin_dice():
	if tumbler > 0 :
		tumbler = tumbler - 1
		#print (tumbler)
		var upAmmount = 4
		if takenA != 1:
		if takenB != 1:
		if takenC != 1:
		if takenD != 1:
		if takenE != 1:
	else :
func _roll_dice():
	remainingRolls = remainingRolls - 1
	get_node("ControlHUD/HUDrollsNumLabel").set("howManyRolls", remainingRolls)
	tumbler = 3
	var upAmmount = 6
	if takenA != 1:
	if takenB != 1:
	if takenC != 1:
	if takenD != 1:
	if takenE != 1:

It reads from the bottom up. First I call the _roll_dice() function to send any dice up that are not “taken” or in use by the player for their ship, captain, or crew. Then a timer is started, to allow the dice time to rise up off the table. Once the timer ends, it calls the _spin_dice() function, which has several calls to the _make_dice_random(dicename) function, picking random numbers of 365 degrees*. This is done as many times as the “tumbler” variable is set to, or in this case, 3 times. Since the dice are rotated on the x, y, and z axis, they get very randomly arranged, and then fall to the table, tumbling around to their final position.

Each dice has 6 small invisible cube areas on the faces of the dice that correspond to the opposing sides number. So, when they hit the table, that reports the number the dice has face up to the variables, so the dice number is set and known. That looks like this:

func _on_AreaA2_body_entered(body):
	if == "TableObject" :
		diceA = 2
		#print (diceA)

And finally, once the dice have come to rest, they are checked for their number:

func _check_all_dice():
	#print ("dice check")
	#print (diceA, diceB, diceC, diceD, diceE)
	# Check for Ship
	if hasShip == 0 :
		if diceA == 4 :
			hasShip = 1
			takenA = 1
		elif diceB == 4 :
			hasShip = 1
			takenB = 1
		elif diceC == 4 :
			hasShip = 1
			takenC = 1
		elif diceD == 4 :
			hasShip = 1
			takenD = 1
		elif diceE == 4 :
			hasShip = 1
			takenE = 1
	# Now check for captain
	if hasShip == 1 :
		if diceA == 5 :
			hasCapt = 1
			takenA = 1
		elif diceB == 5 :
			hasCapt = 1
			takenB = 1
		elif diceC == 5 :
			hasCapt = 1
			takenC = 1
		elif diceD == 5 :
			hasCapt = 1
			takenD = 1
		elif diceE == 5 :
			hasCapt = 1
			takenE = 1
	# Now check for crew
	if hasCapt == 1 :
		if diceA == 6 :
			hasCrew = 1
			takenA = 1
		elif diceB == 6 :
			hasCrew = 1
			takenB = 1
		elif diceC == 6 :
			hasCrew = 1
			takenC = 1
		elif diceD == 6 :
			hasCrew = 1
			takenD = 1
		elif diceE == 6 :
			hasCrew = 1
			takenE = 1
	# Now check cargo
	if hasCrew == 1:
		if remainingRolls > 0:
		_update_score(diceA + diceB + diceC + diceD + diceE - 15)
	else :
	if remainingRolls > 0:
		if theCurrentPlayer == 0:
	else :

This string of if/elif statements is really ugly, but gdScript doesn’t have a “switch” or “case” block method. It does have a “match” method, but I couldn’t understand how to implement it properly.

Hopefully that was interesting and or helpful to other newbies like myself. You can always check out the full source code for the game at my GitLab.

Linux – keep it simple.

* Yes, I know there are really only 360 degrees in a circle, but this actually improved the random factor, based on my research, because I was under the impression that the random number generator seemed to favor slightly lower numbers, so I added a few. If it goes over 360, it just keeps rotating, as if from 0.

Ship, Captain, and Crew: Sounds Good…


Well, one of those things every game needs is sounds. Not just sound effects, but also background music. It’s a really easy way to enhance the game experience without actually changing any part of the game mechanics. The only problem was, I didn’t know how to do it in Godot.

So, after reading several tutorials, and reading Godot Engine’s how to’s and Q/A’s, I started out by adding some music to my game. At first, per the guides, I just added a new child node to my game launch menu that was an AudioSamplePlayer, and added the music to it. Unfortunately, this worked great in the launch menu scene, but as soon as you changed scenes, such as going to the game table scene, the music would abruptly stop.

So I read several more Q/A’s and guides. The problem is, if you put something in a scene, it ends when the scene does. What I needed was a scene that would be always loaded and was outside of the normal game. Per the guide, I then made a scene consisting of just the AudioSamplePlayer, chose music for it, and saved it as it’s own scene, called MusicPlayer.tscn. Then I went to the project settings and added it to the auto loader when the game starts.

But it didn’t work. In fact, the whole game crashed.

Turns out, since my automatic global scaling script changed all the root nodes children’s scales to meet the screen size, it would crash trying to pull the rectangle size of the MusicPlayer scene (since it doesn’t have one). That was a bit frustrating. But, it turned out to be a simple fix. I changed the MusicPlayer scene to have a Control Node as the root node, and gave it the same size as every other scene in the game, 1920×1080. Then it would allow me to set it’s size, and the game would continue. Seemed a bit hacky to me, but to God be the glory, it worked!

Then there was the question of the dice sounds, when the dice hit the table. Granted, later I’ll be going over the dice mechanics code in the game, but for now, it was simple to add to each dice:

func _on_AreaA6_body_entered(body):
if == “TableObject” :
diceA = 6
#print (diceA)

I simply added one audio node to the game table scene, called diceSoundPlayer, added my dice wave sound to it, and then started it with each collision between dice and table. It worked great!

Feel free to check out my project on my GitLab!

Linux – keep it simple.