XGroup Tutorial: MakeGroup part
Krado @ KradoVision
krado at aol dot com

Tutorial code: Best MakeGroup code so far (It may be a good idea to press F-11 if using IE)

    If you're reading this, then I assume you've learned how to extract files from a GRP.  The code for MakeGroup has changed a LOT since the first version.  1.02.0006 is capable of fetching files from multiple sources.  It does this by using 3 list boxes, two of which are hidden under the main "GrpListBox".  List4, hidden, holds the path of each file being written to the group.  List3, hidden, holds the file's size.  Plus it's much faster!

Note: The code below is from 1.02.0006.  The .0005 code worked fine for the average user created GRP if it was 20 Megs or so.  But when testing XGroup against kextract.exe / kgroup.exe for speed, 132 MB files would have the last 3 filenames sullied and the files would not extract.  I have no idea what caused this.  Nothing I did fixed the problem.  No errors were returned.  
    I had to go back to writing the File List to the GRP first.  Then writing the binary data.  Using all the CON and ART files from Redneck Rampage Rides Again, it took 18 seconds to write 70 files @ 132 megs total on my 866.  Bummer...  It's back to being a little slow but the files are more accurate. Sorry!


' ' Make group button
' ' New code rewritten Sunday, June 13, 2004
Private Sub Command3_Click() 
On Error GoTo mgErr
' ' Call common dialog show save
cd1.CancelError = True
cd1.DialogTitle = "Write GRP File:"
cd1.Filter = "Build Engine Grp files (*.grp)|*.grp"
cd1.InitDir = App.Path
cd1.ShowSave
mgFileName$ = cd1.FileName
clFileName$ = cd1.FileTitle
' ' If check box checked add list.txt
' ' file to current GRP
If Check1.Value = 1 Then
Form2.Caption = "Adding current list file"
' ' The sub that creates the List.txt file is below
' ' Send it clFileName because it appears in the list file
' ' but if Bubba renames the GRP file later it'll look stupid... 
' ' Oh well, I though it was a good idea at the time!
AddList2GRP (clFileName$)
End If
' ' Disable controls to keep user from interrupting the process. DisCons is a sub
' ' that disables the various controls on the form.  Create your own or comment mine out.
DisCons
' ' If write path = a CD drive then tell Bubba
' ' "He cain't do that!", and exit the routine.

Form2.Caption = "Checking write path..."
' ' Yet again protecting Bubba from himself is a pain.  GetDriveType is a WinAPI function added
' ' to the project.  Unless you create your own mod, (cut and paste code at bottom of this page) to 
' ' handle this you should comment out the code that check the type of drive you're wanting to write to.

' ' The code below just reads the "Drive Letter and the colon" from the Common Dialog Box filename,
' ' and checks if it's a CD.  If it's another write protected drive the CatchErr sub will handle it...
chkCdbDrive = GetDriveType(Left(mgFileName$, 2))
If chkCdbDrive = 5 Then
MsgBox "Bubba! Unable to write to CD!", vbOKOnly, "CD Write Error"
Form2.Caption = "MakeGroup"
Drive1.Enabled = True
cd1.FileName = ""
' ' An error has occurred so re-enable the controls so Bubba can fix it.
EnCons
Exit Sub
End If

' '----------------------- Check For Long Filename in list --------
' '  While reading the list of files in the List Box, the length of each file name is checked
' '  If there are more than 12 characters you'll be notified.
Form2.Caption = "Checking filename length..."
For pl = 0 To List1.ListCount - 1
If
Len(List1.List(pl)) > 12 Then
msg$ = "You'll have to rename " & List1.List(pl) & vbCrLf _
& "The name can only be 12 characters!" & vbCrLf _
& "Select the file in list and press R. Type" & vbCrLf _
& "new name into the input box. Hit OK."
Ttl$ = List1.List(pl)
MsgBox msg, vbOKOnly, Ttl
Form2.Caption = "MakeGroup"
' ' If long name detected then close the file and exit the sub
' ' The user can rename the file and before proceding can 
' ' edit a con file if necessary...

Close
#2
' ' EnCons calls a sub to re-enable the controls
EnCons
Exit Sub
End If
Next
pl

' ' ----------------Begin creating GRP file -------------------------
' ' If there are no length errors we begin creating the FileID and  list of files
Open mgFileName$ For Binary As #1 ' New Grp file
Dim
ks As String * 12
Dim
NOF As Long
Dim
fLeng As Long
' ' Make sure not to allocate space for this string! 
Dim
inFileName As String  
' ' -------------- Create Header -------------
ks = "KenSilverman"
NOF = List1.ListCount
Put
#1, , ks
Put
#1, 13, NOF
' ' This may seem a bit odd here.  Once KenSilverman and the number of files is
' ' written in the first 12 bytes and the long NOF written from 13-16. You have to calculate
' ' the write positions of each file name and it's file size because you're jumping from the list
' ' down to where the data is stored and back.  Since you're going back and forth you have 
' ' to calculate this as far as I know.  Plus there's the issue of 20 20s & 00 00s
' ' I tried to use Set Seek but that didn't work out too well.
fp& = 17 ' ' First position of the first file in the list
lp& = 29  ' 'First position of the first File's size in the list
tot& = 0
' ' --------------Create list--------------------
' ' When adding files to your list, List 4 holds the path to the file.
' ' This is how we can get files from multiple local folders, drives, remote drives
' ' mapped drives etc...
For w = 0 To List1.ListCount - 1
' ' First filename is written to byte position 17
Put #1, fp&, List1.List(w)
fLeng = List3.List(w)
' ' First file's size is written to byte position 29
Put
#1, lp&, fLeng
' ' The first file name has been written to the GRP, it's size.  
' ' Now to place following file names & sizes in the proper places you add 16 for the file name 
' ' and then 12 more for the file's size. 
 See Example and more information below
fp& = (fp& + 17) - 1
lp& = (fp& + 13) - 1
Next
w

' ' ------------ Put data in file ------------
Dim b() As Byte
For
i = 0 To List1.ListCount - 1
 ' ' List4 may contain something like Root:\XGroup\ & List1 may contain Game.con
getFromFile = List4.List(i) & List1.List(i)
Open getFromFile For Binary Access Read As #2
Form2.Caption = "Processing: " & List1.List(i)
fLeng = List3.List(i)
' ' The first iteration of the for - next loop, wp& = 0 and tot& = 0
' ' If there are 10 files in the list then (NOF * 16) = 160
' ' You add 16 bytes to that which is "KenSilverman"(12) and number of files 
' ' which is a
Long, 4 bytes, so this would equal 176.  176 is the last byte of the file list.
' ' You add 1 which = 177.  177 is where the program will place the first file, Game.con.  
' '
See the example on the previous page.
' ' The "tot&" counter keeps a running total of the length of the current file
' ' adding the length of the following file so on and so forth.
' ' Game.con's start byte is 177, it's byte length/ filesize is 151190.  To get the write position for
' ' the next file, 2000.map, 177 is added to the length of Game.con.  177 + 151190 = 151367.

wp& = (NOF * 16) + 16 + 1 + tot&
' ' Re Dimension the Byte Array/Buffer to the length of file being added to the group.
ReDim
b(1 To List3.List(i))      
' ' Get the file's data
Get #2, , b
DoEvents
' ' wp& is the start position of the current file being written to the GRP.
Put #1, wp&, b
' ' Close the current "Get From File" and proceed to the iteration.
Close #2
tot& = tot& + fLeng
Next i

mgErr:
Form2.Caption = "MakeGroup"
ReDim b(0) As Byte
Form1.File1.Refresh
' ' Release counters
wp& = 0
tot& = 0
' ' Close any open files in case of error
Close #1, 2
EnCons
' ' If user hits cancel on the common dialog box the err.number = 32755
If Err.Number = 32755 Then
Exit Sub
' ' If the error is caused trying to write the GRP to a write protected disc or CD then:
ElseIf Err.Number = 70 Then
MsgBox "That disc may be Write-Protected?" & vbCrLf _
& "Slide that lil tab and try 'er agin!", vbOKOnly, "Write protected"
' ' Change directories back to the program's path
ChDir App.Path
Exit Sub
Else
' ' Any other error is handled by the gerneral error handler.
' ' Unless you create a general error handler you should comment out 
' ' references to it...

CatchErr
End If
End Sub


' '  This sub creates a list file from the current list, writes in the program's
' '  folder and adds it to the list at the end.

Sub
AddList2GRP(clFileName$)
On Error GoTo ListErr
cTime = Date & " - " & Time ' My Date / Time format
wPath$ = App.Path & "\List.txt"
' ' Open the list text file with "List" designation
' ' and print opener line, Name of file list is for and cTime
' ' For Output will overwrite a previous version.
Open wPath$ For Output As #1
Print #1, "List of files & sizes: " & clFileName$ & " " & cTime
Print #1,
' ' Print the list as it appears in the listbox adding a formatted
' ' filesize to the end of each string

For cl = 0 To List1.ListCount
Print #1, List1.List(cl); " - "; Format(List3.List(cl), "###,###,##0")
Next
' ' Tell 'em who did this
Print #1,
Print #1, "List created with MakeGroup."
Close #1
lFileSize = FileLen(App.Path & "\List.txt")
List1.AddItem "List.txt"
List3.AddItem lFileSize
List4.AddItem App.Path & "\"
' ' GRPS keeps up with the total size of your group and is reported in the
' ' label under the "Remove 1" button.  You may want to comment this out as well.
GRPS = GRPS + lFileSize
Exit Sub
ListErr:
CatchErr
End Sub


Cut and paste the code below into a mod.  It's the get drive type Function code.

Attribute VB_Name = "Module1"
Public Declare Function GetDriveType Lib "Kernel32" _
Alias "GetDriveTypeA" (ByVal nDrive As String) As Long
Private Declare Function GetLogicalDrives Lib "Kernel32" () As Long
Const DRVIE_CDROM = 5
Const DRIVE_FIXED = 3
Const DRIVE_RAMDISK = 6
Const DRIVE_REMOTE = 4
Const DRIVE_REMOVABLE = 2


   Thursday, June, 10 2004

    There have been a couple of questions as to why I calculate the write positions of the filenames and their sizes.  I'll try to answer this as simply as possible.  Think of the file list as being a group of boxes 16 spaces wide.  In reality "Dim ks as String * 12" is unnecessary since Ken's name is 12 characters long and it's constant..  When you add the "* 12" you allocate 12 spaces whether you use all of them or not.  This is where I had my trouble at first.  Since I allocated 12 spaces for Ken's name, I also allocated 12 spaces for the file names, "Dim lFileName as String * 12".  This caused the program to fill in the UNUSED spaces, if a file's name was LESS than 12 characters, with "empties" (ASCII Chr#32, Hex 20).  Using this method the GRP files looked perfect but kextract would give me an error message when I tried to test/extract the files.  The game would NOT play the GRP.
     Once Ken Silverman told me what I may be looking for here, I deleted the "* 12" allocation for the filenames.  Then the program would write the filename in but it would also place the FileSize(LONG) right up against the name ie: Game.con### and the GRP still wouldn't work.  So I came up with the "Magic Number  = 16" and began to calculate where to write the filenames and file sizes in the proper places without the "20 20's".

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
K e n S i l v e r m a n 2 00 00 00
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
G a m e . c o n 00 00 00 00 # # # 00
G m e . c o n 20 20 20 20 # # # 00

In the example table above, line number 5 is wrong if you dim the filenames as String * 12.
Line number 4 is correct when you calculate the start positions of the filename and file size.

    Note: In hex Number of files 2 would not look like a 2, it would be characters that windows will not show properly.

    Once I realized what was happening I came up with my equation and the fp& = 17 (fileposition(LONG) and lp& =29 (lengthPosition(LONG).  If you'll notice in the table above the "G" in Game.con is in position 17 and it's size starts at 29.  Even though line 5 has the 20s, the name is still in the proper position at 33 and it's size at position 45.  So you Dim lFileName as String.  Since there's no allocation, when the string's length is reached the program fills unused spaces with "nulls" ie: 00 00 00...  Note: If you have XGroup.exe version 1.02.0004 then DELETE it and get the newest.  While trying to clean up the code for this I, like a Bubba, added "* 12" to Dim lFileName as string * 12.  I didn't notice it until the next morning when I put up this tutorial and was checking to see if the page would come up.  I nearly had a heart attack!
   
About the only things I intend to change in XGroup will be how you do the Multi-Selection in the list boxes.  You may end up having to hold either the "Ctrl" key or a "Shift" key down while working with these boxes.  I will also add a button that will allow you to select multiple files in the file list box on the MakeGroup form manually and press the " > " button to add this batch to the GRP List box.  I may as well add these features to XGroup since I've added them to the "xLaunch" program...
    Another issue has come up regarding System Requirements.  If you have an older machine, less than a p2 / 233 w/128 mgs of ram, you may get buffer problems especially if you're trying to work with GRPs the size of the Rides.grp.  Some of the art files are 9 megs and that's a lot for Windows to deal with paging to the swap file.  My ole 233 can extract all of the ART files but just barely....


That's it.  Now maybe you can create your own version of XGroup or whatever you want to call it...

    I started my various tests by writing CON and other plain text files to the GRPs.  I could open these GRP files with a program called List.com and read the files in the group, check the file list and file sizes.  I've had List for years and it makes a good program for viewing files of this type.  If you open a file with it you can hit Alt + h to view the file in HEX.  I also used it to study GRP files created with KGroup.exe, by comparing the kgroup files to the MakeGroup files.  You may still be able to find this program on the net somewhere.  Once I got the plain text files right, I began adding maps and testing / comparing those.
    Thanks to Ken Silverman for his help last year when I didn't notice that the "20 20s" were supposed to be "00 00s"!  And I sat and stared at those files for days! 

KradoVision
XGroup
xLaunch

Krado