Catdaddy Posted June 15, 2009 Share Posted June 15, 2009 OK. Most of you will think this a pointless experiment but I was just curious to see if it could be done in AutoIt. The 3D shooter, Duke Nukem 3D, that came out in the mid 90's, used a type of archive for storing it's resource files. It was called a .GRP file or a Group file. The engine author, Ken Silverman, needed a quick and dirty way of consolidating the sound, music and art files and so he came up with this convention. The file format for a .GRP file is as follows: Bytes 0-11 = 12 byte array or 12 byte ID which is "KenSilverman" Bytes 12-15 = long integer (4 bytes) which is total number of files contained in .GRP file (X number of files) Then we have X number of records in the following format: Bytes 16-27 = 12 byte array for filename (this would be the first filename in the group. I imagine it is only 12 bytes because of the older naming convention: 8.3 or xxxxxxxx.xxx Bytes 28-31 = long integer (4 bytes) which is the length of the first file in bytes. And so on. Etc. Finally, after the index, we have X number of file data, stored in respect to the order of the filenames, one after the other, back to back. In short, it is just an uncompressed archive of data! (This was taken from the official 3Drealms site). Soooo...... I have attempted to see if I could write an AutoIt script that could at least SEE what was contained in a .GRP file. From there I thought about making my own extractor but that is down the road a bit. Here is my code so far: CODEDim $B[5] $File = "MY.GRP" $Result = FileOpen ("Result.txt", 1) ; Open and check if file opened for reading OK $GrpFile = FileOpen($File, 0) If $GrpFile = -1 Then MsgBox(0, "Error", "Unable to open file.") Exit EndIf ; Read first 12 bytes for ID $FileID = FileRead($GrpFile, 12) ; If $FileID does not = "KenSilverman" then file is not a true GRP If $FileID <> "KenSilverman" Then MsgBox(16,"Error","This does not appear to be a genuine .GRP file.") Exit EndIf FileWriteLine ($Result, "Group file: " & $File & @CRLF) FileWriteLine ($Result, "File ID: " & $FileID & @CRLF) ; Read next four bytes as long integer for total number of files contained For $L = 1 to 4 $B[$L] = Asc(FileRead($GrpFile, 1)) Next $Hex = Hex($B[4],2) & Hex($B[3],2) & Hex($B[2],2) & Hex($B[1],2) $TotalNumFiles = Dec($Hex) FileWriteLine ($Result, "Total Number of Files: " & $TotalNumFiles & @CRLF & @CRLF) For $T = 1 To $TotalNumFiles Dim $B[5] ; Read next 12 bytes for filename of first file $FileName = FileRead($GrpFile, 12) ; Read next 4 bytes as long integer for file length For $L = 1 to 4 $B[$L] = Asc(FileRead($GrpFile, 1)) Next $Hex = Hex($B[4],2) & Hex($B[3],2) & Hex($B[2],2) & Hex($B[1],2) $FileLength = Dec($Hex) FileWriteLine ($Result, $FileName & "," & $FileLength & @CRLF) Next FileClose($GrpFile) FileClose($Result) Essentially, I run this from the same folder where my MY.GRP is located and it creates a text file named RESULTS.TXT. Well, here are the results when I run this script: Group file: MY.GRP File ID: KenSilverman Total Number of Files: 4 TILES014.ART,1207332 W2DDEFS.CON ARWOLF.MAP MACHAMB.VOC What it SHOULD be displaying is filename, then a comma, then the file length or byte size. As you can see, it displays the byte size for the first file and then nothing for the rest. It seems to read the filenames correctly but not the file size. Of course, when I decide to write an extractor for these files I would need to know the correct position in the file where it's located and how long each file is bytewise. Yes, I know, the game came with it's own handlers (KGROUP.EXE and KEXTRACT.EXE). Like I said, probably a waste of time, but I just wanted to see if it could be done in AutoIt. If some kind soul out there would have the time to take a look at this I would greatly appreciate it. I've included a sample .GRP file. It's in a zip file called MY.zip. I'm using AutoIt version 3.3.0.0 if that helps any. I think my lack of knowledge of how AutoIt reads an open file is to blame but I'm just not sure where I'm going wrong.MY.zip Link to comment Share on other sites More sharing options...
Inverted Posted June 16, 2009 Share Posted June 16, 2009 These are the results for the full game's DUKE3D.GRP : expandcollapse popupGroup file: MY.GRP File ID: KenSilverman Total Number of Files: 456 LOGO.ANM CINEOV2.ANM CINEOV3.ANM RADLOGO.ANM DUKETEAM.ANM,66467 VOL41A.ANM VOL42A.ANM VOL43A.ANM VOL4E1.ANM VOL4E2.ANM VOL4E3.ANM DEFS.CON GAME.CON USER.CON D3DTIMBR.TMB,3328 DUKE3D.BIN LOOKUP.DAT PALETTE.DAT TABLES.DAT FLYBY.VOC SECRET.VOC BLANK.VOC ROAM06.VOC ROAM58.VOC PREDRG.VOC GBLASR01.VOC,5243 PREDPN.VOC PREDDY.VOC CHOKN12.VOC PREDRM.VOC LIZSPIT.VOC PIGRM.VOC ROAM29.VOC ROAM67.VOC PIGRG.VOC PIGPN.VOC PIGDY.VOC PIGWRN.VOC OCTARM.VOC OCTARG.VOC OCTAAT1.VOC OCTAAT2.VOC OCTAPN.VOC OCTADY.VOC BOS1RM.VOC BOS1RG.VOC BOS1PN.VOC BOS1DY.VOC KICKHIT.VOC RICOCHET.VOC,4683 BULITHIT.VOC,2170 PISTOL.VOC CLIPOUT.VOC CLIPIN.VOC CHAINGUN.VOC,3198 SHOTGNCK.VOC,7812 RPGFIRE.VOC BOMBEXPL.VOC,21020 PBOMBBNC.VOC,1360 WPNSEL21.VOC,5468 SHRINK.VOC LSRBMBPT.VOC,1926 LSRBMBWN.VOC,2618 SHRINKER.VOC,9701 VENTBUST.VOC,7522 GLASS.VOC GLASHEVY.VOC,8663 SHORTED.VOC SPLASH.VOC ALERT.VOC REACTOR.VOC SUBWAY.VOC GEARGRND.VOC,8697 GASP.VOC GASPS07.VOC PISSING.VOC KNUCKLE.VOC DRINK18.VOC EXERT.VOC HARTBEAT.VOC,4295 PAIN13.VOC PAIN28.VOC PAIN39.VOC PAIN87.VOC WETFEET.VOC LAND02.VOC DUCTWLK.VOC PAIN54.VOC PAIN75.VOC PAIN93.VOC PAIN68.VOC DAMN03.VOC DAMNIT04.VOC,5530 COMEON02.VOC,7031 WAITIN03.VOC,17544 COOL01.VOC AHMUCH03.VOC,22320 DANCE01.VOC LETSRK03.VOC,9430 READY2A.VOC RIPEM08.VOC ROCKIN02.VOC,8132 AHH04.VOC GULP01.VOC PAY02.VOC AMESS06.VOC BITCHN04.VOC,11125 DOOMED16.VOC,23392 HOLYCW01.VOC,12449 HOLYSH02.VOC,12495 IMGOOD12.VOC,15165 ONLYON03.VOC,16055 PIECE02.VOC RIDES09.VOC 2RIDE06.VOC THSUK13A.VOC,18400 WANSOM4A.VOC,12800 MYSELF3A.VOC,22239 NEEDED03.VOC,16086 SHAKE2A.VOC DUKNUK14.VOC,37069 GETSOM1A.VOC,11775 GOTHRT01.VOC,21697 GROOVY02.VOC,8942 WHRSIT05.VOC,8231 BOOBY04.VOC DIESOB03.VOC,16365 DSCREM04.VOC,30563 LOOKIN01.VOC,17243 PISSIN01.VOC,12783 GETITM19.VOC,1961 SCUBA.VOC JETPAKON.VOC,9617 JETPAKI.VOC JETPAKOF.VOC,9980 GOGGLE12.VOC,1746 THUD.VOC SQUISH.VOC TELEPORT.VOC,5303 GBELEV01.VOC,14121 GBELEV02.VOC,2028 SWITCH.VOC FLUSH.VOC QUAKE.VOC MONITOR.VOC POOLBAL1.VOC,2036 ONBOARD.VOC BUBBLAMB.VOC,29114 MACHAMB.VOC WIND54.VOC STEAMHIS.VOC,8359 BARMUSIC.VOC,77801 WARAMB13.VOC,42180 WARAMB21.VOC,40927 WARAMB23.VOC,42467 WARAMB29.VOC,18103 COMPAMB.VOC SLIDOOR.VOC OPENDOOR.VOC,7097 EDOOR10.VOC EDOOR11.VOC FSCRM10.VOC H2OGRGL2.VOC,24238 GRIND.VOC ENGHUM.VOC LAVA06.VOC PHONBUSY.VOC,10853 ROAM22.VOC AMB81B.VOC ROAM98B.VOC H2ORUSH2.VOC,15647 PROJRUN.VOC FIRE09.VOC !PRISON.VOC !PIG.VOC !BOSS.VOC MICE3.VOC DRIP3.VOC ITEM15.VOC BONUS.VOC CATFIRE.VOC KILLME.VOC SHOTGUN7.VOC,52477 DMDEATH.VOC HAPPEN01.VOC,13719 DSCREM15.VOC,10642 DSCREM16.VOC,9677 DSCREM17.VOC,5529 DSCREM18.VOC,12210 DSCREM38.VOC,13499 RIP01.VOC NOBODY01.VOC,36937 CHEW05.VOC LETGOD01.VOC,16172 HAIL01.VOC BLOWIT01.VOC,22265 EATSHT01.VOC,25003 FACE01.VOC INHELL01.VOC,21017 SUKIT01.VOC PISSES01.VOC,27560 MUSTDIE.VOC DEFEATED.VOC,37429 MONOLITH.VOC,52923 HYDRO50.VOC ASWTCH23.VOC,4799 HYDRO22.VOC HYDRO24.VOC HYDRO27.VOC HYDRO34.VOC HYDRO40.VOC HYDRO43.VOC VAULT04.VOC 2BWILD.VOC FREEZE.VOC B2ATK02.VOC B2ATK01.VOC B2DIE03.VOC B2PAIN03.VOC,11919 B2REC03.VOC B3ROAM01.VOC,22941 B3DIE03G.VOC,24101 B3REC03G.VOC,11333 B3PAIN04.VOC,21625 CTRLRM25.VOC,17741 HLIDLE03.VOC,16701 LIZSHIT3.VOC,4539 ADOOR1.VOC ADOOR2.VOC FORCE01.VOC JONES04.VOC QUAKE06.VOC TERMIN01.VOC,12115 BORN01.VOC CON03.VOC SNAKRM.VOC SNAKRG.VOC SNAKATA.VOC SNAKATB.VOC SNAKPN.VOC SNAKDY.VOC COMMRM.VOC COMMRG.VOC COMMAT.VOC COMMPN.VOC COMMDY.VOC COMMSP.VOC SLIMAT.VOC SLHTCH01.VOC,12137 SLIDIE03.VOC,5569 SLIREC06.VOC,12819 SLIROA02.VOC,16099 KTITX.VOC WHIPYU01.VOC,29895 GUNHIT2.VOC HEADRIP3.VOC,76171 BUCKLE.VOC JETP2.VOC ZIPPER2.VOC NEWS.VOC WHISTLE.VOC CHEER.VOC GMEOVR05.VOC,9558 GRABBAG.VOC NAME01.VOC R&R01.VOC LANI08.VOC LANI05.VOC LANIDUK2.VOC,68419 TYPING.VOC CLANG1.VOC INTRO4H1.VOC,95868 INTRO4H2.VOC,169814 INTROA.VOC INTROB.VOC INTROC.VOC SQUISH1A.VOC,11839 SBR1C.VOC BANG1.VOC SKIDCR1.VOC MUZAK028.VOC,96135 MUZAKDIE.VOC,25468 ENLARGE.VOC DOM03.VOC DOM09.VOC DOM11.VOC DOM12.VOC DOLPHIN.VOC DEEPFRY1.VOC,30981 DOGWHINE.VOC,29271 DOGYELP.VOC SWEET03.VOC SWEET04.VOC SWEET05.VOC SWEET16.VOC TRUMBLE.VOC TCLAP2A.VOC ALARM1A.VOC RAIN3A.VOC JEEP2A.VOC CDOOR1B.VOC WAVE1A.VOC EXSHOT3B.VOC,6970 SHORN1.VOC SBELL1A.VOC GRUN.VOC BRUN.VOC GSCORE.VOC BSCORE.VOC TANK3A.VOC BLROAM2A.VOC,20550 BLREC4B.VOC BLRIP1A.VOC BLRIP1B.VOC BLSPIT1A.VOC,22379 BLPAIN1B.VOC,8225 BLDIE3A.VOC BQROAM2A.VOC,26563 BQREC2A.VOC BQPAIN2A.VOC,13268 BQSHOCK3.VOC,54390 BQEGG1A.VOC BQDIE1A.VOC ABORT01.VOC KICK01-I.VOC,18528 AISLE402.VOC,18616 ANNOY03.VOC BOOKEM03.VOC,13842 CRY01.VOC DETRUCT2.VOC,20641 EAT08.VOC ESCAPE01.VOC,19538 MAKEDAY1.VOC,20287 SOHELP02.VOC,12791 VACATN01.VOC,30572 YIPPIE01.VOC,18682 YOHOHO01.VOC,21654 YOHOHO09.VOC,37485 INDPNC01.VOC,17265 MDEVL01.VOC MEAT04-N.VOC,18681 PARTY03.VOC POSTAL01.VOC,19103 SMACK02.VOC VOCAL02.VOC BEBACK01.VOC,5862 GETCRAP1.VOC,14673 GUYSUK01.VOC,11389 SLACKER1.VOC,17782 GOAWAY.VOC GRABBAG.MID STALKER.MID DETHTOLL.MID,7185 STREETS.MID WATRWLD1.MID,25371 SNAKE1.MID THECALL.MID STORM.MID FATCMDR.MID GUTWRNCH.MID,45372 NAMES.MID GLOOMY.MID FUTURMIL.MID,49106 INTENTS.MID INVADER.MID SUBWAY.MID AHGEEZ.MID 233C.MID ALFREDH.MID ALIENZ.MID GOTHAM.MID INHIDING.MID,49695 LORDOFLA.MID,39852 PIZZED.MID ROBOCREP.MID,12089 SPOOK.MID STALAG.MID URBAN.MID WHOMP.MID XPLASMA.MID BRIEFING.MID,8902 MISSIMP.MID WAREHAUS.MID,62150 BAKEDGDS.MID,15857 PREPD.MID CF.MID LEMCHILL.MID,34541 LAYERS.MID FLOGHORN.MID,4351 POB.MID DEPART.MID RESTRICT.MID,20825 TILES000.ART,1764538 TILES001.ART,1687911 TILES002.ART,847094 TILES003.ART,1113891 TILES004.ART,1147073 TILES005.ART,934595 TILES006.ART,587951 TILES007.ART,1054311 TILES008.ART,592057 TILES009.ART,1269998 TILES010.ART,2281496 TILES011.ART,57815 TILES012.ART,1060417 TILES013.ART,998283 TILES014.ART,6160 TILES015.ART,6160 TILES016.ART,1858901 TILES017.ART,722612 TILES018.ART,1490897 TILES019.ART,867463 E1L3.MAP E1L4.MAP E1L5.MAP E1L6.MAP E1L7.MAP E2L1.MAP E2L2.MAP E2L3.MAP E2L4.MAP E2L5.MAP E2L6.MAP E2L7.MAP E2L8.MAP E2L9.MAP E2L10.MAP E2L11.MAP E3L1.MAP E3L2.MAP E3L3.MAP E3L4.MAP E3L5.MAP E3L6.MAP E3L7.MAP E3L8.MAP E3L9.MAP E3L10.MAP E1L1.MAP E1L2.MAP E1L8.MAP E3L11.MAP E4L1.MAP E4L2.MAP E4L3.MAP E4L4.MAP E4L5.MAP E4L6.MAP E4L7.MAP E4L8.MAP E4L9.MAP E4L10.MAP E4L11.MAP DEMO2.DMO DEMO1.DMO DEMO3.DMO Link to comment Share on other sites More sharing options...
Zedna Posted June 16, 2009 Share Posted June 16, 2009 (edited) Look here at example for File functions from WinAPI UDF:http://www.autoitscript.com/forum/index.php?showtopic=76829expandcollapse popup#include <WinAPI.au3> Global $pos, $nBytes $tBuffer = DLLStructCreate("char[12]") $iBuffer = DLLStructCreate("int") Dim $B[5] $File = @ScriptDir & "\MY.GRP" $Result = FileOpen("Result.txt", 1) ; Open and check if file opened for reading OK $GrpFile = _WinAPI_CreateFile($File, 2, 2) If $GrpFile = 0 Then MsgBox(0, "Error", "Unable to open file.") Exit EndIf ; Read first 12 bytes for ID _WinAPI_ReadFile($GrpFile, DLLStructGetPtr($tBuffer), 12, $nBytes) $FileID = DLLStructGetData($tBuffer, 1) $pos += 12 ; If $FileID does not = "KenSilverman" then file is not a true GRP If $FileID <> "KenSilverman" Then MsgBox(16, "Error", "This does not appear to be a genuine .GRP file.") Exit EndIf FileWriteLine($Result, "Group file: " & $File & @CRLF) FileWriteLine($Result, "File ID: " & $FileID & @CRLF) ; Read next four bytes as long integer for total number of files contained _WinAPI_SetFilePointer($GrpFile, $pos) _WinAPI_ReadFile($GrpFile, DLLStructGetPtr($iBuffer), 4, $nBytes) $TotalNumFiles = DLLStructGetData($iBuffer, 1) $pos += 4 FileWriteLine($Result, "Total Number of Files: " & $TotalNumFiles & @CRLF & @CRLF) For $T = 1 To $TotalNumFiles ; Read next 12 bytes for filename of first file _WinAPI_SetFilePointer($GrpFile, $pos) _WinAPI_ReadFile($GrpFile, DLLStructGetPtr($tBuffer), 12, $nBytes) $FileName = DLLStructGetData($tBuffer, 1) $pos += 12 ; Read next 4 bytes as long integer for file length _WinAPI_SetFilePointer($GrpFile, $pos) _WinAPI_ReadFile($GrpFile, DLLStructGetPtr($iBuffer), 4, $nBytes) $FileLength = DLLStructGetData($iBuffer, 1) $pos += 4 FileWriteLine($Result, $FileName & "," & $FileLength & @CRLF) Next $tBuffer = 0 $iBuffer = 0 FileClose($Result) _WinAPI_CloseHandle($GrpFile)result:Group file: C:\Program Files\AutoIt3\MY.GRP File ID: KenSilverman Total Number of Files: 4 TILES014.ART,1207332 W2DDEFS.CON,35992 ARWOLF.MAP,434302 MACHAMB.VOC,61685 Edited June 16, 2009 by Zedna Resources UDF Â ResourcesEx UDF Â AutoIt Forum Search Link to comment Share on other sites More sharing options...
Catdaddy Posted June 16, 2009 Author Share Posted June 16, 2009 Look here at example for File functions from WinAPI UDF: http://www.autoitscript.com/forum/index.php?showtopic=76829 This is great! Thank you, Zedna. My includes library was woefully lacking. I really enjoy being able to to move the file pointer to any position I like. This will help tremendously when I write the extractor. Again, thank you. Link to comment Share on other sites More sharing options...
Catdaddy Posted September 16, 2009 Author Share Posted September 16, 2009 I doubt anyone is interested, but here is the solution I came up with for the above problem (with a heaping help from Zedna): expandcollapse popup#include <ButtonConstants.au3> #include <EditConstants.au3> #include <GUIConstantsEx.au3> #include <StaticConstants.au3> #include <WindowsConstants.au3> #include <WinAPI.au3> $Form1 = GUICreate("Unpack", 419, 244, -1, -1) $Input1 = GUICtrlCreateInput("", 16, 40, 385, 21) $Button1 = GUICtrlCreateButton("Search", 336, 72, 65, 25, 0) $Label1 = GUICtrlCreateLabel("GRP File:", 16, 16, 49, 17) $Input2 = GUICtrlCreateInput("", 16, 136, 385, 21) $Label2 = GUICtrlCreateLabel("Folder to unpack to:", 16, 112, 99, 17) $Button2 = GUICtrlCreateButton("Search", 336, 168, 65, 25, 0) $Button3 = GUICtrlCreateButton("Extract", 169, 192, 89, 33, 0) GUISetState(@SW_SHOW) While 1 $nMsg = GUIGetMsg() Switch $nMsg Case $GUI_EVENT_CLOSE Exit Case $Button1 $GrpFile = FileOpenDialog("Choose GRP file to extract from", @ScriptDir & "\", "GRP Files (*.grp)", 1 + 2) GUICtrlSetData($Input1, $GrpFile) Case $Button2 $GrpFolder = FileSelectFolder("Choose a folder to extract to", "", 1) GUICtrlSetData($Input2, $GrpFolder) Case $Button3 $File = GUICtrlRead($Input1) $Folder = GUICtrlRead($Input2) GUIDelete($Form1) ExitLoop EndSwitch WEnd Dim $Pos, $Bytes, $sFile, $hFile $tBuffer = DLLStructCreate("char[12]") $iBuffer = DLLStructCreate("int") ;Open and check if file opened for reading OK $GrpFile = _WinAPI_CreateFile($File, 2, 2) If $GrpFile = 0 Then MsgBox(0, "Error", "Unable to open file.") Return EndIf ;Read first 12 bytes for ID _WinAPI_ReadFile($GrpFile, DLLStructGetPtr($tBuffer), 12, $Bytes) $FileID = DLLStructGetData($tBuffer, 1) $Pos += 12 ;If $FileID does not = "KenSilverman" then file is not a true GRP If $FileID <> "KenSilverman" Then Exit EndIf ;Read next four bytes as long integer for total number of files contained _WinAPI_SetFilePointer($GrpFile, $Pos) _WinAPI_ReadFile($GrpFile, DLLStructGetPtr($iBuffer), 4, $Bytes) $TotalNumFiles = DLLStructGetData($iBuffer, 1) $Pos += 4 ProgressOn("Extracting", "Filename", "0 percent") Dim $IntFile[$TotalNumFiles + 1] Dim $FileLen[$TotalNumFiles + 1] $IntFile[0] = $TotalNumFiles For $T = 1 To $TotalNumFiles ;Read next 12 bytes for filename of first file _WinAPI_SetFilePointer($GrpFile, $Pos) _WinAPI_ReadFile($GrpFile, DLLStructGetPtr($tBuffer), 12, $Bytes) $FileName = DLLStructGetData($tBuffer, 1) $IntFile[$T] = $FileName $Pos += 12 ;Read next 4 bytes as long integer for file length _WinAPI_SetFilePointer($GrpFile, $Pos) _WinAPI_ReadFile($GrpFile, DLLStructGetPtr($iBuffer), 4, $Bytes) $FileLength = DLLStructGetData($iBuffer, 1) $FileLen[$T] = $FileLength $Pos += 4 Next ;Write archived files to selected folder For $L = 1 To $TotalNumFiles ;Read each packed file in archive to $ExFile buffer _WinAPI_SetFilePointer($GrpFile, $Pos) $fBuffer = DLLStructCreate("byte[" & $FileLen[$L] & "]") _WinAPI_ReadFile($GrpFile, DLLStructGetPtr($fBuffer), $FileLen[$L], $Bytes) $ExFile = DLLStructGetData($fBuffer, 1) ;<=== Buffer that holds current file $Pos += $FileLen[$L] ;Write packed file to selected folder as $IntFile filename $sFile = ($Folder & "\" & $IntFile[$L]) DLLStructSetData($fBuffer, 1, $ExFile) $hFile = _WinAPI_CreateFile($sFile, 1) _WinAPI_WriteFile($hFile, DLLStructGetPtr($fBuffer), $FileLen[$L], $Bytes) _WinAPI_CloseHandle($hFile) $Perc = Round(($L/$TotalNumFiles * 100), 0) ProgressSet($Perc, $Perc & " percent", $IntFile[$L]) Next $fBuffer = 0 $tBuffer = 0 $iBuffer = 0 $Pos = 0 _WinAPI_CloseHandle($GrpFile) ProgressOff ( ) In order for this to run correctly you will need to add the contents of Zedna's files above to your Constants.au3 and WinAPI.au3 files in your Includes folder. And, of course, you will need a GRP file from DukeNukem. This will be incorporated into the launcher I am writing for EDuke32. Anyone interested in running Duke3D natively in Windows (without DosBox) you should check it out here: https://eduke32.com I know it's an old game but I've got a soft spot for the classics. Enjoy. Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now