This is the second part of a multi-part entry about a utility I wrote called advib. You can find the prior part (and eventual 3 or more parts) as well as any other advib-related entries under the advib tag.
This is where I will try and dissect exactly how advib works, possibly line-by-line.
The program is actually relatively simple: gather some information from the user and game disc itself then feed this to ImgBurn which takes and runs with it.
The batch portion of the formula comes from being able to feed it a list of game titles in a text file, looping through each in turn.
I actually have some extra functionality I was working on involving using a CLI utility to get both the MD5 and SHA1 hashes and then saving the hashes, game titles and a date/time stamp into a CSV file. I figured as long as I had a verified image of the game what better time to get the hash? And the date/time just seemed like an easy way to track which games were done and when. But I never quite finished the intended functionality related to that and a separate utility just for that seems more appropriate anyway. For these reasons I’m going to ignore this extra functionality even though you’ll see this in the actual source code.
The work flow is basically to figure out which games I want to rip and for which platform. For instance for a stack of PS2 games I would create a text file containing the titles of the games without spaces, one per line:
alien-hominid alone-in-the-dark aqua-aqua armored-core-3
I could then tell advib the name of the text file, the drive letter of the optical drive and the platform. For instance:
advib gamelist.txt D: PS2
This may or may not sound simple but there’s actually quite a few steps to go along with it.
Here is a flowchart I came up with for how it works. I used the free version of the web site smartdraw.com which unfortunately means the exported image of the flowchart has a bunch of watermarks all over it. So I took a screen shot of my flowchart. Had to shrink it a little to fit but hopefully you can the gist.
To try and lay out the program loop in even-more-psuedo-psuedo-code, it goes something like this:
Take in the three (technically there’s a fourth option, debug, but I’m skipping that for now) command line options of:
- A text file of games to rip, one per line
- The drive letter of intended optical drive, like D:
- the name of the console, PS1 or PS2 in this case
The first step is to test and make sure it received all 3 options from the command line by testing the value of each against an empty string.
In the case of a one of the three options being an empty string the subroutine “usage help” is called to display a brief explanation of how to use the program and the program immediately exits.
A variable is created correlating to each option as it is tested to exist.
For instance the second option is the drive letter of the optical drive. In CMD batch file speak, that would %2 with a value of something like D: as it exists in the batch file:
if [%2]== (goto :usagehelp) ELSE SET CDDRIVE=%2
So this is asking: is %2 an empty string? If the answer is YES show some usage help for advib and exit. If the answer is NO set a variable represented by %2 to the value of the second parameter, like D:.
With the three options defined, advib does a second test against the first parameter, now defined as gamelist, to see if it’s defined. This may be redundant but it also doesn’t hurt anything. Of course I also don’t set anything in particular to happen if it’s false. It’s just the way I happen to execute the next command: showing the content of the text file as defined by the variable.
if defined gamelist (echo List of games set to rip and verify: && type %gamelist% && echo:)
This just makes sure gamelist is defined then displays a helpful message and the contents of the gamelist text file with the type command.
The next step is again testing if that gamelist variable is defined and if so calling the :continueon subroutine. I think I did this separately because I couldn’t figure out how to roll calling :continueon into the above if defined block (I think this way is more readable anyway).
The :continueon subroutine
This is the real “meat” of the utility. Where all the real work gets done. At first glance it might seem like the is subroutine is not doing very much. It’s actually kind of subtle if you don’t know what you’re looking at:
for /f "tokens=1" %%i in (%gamelist%) do (SET GAMENAME=%%i) && (call :RIPVERIFY)
The CMD scripting…well I guess it’s more of a syntax than a language but…the first part is straight forward: the start of a for-loop. Quite similar to any and all languages with for-loops.
The tokens=1 part is just telling it to look at the first part of the the first line in the text file. The game name in other words is the first token. This “token” Is stored in a variable, %i. And it’s %%i because it’s in a batch file (on the CLI itself, outside a batch file, it’s just one %).
This is followed by in (%gamelist%) which is somewhat self explanatory: the token is the first “word” up until a space, stored in variable %i and we’re looking in whatever the value of %gamelist% is. In this case as established above %gamelist% is the name of the text file containing the list of games e.g. %1 passed in to advib.
Next is do followed by creating and setting the value of a new variable, gamename, conveniently set it to the value of %i.
And this is where the double ampersand (&&) comes in so I can also call the next subroutine, :RIPVERIFY, on the same line. Note: this is directly related to the setlocal EnableDelayedExpansion line. I’ve decided to cover this in a separate “side bar” at the very bottom of this post.
So what the :CONTINUEON subroutine is doing is calling :RIPVERIFY over and over again for each line is the text file defined as %gamelist%. The subtle thing is that :RIPVERIFY then returns to :CONTINUEON. This may not be obvious just looking at it.
So :CONTINUEON goes through a for-loop and calls :RIPVERIFY, which goes all the way through and returns control back to :CONTINUEON which gets the name of the next game from the text file and calls :RIPVERIFY again. This it keeps doing until it reaches the end of the text file.
I only thought this might be confusing because :RIPVERIFY does not explicitly itself try to call back to :CONTINUEON. So you just kind of have to know that’s what’s happening.
The :ripverify subroutine
This subroutine is actually the main part of the program. Or another way of putting it: this subroutine is the program as this is where all the actual work is done. Everything leading up to it is gathering information so this subroutine will work.
Once the program is running this is the first thing that will be seen so I inserted a line asking for the next game, providing the name of that game, the console the game is for and the drive letter of the optical drive:
echo Insert %GAMENAME% for %CONSOLE% into drive %CDDRIVE%...
This is less important the first time through and more important on subsequent runs.
The next thing I did is blank out the FILE-EXT variable. I found the program was keeping the value of the last time it looped so it has to be zero’d out each time through the loop.
Now we come to the first ImgBurn command. ImgBurn is kind of whole other thing almost worthy of a separate blog entry all to itself. But instead I’ve stuck some information in the “sidebar” at the very bottom of this page if you want to learn more about it.
This is the ImgBurn command I run:
ImgBurn.exe /mode verify /src %CDDRIVE% /info "gameinfo\%GAMENAME%_%CONSOLE%.txt" /CLOSEINFO /WAITFORMEDIA
ImgBurn is first and foremost all about writing to optical media. As the name implies. Any function outside of that is really just there to directly or indirectly help in some way with that primary function.
ImgBurn comes with several “modes” one of which is a “gather information about media” mode. So that is all this command is really doing: open ImgBurn, gather some standard information about the disc that is found, store the information in a text file and close when done.
As you can see the %CDDRIVE% variable is already established and can provide which drive to look at for the media and %GAMENAME% and %CONSOLE% are both used in the name of the text file.
This is actually relatively simple as far as it goes. At least when compared to the next line:
if exist "gameinfo\%GAMENAME%_%CONSOLE%.txt" (for /f "usebackq tokens=3 delims= " %%z in (`type gameinfo\%GAMENAME%_%CONSOLE%.txt ^| find /i "current profile"`) do set DISCTYPE=%%z) else (echo could not find text file)
This might look like a bunch of random garbage and no joke: CMD for loops are for crap. Here is the first couple lines of a typical ImgBurn-generated disc info text file.
HL-DT-ST DVDRAM GP08NU6B 1.00 (USB) Current Profile: DVD-ROM
First we start with an if exist statement. This may be redundant but I’m just checking to see if advib can in fact find the recently created text file. I have an else at the end but it doesn’t look like I try and exit out of advib or anything so that looks like an oversight on my part.
Then the for-loop. First is the /f indicating a “file set”. At least that’s the best I can tell from the help file. All I know is I have to use that for the loop to work.
Next is “usebackq tokens=3 delims= “. This actually kind of important:
- usebackq means if needed the “back tick” which is this ` character can be used in addition to quotes.
- tokens=3 means I’m going to grab the third thing that matches.
- and lastly, the delmins= ” refers the deliminator to be used (a single space character)
The back tick is as much convenience as anything. As you can see from the disc info sampler above, the “DVD-ROM” line is the third item on that line, as defined by spaces on either side of the characters.
In other words: the third “word” surrounded by spaces, is the string DVD-ROM.
Oh, we’re not even done with the for-loop yet. The %%z establishes a variable for the actions in the for loop and in just makes it official.
(`type gameinfo\%GAMENAME%_%CONSOLE%.txt ^| find /i "current profile"`)
This is using some old fashioned text parsing and very simple parsing at that: the type command simply displays the contents of a text file on the screen. Going back to like 1985 or something. But I pipe this to a (case-ignoring) find command looking for the string current profile. As you can see above this is on the second line of the text file (I’m using variables surrounded by % signs to make up the name of the text file).
So lets bring it all together: open the text file, find the string current profile and grab the third word on the same line as the current profile string as defined by normal spaces (a “word” in this context are a series of numbers/letters separated by a space). The string DVD-ROM in other words. That’s it. Very simple.
The last part of the loop is simply setting that disc type to the previously established %%z variable:
do set DISCTYPE=%%z
If you understood this then…congratulations. The statement is ended with an else with an echo that the text file could not be found.
The next line is to set the file extensions based on whether the media is CD or DVD: when it’s CD the file extension is BIN and what I call the verify extension is CUE. This is because during the last step – verifying the physical disc versus the file ripped – ImgBurn uses the CUE file to verify CD media but just the ISO file for DVDs (there’s an exception to this for DVDs I’ll be adding in to the new version).
Here is the IF statement I came up with:
if /i "%DISCTYPE%" == "cd-rom" (set FILE-EXT=BIN && set VEREXT=CUE) else (SET FILE-EXT=ISO && set VEREXT=ISO)
This just takes the established DISCTYPE variable and checks it against a string value of CD-ROM (the /i is to indicate “ignore case”). Since the only choices are ISO or BIN/CUE I only really have to establish if the disc is a CD or not. If it’s not a CD I just set the variable to ISO. This makes it relatively simple in the choices. There could be another else here to catch any errant values and respond elegantly. Not sure how such an errant value would happen. If somebody was screwing around in another console window with variable values. Even then I don’t think it would have an effect.
The next line is what all the program has been leading up to: the ImgBurn command.
If you’ve been following this whole post it will probably make sense:
ImgBurn.exe /mode read /src %CDDRIVE% /dest %~dp0%CONSOLE%\%GAMENAME%_%CONSOLE%.%FILE-EXT% /start /CLOSESUCCESS /WAITFORMEDIA
The first three parameters, /mode read /src, simply puts ImgBurn into “read mode” as opposed to burner mode and then specifies a source of reading. The source being the previously established variable for CDDRIVE. Next is the /dest flag followed by the path to the text file containing the list of games (which was established as existing in the above IF statement). If you happen to be curious about that %~dp0 see the sidebar below.
This is followed by /start, /closesuccess and /waitformedia which is simply telling ImgBurn to close when it’s done, wait for the optical drive to finish spinning up the disc and acknowledge the media exists and to start. Maybe that could be in a more intuitive order but it’s not like that line is seen while running the program.
The next line is testing for an error return code:
IF %ERRORLEVEL% NEQ 0 GOTO ERROR-OCCURRED
For whatever reason it became standard on both CMD and Unix shells like BASH that “no errors” is a return of zero. I’m not sure why that was established. But as you probably guessed from the line above, if the error level returned is anything other than zero the program jumps to another section of code labeled error-occurred (the NEQ just means “not equal to”).
This is actually a return code generated by ImgBurn itself so there is not a lot I can do about it programmatically – not as a batch file anyway – other than acknowledge it and and output a message to try the disc again later.
Here is the error-occurred sub-routine:
:ERROR-OCCURRED ECHO An error occurred with game %GAMENAME%, disc must be re-done ECHO error code returned was %errorlevel%
This is just the end of the program. Just exits on error.
Next is the verify part of the process: when ImgBurn is done ripping the disc it immediately verifies the ripped file(s) against the actual physical disc:
ImgBurn.exe /mode verify /src %CDDRIVE% /dest %~dp0%CONSOLE%\%GAMENAME%_%CONSOLE%.%VEREXT% /start /eject yes /CLOSESUCCESS /WAITFORMEDIA
As with the prior ImgBurn command, a mode a set: this time to verify. And as before a source and destination are established and a start command. But this time there’s the /eject yes inserted there which…tells ImgBurn to eject the disc when it’s done verifying. Shocking, I know. The last two are the same as before: telling ImgBurn to wait for the optical drive to flag the disc as ready to access and to close when it has completed successfully.
Then there is the same ERROR LEVEL check as before.
There are few lines left in the batch file but they are all for generating and storing the MD5 and SHA1 hashes and storing the values into a CSV file. I’m not going to bother going through that as I never really got that to work exactly the way I wanted.
The explanations were getting quite long so I thought I would expand on a few may-or-may not matter subjects in a separate section. These are directly or indirectly related to advib but an explanation doesn’t necessarily help dissect the program itself.
Basically, this line allows for variables to be set to a value and then those values immediately used before the whole batch file or subroutine has finished executing. Truthfully I still don’t entirely understand EnableDalayedExpansion, even after reading the lengthy and detailed explanation on ss64.com. I do know I couldn’t get the program to work prior to adding in the EnableDalayedExpansion line, which was enough reason to make sure it was there.
As I mentioned, ImgBurn is mainly oriented towards burning things to optical media. Any other functions are directly or indirectly to ultimately doing the burning.
One of the things that can be done is gathering information on the current media and saving this to a text file. This file contains a bunch of information on the media besides the CD/DVD identification.
The ImgBurn user forum has a much better explanation than I could ever hope for (covering both command line flags and all the settings in the GUI).
This sidebar is really about this snippet of this line above:
ImgBurn.exe /mode verify /src %CDDRIVE% /info "gameinfo\%GAMENAME%_%CONSOLE%.txt"
This line puts ImgBurn into verify mode, sets the drive letter with the disc in it as the “source”, lets ImgBurn know I want information on the disc and provides the name of the text file. It’s not literally verifying a disc as you can see, I just have to set that to get it to output the text file.
There’s actually a giant list of information on the disc in this text file, I just happened to only need the CD/DVD information. Actually I ended up with a whole bunch of these information text files when I was done ripping my collections of games. This is kind of a separate piece of information I could keep for later reference or parsing if I wanted to.
The wonders of the %0
I used %~dp0 in the file above but didn’t explain it at all. As a breif explanation, this is actually referring to whatever is to the left of %1 and it only shows up as a value in a batch file.
Or in the case of advib, since the text file containing the list of games is the first thing passed to advib, the text file is %1. Which makes anything and everything to the left of that text file name effectively %0.
In this case I wanted the current drive and path where the file is setting. And this is what I get when I use that the tilde and dp: the drive and the path.
So effectively %~dp0 translates to “expand out the current drive and path”. So if advib were sitting at c:\advib\advib.cmd then the value of %~dp0 would be c:\advib\.
Note the trailing back slash there. So if I use %~dp0advib this is the same as writing c:\advib\advib.cmd. The %~dp0 returns any “working directory”. So if advib were in c:\program files\advib\ then the value of %~dp0 would be c:\program files\advb\.
So in the line:
There is no space between the %~dp0 and the %console%: if I added a slash it would be the path with an extra slash, like c:\advib\\ps2\gamename_ps2.iso. Which may or may not work actually. But I just assume get it right.