Sep 222017
 

Changeicon logo[1] 1. Introduction

Note: A more step-by-step guide is to be found farther below, see point 5.!

Recently, [Umlüx] has reminded me of his idea to be able to (visually) tag folders on Windows for specific purposes. For convenience, it’s supposed to work by right-clicking a folder in Windows Explorer, opening a submenu from the context menu, and then by picking the proper tag. After that, the folder icon should change, indicating that something’s special about this one. Windows can actually do the “tagging” part by itself using desktop.ini files, but manually writing them is a pain of course, hence the right-click idea.

The thing is, there already are tools to address folder tagging on Windows, but they’re often not feature complete, have limited XP compatibility or they lack very important features like timestamp preservation. Others have that last part, but only in paid versions of their software. So, time to do it by ourselves!

I picked his Powershell code up, and while it would run on my ancient XP x64 machines, implementing the required menu structure proved to be impossible, especially the cascading part. Ah, let me just show you the final product first, so you know what it was that I wanted (in my case, it’s meant for tagging media folders):

Changeicon context menu

“Changeicon” context menu (click to enlarge)

2. Creating the menu structure (XP + Vista compatible)

Starting with Windows 7, Microsoft introduced a new way to create submenus in the context menu, including icons, all in the Windows Registry. Older versions of Windows like Vista or XP however can’t do that, and I wanted a solution that works on all of them, from XP to Win10. So how can programs like 7-zip create such Explorer submenus with icons on legacy systems? They do so by injecting a COM .dll into the graphical shell, extending its capabilities. Typically, those are written in C++, and that’s not something I want to or can even do.

Luckily, we don’t have to develop that by ourselves, as somebody has already done it: Meet the [KuShellExtension]!

KuShellExtension – or KuShellExt in short – is a set of two library files, KuShellExtension.dll as well as KuShellExtension64.dll, the latter being meant for 64-bit Vista machines. It’s compiled for NT 5.2 though, so it’ll also work on XP x64 and Server 2003 x64. On top of that, it still works even on Windows 10! With the libraries comes an XML configuration file and some simple installer/uninstaller shell scripts.

In the example we’ve seen in the screenshot above, the respective configuration in config.xml would look like this:

expand/collapse source code
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <config version="1">
  3.  
  4.   <!-- Gobal variables -->
  5.   <var name="LEGACY_STYLE">false</var>
  6.   <var name="HIDE_MISSING">false</var>
  7.   <var name="ICON_DIR">${var:APPDATA}\changeicon\icons\</var>
  8.   <var name="INSTALL_DIR">${var:APPDATA}\changeicon\bin\</var>
  9.  
  10.   <!-- Menu -->
  11.  
  12.   <menu name="Tag Folder" class="folder" icon="${var:ICON_DIR}MainIcon.ico">
  13.     <!-- Submenus -->
  14.     <menuitem name="Tag as &quot;Currently watching&quot;" class="folder" icon="${var:ICON_DIR}CurrentlyWatching.ico" action="execute" console="false" multiple="N" workdir="C:\">
  15.       wscript &quot;//B&quot; &quot;//Nologo&quot; &quot;${var:INSTALL_DIR}changeicon.vbs&quot; &quot;%1&quot; &quot;CurrentlyWatching&quot;
  16.     </menuitem>
  17.     <menuitem name="Tag as &quot;Ongoing release&quot;" class="folder" icon="${var:ICON_DIR}WorkInProgress.ico" action="execute" console="false" multiple="N" workdir="C:\">
  18.       wscript &quot;//B&quot; &quot;//Nologo&quot; &quot;${var:INSTALL_DIR}changeicon.vbs&quot; &quot;%1&quot; &quot;WorkInProgress&quot;
  19.     </menuitem>
  20.     <menuitem name="Tag as &quot;Freshly completed&quot;" class="folder" icon="${var:ICON_DIR}NewlyCompleted.ico" action="execute" console="false" multiple="N" workdir="C:\">
  21.       wscript &quot;//B&quot; &quot;//Nologo&quot; &quot;${var:INSTALL_DIR}changeicon.vbs&quot; &quot;%1&quot; &quot;NewlyCompleted&quot;
  22.     </menuitem>
  23.     <menuitem name="----"></menuitem> <!-- Separator -->
  24.     <menuitem name="Tag as &quot;Favorite&quot;" class="folder" icon="${var:ICON_DIR}Favorite.ico" action="execute" console="false" multiple="N" workdir="C:\">
  25.       wscript &quot;//B&quot; &quot;//Nologo&quot; &quot;${var:INSTALL_DIR}changeicon.vbs&quot; &quot;%1&quot; &quot;Favorite&quot;
  26.     </menuitem>
  27.     <menuitem name="Tag as &quot;Top series&quot;" class="folder" icon="${var:ICON_DIR}Star.ico" action="execute" console="false" multiple="N" workdir="C:\">
  28.       wscript &quot;//B&quot; &quot;//Nologo&quot; &quot;${var:INSTALL_DIR}changeicon.vbs&quot; &quot;%1&quot; &quot;Star&quot;
  29.     </menuitem>
  30.     <menuitem name="Tag as &quot;Keep an Eye on for later (High priority)&quot;" class="folder" icon="${var:ICON_DIR}KeepAnEyeOnHighPrio.ico" action="execute" console="false" multiple="N" workdir="C:\">
  31.       wscript &quot;//B&quot; &quot;//Nologo&quot; &quot;${var:INSTALL_DIR}changeicon.vbs&quot; &quot;%1&quot; &quot;KeepAnEyeOnHighPrio&quot;
  32.     </menuitem>
  33.     <menuitem name="Tag as &quot;Keep an Eye on for later&quot;" class="folder" icon="${var:ICON_DIR}KeepAnEyeOn.ico" action="execute" console="false" multiple="N" workdir="C:\">
  34.       wscript &quot;//B&quot; &quot;//Nologo&quot; &quot;${var:INSTALL_DIR}changeicon.vbs&quot; &quot;%1&quot; &quot;KeepAnEyeOn&quot;
  35.     </menuitem>
  36.     <menuitem name="Tag as &quot;Keep an Eye on for later (Low priority)&quot;" class="folder" icon="${var:ICON_DIR}KeepAnEyeOnLowPrio.ico" action="execute" console="false" multiple="N" workdir="C:\">
  37.       wscript &quot;//B&quot; &quot;//Nologo&quot; &quot;${var:INSTALL_DIR}changeicon.vbs&quot; &quot;%1&quot; &quot;KeepAnEyeOnLowPrio&quot;
  38.     </menuitem>
  39.     <menuitem name="Tag as &quot;Not interested&quot;" class="folder" icon="${var:ICON_DIR}NotInterested.ico" action="execute" console="false" multiple="N" workdir="C:\">
  40.       wscript &quot;//B&quot; &quot;//Nologo&quot; &quot;${var:INSTALL_DIR}changeicon.vbs&quot; &quot;%1&quot; &quot;NotInterested&quot;
  41.     </menuitem>
  42.     <menuitem name="----"></menuitem> <!-- Separator -->
  43.     <menuitem name="Tag as &quot;Fluff&quot;" class="folder" icon="${var:ICON_DIR}SweetFluff.ico" action="execute" console="false" multiple="N" workdir="C:\">
  44.       wscript &quot;//B&quot; &quot;//Nologo&quot; &quot;${var:INSTALL_DIR}changeicon.vbs&quot; &quot;%1&quot; &quot;SweetFluff&quot;
  45.     </menuitem>
  46.     <menuitem name="Tag as &quot;Sweet, sweet Yuri!&quot;" class="folder" icon="${var:ICON_DIR}Yuri.ico" action="execute" console="false" multiple="N" workdir="C:\">
  47.       wscript &quot;//B&quot; &quot;//Nologo&quot; &quot;${var:INSTALL_DIR}changeicon.vbs&quot; &quot;%1&quot; &quot;Yuri&quot;
  48.     </menuitem>
  49.     <menuitem name="----"></menuitem> <!-- Separator -->
  50.     <menuitem name="Tag as &quot;A/V main folder&quot;" class="folder" icon="${var:ICON_DIR}AVMain.ico" action="execute" console="false" multiple="N" workdir="C:\">
  51.       wscript &quot;//B&quot; &quot;//Nologo&quot; &quot;${var:INSTALL_DIR}changeicon.vbs&quot; &quot;%1&quot; &quot;AVMain&quot;
  52.     </menuitem>
  53.     <menuitem name="----"></menuitem>
  54.     <menuitem name="Remove tag" class="folder" icon="X:\icons\Trash.ico" action="execute" console="false" multiple="N" workdir="C:\">
  55.       wscript &quot;//B&quot; &quot;//Nologo&quot; &quot;${var:INSTALL_DIR}changeicon.vbs&quot; &quot;%1&quot; &quot;del&quot;
  56.     </menuitem>
  57.   </menu>
  58. </config>

As you can see, it defines the main menu with its icon and then several submenu entries with their own icons. Also, it’s not calling my modified version of Umlüx’ Powershell script, but a Visual Basic Script instead. The reason for this shall be explained in point 4., for now we only care about the menu structure.

Looking at the variables defined on top of the XML data, ICON_DIR and INSTALL_DIR, they reference an icon folder and a program folder, both in %APPDATA%\changeicon\, so I put that into the current users’ profile folder. The icons to be used have to be put into the ICON_DIR, the scripts and an unlocker program are to be put into INSTALL_DIR. That doesn’t affect KuShellExt itself though, you can install that anywhere.

Note that while KuShellExt is loaded, you can edit its configuration on the fly. The library will detect changes made to it, and reload the updated configuration automatically, so you don’t have to unload and load the .dll when making changes to the menus or icon names.

Now, we still need the scripts and [Unlocker 1.9.2] (this is just the .exe, without the adware that usually comes with version 1.9.2). As to why Unlocker is required, well, let’s talk about that and also give you the first script:

3. The timestamp issue and the Powershell script that does the tagging

Note: Windows XP or Vista users may have to install the Windows Management Framework Core package (including Powershell 2.0) first.

This whole solution will alter folder appearances by placing hidden desktop.ini files into them. Writing such a file will alter the folders’ modification time though, or in short it’s “mtime”. And that’s bad if you have software that relies on that piece of meta data. One typical example would be backup software, that decides whether files have to be backupped based on the mtime.

In my own case, the backup problem applies to my rsync backup system, but there’s even more; I’ve written myself a Perl script that walks through my entire video folder, generating an HTML report out of the data, sorted by “latest first”. That way, I can see what movies or series have been added or modified recently. That script also depends on the mtime, so having it change when tagging folders is not acceptable!

Time to look at Umlüx’ script, or rather my modified version of it, changeicon.ps1:

changeicon.ps1, expand/collapse source code
  1. # Change Icon v0.815 Karl Veratschnig 2017
  2. # Modified by Michael Lackner
  3. #
  4. # Licensed under the GNU General Public license version 3.0 with express
  5. # permission by Karl Veratschnig.
  6. #
  7. # usage: 
  8. # .\changeicon.ps1 *dir_path* *icon*
  9. # i.e. ./changeicon.ps1 c:\testfolder Favorite
  10. #
  11. # run from anywhere:
  12. # Powershell.exe set-executionpolicy remotesigned -File "path to ps1"
  13. #
  14. # use "del" icon to revert folder to normal
  15.  
  16. ###
  17. # Update-ExplorerIcon refreshes icons in Windows Explorer by rebuilding its icon cache
  18. # Written by Idera,
  19. # http://community.idera.com/powershell/powertips/b/tips/posts/refreshing-icon-cache
  20. ###
  21. function Update-ExplorerIcon {
  22.   [CmdletBinding()]
  23.   param()
  24.  
  25.   $code = @'
  26.   private static readonly IntPtr HWND_BROADCAST = new IntPtr(0xffff); 
  27.   private const int WM_SETTINGCHANGE = 0x1a; 
  28.   private const int SMTO_ABORTIFHUNG = 0x0002; 
  29.  
  30.   [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)]
  31.   static extern bool SendNotifyMessage(IntPtr hWnd, uint Msg, UIntPtr wParam,IntPtr lParam);
  32.  
  33.   [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)] 
  34.   private static extern IntPtr SendMessageTimeout ( IntPtr hWnd, int Msg, IntPtr wParam, string lParam, uint fuFlags, uint uTimeout, IntPtr lpdwResult ); 
  35.  
  36.   [System.Runtime.InteropServices.DllImport("Shell32.dll")] 
  37.   private static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2);
  38.  
  39.   public static void Refresh() {
  40.     SHChangeNotify(0x8000000, 0x1000, IntPtr.Zero, IntPtr.Zero);
  41.     SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, IntPtr.Zero, null, SMTO_ABORTIFHUNG, 100, IntPtr.Zero); 
  42.   }
  43. '@
  44.   Add-Type -MemberDefinition $code -Namespace MyWinAPI -Name Explorer 
  45.   [MyWinAPI.Explorer]::Refresh()
  46. }
  47.  
  48. ###
  49. # User-configurable block
  50. ###
  51. $iconpath = "$env:APPDATA\icons\"           # Where icon files reside
  52. $installpath = "$env:APPDATA\bin\"          # Where scripts & binaries reside
  53. $unlocker = "unlocker.exe"                  # Name of the handle unlocker to use
  54. $ulparams = "/S"                            # Unlock parameter to supply to the unlocker
  55.  
  56. ###
  57. # Non-user-configurable block / main program
  58. ###
  59. $folder = Get-Item $args[0]                 # Get folder from arguments
  60. & $installpath$unlocker "$folder" $ulparams # Remove open handles from folder
  61.                                             # WARNING: If files inside *are* open in
  62.                                             # other programs, their behavior might
  63.                                             # become unstable. Data loss is possible!
  64. $icon = $args[1]                            # Get icon name from arguments
  65. $olddate = $folder.LastWriteTime            # Get modification time stamp (mtime)
  66. $folder.attributes="Normal"                 # Reset folder attributes
  67. if (Test-Path "$folder\desktop.ini") {      # Check for existing desktop.ini
  68.   & $installpath$unlocker "$folder\desktop.ini" $ulparams # Unlock it, just to make sure
  69.   Remove-Item "$folder\desktop.ini" -Force    # Delete it if present
  70. }
  71. if($icon -ne "del") {                       # If op is to tag, not to delete...
  72.   $stream = [System.IO.StreamWriter] "$folder\desktop.ini" #... build new desktop.ini
  73.   $stream.WriteLine("[.ShellClassInfo]")            # Category
  74.   $stream.WriteLine("IconFile=$iconpath\$icon.ico") # Icon file
  75.   $stream.WriteLine("IconIndex=0")                  # Icon #0 in file
  76.   $stream.WriteLine("IconResource=$iconpath\$icon.ico,0") # Icon #0 in file (for modern OS)
  77.   $stream.WriteLine("Infotip=$icon")                # Info tip set to icon name
  78.   $stream.WriteLine("ConfirmFileOp=0")              # Disable special folder handling
  79.   $stream.WriteLine("TimeStamp=$olddate")           # Remark time stamp in the file for
  80.                                                     # safety/recovery purposes
  81.   $stream.close()                                   # Close file
  82.   $folder.attributes="ReadOnly"                     # Set folder to RO to enable special
  83.                                                     # desktop.ini handling
  84.   (Get-Item "$folder\desktop.ini").attributes = "Hidden,System" # Set desktop.ini
  85.                                                     # attributes to Hidden+System, also
  86.                                                     # to enable special folder handling
  87. } else {                                    # If op is to delete, not to tag...
  88.   if (Test-Path "$folder\desktop.ini") {      # Check whether desktop.ini really exists
  89.     & $installpath$unlocker "$folder\desktop.ini" $ulparams # Unlock it, just to make sure
  90.     Remove-Item "$folder\desktop.ini" -Force    # Delete it if present
  91.   }
  92. }
  93.  
  94. Update-ExplorerIcon # Rebuild Windows Explorers' icon cache
  95.  
  96. Start-Sleep -s 2    # This is to work around a race condition against writes/flushes on
  97.                     # some systems, so we give the system a little bit of time for its
  98.                     # "beauty sleep" before resetting the mtime.
  99.  
  100. $folder.LastWriteTime = Get-Date $olddate   # Reset original mtime stamp

 

As you can see, the mtime is first being read from the folder by using $folder.LastWriteTime, and then reset to that value after writing the desktop.ini to the target directory. Also, it calls unlocker.exe /S on that folder to unlock it first, no matter whether there are open files within that folder or anything. This is done to avoid the dangerous situation, where the code would write the desktop.ini, but would then fail to update the folders’ timestamp due to open handles on the directory. Often this would also be caused by Windows Explorer itself, especially if you have open subfolders when tagging.

Forcefully unlocking the folder first deals with that problem. Please keep in mind that doing this may make programs relying on their open handles to behave in an undefined way however. Loss of data in that folder could be possible, e.g. if a text editor loses its handle for writing to a file in that folder.

In case something still goes wrong, the mtime is also being remarked in the desktop.ini file, in the value TimeStamp.

Here’s a sample .ini, as generated by changeicon, the format of the timestamp depends on your locale:

  1. [.ShellClassInfo]
  2. IconFile=C:\Documents and Settings\USERNAME\Application Data\changeicon\icons\WorkInProgress.ico
  3. IconIndex=0
  4. IconResource=C:\Documents and Settings\USERNAME\Application Data\changeicon\icons\WorkInProgress.ico,0
  5. Infotip=WorkInProgress
  6. ConfirmFileOp=0
  7. TimeStamp=09/22/2017 08:59:08

Now, there is one cosmetic issue left here…

4. Hiding that ugly terminal window

You can try to hide the window that pops up when calling a Powershell script, but it never really works reliably. So, are we going to tolerate that thing popping up every time we tag a folder? As if! That’s where the VBScript code comes in. This is an old trick I’ve used to run cmd batch scripts in a hidden way before, and this also works for calling Powershell scripts. I call this changeicon.vbs:

changeicon.vbs, expand/collapse source code
  1. ' Declare variables and create shell object:
  2. Dim AppData, WinDir, PowerShell, arg0, arg1
  3. Set WshShell = Wscript.CreateObject("WScript.Shell")
  4.  
  5. '''
  6. ' User-configurable part (file/folder locations)
  7. '''
  8. AppData = wshShell.ExpandEnvironmentStrings("%APPDATA%")
  9. WinDir = wshShell.ExpandEnvironmentStrings("%WINDIR%")
  10. PowerShell = WinDir & "\system32\WindowsPowerShell\v1.0\powershell.exe"
  11.  
  12. '''
  13. ' Non-user-configurable part
  14. '''
  15. ' Pull command line arguments into variables:
  16. arg0 = WScript.Arguments.Item(0)
  17. arg1 = WScript.Arguments.Item(1)
  18. ' Execute program:
  19. WshShell.Run """" & PowerShell & """ """ & "-File" & """ """ & AppData & "\changeicon\bin\changeicon.ps1" & """ """ & arg0 & """ """ & arg1 & """", 0
  20. Set WshShell = Nothing

 

5. Actually running that Frankenstein solution

In essence, you need to do the following:

  1. Pick a folder for changeicon.ps1, changeicon.vbs and unlocker.exe, and put those three files into it.
  2. Pick a folder for your icons, and place all your desired icons into it, no subfolders!
  3. Edit changeicon.ps1 and changeicon.vbs and change the install/icon paths.
  4. Install KuShellExtension and run its DLL hook script install.cmd.
  5. Edit KuShellExts’ settings.xml to reflect your menu structure and the corresponding menu icons and commands to execute. Better don’t delete all the comments in that file, the documentation can be pretty helpful at times.

Unfortunately I can’t share the icons I’ve created because they’re based on Microsofts’ icons, but you can easily find icons online or make your own with Microangelo or IcoFX. Both are commercial software for Windows, but you could also use the Gimp for that.

6. Enjoy tagging folders

Folders tagged with changeicon

Folders tagged with changeicon

With that, it’s much, much easier to keep track of things and to not forget what kind of stuff (*cough* tons of Anime *cough*) I still need to watch or keep an eye on for later.

But it’s not limited to that; You could use tagged folders for pretty much anything, like designating them to specific purposes or use them for document or work classification, whatever.

It’s interesting that even Windows 10 still can’t do that via the GUI by default by now…

Anyway, thanks fly out to [Umlüx] for writing the most important part at the core of this mess, the Powershell script, and also to [Idera] for the icon refreshing code I grabbed from their site! Also, if you want Umlüx’ modern solution for Windows 7+, which is based on pure Powershell code and Registry entries, you’d need to contact him directly. You may wish to do so, if you don’t need XP or Vista, because then you wouldn’t need to rely on the KuShellExtension anymore.

Happy tagging!

 

[1] Logo based on the Windows 10 Custom Folder Icons Pack made by Terraromaster

Jul 072017
 

Nekopara Vol.3 logo1. Introduction

Of course I would never play something like Nekopara *cough*, so this is just a post describing a technical solution to a compatibility problem! Ok?! Good.

Yeah, it’s another one of those “something broke on XP / XP x64, so let’s fix it” articles. I’ve already been pla…  eh.. investigating Nekopara Volumes 0, 1 and 2, and while the developer claims it needs Windows Vista or higher, those titles worked just fine on XP and XP x64. The final Volume 3 however broke.

I wondered why, given they’re all pretty similar, so I started unpacking the .exe files, looking for information. What I found in the meta data was that Vol.0-2 have been using the TVP(Kirikiri) or maybe the forked [Kirikiri Z] game scripting engine, whereas Vol.3 swapped that for the [Ares CatSystem2] engine, for whatever reason. My assumption would be, that the CatSystem2 thingy was actually built for Vista+ for real, thus breaking XP compatibility. Plus, some other minor components are broken as well (some installers, patches, etc., just like the older volumes).

Now, I’ve already been talking to a guy called UncleVasya / Oleg Ovcharenko, who built a [stub DLL solution] for games based on the Clausewitz Engine (Europa Universalis 4, Hearts of Iron 4, Crusader Kings 2 and finally Stellaris), making it work on XP. It’s pretty similar to the XCOM hacks[1][2]. So I asked him about this one as well, and with quite some work and some additional (important) hints from him regarding the Steam version, I managed to make it run!

So, first things first: Thanks Oleg, you’re doing great work! :)

I will now show you how to make this visual novel / game work on XP x64 and XP, both for the slightly trickier Steam version (whether you choose to play the censored or the uncensored version doesn’t matter, the corresponding patch will be discussed as well), as well as the normal version.

Note: All screenshots in this post are 8-bit (256 color) PNG files. They may look a bit bad at times, but better than JPEG in the case of those specific images. Reason for not using truecolor PNG: 8-bit saves a ton of bandwidth.

2. How to make the non-Steam version work on XP / XP x64

Software required:

  1. [Nekopara Vol.3]
  2. [7-zip] archiver
  3. NTCore [CFF Explorer] (optional; only needed for patches)
  4. Olegs’ [patcher]

2a. The main game

First, buy the game and download it. Do not pirate it! You suck if you do (I actually fooled around with a pirated version as well, but only after buying the game). When running the installer, you’ll notice that it already breaks early on after invoking the launcher:

Nekopara Vol.3s' installer already breaks

Nekopara Vol.3s’ installer already fails to execute on XP

As you can see, it calls InitializeCriticalSectionEx(), which is a newer, Vista+ version of InitializeCriticalSection(), see the MSDN[1][2] for details. Since the new version works differently, you can’t just hex edit your way out of this one.

First, unpack Olegs’ patcher to some subdirectory of your choice. Then, unpack the Nekopara Vol.3 installer (the .exe file) into a subfolder using 7-zip, and look for a file called INSTALL.exe. Copy that file into the directory where Olegs patcher resides, so where files like xp_EU4_1.21.cmd and xp_Stellaris_1.6.cmd can be found.

Since the scripts from Oleg aren’t made for hacking our files, we’ll write a new one for this, let’s call it xp_installer.cmd. Edit that with a text editor, and add the following lines:

@ECHO OFF
rundll32.exe zernel32.dll,PatchFile INSTALL.exe

Make sure xp_installer.cmd, zernel32.dll and the INSTALL.exe from Nekopara are in the same directory, then execute xp_installer.cmd. either by just double clicking it, or by opening a cmd terminal and by running it from there. Like this (you don’t need to run the extra commands, they’re just there to show you more information):

Olegs' patch doing its magic on INSTALL.exe

Olegs’ patch doing its magic on INSTALL.exe!

After that, rename your original INSTALL.exe in the directory where you unpacked the Nekopara Vol.3 installer, creating a backup file. Copy the following files from the patcher directory back to the installer directory: INSTALL.exe, zernel32.dll, z3d9.dll, zs2_32.dll and normaliz.dll. The “z” files are now implementing the missing functions, while redirecting all the others to the real Windows libraries like kernel32.dll, d3d9.dll, ws2_32.dll etc.

You don’t need to repack anything, just run INSTALL.exe directly, and you’ll no longer be greeted with an error message, but with this:

The installer works now

The installer works now, great

Install the game to a directory of your choice. Now, if you click the NEKOPARAvol3.exe in the directory where the game was installed, the same launcher comes up again, but now it allows you to configure and play the actual game instead of installing it…

Nekopara Vol.3s' launcher after installation

Nekopara Vol.3s’ launcher after installation

…or does it? Well, the “System settings” part’ll work, yes, but when clicking that alluring “Start” button, you’ll run into yet another wall:

Nekopara still won't execute due to GetTickCount64()

What now? GetTickCount64(), that’s what.

Guess which function call doesn’t exist on XP? See the MSDN[1][2] again. GetTickCount64() really is an improvement over GetTickCount(), but still, XP simply doesn’t have this either. As you can see from the title bar, the offending binary is cs2.exe, which is the actual game. We can get rid of the issue by using Olegs’ patcher again, so it’s the same process as with INSTALL.exe, just use this script instead, call it xp_cs2.cmd or something:

@ECHO OFF
rundll32.exe zernel32.dll,PatchFile cs2.exe

Again, in case something goes wrong, rename your original cs2.exe before copying back the patched version with its .dll files. After copying back, you can run the game either by invoking cs2.exe directly, or by launching it from the NEKOPARAvol3.exe launcher:

Running the non-Steam version of Nekopara Vol.3 on XP x64

Running the non-Steam version of Nekopara Vol.3 on XP x64 (click to enlarge)

2b. Making patches work as well

Patches are essentially also just self-extracting archives that execute a launcher after unpacking. We’ll discuss the patch 11 in this case. Running it will produce a different kind of error (people who know the content restoration patches for the Steam version may have seen this error as well):

Nekopara Vol.3 patch failure

Nekopara Vol.3 patch failure, due to it not being “a valid Win32 application”.

This error means that the header of the binary is asking for a more modern platform. This may make sense, if the program really calls modern functions, but you know, there are modern applications that don’t ask for it and then fail with calls to things like GetTickCount64(), and there are programs which ask for a modern platform without ever having an actual need for it. The patchers are in the latter category of programs.

Unpack the patcher nekopara3_v11_update.exe using 7-zip, and look for a file called updater.exe. Create a backup copy of it, then open this file in NTCores’ CFF Explorer, and click on the “Optional Header” part. You’ll see something like this, I’ve marked the relevant lines with some red blocks for you:

updater.exe in CFF Explorer

updater.exe in CFF Explorer (click to enlarge)

The marked fields show values like 0006 and 0000, as you can see. The significant number is the last or rightmost, so 6 and 0. This corresponds to the platform target Windows NT 6.0, or in other words: Windows Vista. Just rewrite that to show the following numbers, then save the file:

Patch the header to NT 5.1

Patch the header to NT 5.1 (click to enlarge)

NT 5.1 (0005, 0001) equals Windows XP. Note that the kernel versions 5.0 mean Windows 2000, 5.2 means Server 2003 or XP x64 (slightly more modern). Again, no need to repack anything, just save the file after the modifications have been made and execute updater.exe afterwards, you should be getting this:

Nekopara Vol.3 non-Steam patcher working on XP

And here we have a working patcher (click to enlarge)

Yay! And now, for the Steam version of Nekopara Vol.3…

3. How to make the Steam version work on XP / XP x64

Software required:

  1. [Nekopara Vol.3] on Steam (a censored version)
  2. [Content restoration patch] (optional; only required if you have to do perverted things to the cat girls)
  3. at0ms’ [Steamless]
  4. A Windows Vista or newer machine (needed to run Steamless, can be a virtual machine)
  5. [7-zip] archiver
  6. NTCore [CFF Explorer] (optional; only needed for the content restoration patch)
  7. Olegs’ [patcher]

3a. The main game

First, buy the game on Steam and download it. If you really need the uncensored version (you probably do, heh?), buy the content restoration patch at Denpasoft and download that as well. Of course, running the game as-is won’t work, otherwise we wouldn’t need this article in the first place:

The Steam version of Nekopara Vol.3 breaks on XP as well of course

The Steam version of Nekopara Vol.3 breaks on XP as well of course, due to GetTickCount64() call, a newer and better version of GetTickCount(), see MSDN[1][2].

Now, what I didn’t get at first was that patching the Steam versions’ NEKOPARAvol3.exe can never work out of the box. The reason is, that the offending function calls aren’t plainly there for us to see – the actual game binary cs2.exe is encrypted and packed into a SteamStub binary as its payload data. This is a part of the Steamworks DRM system wrapping our program up.

To be able to patch it, we (unfortunately) need to crack its cryptographic DRM protection system first. Now, let me say this again: I do not condone piracy. Don’t fucking crack and distribute this game. You’re an ass if you do. Removing the DRM part is only being done so we can fix the game on XP, keep that in mind!

Well, let’s start; First, boot up a Vista or newer Windows, and install Steamless on it. I actually tried to compile Steamless for XP, but this is .Net 4.5.2 stuff. To make it work on .Net 4.0 would require modifications of its build files / source code, which is a bit over my head right now. So we’re stuck with needing a modern Windows OS to do this. Copy the problematic NEKOPARAvol3.exe from your Steam game installation directory over to that machine, or just install Steam and the game on the modern Windows OS as well (which is what I actually did).

Launch Steamless, open that .exe and decrypt / unpack it, Steamless will leave your binary alone, and create a new, fixed one, so you don’t need to create a manual backup copy:

Steamless cracking NEKOPARAvol3.exe

Steamless cracking NEKOPARAvol3.exe (click to enlarge)

Copy the fixed file back to XP, and rename it back to NEKOPARAvol3.exe. Create a backup of the original .exe in your Steam game installation directory, while you’re at it.

Unpack Olegs’ patcher in a directory of your choice, and move the NEKOPARAvol3.exe there as well, that’s where files akin to xp_EU4_1.21.cmd and xp_Stellaris_1.6.cmd can be found. Since those patcher scripts aren’t targeted at Nekopara Vol.3, we’ll write our own, call it xp_neko_3.cmd or something, open it in a text editor and enter the following lines:

@ECHO OFF
rundll32.exe zernel32.dll,PatchFile NEKOPARAvol3.exe

Make sure that NEKOPARAvol3.exe, zernel32.dll and xp_neko_3.cmd are together in the same folder, then execute xp_neko_3.cmd either by double-clicking it, or by opening a cmd terminal and executing it from there. Like this:

Olegs' patcher handling the decrypted NEKOPARAvol3.exe

Olegs’ patcher handling the now-decrypted Steam version of NEKOPARAvol3.exe

Copy the fully fixed .exe back into the Steam game installation directory, together with the patchers’ stub libraries zernel32.dll, z3d9.dll, zs2_32.dll and normaliz.dll, which will handle the functions usually missing on XP.

Now, run the game either by executing NEKOPARAvol3.exe, or by launching it from within Steam, and you should be greeted with something like this:

Nekopara Vol.3 running on XP x64 in its Steam version

Nekopara Vol.3 running on XP x64 in its Steam version (click to enlarge)

Great (or something)!

Please be aware that if the binary is ever overwritten by Steam because of some update or whatever, you have to re-do the procedure, meaning the Steamless unpacking plus applying Olegs’ patch. If the game terminates without any error when launched from within Steam, try to run NEKOPARAvol3.exe directly instead, and you’ll see the error messages – Steam tends to suppress them.

3b. The content restoration patch (this also applies to the patches for Nekopara Vol.1 and Vol.2)

So you want to lewd the cat girls? Perverted! Plus, Windows XP / XP x64 won’t let you, because the patch is asking for a newer platform (despite not actually requiring it though):

Nekopara Vol.3 content restoration patch failure on XP

Nekopara Vol.3 content restoration patch failure on XP, due to the patch not being “a valid Win32 application”.

But if you absolutely have to, here’s how. Unpack the nekopara_vol3_Steam_R18DLC.exe you bought and downloaded from Denpasoft using 7-zip. Look for the file SteamPatch.exe, and open it in CFF Explorer:

The Nekopara Vol.3 Content restoration patchs' SteamPatch.exe in CFF Explorer

The Nekopara Vol.3 Content restoration patchs’ SteamPatch.exe in CFF Explorer

Now, this is similar to the procedure described for updater.exe for a non-Steam versions’ patch. The significant (rightmost) numbers in the fields where it days 0006 and 0000 represent Windows NT 6.0, or in other words Windows Vista. Since the patcher doesn’t really need any Vista-specific functions, we’ll just fix the header that is currently asking for a NT 6.0 platform as follows:

The Nekopara Vol.3 Content restoration patchs' SteamPatch.exe in CFF Explorer, fixed for XP

Change the fields to 5.1 (0005 and 0001 respectively) to have it check for XP+ instead, and it’s fixed!

Save the file after modifying it. Just like for the non-Steam version patches, there is no need to repack anything. Just run updater.exe directly, and you’ll now get this:

The Nekopara Vol.3 content restoration patch for the Steam version working on XP x64

The Nekopara Vol.3 content restoration patch for the Steam version working on XP x64. Because you’re in it for the Hentai.

There you go, pervert! You now have the fully restored version of Nekopara Vol.3 on Steam, running on XP or XP x64.

And last but not least: Thanks again, Oleg! I made you touch some weird shit, but you still fixed it and gave me the right ideas about the Steam version as well, yay! ;)

4. Bonus feature: How to make Mechwarrior Online work on XP / XP x64 after their launcher upgrade

While entirely unrelated to the weird Japanese shit above, I’ll just mention this here as well, because it doesn’t deserve its own post, given the simplicity of the “solution”; Piranha Games decided to give Mechwarrior Online (MWO) a new game launcher called “MWO Portal”, that is now built with .Net 4.5.2, just like Steamless, breaking it on XP. Mind you, the game itself would still work just fine, even the 64-bit version on XP x64.

The new MWOPortal launcher

Windows XP / XP x64 users will likely never see this launcher work on their OS (Unless ExtendedXP really takes off, it’s pretty good already, but yeah).

Since hacking .Net 4.5 stuff to run on .Net 4 / .Net 4 CP is not something I can do yet, MWO would be gone from all XP machines. There is an easy fix for this though:

Get the game on Steam! The Steam version doesn’t include the launcher, as Steam itself is handling both the execution and the updates of MWO. Without the launcher, MWO still works just fine! :)

May 312017
 

HakuNeko logo1.) What for?

Usually, porting my favorite manga ripper [HakuNeko][1] would involve slightly more exotic target platforms like [FreeBSD UNIX]. This changed with version 1.4.2 however, as this version – the most current at the time of writing – would no longer compile on Windows machines due to some issues with its build toolchain. And that’s the most common platform for the tool!

This is what the lead developer had to say about the issue while even suggesting the use of [FMD] instead of HakuNeko on Windows:

“The latest release does not compile under windows due to some header include contradictions of sockets […]”
    -in a [comment][1] to HakuNeko ticket #142 by [Ronny Wegener], HakuNeko project leader

[1] Edit: Links have been fixed, as the HakuNeko project has now been moved to HakuNeko Legacy due to the development of its replacement, [HakuNeko S].

Normally I wouldn’t mind that much and keep using 1.4.1 for now, but unfortunately this is not an option. Quite a few Manga websites have changed by now, breaking compatibility with the previous version. As this is breaking most of HakuNekos’ functionality for some important sites, it became quite unusable on Windows, leaving Linux as the only officially supported platform.

As using virtual machines or remote VNC/X11 servers for HakuNeko proved to be too tedious for me, I thought I’d try to build this by myself. As the MSYS2/MinGW Windows toolchain seemed to be broken for 1.4.2, I tried – for the very first time – to cross-compile on Linux, choosing CentOS 7.3 x86_64 and MinGW32 4.9.3 for the task. This was quite the challenge, given that HakuNeko comes completely unprepared for cross-compiling.

2.) First, the files

Took me many hours / days to get it done – 100% statically linked too – and it’s finished now! What I won’t provide is an installer (don’t care about that), but here are my v1.4.2 builds for Windows:

As of today, those versions have been tested successfully on the following operating systems:

  • Windows XP Professional SP3 / POSReady2009
  • Windows XP Professional x64 Edition SP2 w. Server 2003 updates
  • Windows Server 2003 R2 x64 SP2
  • Windows Vista Enterprise x64 SP2
  • Windows 7 Professional x64 SP1
  • Windows 10 Professional x64 build #1607

Please be aware that not all of the functionality has been tested by me, just a few downloads that wouldn’t have worked with 1.4.1, a few more random downloads here and there, plus chapter-wise .cbz packaging. It’s looking pretty good I think. Here are some sample screen shots as “proof” of it working (click to enlarge):

HakuNeko 1.4.2 downloading "Kobayashi-san Chi no Maid Dragon" on XP x64

HakuNeko 1.4.2 downloading “Kobayashi-san Chi no Maid Dragon” on XP x64 (Note: I own that Manga in paper form)

 

3.) What has been done to make this work?

3a.) Initial work:

First of all, cross-compiling is a bottomless, hellish pit, a horrible place that you never want to enter unless a.) The build toolchain of the software you wanna compile is very well prepared for it or b.) you really have/want to get your hands on that build or c.) you hate yourself so much you have to hurt yourself or d.) you actually enjoy c.).

The reasons for choosing cross-compiling were that Ronny Wegener had said, that the MSYS2/MinGW32 build would fail on Windows, plus it would require GCC version 5.3 to link with the bundled, pre-built static libraries (OpenSSL, cURL, wxWidgets).

So I thought it would be more likely to work if I were to run my own MinGW setup on Linux, not relying on the bundled stuff but linking against the libraries that come with MinGW32 4.9.3 on my platform of choice – CentOS 7.3 Linux.

One exception was the GUI library wxWidgets 3.0.2 that I had to cross-compile and statically link by myself as well, but luckily, that’s easy despite its size. wxWidgets is one piece of software that does come well-prepared for cross-compiling! In my case, that made it as simple as this (parallel compile with 6 CPUs):

$ ./configure --prefix=/usr/local/i686-w64-mingw32 --host=i686-w64-mingw32 --build=x86_64-linux \
 --enable-unicode --with-expat --with-regex --with-opengl --with-libpng --with-libjpeg --with-libtiff \
 --with-zlib --with-msw --enable-ole --enable-uxtheme --disable-shared
$ make -j6
# make install

3b.) HakuNeko build toolchain / Makefile modifications for cross-compiling:

HakuNeko is much harder, and I don’t even remember half of what I did, but most of it was manually editing the ./Makefile after $ ./configure --config-mingw32 would have produced something rather broken.

Let’s get to it, file paths are relative to the source root. First, edit the following parts of the ./Makefile (you need to look for them in different places of the file). First, the PREFIX, should be in the bottom half of the file:

PREFIX = /usr/local/i686-w64-mingw32/

CC and the CFLAGS:

CC = i686-w64-mingw32-g++
CFLAGS = -c -Wall -O2 -std=c++11 \
 -I/usr/local/i686-w64-mingw32/lib/wx/include/i686-w64-mingw32-msw-unicode-static-3.0 \
 -I/usr/local/i686-w64-mingw32/include/wx-3.0 -D_FILE_OFFSET_BITS=64 -D__WXMSW__ -mthreads \
 -DCURL_STATICLIB -I/usr/i686-w64-mingw32/sys-root/mingw/include

Add -DPORTABLE, if you want to build the portable version of HakuNeko.

Then, the Windows resource compiler, controlled by RC and RCFLAGS:

RC = /usr/bin/i686-w64-mingw32-windres
RCFLAGS = -J rc -O coff -F pe-i386 -I/usr/i686-w64-mingw32/sys-root/mingw/include \
 -I/usr/local/i686-w64-mingw32/include

And finally, the static linking part, which is the hardest stuff to get done right, LD, LDFLAGS and LDLIBS:

LD = i686-w64-mingw32-g++
LDFLAGS = -s -static -static-libgcc -static-libstdc++ -mwindows -DCURL_STATICLIB
LDLIBS = -L/usr/local/i686-w64-mingw32/lib   -Wl,--subsystem,windows -mwindows \
 -lwx_mswu_xrc-3.0-i686-w64-mingw32 -lwx_mswu_webview-3.0-i686-w64-mingw32 \
 -lwx_mswu_qa-3.0-i686-w64-mingw32 -lwx_baseu_net-3.0-i686-w64-mingw32 \
 -lwx_mswu_html-3.0-i686-w64-mingw32 -lwx_mswu_adv-3.0-i686-w64-mingw32 \
 -lwx_mswu_core-3.0-i686-w64-mingw32 -lwx_baseu_xml-3.0-i686-w64-mingw32 \
 -lwx_baseu-3.0-i686-w64-mingw32 -L/usr/i686-w64-mingw32/sys-root/mingw/lib -lcurl -lidn -liconv \
 -lssh2 -lssl -lcrypto -lpng -ljpeg -ltiff -lexpat -lwxregexu-3.0-i686-w64-mingw32 -lz -lrpcrt4 \
 -lwldap32 -loleaut32 -lole32 -luuid -lws2_32 -lwinspool -lwinmm -lshell32 -lcomctl32 -lcomdlg32 \
 -ladvapi32 -lwsock32 -lgdi32

Took a while to find the libraries (and static library order!) necessary to satisfy all the dependencies properly.

If you need it, here is the modified Makefile I’ve used to cross-compile:

  • [HakuNeko Makefile] for cross-compiling HakuNeko 1.4.2 for Windows on CentOS 7.3 x86_64 Linux (needs statically linked & installed wxWidgets first).

3c.) Source code modifications:

However, something will still not be quite right, because some of the crypto libraries will provide the MD5 functions MD5_Init(), MD5_Update() as well as MD5_Final(), and those are already defined by HakuNeko itself. This will break the static linking, as redundant definitions won’t work. We’ll rely on the libraries (libcrypto, libssl), and comment the built-in stuff out in src/v7/v7.c:

void MD5_Init(MD5_CTX *c);
void MD5_Update(MD5_CTX *c, const unsigned char *data, size_t len);
void MD5_Final(unsigned char *md, MD5_CTX *c);

…becomes:

/* void MD5_Init(MD5_CTX *c);
 * void MD5_Update(MD5_CTX *c, const unsigned char *data, size_t len);
 * void MD5_Final(unsigned char *md, MD5_CTX *c);
 */

On top of that, the configure system may have generated src/main.cpp as well as src/main.h. Those are bogus files, turning the entire tool into nothing but one large “Hello World” program. Plus, that’s hard to debug, as the binary won’t even output “Hello World” on a Windows terminal when it’s built as a GUI tool. I only found the issue when testing it with Wine on Linux. ;)

Please delete src/main.cpp and src/main.h before continuing.

Now, if you’re really lucky, you should be able to run something like $ make -j6 and watch everything work out nicely. Or watch it crash and burn, which is the much, much more likely scenario, given I’ve likely only given you half of what I did to the build tools.

Well, in any case, no need to run $ make install of course, just grab the binary build/msw/bin/hakuneko.exe and copy it off to some Windows machine, then try to run it. If you’ve built the portable version, you may wish to rename the file to hakuneko-portable.exe, just like the official developers do.

4.) The future

Let’s just hope that the developers of HakuNeko can get this fixed for versions >=1.4.3, because I really, really don’t want to keep doing this. It’s extremely painful, as cross-compiling is exactly the kind of living hell I heard a lot of people saying it is! I think it’s a miracle I managed to compile and run it at all, and it was so frustrating and tedious for somebody like me (who isn’t a developer).

The statement that this took “hours / days” wasn’t an exaggeration. I think it was something like 10-12 man hours of pure frustration to get it done. I mean, it does feel pretty nice when it finally works, but I wouldn’t bet on myself being able to do this again for future versions…

So please, make it work on Windows again, if possible, and keep HakuNeko cross-platform! It’s still my favorite tool for the task! Thanks! :)

Mar 012017
 

Notepadqq @ CentOS 6 Linux logoIt’s rather rare for me to look for a replacement of some good Windows software for Linux/UNIX instead of the other way around, but the source code editor [Notepad++] is one example of such software. The program gedit on the Gnome 2 desktop environment of my old CentOS 6 enterprise Linux isn’t bad, but it isn’t exactly good either. The thing I was missing most was a search & replace engine capable of regular expressions.

Of course, vi can do it, but at times, vi can be a bit hard to use, so I kinda looked for a Notepad++ replacement. What I found was [Notepadqq], which is basically a clone using the Qt5 UI. However, this editor is clearly made for more modern systems, but I still looked for a way to get it to compile and run on my CentOS 6.8 x86_64 Linux system. And I found one. Here are the most important prerequisites:

  • A new enough GCC (I used 6.2.0), because the v4.4.7 platform compiler won’t work with the modern C++ stuff
  • Qt5 libraries from the [EPEL] repository
  • git

First, you’ll want a new compiler. That part is surprisingly easy, but a bit time consuming. First, download a fresh GCC tarball from a server on the [mirrors list], those are in the releases/ subdirectory, so a file like gcc-6.3.0.tar.bz2 (My version is still 6.2.0). It seems Notepadqq only needs some GCC 5, but since our platform compiler won’t cut it anyway, why not just use the latest?

Now, once more, this will take time, could well be hours, so you might wanna do the compilation step over night, the last step needs root privileges:

$ tar -xzvf ./gcc-6.3.0.tar.bz2
$ cd ./gcc-6.3.0/
$ ./configure --program-suffix="-6.3.0"
$ make
# make install

And when you do this, please never forget to add a --program-suffix for the configuration step!  You might seriously fuck things up if you miss that! So double-check it!

When that’s finally done, let’s handle Qt5 next. I’ll be using a binary distribution to make things easy, and yeah, I didn’t just install the necessary packages, I got the whole Qt5 blob instead, too lazy to do the cherry picking. Oh, and if you don’t have it, add git as well:

# yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-6.noarch.rpm
# yum install qt5* git

I assume # yum install qt5-qtwebkit qt5-qtwebkit-devel qt5-qtsvg qt5-qtsvg-devel qt5-qttools qt5-qttools-devel should also be enough according to the requirements, but I didn’t try that. Now, enter a free directory or one you generally use for source code and fetch the latest Notepadqq version (this will create a subfolder we’ll cd to):

$ git clone https://github.com/notepadqq/notepadqq.git
$ cd ./notepadqq

After that, we need to make sure that we’ll be using the correct compiler and that we’re linking against the correct libraries that came with it (like libstdc++.so.6.*). To do that, set the following environment variables, assuming you’re using the bash as your shell (use lib/ instead of lib64/ folders if you’re on 32-bit x86):

$ export CC="gcc-6.3.0"
$ export CXX="g++-6.3.0"
$ export CPP="cpp-6.3.0"
$ export CFLAGS="-I/usr/local/include/ -L/usr/local/lib64/"
$ export CXXFLAGS="-I/usr/local/include/ -L/usr/local/lib64/"
$ export LDFLAGS="-L/usr/local/lib64/"

The C-related settings are probably not necessary as Qt5 stuff should be pure C++, but you’ll never know, so let’s play it safe.

With that we’re including and linking against the correct libraries and we’ll be using our modern compiler as well. Time to actually compile Notepadqq. To do that, we’ll still need to tell it where to find the Qt5 versions of the qmake and lrelease binaries, but luckily, we can solve that with some simple configuration options. So, let’s do this, the last step requires root privileges again, from within the notepadqq/ directory that git clone created for us:

$ ./configure --qmake /usr/bin/qmake-qt5 --lrelease /usr/bin/lrelease-qt5
$ make
# make install

Now, there are some weird linking issues that I never got fixed on CentOS (some developer please tell me how, I have the same crap when building x265!). Because of that we still can’t launch Notepadqq as-is, we need to give it an LD_LIBRARY_PATH to find the proper libraries at runtime. Let’s just create an executable launcher script /usr/local/sbin/notepadqq.sh for that. Edit it and enter the following code:

#!/bin/sh
LD_LIBRARY_PATH="/usr/local/lib64" /usr/local/bin/notepadqq "$@"

Use this as your launcher script for Notepadqq and you’re good to go with your Notepad++ replacement on good old CentOS 6.x:

Running Notepadqq on CentOS 6 Linux

Running the latest Notepadqq on CentOS 6 Linux with Qt5 version 5.6.1, state 2017-03-01

Now, let’s see whether it’s even that good actually… :roll:

Nov 242016
 

Broken Windows logo[1] I know what I should do if a system service on Microsoft Windows starts crashing of course; Fixing it is the way to go! But sometimes you simply can’t, because the component causing a certain instability can’t be swapped out or updated. Now Windows services do have a mechanism for monitoring and restarting a service upon failure, but it seems that only works if the system gets an actual error code back from the service upon termination. But it doesn’t seem to work (at least for me) if the service just dies abnormally. Windows recognizes the service has stopped somehow of course, but the restart procedure just doesn’t kick in.

So I thought I’d do it myself, programmatically. And it’s actually pretty easy. I solved this with VBScript, Windows Batch and Mark Russinovichs’ pslist plus grep. So the prerequisites are:

  • Microsoft Windows (well, huh..)
  • MS Windows Script(ing) Host / VBScript, Windows should come with this preinstalled since Windows 2000.
  • [pslist]
  • [grep][src] (grep is optional, I used GNU grep 2.5.4 in this case, licensed under the [GPLv3+])

Make sure the pstools and grep are within your %PATH%, so Windows can find those .exe files. If you don’t want to use grep, you can also use Microsofts’ own find command, if your version of Windows has it.

I divided this into two small scripts. Since the main part is Batch, it might be problematic if you run it at very short intervals, checking for the services’ status, because you get a command window popping up on the desktop. Since most users wouldn’t want that, another script acts as a launcher, hiding the cmd.exe window so it’s run fully in the background without disturbing any potential users or administrators. The launcher looks like this, in my case it’s meant to watch over an Apache web server:

  1. Set WshShell = CreateObject("WScript.Shell")
  2. WshShell.Run chr(34) & "C:\Server\Scripts\monitor-httpd.bat" & Chr(34), 0
  3. Set WshShell = Nothing

And that script C:\Server\Scripts\monitor-httpd.bat we’re launching looks like this:

  1. @ECHO OFF
  2. FOR /F "tokens=* delims= usebackq" %%I IN (`pslist ^| grep httpd`) DO SET HTTPDSTATUS=%%I
  3. IF NOT DEFINED HTTPDSTATUS (net start "Apache2.2") ELSE (SET HTTPDSTATUS=)

A version relying on Microsoft find instead of GNU grep could look like this:

  1. @ECHO OFF
  2. FOR /F "tokens=* delims= usebackq" %%I IN (`pslist ^| find /I "httpd"`) DO SET HTTPDSTATUS=%%I
  3. IF NOT DEFINED HTTPDSTATUS (net start "Apache2.2") ELSE (SET HTTPDSTATUS=)

To get a services’ exact name, just launch services.msc from Start \ Run or run the command net start on a cmd terminal.

As you can see, this greps “httpd” from the process list and pushes its output into %%I and finally into %HTTPDSTATUS%. We have to use a FOR /F for that, as Windows has no way of pushing command outputs from subshells into shell variables like UNIX has (like e.g. var=`command` or var=$(command)). Then we check for the status of that variable. If it’s not defined, then the process http.exe was nowhere to be found! In that case we restart the associated system service (needs proper permissions!). If the variable is defined, we do nothing but unsetting it, since we can assume the service is operating normally. Or at the very least it’s running. ;)

You can automate that by using the Windows task scheduler:

Scheduling an Apache web server "watchdog"

Scheduling an Apache web server “watchdog” (German Windows)

Create a Schedule to your liking and you’re done! If you can afford the affected service to be down for 5 minutes and no longer, just run it every 4 minutes or so.

The solution shown above can easily be adapted to monitor and restart any Windows service you have, as long as the service isn’t fundamentally broken so that it wouldn’t even start up anymore. Also, you can do a lot more, like sending notification eMails with a command line mailer like [blat] when crashes do occur. Of course, this is only useful for services that crash rarely. If it dies every few minutes, you should reaaally fix it instead of just pushing the restart button all the time… ;)

And that’s that!

[1] © Mar.0007. Original Version for desktopwallpapers4.me.

Nov 192016
 

FreeBSD GMABoost logoRecently, after finding out that the old Intel GMA950 profits greatly from added memory bandwidth (see [here]), I wondered if the overclocking mechanism applied by the Windows tool [here] had leaked into the public after all this time. The developer of said tool refused to open source the software even after it turning into abandonware – announced support for GMA X3100 and X4500 as well as MacOS X and Linux never came to be. Also, he did not say how he managed to overclock the GMA950 in the first place.

Some hackers disassembled the code of the GMABooster however, and found out that all that’s needed is a simple PCI register modification that you could probably apply by yourself on Microsoft Windows by using H.Oda!s’ [WPCREdit].

Tools for PCI register modification do exist on Linux and UNIX as well of course, so I wondered whether I could apply this knowledge on FreeBSD UNIX too. Of course, I’m a few years late to the party, because people have already solved this back in 2011! But just in case the scripts and commands disappear from the web, I wanted this to be documented here as well. First, let’s see whether we even have a GMA950 (of course I do, but still). It should be PCI device 0:0:2:0, you can use FreeBSDs’ own pciconf utility or the lspci command from Linux:

# lspci | grep "00:02.0"
00:02.0 VGA compatible controller: Intel Corporation Mobile 945GM/GMS, 943/940GML Express Integrated Graphics Controller (rev 03)
 
# pciconf -lv pci0:0:2:0
vgapci0@pci0:0:2:0:    class=0x030000 card=0x30aa103c chip=0x27a28086 rev=0x03 hdr=0x00
    vendor     = 'Intel Corporation'
    device     = 'Mobile 945GM/GMS, 943/940GML Express Integrated Graphics Controller'
    class      = display
    subclass   = VGA

Ok, to alter the GMA950s’ render clock speed (we are not going to touch it’s 2D “desktop” speed), we have to write certain values into some PCI registers of that chip at 0xF0hex and 0xF1hex. There are three different values regulating clockspeed. Since we’re going to use setpci, you’ll need to install the sysutils/pciutils package on your machine via # pkg install pciutils. I tried to do it with FreeBSDs’ native pciconf tool, but all I managed was to crash the machine a lot! Couldn’t get it solved that way (just me being too stupid I guess), so we’ll rely on a Linux tool for this. Here is my version of the script, which I call gmaboost.sh. I placed that in /usr/local/sbin/ for global execution:

  1. #!/bin/sh
  2.  
  3. case "$1" in
  4.   200) clockStep=34 ;;
  5.   250) clockStep=31 ;;
  6.   400) clockStep=33 ;;
  7.   *)
  8.     echo "Wrong or no argument specified! You need to specify a GMA clock speed!" >&2
  9.     echo "Usage: $0 [200|250|400]" >&2
  10.     exit 1
  11.   ;;
  12. esac
  13.  
  14. setpci -s 02.0 F0.B=00,60
  15. setpci -s 02.0 F0.B=$clockStep,05
  16.  
  17. echo "Clockspeed set to "$1"MHz"

Now you can do something like this: # gmaboost.sh 200 or # gmaboost.sh 400, etc. Interestingly, FreeBSDs’ i915_kms graphics driver seems to have set the 3D render clock speed of my GMA950 to 400MHz already, so there was nothing to be gained for me in terms of performance. I can still clock it down to conserve energy though. A quick performance comparison using a crappy custom-recorded ioquake3 demo shows the following results:

  • 200MHz: 30.6fps
  • 250MHz: 35.8fps
  • 400MHz: 42.6fps

Hardware was a Core 2 Duo T7600 and the GPU was making use of two DDR-II/667 4-4-4 memory modules in dual channel configuration. Resolution was 1400×1050 with quite a few changes in the Quake III configuration to achieve more performance, so your results won’t be comparable, even when running ioquake3 on identical hardware. I’d post my ~/.ioquake3/baseq3/q3config.cfg here, but in my stupidity I just managed to freaking wipe the file out. Now I have to redo all the tuning, pfh.

But in any case, this really works!

Unfortunately, it only applies to the GMA950. And I still wonder what it was that was so wrong with # pciconf -w -h pci0:0:2:0 0xF0 0060 && pciconf -w -h pci0:0:2:0 0xF0 3405 and the like. I tried a few combinations just in case my byte order was messed up or in case I really had to write single bytes instead of half-words, but either the change wouldn’t apply at all, or the machine would just lock up. Would be nice to do this with only BSD tools on actual FreeBSD UNIX, but I guess I’m just too stupid for pciconf

Jul 272016
 

x264 logoSince I’ve been doing a bit of Anime batch video transcoding with x264 and x265 in the last few months, I thought I’d document this for myself here. My goal was to loop over an arbitrary amount of episodes and just batch-transcode them all at once. And that on three different operating systems: Windows (XP x64), Linux (CentOS 6.8 x86_64) and FreeBSD 10.3 UNIX, x86_64. Since I’ve started to split the work across multiple machines, I lost track of what was where and which machine finished what, and when.

So I thought, why not let the loop send me a small notification email upon completion? And that’s what I did. On Linux and UNIX this relies on the bash shell and the mailx command. Please note that I’m talking about [Heirloom mailx], not some other mail program by the same name! I’m mentioning this, because there is a different default mailx on FreeBSD, that won’t work for this. That’s why I put alias mailx='/usr/local/bin/mailx' in my ~/.bash_profile on that OS after installing the right program to make it the default for my user.

On Windows, my loops depend on my own [colorecho] command (you can replace that with cmds’ ECHO if you want) as well as the command line mailer [blat]. Note that, if you need to use SSL/TLS encryption when mailing, blat can’t do that. A suitable replacement could be [mailsend]. Please note, that mailsend does not work on Windows XP however.

In the x265 case, avconv (from the [libav] package) is required on all platforms. You can get my build for Windows [here]. If you don’t like it, the wide-spread [ffmpeg] can be a suitable drop-in replacement.

Now, when setting up blat on Windows, make sure to run blat -help first, and learn the details about blat -install. You need to run that with certain parameters to set it up for your SMTP mail server. For whatever reason, blat reads some of that data from the registry (ew…), and blat -install will set that up for you.

Typically, when I transcode, I do so on the elementary streams rather than .mkv files directly. So I’d loop through some source files and extract the needed streams. Let’s say we have “A series – episode 01.mkv” and some more, all the way up to “A series – episode 13.mkv”, then, assuming track #0 is the video stream…

On Windows:

FOR %I IN (01,02,03,04,05,06,07,08,09,10,11,12,13) DO mkvextract tracks "A series - episode %I.mkv" ^
 0:%I\video.h264

On Linux/UNIX:

for i in {01..13}; do mkvextract tracks "A series - episode $i.mkv" 0:$i/video.h264; done

mkvextract will create the non-existing subfolder for us, and a x264 transcoding loop would then look like this on Windows:

expand/collapse source code
cmd /V /C "ECHO OFF & SET MACHINE=NOVASTORM& SET EPNUM=13& SET SERIES="AnimeX"& (FOR %I IN ^
 (01,02,03,04,05,06,07,08,09,10,11,12,13) DO "c:\Program Files\VFX\x264cli\x264-10b.exe" --fps ^
 24000/1001 --preset veryslow --tune animation --open-gop -b 16 --b-adapt 2 --b-pyramid normal -f ^
 -2:0 --bitrate 2500 --aq-mode 1 -p 1 --slow-firstpass --stats %I\v.stats -t 2 --no-fast-pskip ^
 --cqm flat --non-deterministic --demuxer lavf %I\video.h264 -o %I\pass1.264 & colorecho "Pass 1 ^
 done for Episode %I/"!EPNUM!" of "!SERIES!"" 10 & ECHO. & ^
 "c:\Program Files\VFX\x264cli\x264-10b.exe" --fps 24000/1001 --preset veryslow --tune animation ^
 --open-gop -b 16 --b-adapt 2 --b-pyramid normal -f -2:0 --bitrate 2500 --aq-mode 1 -p 2 --stats ^
 %I\v.stats -t 2 --no-fast-pskip --cqm flat --non-deterministic --demuxer lavf %I\video.h264 -o ^
 %I\pass2.264 & colorecho "Pass 2 done for Episode %I/"!EPNUM!" of "!SERIES!"" 10) & echo !SERIES! ^
 transcoding complete | blat - -t "myself@another.mailhost.com" -c "myself@mailhost.com" -s "x264 ^
 notification from !MACHINE!" & SET MACHINE= & SET EPNUM= & SET SERIES="

Note that I always write all the iteration out in full here. That’s because cmd can’t do loops with leading zeroes in the iterator. The reason for this is that those source files usually have them in their lower episode numbers. If it wasn’t 01,02, … ,12,13, but 1,2, … ,12,13 instead, you could do FOR /L %I IN (1,1,13) DO. But this isn’t possible in my case. Even if elements need alphanumeric names like here,  FOR %I IN (01,02,03,special1,special2,ova1,ova2) DO, you still won’t need that syntax on Linux/UNIX because the bash can have iterator groups like for i in {{01..13},special1,special2,ova1,ova2}; do. Makes me despise the cmd once more. ;)

Edit:

Ah, according to [this], you can actually do something like cmd /V /C "FOR /L %I IN (1,1,13) DO (SET "fI=00%I" & echo "!fI!:~-2")", holy shit. It actually works and gives you leading zeroes. :~-2 for 2 digits, :~-3 for three. Expand fI for more in this example. I mean, what is this even? Some number formatting magic? I probably don’t even wanna know… Couldn’t find any way of having several groups for the iterator however. Meh. Still don’t like it.

So, well, it’s like this on Linux/UNIX:

expand/collapse source code
(export MACHINE=BEAST EPNUM=13 SERIES='AnimeX'; for i in {01..13}; do nice -n19 x264 --fps \
24000/1001 --preset veryslow --tune animation --open-gop -b 16 --b-adapt 2 --b-pyramid normal -f \
-2:0 --bitrate 2500 --aq-mode 1 -p 1 --slow-firstpass --stats $i/v.stats -t 2 --no-fast-pskip \
--cqm flat --non-deterministic --demuxer lavf $i/video.h264 -o $i/pass1.264 && echo && echo -e \
"\e[1;31m`date +%H:%M`, pass 1 done for episode $i/$EPNUM of $SERIES\e[0m" && echo && nice -n19 \
x264 --fps 24000/1001 --preset veryslow --tune animation --open-gop -b 16 --b-adapt 2 --b-pyramid \
normal -f -2:0 --bitrate 2500 --aq-mode 1 -p 2 --stats $i/v.stats -t 2 --no-fast-pskip --cqm flat \
--non-deterministic --demuxer lavf $i/video.h264 -o $i/pass2.264 && echo && echo -e \
"\e[1;31m`date +%H:%M`, pass 2 done for episode $i/$EPNUM of $SERIES\e[0m" && echo; done && echo \
"$SERIES transcoding complete" | mailx -s "x264 notification from $MACHINE" -r \
"myself@mailhost.com" -c "myself@another.mailhost.com" -S smtp-auth="login" -S \
smtp="smtp.mailhost.com" -S smtp-auth-user="myuser" -S smtp-auth-password="mysecurepassword" \
myself@mailhost.com)

The variable $MACHINE or %MACHINE%/!MACHINE! specifies the machines’ host name. This will be noted in the email, so I know which machine just completed something. $EPNUM – or %EPNUM%/!EPNUM! on Windows – is used for periodic updates on the shell. The output would be like “Pass 1 done for Episode 07/13 of AnimeX” in green on Windows and bold red on Linux/UNIX (just change the color to your liking).

Finally, $SERIES aka %SERIES%/!SERIES! would be the series’ name. So say, the UNIX machine named “BEAST” above is done with this loop. The email would come with the subject line “x264 notification from BEAST” and would read “AnimeX transcoding complete” in plain text. That’s all.

Please note, that cmd batch on Windows is extremely creepy. Every whitespace (especially the leading ones when doing multi-line like this for display) needs to be exactly where it is. The same goes for double quotes where you might think they aren’t needed. They are! Also, this needs delayed variable expansion once again, which is why we see variables like !EPNUM! instead of %EPNUM% and why it’s called in a subshell by running cmd /V /C.

On Linux/UNIX we don’t need to rely on some specific API like cmds’ SetConsoleTextAttribute() to print colors, as most terminals understand ANSI color codes.

And this is what it looks like for x265:

Windows:

expand/collapse source code
cmd /V /C "ECHO OFF & SET MACHINE=NOVASTORM& SET EPNUM=13& SET SERIES="AnimeX"& (FOR %I IN ^
 (01,02,03,04,05,06,07,08,09,10,11,12,13) DO avconv -r 24000/1001 -i %I\video.h264 -f yuv4mpegpipe ^
 -pix_fmt yuv420p -r 24000/1001 - 2>NUL | "C:\Program Files\VFX\x265cli-mb\x265.exe" - --y4m -D 10 ^
 --fps 24000/1001 -p veryslow --pmode --pme --open-gop --ref 6 --bframes 16 --b-pyramid --bitrate ^
 2500 --rect --amp --aq-mode 3 --no-sao --qcomp 0.75 --no-strong-intra-smoothing --psy-rd 1.6 ^
 --psy-rdoq 5.0 --rdoq-level 1 --tu-inter-depth 4 --tu-intra-depth 4 --ctu 32 --max-tu-size 16 ^
 --pass 1 --slow-firstpass --stats %I\v.stats --sar 1 --range full -o %I\pass1.h265 & colorecho ^
 "Pass 1 done for Episode %I/"!EPNUM!" of "!SERIES!"" 10 & ECHO. & avconv -r 24000/1001 -i ^
 %I\video.h264 -f yuv4mpegpipe -pix_fmt yuv420p -r 24000/1001 - 2>;NUL | ^
 "C:\Program Files\VFX\x265cli-mb\x265.exe" - --y4m -D 10 --fps 24000/1001 -p veryslow --pmode ^
 --pme --open-gop --ref 6 --bframes 16 --b-pyramid --bitrate 2500 --rect --amp --aq-mode 3 ^
 --no-sao --qcomp 0.75 --no-strong-intra-smoothing --psy-rd 1.6 --psy-rdoq 5.0 --rdoq-level 1 ^
 --tu-inter-depth 4 --tu-intra-depth 4 --ctu 32 --max-tu-size 16 --pass 2 --stats %I\v.stats --sar ^
 1 --range full -o %I\pass2.h265 & colorecho "Pass 2 done for Episode %I/"!EPNUM!" of "!SERIES!"" ^
 10) & echo !SERIES! transcoding complete | blat - -t "myself@another.mailhost.com" -c ^
 "myself@mailhost.com" -s "x265 notification from !MACHINE!" & SET MACHINE= & SET EPNUM= & SET ^
 SERIES="

Linux/UNIX:

expand/collapse source code
(export MACHINE=BEAST EPNUM=13 SERIES='AnimeX'; for i in {01..13}; do avconv -r 24000/1001 -i \
$i/video.h264 -f yuv4mpegpipe -pix_fmt yuv420p -r 24000/1001 - 2>/dev/null | nice -19 x265 - --y4m \
-D 10 --fps 24000/1001 -p veryslow --open-gop --ref 6 --bframes 16 --b-pyramid --bitrate 2500 \
--rect --amp --aq-mode 3 --no-sao --qcomp 0.75 --no-strong-intra-smoothing --psy-rd 1.6 --psy-rdoq \
5.0 --rdoq-level 1 --tu-inter-depth 4 --tu-intra-depth 4 --ctu 32 --max-tu-size 16 --pass 1 \
--slow-firstpass --stats $i/v.stats --sar 1 --range full -o $i/pass1.h265 && echo && echo -e \
"\e[1;31m`date +%H:%M`, pass 1 done for episode $i/$EPNUM of $SERIES\e[0m" && echo && avconv -r \
24000/1001 -i $i/video.h264 -f yuv4mpegpipe -pix_fmt yuv420p -r 24000/1001 - 2>/dev/null | nice \
-19 x265 - --y4m -D 10 --fps 24000/1001 -p veryslow --open-gop --ref 6 --bframes 16 --b-pyramid \
--bitrate 2500 --rect --amp --aq-mode 3 --no-sao --qcomp 0.75 --no-strong-intra-smoothing --psy-rd \
1.6 --psy-rdoq 5.0 --rdoq-level 1 --tu-inter-depth 4 --tu-intra-depth 4 --ctu 32 --max-tu-size 16 \
--pass 2 --stats $i/v.stats --sar 1 --range full -o $i/pass2.h265 && echo && echo -e \
"\e[1;31m`date +%H:%M`, pass 2 done for episode $i/$EPNUM of $SERIES\e[0m" && echo; done && echo \
"$SERIES transcoding complete" | mailx -s "x265 notification from $MACHINE" -r \
"myself@mailhost.com" -c "myself@another.mailhost.com" -S smtp-auth="login" -S \
smtp="smtp.mailhost.com" -S smtp-auth-user="myuser" -S smtp-auth-password="mysecurepassword" \
myself@mailhost.com)

And that’s it. The loops for audio transcoding are simpler, as that part is so fast, it doesn’t need email notifications. Runs for minutes rather than days. When all is done, I’d usually fire up the MKVToolnix GUI, and prepare a mux for the first episode. There is a nice “copy command line to clipboard” function there when you click on “Muxing” after everything is set up. With that I can build another loop that muxes everything to final .mkv files. On Windows that part is more complicated if you want Unicode support, so I needed to create input files by using a generator I wrote in Perl for that, but that’s for another day… :)

Oh, and if you wanna ssh into your Linux or UNIX boxes from afar to check on your transcoders, consider launching them on a GNU screen] session. It’s immensely useful! Too bad it won’t work on the Windows cmd. :(

Jan 292016
 

WP comment nesting logoIt’s not like a lot of people are actively commenting on this weblog, but there are at least two posts which do have quite a few replies (well, for my standards), the one about using [48GB of RAM on an X58 chipset mainboard], and the other about [SSD TRIM, exFAT, EXT3/4, ASPI and UDF 2.5 on Windows XP / XP x64]. To make it possible for users to interact and discuss in a better fashion, I had enabled nested comments. However, the comments took too much horizontal space away, limiting the usable depth of nesting levels. The last (10th) comment would be like ~2cm narrow, and extremely hard to read.

Finally, I thought I should really fix that. So I found that the corresponding CSS code for nested comments was in my themes’ subfolder, in a file called wp-content/themes/<theme name>/style.css. By inspecting my website with the [Vivaldi] web browser (based on Chromium), i found that the CSS class ul.children was likely to blame, as the comments are actually a combination of ordered and unordered HTML lists, see here:

  1. ul.children { padding-left: 20px; }

On top of that, <ul> gets a wide margin-left set by default as well, worsening the situation. This resulted in child comments indenting by something like 40 pixels. For nothing. So I fixed that:

  1. ul.children {
  2.         padding-left: 0px;
  3.         margin-left: 5px;
  4. }

That way it looks much, much more compact and much nicer. This gave me enough space to make the nesting levels even deeper without sacrificing readability (like it was before at 10 levels), so I decided to go for 16 levels. Better than nothing.

The WordPress guys however – in their infinite wisdom – limited the depth to 10 levels, so I was already at the maximum?! Back to inspecting with Vivaldi – the comment settings page this time. That way I found out that the limit is set in wp-admin/options-discussion.php. This is the corresponding code:

  1. <?php
  2. /**
  3.  * Filter the maximum depth of threaded/nested comments.
  4.  *
  5.  * @since 2.7.0.
  6.  *
  7.  * @param int $max_depth The maximum depth of threaded comments. Default 10.
  8.  */
  9. $maxdeep = (int) apply_filters( 'thread_comments_depth_max', 10 );
  10.  
  11. $thread_comments_depth = '</label><label for="thread_comments_depth"><select name="thread_comments_depth" id="thread_comments_depth">';
  12. for ( $i = 2; $i <= $maxdeep; $i++ ) {
  13.         $thread_comments_depth .= "<option value='" . esc_attr($i) . "'";
  14.         if ( get_option('thread_comments_depth') == $i ) $thread_comments_depth .= " selected='selected'";
  15.         $thread_comments_depth .= ">$i</option>";
  16. }
  17. $thread_comments_depth .= '</select>';

Yeah, ok. So I just changed the $maxdeep assignment to this:

  1. $maxdeep = (int) apply_filters( 'thread_comments_depth_max', 16 );

And with that we’re done, as everything seems to be working properly:

Nesting more than 10 WordPress comments

Nesting more than 10 WordPress comments without relying on some additional CPU-hungry plugin.

Of course, I now need to keep track of my changes, because updating either WordPress itself or my theme will revert my changes back to the previous behavior. But since I haven’t modified my WordPress installation that much so far, I can still live with patching everything back in manually after an upgrade.

And another tiny improvement made…

Update: I just noticed, that sometimes, very long “words” (like HTTP or FTP links without any spaces in them) would overflow the barriers of the div layers they were sitting in in the comments, so while I was dealing with restoring my modifications after a theme update, I fixed the word wrapping as well. The related CSS code sits in wp-content/themes/<theme name>/style.css again. So, I look for the following part…

  1. .comment-body p { line-height: 1.5em; }

…and change it to this:

  1. .comment-body p {
  2.         line-height: 1.5em;
  3.         word-break: break-word;
  4. }

Now even very long words like links will be wrapped properly! With word-break: break-word; only words without spaces will be broken where necessary, so normal sentences with whitespaces for delimiters will still be broken at the spaces, like it should be!

Jan 262016
 

HakuNeko logoSince I’ve started using FreeBSD as a Linux and Windows replacement, I’ve naturally always been looking at porting my “known good” software over to the UNIX OS, or at replacing it by something that gets the job done without getting on my nerves too much at the same time. For most parts other than TrueCrypt, that was quite achievable, even though I had to endure varying degrees of pain getting there. Now, my favorite Manga / Comic ripper on Windows, [HakuNeko] was the next piece of software on the list. It’s basically just a more advanced Manga website parser and downloader based on stuff like [cURL], [OpenSSL] or the [wxWidgets] GUI libraries.

I didn’t even know this until recently (shame on me for never looking closely), but HakuNeko is actually free software licensed under the MIT license. Unfortunately, the source code and build system are quite Linux- and Windows-centric, and there exist neither packages nor ports of it for FreeBSD UNIX. Actually, the code doesn’t even build on my CentOS 6.7 Linux right now (I have yet to figure out the exact problem), but I managed to fix it up so it can compile and work on FreeBSD! And here’s how, step by step:

1.) Prerequisites

Please note that from here on, terminal commands are shown in this form: $ command or # command. Commands starting with a $ are to be executed as a regular user, and those starting with # have to be executed as the superuser root.

Ok, this has been done on FreeBSD 10.2 x86_32 using HakuNeko 1.3.12, both are current at the time of writing. I guess it might work on older and future releases of FreeBSD with different releases of HakuNeko as well, but hey, who knows?! That having been said, you’ll need the following software on top of FreeBSD for the build system to work (I may have missed something here, if so, just install the missing stuff like shown below):

  • cURL
  • GNU sed
  • GNU find
  • bash
  • OpenSSL
  • wxWidgets 2.8.x

Stuff that’s not on your machine can be fetched and installed as root from the official package repository, Like e.g.: # pkg install gsed findutils bash wx28-gtk2 wx28-gtk2-common wx28-gtk2-contrib wx28-gtk2-contrib-common

Of course you’ll need the HakuNeko source code as well. You can get it from official sources (see the link in first paragraph) or download it directly from here in the version that I’ve used successfully. If you take my version, you need 7zip for FreeBSD as well: # pkg install p7zip.

Unpack it:

  • $ 7z x hakuneko_1.3.12_src.7z (My version)
  • $ tar -xzvf hakuneko_1.3.12_src.tar.gz (Official version)

The insides of my archive are just vanilla as well however, so you’ll still need to do all the modifications by yourself.

2.) Replace the shebang lines in all scripts which require it

Enter the unpacked source directory of HakuNeko and open the following scripts in your favorite text editor, then replace the leading shebang lines #!/bin/bash with #!/usr/local/bin/bash:

  • ./configure
  • ./config_clang.sh
  • ./config_default.sh
  • ./res/parsers/kissanime.sh

It’s always the first line in each of those scripts, see config_clang.sh for example:

  1. #!/bin/bash
  2.  
  3. # import setings from config-default
  4. . ./config_default.sh
  5.  
  6. # overwrite settings from config-default
  7.  
  8. CC="clang++"
  9. LD="clang++"

This would have to turn into the following (I also fixed that comment typo while I was at it):

  1. #!/usr/local/bin/bash
  2.  
  3. # import settings from config-default
  4. . ./config_default.sh
  5.  
  6. # overwrite settings from config-default
  7.  
  8. CC="clang++"
  9. LD="clang++"

3.) Replace all sed invocations with gsed invocations in all scripts which call sed

This is needed because FreeBSDs sed and Linux’ GNU sed aren’t exactly that compatible in how they’re being called, different options and all.

In the text editor vi, the expression :%s/sed /gsed /g can do this globally over an entire file (mind the whitespaces, don’t omit them!). Or just use a convenient graphical text editor like gedit or leafpad for searching and replacing all occasions. The following files need sed replaced with gsed:

  • ./configure
  • ./res/parsers/kissanime.sh

4.) Replace all find invocations with gfind invocations in ./configure

Same situation as above with GNU find, like :%s/find /gfind /g or so, but only in one file:

  • ./configure

5.) Fix the make check

This is rather cosmetic in nature as $ ./configure won’t die if this test fails, but you may still wish to fix this. Just replace the string make --version with gmake --version (there is only one occurrence) in:

  • ./configure

6.) Fix the DIST variables’ content

I don’t think that this is really necessary either, but while we’re at it… Change the DIST=linux default to DIST=FreeBSD in:

  • ./configure

Again, only one occurrence.

7.) Run ./configure to create the Makefile

Enough with that, let’s run the first part of the build tools:

  • $ ./configure --config-clang

Notice the --config-clang option? We could use GCC as well, but since clang is FreeBSDs new and default platform compiler, you should stick with that whenever feasible. It works for HakuNeko, so we’re gonna use the default compiler, which means you don’t need to install the entire GCC for just this.

There will be error messages looking quite intimidating, like the basic linker test failing, but you can safely ignore those. Has something to do with different function name prefixes in FreeBSDs libc (or whatever, I don’t really get it), but it doesn’t matter.

However, there is one detail that the script will get wrong, and that’s a part of our include path. So let’s handle that:

8.) Fix the includes in the CFLAGS in the Makefile

Find the line containing the string CFLAGS = -c -Wall -O2 -I/usr/lib64/wx/include/gtk2-unicode-release-2.8 -I/usr/include/wx-2.8 -D_FILE_OFFSET_BITS=64 -D_LARGE_FILES -D__WXGTK__ -pthread or similar in the newly created ./Makefile. After the option -O2 add the following: -I/usr/local/include. So it looks like this: CFLAGS = -c -Wall -O2 -I/usr/local/include -I/usr/lib64/wx/include/gtk2-unicode-release-2.8 -I/usr/include/wx-2.8 -D_FILE_OFFSET_BITS=64 -D_LARGE_FILES -D__WXGTK__ -pthread. That’s it for the Makefile.

9.) Fix the Linux-specific conditionals across the C++ source code

And now the real work starts, because we need to fix up portions of the C++ code itself as well. While the code would build and run fine on FreeBSD, those relevant parts are hidden behind some C++ preprocessor macros/conditionals looking for Linux instead. Thus, important parts of the code can’t even compile on FreeBSD, because the code only knows Linux and Windows. Fixing that isn’t extremely hard though, just a bit of copy, paste and/or modify. First of all, the following files need to be fixed:

  • ./src/MangaConnector.cpp
  • ./src/Logger.cpp
  • ./src/MangaDownloaderMain.cpp
  • ./src/MangaDownloaderConfiguration.cpp

Now, what you should look for are all conditional blocks which look like #ifdef __LINUX__. Each will end with an #endif line. Naturally, there are also #ifdef __WINDOWS__ blocks, but those don’t concern us, as we’re going to use the “Linux-specific” code, if you can call it that. Let me give you an example right out of MangaConnector.cpp, starting at line #20:

  1. #ifdef __LINUX__
  2. wxString MCEntry::invalidFileCharacters = wxT("/\r\n\t");
  3. #endif

Now given that the Linux code builds just fine on FreeBSD, the most elegant and easier version would be to just alter all those #ifdef conditionals to inclusive #if defined ORs, so that they trigger for both Linux and FreeBSD. If you do this, the block from above would need to change to this:

  1. #if defined __LINUX__ || __FreeBSD__
  2. wxString MCEntry::invalidFileCharacters = wxT("/\r\n\t");
  3. #endif

Should you ever want to create different code paths for Linux and FreeBSD, you can also just duplicate it. That way you could later make changes for just Linux or just FreeBSD separately:

  1. #ifdef __LINUX__
  2. wxString MCEntry::invalidFileCharacters = wxT("/\r\n\t");
  3. #endif
  4. #ifdef __FreeBSD__
  5. wxString MCEntry::invalidFileCharacters = wxT("/\r\n\t");
  6. #endif

Whichever way you choose, you’ll need to find and update every single one of those conditional blocks. There are three in Logger.cpp, three in MangaConnector.cpp, two in MangaDownloaderConfiguration.cpp and again three in MangaDownloaderMain.cpp. Some are more than 10 lines long, so make sure to not make any mistakes if duplicating them.

Note that you can maybe extend compatibility even further with additional directives like __OpenBSD__ or __NetBSD__ for additional BSDs or __unix__ for a wide range of UNIX systems like AIX or HP-UX. None of which has been tested by me of course.

When all of that is done, it’s compile and install time:

10.) Compile and install

You can compile as a regular user, but the installation needs root by default. I’ll assume you’ll want to install HakuNeko system-wide, so, we’ll leave the installation target directories on their defaults below /usr/local/. While sitting in the unpacked source directory, run:

  • $ gmake
  • # gmake install

If nothing starts to crash and burn, this should compile and install the code. clang will show some warnings during compilation, but you can safely ignore that.

11.) Start up the white kitty

The installation procedure will also conveniently update your window manager as well, if you’re using panels/menus. Here it’s Xfce4:

HakuNeko is showing up as an "Internet" tool

HakuNeko (“White Cat”) is showing up as an “Internet” tool. Makes sense.

With the modifications done properly it should fire up just fine after initializing its Manga connectors:

HakuNeko with the awesomeness that is "Gakkou Gurashi!" being selected from the HTTP source "MangaReader"

HakuNeko with the awesomeness that is “Gakkou Gurashi!” being selected from the HTTP source [MangaReader].

Recently the developers have also added [DynastyScans] as a source, which provides access to partially “rather juicy” Yuri Dōjinshi (self-published amateur and sometimes semi-professional works) of well-known Manga/Anime, if you’re into that. Yuri, that is (“girls love”). Mind you, not all, but a lot of the stuff on DynastyScans can be considered NSFW and likely 18+, just as a word of warning:

HakuNeko fetching a Yuru Yuri Dōjinshi from DynastyScans, bypassing their download limits by not fetching packaged ZIPs - it works perfectly!

HakuNeko fetching a Yuru Yuri Dōjinshi called “Secret Flowers” from DynastyScans, bypassing their download limits by not fetching packaged ZIPs – it works perfectly!

Together with a good comic book reader that can read both plain JPEG-filled folders and stuff like packaged .cbz files, HakuNeko makes FreeBSD a capable comic book / Manga reading system. My personal choice for a reader to accompany HakuNeko would be [QComicBook], which you can easily get on FreeBSD. There are others you can fetch from the official package repository as well though.

Final result:

HakuNeko and QComicBook make a good team on FreeBSD UNIX

HakuNeko and QComicBook make a good team on FreeBSD UNIX – I like the reader even more than ComicRack on Windows.

And, at the very end, one more thing, even though you’re likely going to be aware of this already: Just like Anime fansubs, fan-translated Manga or even Dōjinshi are sitting in a legal grey zone, as long as the book in question hasn’t been licensed in your country. It’s being tolerated, but if it does get licensed, ownership of a fan-translated version will likely become illegal, which means you should actually buy the stuff at that point in time.

Just wanted to have that said as well.

Should you have trouble building HakuNeko on FreeBSD 10 UNIX (maybe because I missed something), please let me know in the comments!

Sep 232014
 

CD burning logo[1] At work I usually have to burn a ton of heavily modified Knoppix CDs for our lectures every year or so. The Knoppix distribution itself is being built by me and a colleague to get a highly secure read-only, server-controlled environment for exams and lectures. Now, usually I’m burning on both a Windows box with Ahead [Nero], and on Linux with the KDE tool [K3B] (despite being a Gnome 2 user), both GUI tools. My Windows box had 2 burners, my Linux box one. To speed things up and increase disc quality at the same time the idea was to plug more burners into the machines and burn each individual disc slower, but parallelized.

I was shocked to learn that K3B can actually not burn to multiple burners at once! I thought I was just being blind, stumbling through the GUI like an idiot, but it’s actually really not there. Nero on the other hand managed to do this for what I believe is already the better part of a decade!

True disc burning stations are just too expensive, like 500€ for the smaller ones instead of the 80-120€ I had to spend on a bunch of drives, so what now? Was I building this for nothing?

Poor mans disc station

Poor mans disc station. Also a shitty photograph, my apologies for that, but I had no real camera available at work.

Well, where there is a shell, there’s a way, right? Being the lazy ass that I am, I was always reluctant to actually use the backend tools of K3B on the command line myself. CD/DVD burning was something I had just always done on a GUI. But now was the time to script that stuff myself, and for simplicities sake I just used the bash. In addition to the shell, the following core tools were used:

  • cut
  • grep
  • mount
  • sudo (For a dismount operation, might require editing /etc/sudoers)

Also, the following additional tools were used (most Linux distributions should have them, conservative RedHat derivatives like CentOS can get the stuff from [EPEL]):

  • [eject(eject and retract drive trays)
  • [sdparm(read SATA device information)
  • sha512sum (produce and compare high-quality checksums)
  • wodim (burn optical discs)

I know there are already scripts for this purpose, but I just wanted to do this myself. Might not be perfect, or even good, but here we go. The work(-in-progress) is divided into three scripts. The first one is just a helper script generating a set of checksum files from a master source (image file or disc) that you want to burn to multiple discs later on, I call it create-checksumfiles.sh. We need one file for each burner device node later, because sha512sum needs that to verify freshly burned discs, so that’s why this exists:

expand/collapse source code
  1. #!/bin/bash
  2.  
  3. wrongpath=1 # Path for the source/master image is set to invalid in the
  4.             # beginning.
  5.  
  6. # Getting path to the master CD or image file from the user. This will be
  7. # used to generate the checksum for later use by multiburn.sh
  8. until [ $wrongpath -eq 0 ]
  9. do
  10.   echo -e "Please enter the file name of the master image or device"
  11.   echo -e "(if it's a physical disc) to create our checksum. Please"
  12.   echo -e 'provide a full path always!'
  13.   echo -e "e.g.: /home/myuser/isos/master.iso"
  14.   echo -e "or"
  15.   echo -e "/dev/sr0\n"
  16.   read -p "&gt; " -e master
  17.  
  18.   if [ -b $master -o -f $master ] &amp;&amp; [ -n "$master" ]; then
  19.     wrongpath=0 # If device or file exists, all ok: Break this loop.
  20.   else
  21.     echo -e "\nI can find neither a file nor a device called $master.\n"
  22.   fi
  23. done
  24.  
  25. echo -e "\nComputing SHA512 checksum (may take a few minutes)...\n"
  26.  
  27. checksum=`sha512sum $master | cut -d' ' -f1` # Computing checksum.
  28.  
  29. # Getting device node name prefix of the users' CD/DVD burners from the
  30. # user.
  31. echo -e "Now please enter the device node prefix of your disc burners."
  32. echo -e "e.g.: \"/dev/sr\" if you have burners called /dev/sr1, /dev/sr2,"
  33. echo -e "etc."
  34. read -p "&gt; " -e devnode
  35.  
  36. # Getting number of burners in the system from the user.
  37. echo -e "\nNow enter the total number of attached physical CD/DVD burners."
  38. read -p "&gt; " -e burners
  39.  
  40. ((burners--)) # Decrementing by 1. E.g. 5 burners means 0..4, not 1..5!
  41.  
  42. echo -e "\nDone, creating the following files with the following contents"
  43. echo -e "for later use by the multiburner for disc verification:"
  44.  
  45. # Creating the per-burner checksum files for later use by multiburn.sh.
  46. for ((i=0;i&lt;=$burners;i++))
  47. do
  48.   echo -e " * sum$i.txt: $checksum $devnode$i"
  49.   echo "$checksum $devnode$i" &gt; sum$i.txt
  50. done
  51.  
  52. echo -e ""

As you can see it’s getting its information from the user interactively on the shell. It’s asking the user where the master medium to checksum is to be found, what the users burner / optical drive devices are called, and how many of them there are in the system. When done, it’ll generate a checksum file for each burner device, called e.g. sum0.txt, sum1.txt, … sum<n>.txt.

Now to burn and verify media in a parallel fashion, I’m using an old concept I have used before. There are two more scripts, one is the controller/launcher, which will then spawn an arbitrary amount of the second script, that I call a worker. First the controller script, here called multiburn.sh:

expand/collapse source code
  1. #!/bin/bash
  2.  
  3. if [ $# -eq 0 ]; then
  4.   echo -e "\nPlease specify the number of rounds you want to use for burning."
  5.   echo -e "Each round produces a set of CDs determined by the number of"
  6.   echo -e "burners specified in $0."
  7.   echo -e "\ne.g.: ./multiburn.sh 3\n"
  8.   exit
  9. fi
  10.  
  11. #@========================@
  12. #| User-configurable part:|
  13. #@========================@
  14.  
  15. # Path that the image resides in.
  16. prefix="/home/knoppix/"
  17.  
  18. # Image to burn to discs.
  19. image="knoppix-2014-09.iso"
  20.  
  21. # Number of rounds are specified via command line parameter.
  22. copies=$1
  23.  
  24. # Number of available /dev/sr* devices to be used, starting
  25. # with and including /dev/sr0 always.
  26. burners=3
  27.  
  28. # Device node name used on your Linux system, like "/dev/sr" for burners
  29. # called /dev/sr0, /dev/sr1, etc.
  30. devnode="/dev/sr"
  31.  
  32. # Number of blocks per complete disc. You NEED to specify this properly!
  33. # Failing to do so will break the script. You can read the block count 
  34. # from a burnt master disc by running e.g. 
  35. # ´sdparm --command=capacity /dev/sr*´ on it.
  36. blocks=340000
  37.  
  38. # Burning speed in factors. For CDs, 1 = 150KiB/s, 48x = 7.2MiB/s, etc.
  39. speed=32
  40.  
  41. #@===========================@
  42. #|NON user-configurable part:|
  43. #@===========================@
  44.  
  45. # Checking whether all required tools are present first:
  46. # Checking for eject:
  47. if [ ! `which eject 2&gt;/dev/null` ]; then
  48.   echo -e "\e[0;33meject not found. $0 cannot operate without eject, you'll need to install"
  49.   echo -e "the tool before $0 can work. Terminating...\e[0m"
  50.   exit
  51. fi
  52. # Checking for sdparm:
  53. if [ ! `which sdparm 2&gt;/dev/null` ]; then
  54.   echo -e "\e[0;33msdparm not found. $0 cannot operate without sdparm, you'll need to install"
  55.   echo -e "the tool before $0 can work. Terminating...\e[0m"
  56.   exit
  57. fi
  58. # Checking for sha512sum:
  59. if [ ! `which sha512sum 2&gt;/dev/null` ]; then
  60.   echo -e "\e[0;33msha512sum not found. $0 cannot operate without sha512sum, you'll need to install"
  61.   echo -e "the tool before $0 can work. Terminating...\e[0m"
  62.   exit
  63. fi
  64. # Checking for sudo:
  65. if [ ! `which sudo 2&gt;/dev/null` ]; then
  66.   echo -e "\e[0;33msudo not found. $0 cannot operate without sudo, you'll need to install"
  67.   echo -e "the tool before $0 can work. Terminating...\e[0m"
  68.   exit
  69. fi
  70. # Checking for wodim:
  71. if [ ! `which wodim 2&gt;/dev/null` ]; then
  72.   echo -e "\e[0;33mwodim not found. $0 cannot operate without wodim, you'll need to install"
  73.   echo -e "the tool before $0 can work. Terminating...\e[0m\n"
  74.   exit
  75. fi
  76.  
  77. ((burners--)) # Reducing number of burners by one as we also have a burner "0".
  78.  
  79. # Initial burner ejection:
  80. echo -e "\nEjecting trays of all burners...\n"
  81. for ((g=0;g&lt;=$burners;g++))
  82. do
  83.   eject $devnode$g &amp;
  84. done
  85. wait
  86.  
  87. # Ask user for confirmation to start the burning session.
  88. echo -e "Burner trays ejected, please insert the discs and"
  89. echo -e "press any key to start.\n"
  90. read -n1 -s # Wait for key press.
  91.  
  92. # Retract trays on first round. Waiting for disc will be done in
  93. # the worker script afterwards.
  94. for ((l=0;l&lt;=$burners;l++))
  95. do
  96.   eject -t $devnode$l &amp;
  97. done
  98.  
  99. for ((i=1;i&lt;=$copies;i++)) # Iterating through burning rounds.
  100. do
  101.   for ((h=0;h&lt;=$burners;h++)) # Iterating through all burners per round.
  102.   do
  103.     echo -e "Burning to $devnode$h, round $i."
  104.     # Burn image to burners in the background:
  105.     ./burn-and-check-worker.sh $h $prefix$image $blocks $i $speed $devnode &amp;
  106.   done
  107.   wait # Wait for background processes to terminate.
  108.   ((j=$i+1));
  109.   if [ $j -le $copies ]; then
  110.     # Ask user for confirmation to start next round:
  111.     echo -e "\nRemove discs and place new discs in the drives, then"
  112.     echo -e "press a key for the next round #$j."
  113.     read -n1 -s # Wait for key press.
  114.     for ((k=0;k&lt;=$burners;k++))
  115.     do
  116.       eject -t $devnode$k &amp;
  117.     done
  118.     wait
  119.   else
  120.     # Ask user for confirmation to terminate script after last round.
  121.     echo -e "\n$i rounds done, remove discs and press a key for termination."
  122.     echo -e "Trays will close automatically."
  123.     read -n1 -s # Wait for key press.
  124.     for ((k=0;k&lt;=$burners;k++))
  125.     do
  126.       eject -t $devnode$k &amp; # Pull remaining empty trays back in.
  127.     done
  128.     wait
  129.   fi
  130. done

This one will take one parameter on the command line which will define the number of “rounds”. Since I have to burn a lot of identical discs this makes my life easier. If you have 5 burners, and you ask the script to go for 5 rounds that would mean you get 5 × 5 = 25 discs, if all goes well. It also needs to know the size of the medium in blocks for a later phase. For now you have to specify that within the script. The documentation inside shows you how to get that number, basically by checking a physical master disc with sdparm –command=capacity.

Other things you need to specify are the path to the image, the image files’ name, the device node name prefix, and the burning speed in factor notation. Also, of course, the number of physical burners available in the system. When run, it’ll eject all trays, prompt the user to put in discs, and launch the burning & checksumming workers in parallel.

The controller script will wait for all background workers within a round to terminate, and only then prompt the user to remove and replace all discs with new blank media. If this is the last round already, it’ll prompt the user to remove the last media set, and will then retract all trays by itself at the press of any key. All tray ejection and retraction is done automatically, so with all your drive trays still empty and closed, you launch the script, it’ll eject all drive trays for you, and retract after a keypress signaling the script all trays have been loaded by the user etc.

Let’s take a look at the worker script, which is actually doing the burning & verifying, I call this burn-and-check-worker.sh:

expand/collapse source code
  1. #!/bin/bash
  2.  
  3. burner=$1   # Burner number for this process.
  4. image=$2    # Image file to burn.
  5. blocks=$3   # Image size in blocks.
  6. round=$4    # Current round (purely to show the info to the user).
  7. speed=$5    # Burning speed.
  8. devnode=$6  # Device node prefix (devnode+burner = burner device).
  9. bwait=0     # Timeout variable for "blank media ready?" waiting loop.
  10. mwait=0     # Timeout variable for automount waiting loop.
  11. swait=0     # Timeout variable for "disc ready?" waiting loop.
  12. m=0         # Boolean indicating automount failure.
  13.  
  14. echo -e "Now burning $image to $devnode$burner, round $round."
  15.  
  16. # The following code will check whether the drive has a blank medium
  17. # loaded ready for writing. Otherwise, the burning might be started too
  18. # early when using drives with slow disc access.
  19. until [ "`sdparm --command=capacity $devnode$burner | grep blocks:\ 1`" ]
  20. do
  21.   ((bwait++))
  22.   if [ $bwait -gt 30 ]; then # Abort if blank disc cannot be detected for 30 seconds.
  23.     echo -e "\n\e[0;31mFAILURE, blank media did not become ready. Ejecting and aborting this thread..."
  24.     echo -e "(Was trying to burn to $devnode$burner in round $round,"
  25.     echo -e "failed to detect any blank medium in the drive.)\e[0m"
  26.     eject $devnode$burner
  27.     exit
  28.   fi
  29.   sleep 1 # Sleep 1 second before next check.
  30. done
  31.  
  32. wodim -dao speed=$speed dev=$devnode$burner $image # Burning image.
  33.  
  34. # Notify user if burning failed.
  35. if [[ $? != 0 ]]; then
  36.   echo -e "\n\e[0;31mFAILURE while burning $image to $devnode$burner, burning process ran into trouble."
  37.   echo -e "Ejecting and aborting this thread.\e[0m\n"
  38.   eject $devnode$burner
  39.   exit
  40. fi
  41.  
  42. # The following code will eject and reload the disc to clear the device
  43. # status and then wait for the drive to become ready and its disc to
  44. # become readable (checking the discs block count as output by sdparm).
  45. eject $devnode$burner &amp;&amp; eject -t $devnode$burner
  46. until [ "`sdparm --command=capacity $devnode$burner | grep $blocks`" = "blocks: $blocks" ]
  47. do
  48.   ((swait++))
  49.   if [ $swait -gt 30 ]; then # Abort if disc cannot be redetected for 30 seconds.
  50.     echo -e "\n\e[0;31mFAILURE, device failed to become ready. Aborting this thread..."
  51.     echo -e "(Was trying to access $devnode$burner in round $round,"
  52.     echo -e "failed to re-read medium for 30 seconds after retraction.)\e[0m\n."
  53.     exit
  54.   fi
  55.   sleep 1 # Sleep 1 second before next check to avoid unnecessary load.
  56. done
  57.  
  58. # The next part is only necessary if your system auto-mounts optical media.
  59. # This is usually the case, but if your system doesn't do this, you need to
  60. # comment the next block out. This will otherwise wait for the disc to
  61. # become mounted. We need to dismount afterwards for proper checksumming.
  62. until [ -n "`mount | grep $devnode$burner`" ]
  63. do
  64.   ((mwait++))
  65.   if [ $mwait -gt 30 ]; then # Warn user that disc was not automounted.
  66.     echo -e "\n\e[0;33mWARNING, disc did not automount as expected."
  67.     echo -e "Attempting to carry on..."
  68.     echo -e "(Was waiting for disc on $devnode$burner to automount in"
  69.     echo -e "round $round for 30 seconds.)\e[0m\n."
  70.     m=1
  71.     break
  72.   fi
  73.   sleep 1 # Sleep 1 second before next check to avoid unnecessary load.
  74. done
  75. if [ ! $m = 1 ]; then # Only need to dismount if disc was automounted.
  76.   sleep 1 # Give the mounter a bit of time to lose the "busy" state.
  77.   sudo umount $devnode$burner # Dismount burner as root/superuser.
  78. fi
  79.  
  80. # On to the checksumming.
  81. echo -e "Now comparing checksums for $devnode$burner, round $round."
  82. sha512sum -c sum$burner.txt # Comparing checksums.
  83. if [[ $? != 0 ]]; then # If checksumming produced errors, notify user. 
  84.   echo -e "\n\e[0;31mFAILURE while burning $image to $devnode$burner, checksum mismatch.\e[0m\n"
  85. fi
  86.  
  87. eject $devnode$burner # Ejecting disc after completion.

So as you can probably see, this is not very polished, scripts aren’t using configuration files yet (would be a nice to have), and it’s still a bit chaotic when it comes to actual usability and smoothness. It does work quite well however, with the device/disc readiness checking as well as the anti-automount workaround having been the major challenges (now I know why K3B ejects the disc before starting its checksumming, it’s simply impossible to read from the disc after burning finishes).

When run, it looks like this (user names have been removed and paths altered for the screenshot):

Multiburner

“multiburner.sh” at work. I was lucky enough to hit a bad disc, so we can see the checksumming at work here. The disc actually became unreadable near its end. Verification is really important for reliable disc deployment.

When using a poor mans disc burning station like this, I would actually recommend putting stickers on the trays like I did. That way, you’ll immediately know which disc to throw into the garbage bin.

This could still use a lot of polishing, and it’s quite sad, that the “big” GUI tools can’t do parallel burning, but I think I can now make due. Oh, and I actually also tried Gnomes “brasero” burning tool, and that one is far too minimalistic and can also not burn to multiple devices at the same time. They may be other GUI fatsos that can do it, but I didn’t want to try and get any of those installed on my older CentOS 6 Linux, so I just did it the UNIX way, even if not very elegantly. ;)

Maybe this can help someone out there, even though I think there might be better scripts than mine to get it done, but still. Otherwise, it’s just documentation for myself again. :)

Edit: Updated the scripts to implement a proper blank media detection to avoid burning starting prematurely in rare cases. In addition to that, I added some code to detect burning errors (where the burning process itself would fail) and notify the user about it. Also applied some cosmetic changes.

Edit 2: Added tool detection to multiburn.sh, and removed redundant color codes in warning & error messages in burn-and-check-worker.sh.

[1] Article logo based on the works of Lorian and Marcin Sochacki, “DVD.png” licensed under the CC BY-SA 3.0.