diff --git a/CRC16.bmx b/CRC16.bmx new file mode 100644 index 0000000..2221832 --- /dev/null +++ b/CRC16.bmx @@ -0,0 +1,61 @@ +'Ported code from Mednafen + +Global CRC16_tab:Int[] = + [$0000, $1021, $2042, $3063, $4084, $50a5, $60c6, $70e7, + $8108, $9129, $a14a, $b16b, $c18c, $d1ad, $e1ce, $f1ef, + $1231, $0210, $3273, $2252, $52b5, $4294, $72f7, $62d6, + $9339, $8318, $b37b, $a35a, $d3bd, $c39c, $f3ff, $e3de, + $2462, $3443, $0420, $1401, $64e6, $74c7, $44a4, $5485, + $a56a, $b54b, $8528, $9509, $e5ee, $f5cf, $c5ac, $d58d, + $3653, $2672, $1611, $0630, $76d7, $66f6, $5695, $46b4, + $b75b, $a77a, $9719, $8738, $f7df, $e7fe, $d79d, $c7bc, + $48c4, $58e5, $6886, $78a7, $0840, $1861, $2802, $3823, + $c9cc, $d9ed, $e98e, $f9af, $8948, $9969, $a90a, $b92b, + $5af5, $4ad4, $7ab7, $6a96, $1a71, $0a50, $3a33, $2a12, + $dbfd, $cbdc, $fbbf, $eb9e, $9b79, $8b58, $bb3b, $ab1a, + $6ca6, $7c87, $4ce4, $5cc5, $2c22, $3c03, $0c60, $1c41, + $edae, $fd8f, $cdec, $ddcd, $ad2a, $bd0b, $8d68, $9d49, + $7e97, $6eb6, $5ed5, $4ef4, $3e13, $2e32, $1e51, $0e70, + $ff9f, $efbe, $dfdd, $cffc, $bf1b, $af3a, $9f59, $8f78, + $9188, $81a9, $b1ca, $a1eb, $d10c, $c12d, $f14e, $e16f, + $1080, $00a1, $30c2, $20e3, $5004, $4025, $7046, $6067, + $83b9, $9398, $a3fb, $b3da, $c33d, $d31c, $e37f, $f35e, + $02b1, $1290, $22f3, $32d2, $4235, $5214, $6277, $7256, + $b5ea, $a5cb, $95a8, $8589, $f56e, $e54f, $d52c, $c50d, + $34e2, $24c3, $14a0, $0481, $7466, $6447, $5424, $4405, + $a7db, $b7fa, $8799, $97b8, $e75f, $f77e, $c71d, $d73c, + $26d3, $36f2, $0691, $16b0, $6657, $7676, $4615, $5634, + $d94c, $c96d, $f90e, $e92f, $99c8, $89e9, $b98a, $a9ab, + $5844, $4865, $7806, $6827, $18c0, $08e1, $3882, $28a3, + $cb7d, $db5c, $eb3f, $fb1e, $8bf9, $9bd8, $abbb, $bb9a, + $4a75, $5a54, $6a37, $7a16, $0af1, $1ad0, $2ab3, $3a92, + $fd2e, $ed0f, $dd6c, $cd4d, $bdaa, $ad8b, $9de8, $8dc9, + $7c26, $6c07, $5c64, $4c45, $3ca2, $2c83, $1ce0, $0cc1, + $ef1f, $ff3e, $cf5d, $df7c, $af9b, $bfba, $8fd9, $9ff8, + $6e17, $7e36, $4e55, $5e74, $2e93, $3eb2, $0ed1, $1ef0] + +Function CRC16:Short(Array:Byte[]) + Local cksum:Short + For Local i = 0 To Len(Array) - 1 + cksum:Short = CRC16_tab[(cksum:Short Shr 8) ~ Array[i]] ~ (cksum Shl 8) + Next + Return ~cksum:Short +End Function +Local QSub:Byte[10] + +rem +QSub[0] = $41 +QSub[1] = $01 +QSub[2] = $01 +QSub[3] = $23 +QSub[4] = $06 +QSub[5] = $05 +QSub[6] = $00 +QSub[7] = $03 +QSub[8] = $08 +QSub[9] = $01 + + +Print Hex(crc16(QSub) + 128) +Print crc16(QSub) +endrem \ No newline at end of file diff --git a/README.md b/README.md index b8ef9e9..e07ed30 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,66 @@ # SBITools -SBITools v0.1 - http://kippykip.com +SBITools v0.2 - http://kippykip.com **Description:** - - This is a small set of conversion tools written in BlitzMax to convert .SBI/.LSD files back into .SUB files, which can be read into popular CD Burning software such as CloneCD. - This way, LibCrypt protected games dumped in other formats can still be burned as 1:1 copies on a real Sony PlayStation console again with the LibCrypt changes intact. + This is a small set of conversion tools written in BlitzMax to reconstruct .SUB files using .SBI/.LSD files, and can even convert a full BIN/CUE/SBI emulator setup into a IMG/CCD/SUB setup which can be put into popular CD Burning programs such as CloneCD. + This way, LibCrypt protected games dumped in other formats can still be burned as 1:1 copies on a real Sony PlayStation console again with the LibCrypt changes fully intact. + **These tools are only intended for intended for PlayStation images, there's no telling how these tools would react to standard Mode 1 PC disc images.** **Requirements:** - - psxt001z, to be in the same directory as SBITools - It can be downloaded from: http://redump.org/download/psxt001z-0.21b1.7z - Source Code: https://github.com/Dremora/psxt001z + psxt001z, to be in the same directory as SBITools + It can be downloaded from: http://redump.org/download/psxt001z-0.21b1.7z + Source Code: https://github.com/Dremora/psxt001z + It's pre-included in the "Releases" section for SBITools + https://github.com/Kippykip/SBITools/releases **Arguments:** -> SBITools.exe -sbi image.(bin/img) subchannel.sbi subchannel.sub -> SBITools.exe -lsd image.(bin/img) subchannel.lsd subchannel.sub +> SBITools.exe -sbi cuefile subchannel.sbi +> SBITools.exe -lsd cuefile subchannel.lsd +> SBITools.exe -cue2ccd cuefile + +**Argument Definitions:** -**Example:** ->SBITools.exe -sbi MediEvil.bin "MediEvil (Europe).sbi" MediEvil.sub + -sbi: Patches an images subchannel with a .SBI file. + -lsd: Patches an images subchannel with a .LSD file. + Both -sbi and -lsd export the patched .SUB file to: + \SUB\GAMENAME\GAMENAME.SUB + -cue2ccd: Converts a 'BIN/CUE/SBI|LSD' setup into a 'IMG/CCD/CUE/SUB' setup. + This makes burning LibCrypt games easily possible with software such + as CloneCD" + It exports the converted files to the "\CCD\GAMENAME\" directory +**Notes:** + It's ***very important*** to note, SBI files do not contain the CRC16 bytes, which is needed for certain games such as V-Rally 2: Championship Edition, MediEvil and most likely a few others. Most LibCrypt games I've tested don't check for this though. + + Regardless, SBITools v0.2 now partially reconstructs the CRC16 using a XOR of $0080 although it's not a perfect reconstruction for the whole SubChannel in anyway (as that's impossible). + Until I find a way to recreate it enough to be playable, some games may not work (I know MediEvil is one of them!) I obviously can't test every game to see if it passes LibCrypt, so in the meantime if you're a purist like me, definitely go for .LSD files instead. They're the superior format (and I'm not sure why they weren't the standard instead of .SBI) +They can be easily found on http://redump.org/disc/DISCID#/lsd -**Note:** + Remember to ***ALWAYS*** test games converted with the **-cue2ccd** function on an emulator before you burn! I personally recommend using BizHawk (which uses Mednafen) and opening the converted game from the .CCD file. + Do ***NOT*** run the game in BizHawk from the .CUE file! The LibCrypt copy protection will kick in if you do that! + +**Upcoming** + - Add a -**cdd2cue** function to reverse the process, just in case. + - Add support for image files that have the tracks seperated (Such as "CoolGame (Track 1).bin, CoolGame (Track 2).bin, CoolGame (Track 3).bin") etc. + - Maybe even remove the need for psxt001z too as it's only used for + generating blank .SUB subchannel, although it is a very useful tool to have in combination with SBITools. + - Add a .LSD to .SBI converter, maybe the inverse too if I find a way to perfectly recreate it. + - Add full XOR support for SBI CRC16 recreation. - The .SUB path will be overridden if it exists! Be careful. - I'm planning on making a BIN/CUE/SBI -> IMG/CUE/CCD/SUB function for the future. - Maybe even remove the need for psxt001z too as it's only used for generating a blank .SUB subchannel initially. - +**Version History** + + Version 0.2 + - .SUB patch functions now also add the CD Audio track data to the subchannel. + Although this change now requires you to specify a .CUE file instead of a Binary + file for -SBI and -LSD functions. SUB files are now exported to the \SUB directory + in these functions too. + - The -SBI function now recreates some of the CRC16 bytes required for handful of games, + although still not 100% compatible. + - Command line functions are no longer case sensitive. (oops) + - Added -cue2ccd, which allows you to do a full burnable conversion. + Version 0.1 + - Initial release # SBI File Format Specifications *HEADER* @@ -42,7 +76,7 @@ SBITools v0.1 - http://kippykip.com Example: S B I NUL MIN SEC FRA DUM [ QSUB ] 53 42 49 00 03 08 05 01 41 01 01 07 06 05 00 23 08 05 - +I'm unsure why they didn't include the modified CRC16 bytes in SBI, as it's extremely important. # LSD File Format Specifications *CONTINUOUS* @@ -55,5 +89,38 @@ SBITools v0.1 - http://kippykip.com Example: MIN SEC FRA [ QSUB ] [CRC16] 03 08 05 41 01 01 07 06 05 00 23 08 05 38 39 + +# LibCrypt failed check, causes and effects +Here's a list of what LibCrypt'ed games will do to the player when it realises the SubChannel data isn't correct. Obviously these aren't all the LibCrypted games (check on Redump.org for that), these are just games I've personally tested and some I've been told about. +Look out for these effects when testing games modified with SBITools on an accurate PSX emulator such as BizHawk. *(Run from .CCD)* -Note: CRC16 is not needed to start LibCrypt games, but it is modified on LibCrypt'ed games. +**Ape Escape (PAL)** +Main menu navigation will be completely disabled, making you unable to start the game. +**Crash Team Racing (PAL)** +Game will hang once at the end of the loading screen (for the level itself). +**Crash Team Racing (PAL)** +Game will hang once at the end of the loading screen (for the level itself). +**Legacy of Kain: Soul Reaver (PAL)** +The game will hang when you're introduced with the combat tutorial when the camera pans to show the enemies. +**Lucky Luke: Western Fever (PAL)** +The game stops when you get to the Mexican guy blocking the bridge, he just won't move from there, ever. Even when you complete the quest. +**MediEvil (PAL)** +Will have a disc error icon upon loading The Hilltop Mausoleum. Interesting to note this was actually the *FIRST* game to use LibCrypt. + **MediEvil 2** - Will also have the same disc error icon as above, except upon loading Kensington. +**PGA European Tour Golf (PAL)** +In the third hole of the first tournament or by selecting some holes, the game will get stuck in "demo" mode (and will not you play anything). +**Resident Evil 3: Nemesis (PAL)** +Will hang at the "Game contains violence and gore" screen. +**Spyro 3: Year of the Dragon** +Interesting case for this one, the game will eventually randomly delete eggs, reset progress with unlocked characters, remove sheep in boss battles, change the language and even tell you off for playing a "hacked copy" + more. +Interesting to note that the game also detected early LibCrypt knockout PPF patches back when the game was first released as it had checksum checks throughout the game, which caused the same effects above. +**This is Football (PAL)** +Hangs on the loading screen going ingame. +**V-Rally: Championship Edition 2 (PAL)** +The game will endlessly load on the heartbeat loading screen (with no disc activity). +**Wip3out (PAL)** +The game will freeze when passing the finish line. +# CloneCD LibCrypt Ripping guide +TODO +# CloneCD LibCrypt Burning guide +TODO \ No newline at end of file diff --git a/functions.bmx b/functions.bmx new file mode 100644 index 0000000..b5c7eb4 --- /dev/null +++ b/functions.bmx @@ -0,0 +1,434 @@ +'PS1 Discs are stored in MODE2, meaning they have 2352 bytes per sector. +Function GetSectorsBySize(Size:Int) + Return Size / 2352 +End Function + +'Minutes, Seconds, Frames to Sector number +Function MSFToSector:Int(Minutes:Int, Seconds:Int, Frames:Int) + '75 frames in a second for CD Audio, for some reason. + Local Calc:Int = Minutes * 60 * 75 + Seconds:Int * 75 + Frames:Int + Return Calc +End Function + +Function SectorToMSF:Int[] (Sector:Int) + Local MSF:Int[3] + Local Minutes:Int = (Sector / 60 / 75) + Local Seconds:Int = ((Sector - Minutes * 60 * 75) / 75) + Local Frames:Int = (Sector - Minutes * 60 * 75 - Seconds * 75) + MSF:Int[0] = Minutes:Int + MSF:Int[1] = Seconds:Int + MSF:Int[2] = Frames:Int + Return MSF +End Function + +Function NumberToHexMSF:Byte(Number:Int) + Local HexStr:String = "$000000" + If(Number:Int <= 9) + HexStr:String = HexStr:String + "0" + Number + Else + HexStr:String = HexStr:String + Number + EndIf + Return Byte(HexStr.ToInt()) +End Function + +'Converts a Sector to a byte offset for a .SUB file +Function SectorToSUBOffset:Int(Sector:Int) + '.SUB files have 96 bytes in each sector, with a 12 byte FF'd header at the beginning. + 'Subtracting 150 is for the 2 second lead-in they have, for whatever reason. + 'http://forum.imgburn.com/index.php?/topic/2122-how-to-convert-mmssff-to-bytes/&do=findComment&comment=25842 + Return (Sector - 150) * 96 + 12 +End Function + +'Patches a .SUB file with a SBI +Function SBIToSub(SBIPath:String, SUB:TBank) + Local SBIFile:TStream = ReadFile(SBIPath:String) + + 'Did the SUB file load? + If(SUB:TBank) + '4801107 = [SUB\0] header as an int + If(SBIFile:TStream And ReadInt(SBIFile) = 4801107) + While Not Eof(SBIFile:TStream) + Local SBI_Minutes:Int + Local SBI_Seconds:Int + Local SBI_Frames:Int + + 'Next 3 bytes are the MSF. For some reason the true number is stored in raw HEX. + 'Thankfully BMX can convert from HEX strings to numbers easily. + SBI_Minutes:Int = Int(Hex(ReadByte(SBIFile:TStream))) + SBI_Seconds:Int = Int(Hex(ReadByte(SBIFile:TStream))) + SBI_Frames:Int = Int(Hex(ReadByte(SBIFile:TStream))) + + 'Convert that MSF to a Sector + Local SectorMSF:Int = MSFToSector(SBI_Minutes:Int, SBI_Seconds:Int, SBI_Frames:Int) + 'Get the offset that the SBI will replace in the SUB file + Local ReplaceOffset:Int = SectorToSUBOffset(SectorMSF) + + 'Dummy byte, it will always be 01 (maybe, I was reading the psxt001z SRC afterall) + 'If it's not, it must of misaligned somehow + If(ReadByte(SBIFile:TStream) <> 1) + RuntimeError("Odd contents in SBI file!") + endif + + 'The next 10 bytes are the replacement QSUB bytes + Local QSub:Byte[10] + For Local i = 0 To 10 - 1 + 'Get the byte, and replace the destination part in the .SUB with the offsets above. + Local Replacement:Byte = ReadByte(SBIFile:TStream) + PokeByte(SUB, ReplaceOffset + i, Replacement) + QSub[i] = Replacement + Next + Print "Replaced 10 byte QSUB at MSF: " + SBI_Minutes:Int + ":" + SBI_Seconds:Int + ":" + SBI_Frames:Int + " (sector: " + SectorMSF:Int + ") at offset: " + Hex(ReplaceOffset:Int) + + 'Now we have to recalculate the CRC16 if we want to make all the games fully work. + 'Although stupidly, the modified CRC16 on LibCrypt games are not included in SBIs, and different LibCrypt games generate them differently + 'Lets assume it needs recalculating, with a 0080 XOR, although some use 8001 but there's no way to easily check. Why did they make this a standard? + Local SUB_CRC16:Short = CRC16(QSub) + '0080 XOR is whatmost LibCrypt games use according to ReDump + Local SUB_CRCA:Byte = (SUB_CRC16 + $0080) Shr 8 + Local SUB_CRCB:Byte = (SUB_CRC16 + $0080) - (SUB_CRCA Shl 8) + 'Have to do it this way as PokeShort will reverse to little endian or something + PokeByte(SUB, ReplaceOffset:Int + 10, SUB_CRCA) + PokeByte(SUB, ReplaceOffset:Int + 11, SUB_CRCB) + Print "Added 2 byte LibCrypt CRC16 to QSUB with a value of: " + Right(Hex(SUB_CRC16:Short + $0080), 4) + Wend + 'Run the SUB audio tracks function + GenSubCDDA(SUB) + Print "Finished SBI -> SUB Patching!" + Else + RuntimeError("Not a valid SBI file.") + EndIf + Else + RuntimeError("Not a valid SUB file.") + EndIf +End Function + +'Patches a .SUB file with a LSD +Function LSDToSub:TBank(LSDPath:String, SUB:TBank) + Local LSDFile:TStream = ReadFile(LSDPath:String) + + 'Did the SUB file load? + If(SUB:TBank) + '4801107 = [SUB ] header + If(LSDFile:TStream) + While Not Eof(LSDFile:TStream) + Local LSD_Minutes:Int + Local LSD_Seconds:Int + Local LSD_Frames:Int + + 'Next 3 bytes are the MSF. For some reason the true number is stored in raw HEX. + 'Thankfully BMX can convert from HEX strings to numbers easily. + LSD_Minutes:Int = Int(Hex(ReadByte(LSDFile:TStream))) + LSD_Seconds:Int = Int(Hex(ReadByte(LSDFile:TStream))) + LSD_Frames:Int = Int(Hex(ReadByte(LSDFile:TStream))) + + 'Convert that MSF to a Sector + Local SectorMSF:Int = MSFToSector(LSD_Minutes:Int, LSD_Seconds:Int, LSD_Frames:Int) + 'Get the offset that the LSD will replace in the SUB file + Local ReplaceOffset:Int = SectorToSUBOffset(SectorMSF) + + 'The next 10 bytes are the replacement bytes + For Local i = 0 To 12 - 1 + 'Get the byte, and replace the destination part in the .SUB with the offsets above. + Local Replacement:Byte = ReadByte(LSDFile:TStream) + PokeByte(SUB, ReplaceOffset:Int + i, Replacement:Byte) + Next + Print "Replaced 12 byte subchannel at MSF: " + LSD_Minutes:Int + ":" + LSD_Seconds:Int + ":" + LSD_Frames:Int + " (sector: " + SectorMSF:Int + ") at offset: " + Hex(ReplaceOffset:Int) + Wend + 'Run the SUB audio tracks function + GenSubCDDA(SUB) + Print "Finished LSD -> Patching!" + Else + RuntimeError("Not a valid LSD file.") + EndIf + Else + RuntimeError("Not a valid SUB file.") + EndIf +End Function + +'Generates SUB information about all the CD tracks +Function GenSubCDDA(SUB:TBank) + Local TotalTracks:Int = CUE.CountCDDA + If(TotalTracks > 0) + Print "Adding CD Audio track data to subchannel" + Local ReplaceOffset:Int + + 'We need to get the offset to start writing the leadin and tracks + 'Although we gotta loop through our CUE list to find it + For Local CueFile:CUE = EachIn CUE.List:TList + If(CueFile.TrackType = "AUDIO") + If(CueFile.Track = 2 And CueFile.Index = 1) 'We'll use index one so we can use SectorToSUBOffset + ReplaceOffset:Int = SectorToSUBOffset(CueFile.Sector) + EndIf + EndIf + Next + + 'Track 2 is also where the audio starts + For Local CDDA_TrackID:Int = 2 To TotalTracks + 1 ' Have to add one so it does the final track (As TotalTracks doesn't count the data one) + 'Start generating the reversal like 2 second lead-in part at the beginning of the CDDA audio soundtrack + Print "Adding '2 second lead-in' for Track: " + CDDA_TrackID + For Local i = 0 To 150 - 1 + Local Countdown:Int = 150 - i + Local CountdownMSF:Int[] = SectorToMSF(Countdown) + Local BinarySeconds:Byte = CountdownMSF[1] + Local BinaryFrames:Byte = CountdownMSF[2] + Local QSub:Byte[10] + + 'Change the QSUB header, XxxCD means countdown + '[01] [TrackID] [00] [MinCD] [SecCD] [FraCD] [00] [Minutes] [Seconds] [Frame] [CRC16]-[CRC16] + PokeByte(SUB, ReplaceOffset + (i * 96), 1) + PokeByte(SUB, ReplaceOffset + (i * 96) + 1, NumberToHexMSF(CDDA_TrackID)) 'Track + PokeByte(SUB, ReplaceOffset + (i * 96) + 2, 0) + PokeByte(SUB, ReplaceOffset + (i * 96) + 3, 0) 'MinCD, Eh just put zero, the leadin will never have minutes + PokeByte(SUB, ReplaceOffset + (i * 96) + 4, NumberToHexMSF(BinarySeconds)) 'SecCD, Seconds Countdown + PokeByte(SUB, ReplaceOffset + (i * 96) + 5, NumberToHexMSF(BinaryFrames)) 'FraCD, Frames Countdown + + 'Fill the QSUB array and CRC16 it + QSub[0] = PeekByte(SUB, ReplaceOffset + (i * 96)) + QSub[1] = PeekByte(SUB, ReplaceOffset + (i * 96) + 1) + QSub[2] = PeekByte(SUB, ReplaceOffset + (i * 96) + 2) + QSub[3] = PeekByte(SUB, ReplaceOffset + (i * 96) + 3) + QSub[4] = PeekByte(SUB, ReplaceOffset + (i * 96) + 4) + QSub[5] = PeekByte(SUB, ReplaceOffset + (i * 96) + 5) + QSub[6] = PeekByte(SUB, ReplaceOffset + (i * 96) + 6) + QSub[7] = PeekByte(SUB, ReplaceOffset + (i * 96) + 7) + QSub[8] = PeekByte(SUB, ReplaceOffset + (i * 96) + 8) + QSub[9] = PeekByte(SUB, ReplaceOffset + (i * 96) + 9) + + Local SUB_CRC16:Short = CRC16(QSub) + Local SUB_CRCA:Byte = SUB_CRC16 Shr 8 + Local SUB_CRCB:Byte = SUB_CRC16 - (SUB_CRCA Shl 8) + + PokeByte(SUB, ReplaceOffset + (i * 96) + 10, SUB_CRCA) + PokeByte(SUB, ReplaceOffset + (i * 96) + 11, SUB_CRCB) + Next + + 'Alright, lets add actual CD Audio track data to the subchanel + ReplaceOffset = ReplaceOffset + (150 * 96) 'Start on the part after the leadin + '[01] [TrackID] [01] [Minutes] [Seconds] [Frame] [00] [Minutes Index1/Unchanged] [Seconds Index1/Unchanged] [Frame Index1/Unchanged] [CRC16] + + Local TrackA:CUE + Local TrackB:CUE + Local QSub:Byte[10] + Print "Adding timecode data for Track: " + CDDA_TrackID + + 'Lets get the important tracks, so we can calculate the size after + For Local CueFile:CUE = EachIn CUE.List:TList + If(CueFile.TrackType = "AUDIO") + If(CueFile.Track = CDDA_TrackID:Int And CueFile.Index = 1) 'If it = CurrentTrack Index 1 + TrackA:CUE = CueFile:CUE + ElseIf(CueFile.Track = CDDA_TrackID:Int + 1 And CueFile.Index = 0) 'If it = the one after the Current Track at Index 0 + TrackB:CUE = CueFile:CUE + EndIf + EndIf + Next + + 'Check if we're on the last track, then we can get the sector difference + Local SectorSize:Int + If(CDDA_TrackID < TotalTracks + 1) + SectorSize:Int = TrackB.Sector - TrackA.Sector + Else + SectorSize:Int = (BankSize(SUB) / 96) - TrackA.Sector + EndIf + + For Local i = 0 To SectorSize - 1 + PokeByte(SUB, ReplaceOffset + (i * 96), 1) 'Just a 1 + PokeByte(SUB, ReplaceOffset + (i * 96) + 1, NumberToHexMSF(CDDA_TrackID)) 'Track ID + PokeByte(SUB, ReplaceOffset + (i * 96) + 2, 1) 'Looks like after the reverse lead-in thing, this is now always a 1 + + 'Alright lets write the MSF of the current sector + Local MSF[] = SectorToMSF(i) + 'Now write it! + PokeByte(SUB, ReplaceOffset + (i * 96) + 3, NumberToHexMSF(MSF[0])) 'Minutes + PokeByte(SUB, ReplaceOffset + (i * 96) + 4, NumberToHexMSF(MSF[1])) 'Seconds + PokeByte(SUB, ReplaceOffset + (i * 96) + 5, NumberToHexMSF(MSF[2])) 'Frames + + 'Fill the QSub array and CRC16 it + QSub[0] = PeekByte(SUB, ReplaceOffset + (i * 96)) + QSub[1] = PeekByte(SUB, ReplaceOffset + (i * 96) + 1) + QSub[2] = PeekByte(SUB, ReplaceOffset + (i * 96) + 2) + QSub[3] = PeekByte(SUB, ReplaceOffset + (i * 96) + 3) + QSub[4] = PeekByte(SUB, ReplaceOffset + (i * 96) + 4) + QSub[5] = PeekByte(SUB, ReplaceOffset + (i * 96) + 5) + QSub[6] = PeekByte(SUB, ReplaceOffset + (i * 96) + 6) + QSub[7] = PeekByte(SUB, ReplaceOffset + (i * 96) + 7) + QSub[8] = PeekByte(SUB, ReplaceOffset + (i * 96) + 8) + QSub[9] = PeekByte(SUB, ReplaceOffset + (i * 96) + 9) + + Local SUB_CRC16:Short = CRC16(QSub) + Local SUB_CRCA:Byte = SUB_CRC16 Shr 8 + Local SUB_CRCB:Byte = SUB_CRC16 - (SUB_CRCA Shl 8) + + PokeByte(SUB, ReplaceOffset + (i * 96) + 10, SUB_CRCA) + PokeByte(SUB, ReplaceOffset + (i * 96) + 11, SUB_CRCB) + Next + 'Move the ReplaceOffset position + ReplaceOffset = ReplaceOffset + (SectorSize * 96) + Next + Print "Finished adding CD Audio subchannel data!" + EndIf +End Function + +Function GenSub(FileName:String, Sectors:Int) + 'For some reason BMX can't start psxt001z directly + Print "Generating blank .SUB with psxt001z" + If(FileType("psxt001z.exe") <> 1) Then RuntimeError("psxt001z.exe is missing! Please download it from GitHub") + 'If the subchannel already exists, delete it. + 'Psxt001z doesn't override it + If(FileType(FileName:String)) Then DeleteFile(FileName:String) + Local psxtoolz:TProcess = CreateProcess:TProcess("cmd /c psxt001z --sub " + Chr(34) + FileName:String + Chr(34) + " " + Sectors:Int) + + While(ProcessStatus(psxtoolz:TProcess) = 1) + Delay 1000 + Wend +End Function + +'Cue Type +Type CUE + Global List:TList = CreateList() + Global BinaryFN:String + Global BinPath:String + Field Track:Int + Field TrackType:String + Field Index:Int + Field MSF:Int[3] + Field Sector:Int + + 'Add a track index + Function AddListing(Track:Int, TrackType:String, Index:Int, Minutes:Int, Seconds:Int, Frames:Int) + Local CUETrack:CUE = New CUE + CUETrack.Track = Track + CUETrack.TrackType = TrackType + CUETrack.Index = Index + CUETrack.MSF[0] = Minutes + CUETrack.MSF[1] = Seconds + CUETrack.MSF[2] = Frames + CUETrack.Sector = MSFToSector(Minutes, Seconds, Frames) + ListAddLast(List:TList, CUETrack) + EndFunction + Function AddCue(CuePath:String) + 'Open the file + Local CueFile:TStream = ReadFile(CuePath:String) + 'Did it load? + If(CueFile) + Local CurrentTrack:Int = 0 + Local CurrentTrackType:String + + 'Barry: What? What is this...? + 'Jill: What is it? + 'Barry: A CUE! + 'Barry: Jill, see if you can't find any other CUES, I'll be examining this... + 'Barry: I hope this is not a BIN file! + If(ReadByte(CueFile) = 0) + RuntimeError("This doesn't seem to be a cue file!") + Else + SeekStream(CueFile, 0) 'Go back to the start. + EndIf + + While Not(Eof(CueFile)) + Local Line:String = Upper(ReadLine(CueFile)) + + 'Remove any spaces at the beginning + While(Line.StartsWith(" ")) + Line = Right(Line, Len(Line) - 1) + Wend + + 'This is the header + If(Line.StartsWith("FILE")) + 'Check for Multi BIN, cuesheets + If(BinaryFN = Null) + Local Split:String[] = Line.Split(Chr(34)) 'Split the quotes + BinaryFN = Split[1] 'Set the filename the CUE links to in our global + Else + RuntimeError("SBITools currently doesn't support multi track .BIN files") 'TODO + EndIf + ElseIf(Line.StartsWith("TRACK")) + Local Split:String[] = Line.Replace(" ", " ").Split(" ") 'Split the spaces, get rid of most duplicate spaces if there are any + 'Set the current track and track type for when the next loop happens + CurrentTrack = Int(Split[1]) + CurrentTrackType = Split[2] + 'AddListing + ElseIf(Line.StartsWith("INDEX")) + Local Split:String[] = Line.Split(" ") 'Split the spaces + Local MSF:String[] = Split[2].Split(":") 'Split the colons from the MSF XX:XX:XX + AddListing(CurrentTrack, CurrentTrackType, Int(Split[1]), Int(MSF[0]), Int(MSF[1]), Int(MSF[2])) + EndIf + Wend + 'Done using this file + CloseFile(CueFile) + Else + RuntimeError(".CUE file doesn't exist!") + EndIf + End Function + 'Counts all the tracks, excluding the data track. + Function CountCDDA:Int() + Local TrackCount:Int = 0 + For Local CueFile:CUE = EachIn CUE.List:TList + If(CueFile.Index = 0) + TrackCount:Int = TrackCount:Int + 1 + EndIf + Next + Return TrackCount:Int + End Function + Function EditBinPath(CuePath:String, NewBinPath:String, ExportPath:String) + Local NewCueFile:TStream = WriteFile(ExportPath:String) + Local CueFile:TStream = ReadFile(CuePath:String) + 'Did it work? + If(NewCueFile) + If(CueFile) + While Not(Eof(CueFile)) + Local Line:String = Upper(ReadLine(CueFile)) + 'Is it the FILE header? + If(Line.StartsWith("FILE")) + 'Change the path + WriteLine(NewCueFile:TStream, "FILE " + Chr(34) + NewBinPath:String + Chr(34) + " BINARY") + Else 'Otherwise just write what was on the old one + WriteLine(NewCueFile:TStream, Line:String) + EndIf + Wend + 'Free them from memory + CloseFile(CueFile) + CloseFile(NewCueFile) + Else + RuntimeError("Couldn't find .CUE file") + EndIf + Else + RuntimeError("Error creating new .CUE file") + EndIf + End Function + + 'Lets get the path to the .BIN file, or .IMG, whatever using the CUE's BaseName + FDRPath array, or by any means possible. + Function GetBinPath:String(FDRPath:String[]) + 'This just gets the name of the cue. Say if it was "C:\CoolGame.cue", this would return "CoolGame" + Local BaseName:String = Left(FDRPath[Len(FDRPath) - 1], Len(FDRPath[Len(FDRPath) - 1]) - 4) + + 'Sometimes in rare cases .CUE files contain the full path to the file, or maybe it could reside next to this very SBITools, lets check for that first + 'Therefore does the Binary file exist with the exact path written in the cue? - Most likely not but lets check anyway + If(FileType(CUE.BinaryFN) = 1) + CUE.BinPath:String = CUE.BinaryFN + Else 'Nope it didn't, lets search it the proper way + For Local i = 0 To Len(FDRPath:String[]) - 2 'Take 1 so it doesn't overflow, take another to remove the CUE filename for this string. + CUE.BinPath:String = CUE.BinPath:String + FDRPath:String[i] + "\" + Next + EndIf + 'Still couldn't find it after all that subfolder searching? + If(FileType(BinPath + CUE.BinaryFN) <> 1) + 'Then maybe the user renamed both files without editing inside the .CUE + 'Lets try looking for it with the BaseName variable (what the .cue is called before the extension) + If(FileType(BinPath + BaseName + ".bin") <> 1) 'Does it exist as a bin? + CUE.BinaryFN = BinPath + BaseName + ".bin" + ElseIf(FileType(BinPath + BaseName + ".img") <> 1) 'Or maybe as a img even? + CUE.BinaryFN = BinPath + BaseName + ".img" + Else + RuntimeError("Can't find the .BIN/.IMG image file anywhere!") + EndIf + EndIf + Return BaseName 'Return the BaseName because it's very useful + EndFunction + + 'Resets everything + Function Clean() + BinaryFN:String = Null + BinPath:String = Null + ClearList(List:TList) + End Function +EndType diff --git a/sbitools.bmx b/sbitools.bmx index 9f52b22..a42865b 100644 --- a/sbitools.bmx +++ b/sbitools.bmx @@ -1,197 +1,334 @@ 'Includes only what it needs to. (Smaller executable) Framework brl.FileSystem Import BRL.Retro -Import PUB.FreeProcess - -'PS1 Discs are stored in MODE2, meaning they have 2352 bytes per sector. -Function GetSectorsBySize(Size:Int) - Return Size / 2352 -End Function - -'Minutes, Seconds, Frames to Sector number -Function MSFToSector:Int(Minutes:Int, Seconds:Int, Frames:Int) - '75 frames in a second for CD Audio, for some reason. - Local Calc:Int = Minutes * 60 * 75 + Seconds:Int * 75 + Frames:Int - Return Calc -End Function - -Function SectorToMSF:Int[] (Sector:Int) - Local MSF:Int[3] - Local Minutes:Int = (Sector / 60 / 75) - Local Seconds:Int = ((Sector - Minutes * 60 * 75) / 75) - Local Frames:Int = (Sector - Minutes * 60 * 75 - Seconds * 75) - MSF:Int[0] = Minutes:Int - MSF:Int[1] = Seconds:Int - MSF:Int[2] = Frames:Int - Return MSF -End Function - -'Converts a Sector to a byte offset for a .SUB file -Function SectorToSUBOffset:Int(Sector:Int) - '.SUB files have 96 bytes in each sector, with a 12 byte FF'd header at the beginning. - 'Subtracting 150 is for the 2 second lead-in they have, for whatever reason. - 'http://forum.imgburn.com/index.php?/topic/2122-how-to-convert-mmssff-to-bytes/&do=findComment&comment=25842 - Return (Sector - 150) * 96 + 12 -End Function - -'Patches a .SUB file with a SBI -Function SBIToSub(SBIPath:String, SUB:TBank) - Local SBIFile:TStream = ReadFile(SBIPath:String) - - 'Did the SUB file load? - If(SUB:TBank) - '4801107 = [SUB ] header - If(SBIFile:TStream And ReadInt(SBIFile) = 4801107) - While Not Eof(SBIFile:TStream) - Local SBI_Minutes:Int - Local SBI_Seconds:Int - Local SBI_Frames:Int - - 'Next 3 bytes are the MSF. For some reason the true number is stored in raw HEX. - 'Thankfully BMX can convert from HEX strings to numbers easily. - SBI_Minutes:Int = Int(Hex(ReadByte(SBIFile:TStream))) - SBI_Seconds:Int = Int(Hex(ReadByte(SBIFile:TStream))) - SBI_Frames:Int = Int(Hex(ReadByte(SBIFile:TStream))) - - 'Convert that MSF to a Sector - Local SectorMSF:Int = MSFToSector(SBI_Minutes:Int, SBI_Seconds:Int, SBI_Frames:Int) - 'Get the offset that the SBI will replace in the SUB file - Local ReplaceOffset:Int = SectorToSUBOffset(SectorMSF) - - 'Dummy byte, it will always be 01 (maybe, I was reading the psxt001z SRC) - 'If it's not, it must of misaligned somehow - If(ReadByte(SBIFile:TStream) <> 1) - RuntimeError("Odd contents in SBI file!") - endif - - 'The next 10 bytes are the replacement bytes - For Local i = 0 To 10 - 1 - 'Get the byte, and replace the destination part in the .SUB with the offsets above. - Local Replacement:Byte = ReadByte(SBIFile:TStream) - PokeByte(SUB:TBank, ReplaceOffset:Int + i, Replacement:Byte) - Next - Print "Replaced 10 byte subchannel at MSF: " + SBI_Minutes:Int + ":" + SBI_Seconds:Int + ":" + SBI_Frames:Int + " (sector: " + SectorMSF:Int + ") at offset: " + Hex(ReplaceOffset:Int) - Wend - Print "Finished!" - Delay 2000 - Else - RuntimeError("Not a valid SBI file.") - EndIf - Else - RuntimeError("Not a valid SUB file.") - EndIf -End Function - -'Patches a .SUB file with a LSD -Function LSDToSub:TBank(LSDPath:String, SUB:TBank) - Local LSDFile:TStream = ReadFile(LSDPath:String) - - 'Did the SUB file load? - If(SUB:TBank) - '4801107 = [SUB ] header - If(LSDFile:TStream) - While Not Eof(LSDFile:TStream) - Local LSD_Minutes:Int - Local LSD_Seconds:Int - Local LSD_Frames:Int - - 'Next 3 bytes are the MSF. For some reason the true number is stored in raw HEX. - 'Thankfully BMX can convert from HEX strings to numbers easily. - LSD_Minutes:Int = Int(Hex(ReadByte(LSDFile:TStream))) - LSD_Seconds:Int = Int(Hex(ReadByte(LSDFile:TStream))) - LSD_Frames:Int = Int(Hex(ReadByte(LSDFile:TStream))) - - 'Convert that MSF to a Sector - Local SectorMSF:Int = MSFToSector(LSD_Minutes:Int, LSD_Seconds:Int, LSD_Frames:Int) - 'Get the offset that the LSD will replace in the SUB file - Local ReplaceOffset:Int = SectorToSUBOffset(SectorMSF) - - 'The next 10 bytes are the replacement bytes - For Local i = 0 To 12 - 1 - 'Get the byte, and replace the destination part in the .SUB with the offsets above. - Local Replacement:Byte = ReadByte(LSDFile:TStream) - PokeByte(SUB:TBank, ReplaceOffset:Int + i, Replacement:Byte) - Next - Print "Replaced 12 byte subchannel at MSF: " + LSD_Minutes:Int + ":" + LSD_Seconds:Int + ":" + LSD_Frames:Int + " (sector: " + SectorMSF:Int + ") at offset: " + Hex(ReplaceOffset:Int) - Wend - Print "Finished!" - Delay 2000 - Else - RuntimeError("Not a valid LSD file.") - EndIf - Else - RuntimeError("Not a valid SUB file.") - EndIf -End Function - -Function GenSub(FileName:String, Sectors:Int) - 'For some reason BMX can't start psxt001z directly - Print "Generating blank .SUB with psxt001z" - Local psxtoolz:TProcess = CreateProcess:TProcess("cmd /c psxt001z --sub " + FileName:String + " " + Sectors:Int) - While(ProcessStatus(psxtoolz:TProcess) = 1) - Delay 1000 - Wend -End Function +Import pub.FreeProcess +Include "CRC16.bmx" +Include "functions.bmx" 'Main Program -Print "SBITools v0.1 - http://kippykip.com" +Print "SBITools v0.2 - http://kippykip.com" Global Prog:Int = 0 +'Check for psxt001z.exe +If(FileType("psxt001z.exe") <> 1) Then RuntimeError("psxt001z.exe is missing! Please download it from GitHub") + 'Are there arguments? If(Len(AppArgs) > 1) - If(AppArgs[1] = "-sbi" And Len(AppArgs) >= 5) + If(Lower(AppArgs[1]) = "-sbi" And Len(AppArgs) >= 4) Prog:Int = 1 - ElseIf(AppArgs[1] = "-lsd" And Len(AppArgs) >= 5) + ElseIf(Lower(AppArgs[1]) = "-lsd" And Len(AppArgs) >= 4) Prog:Int = 2 + ElseIf(Lower(AppArgs[1]) = "-cue2ccd" And Len(AppArgs) >= 3) + Prog:Int = 3 EndIf EndIf Select Prog Case 1 'SBI to SUB - 'Safeguards - If(FileType(AppArgs[2]) <> 1) Then RuntimeError("Image doesn't exist!") + 'Safeguard If(FileType(AppArgs[3]) <> 1) Then RuntimeError("SBI subchannel doesn't exist!") - If(FileType(AppArgs[4])) Then DeleteFile(AppArgs[4]) - If(FileType("psxt001z.exe") <> 1) Then RuntimeError("psxt001z.exe is missing! Please download it from GitHub") + 'Add the cue file + Local CuePath:String = AppArgs[2].Replace("/", "\") + If(FileType(CuePath:String) <> 1) Then RuntimeError("Missing CUE file!") + CUE.AddCue(CuePath:String) + Local FDRPath:String[] = CuePath.Split("\") + Local BaseName:String = CUE.GetBinPath(FDRPath) 'This gives us the BaseName (filename before .cue) and also adds the binary locaton to CUE.BinaryFN + + 'Lets make an export folder + Print "Exporting to: 'SUB\" + BaseName:String + "'\." + If(FileType("SUB") <> 2) 'Does a CCD folder exist? + CreateDir("SUB") + Print "Directory 'SUB' doesn't exist! Creating..." + EndIf + If(FileType("SUB\" + BaseName:String) <> 2) 'Does BaseName folder exist? + CreateDir("SUB\" + BaseName:String) + Print "Directory 'SUB\" + BaseName:String + "' doesn't exist! Creating..." + EndIf + 'Get the sector count - Local Sectors:Int = GetSectorsBySize(Int(FileSize(AppArgs[2]))) + Local Sectors:Int = GetSectorsBySize(Int(FileSize(CUE.BinPath + CUE.BinaryFN))) Print "Image contains " + Sectors:Int + " sectors..." 'Launch psxt001z with the following arguments - GenSub(AppArgs[4], Sectors:Int) + GenSub("SUB\" + BaseName:String + "\" + BaseName + ".sub", Sectors:Int) + 'Load it - Local Subchannel:TBank = LoadBank(AppArgs[4]) + Local Subchannel:TBank = LoadBank("SUB\" + BaseName:String + "\" + BaseName + ".sub") If Not(Subchannel) Then RuntimeError("Error reading created .SUB subchannel...") 'Launch the conversion function SBIToSub(AppArgs[3], Subchannel) - SaveBank(Subchannel, AppArgs[4]) + SaveBank(Subchannel, "SUB\" + BaseName:String + "\" + BaseName + ".sub") 'Save the modified SUB 'Hopefully everything worked + Delay 2000 Case 2 'LSD to SUB - 'Safeguards - If(FileType(AppArgs[2]) <> 1) Then RuntimeError("Image doesn't exist!") + 'Safeguard If(FileType(AppArgs[3]) <> 1) Then RuntimeError("LSD subchannel doesn't exist!") - If(FileType(AppArgs[4])) Then DeleteFile(AppArgs[4]) - If(FileType("psxt001z.exe") <> 1) Then RuntimeError("psxt001z.exe is missing! Please download it from GitHub") + 'Add the cue file + Local CuePath:String = AppArgs[2].Replace("/", "\") + If(FileType(CuePath:String) <> 1) Then RuntimeError("Missing CUE file!") + CUE.AddCue(CuePath:String) + Local FDRPath:String[] = CuePath.Split("\") + Local BaseName:String = CUE.GetBinPath(FDRPath) 'This gives us the BaseName (filename before .cue) and also adds the binary locaton to CUE.BinaryFN + + 'Lets make an export folder + Print "Exporting to: 'SUB\" + BaseName:String + "'\." + If(FileType("SUB") <> 2) 'Does a CCD folder exist? + CreateDir("SUB") + Print "Directory 'SUB' doesn't exist! Creating..." + EndIf + If(FileType("SUB\" + BaseName:String) <> 2) 'Does BaseName folder exist? + CreateDir("SUB\" + BaseName:String) + Print "Directory 'SUB\" + BaseName:String + "' doesn't exist! Creating..." + EndIf + 'Get the sector count - Local Sectors:Int = GetSectorsBySize(Int(FileSize(AppArgs[2]))) + Local Sectors:Int = GetSectorsBySize(Int(FileSize(CUE.BinPath + CUE.BinaryFN))) Print "Image contains " + Sectors:Int + " sectors..." 'Launch psxt001z with the following arguments - GenSub(AppArgs[4], Sectors:Int) + GenSub("SUB\" + BaseName:String + "\" + BaseName + ".sub", Sectors:Int) 'Load it - Local Subchannel:TBank = LoadBank(AppArgs[4]) + Local Subchannel:TBank = LoadBank("SUB\" + BaseName:String + "\" + BaseName + ".sub") If Not(Subchannel) Then RuntimeError("Error reading created .SUB subchannel...") 'Launch the conversion function LSDToSub(AppArgs[3], Subchannel) - SaveBank(Subchannel, AppArgs[4]) + SaveBank(Subchannel, "SUB\" + BaseName:String + "\" + BaseName + ".sub") 'Save the modified SUB 'Hopefully everything worked + Delay 2000 + Case 3 'CCD Generator + 'This was pretty handy + 'https://books.google.com.au/books?id=76HVAwAAQBAJ&pg=PA233&lpg=PA233&dq=entry+1+clonecd&source=bl&ots=XEUM8fr_Sp&sig=GxJHxBp5AEFxbVgNMiGt69lB8Ds&hl=en&sa=X&ved=2ahUKEwjU5rqs_ezdAhVNITQIHTtxCyYQ6AEwA3oECAYQAQ#v=onepage&q=entry%201%20clonecd&f=false + + 'Add the cue file + Local CuePath:String = AppArgs[2].Replace("/", "\") + If(FileType(CuePath:String) <> 1) Then RuntimeError("Missing CUE file!") + CUE.AddCue(CuePath:String) + Local FDRPath:String[] = CuePath.Split("\") + Local BaseName:String = CUE.GetBinPath(FDRPath) 'This gives us the BaseName (filename before .cue) and also adds the binary locaton to CUE.BinaryFN + + 'Useful CUE information we'll use below + Local TrackCount:Int = CUE.CountCDDA() + Local TotalSectors:Int = GetSectorsBySize(Int(FileSize(CUE.BinPath:String + CUE.BinaryFN))) + 'Local TotalMSF:Int[] = SectorToMSF(TotalSectors:Int) 'Not used right now + Local TotalMSFLeadin:Int[] = SectorToMSF(TotalSectors:Int + 150) + + 'Lets make an export folder + Print "Exporting to: 'CCD\" + BaseName:String + "'\." + If(FileType("CCD") <> 2) 'Does a CCD folder exist? + CreateDir("CCD") + Print "Directory 'CCD' doesn't exist! Creating..." + EndIf + If(FileType("CCD\" + BaseName:String) <> 2) 'Does the basename folder exist? + CreateDir("CCD\" + BaseName:String) + Print "Directory 'CCD\" + BaseName:String + "' doesn't exist! Creating..." + EndIf + + Print "Creating CCD file" + Local CCDFile:TStream = WriteFile("CCD\" + BaseName:String + "\" + BaseName:String + ".ccd") + + '0x04, otherwise if there are music tracks it's 0x00 + Local Control:String = "Control=0x04" + If(TrackCount:Int > 0) + Control:String = "Control=0x00" + EndIf + + 'Here we go! Header + Print "Writing CCD headers" + WriteLine(CCDFile, "[CloneCD]") + WriteLine(CCDFile, "Version=3") + WriteLine(CCDFile, "[Disc]") + WriteLine(CCDFile, "TocEntries=" + (4 + TrackCount:Int)) 'Toc = 4 always, if the image has CD tracks then it 4 + MusicTrackCount, not including the data track I guess. + WriteLine(CCDFile, "Sessions=1") + WriteLine(CCDFile, "DataTracksScrambled=0") + WriteLine(CCDFile, "CDTextLength=0") + WriteLine(CCDFile, "[Session 1]") + WriteLine(CCDFile, "PreGapMode=2") 'PS1 games are MODE2 + WriteLine(CCDFile, "PreGapSubC=1") 'Hell yeah we want a subchannel + + 'Entry 0, Always the same for PS1 dumps I believe + Print "Writing CCD Entries" + WriteLine(CCDFile, "[Entry 0]") + WriteLine(CCDFile, "Session=1") + WriteLine(CCDFile, "Point=0xa0") + WriteLine(CCDFile, "ADR=0x01") + WriteLine(CCDFile, "Control=0x04") + WriteLine(CCDFile, "TrackNo=0") + WriteLine(CCDFile, "AMin=0") + WriteLine(CCDFile, "ASec=0") + WriteLine(CCDFile, "AFrame=0") + WriteLine(CCDFile, "ALBA=-150") + WriteLine(CCDFile, "Zero=0") + WriteLine(CCDFile, "PMin=1") + WriteLine(CCDFile, "PSec=32") + WriteLine(CCDFile, "PFrame=0") + WriteLine(CCDFile, "PLBA=6750") + + 'Entry 1 + WriteLine(CCDFile, "[Entry 1]") + WriteLine(CCDFile, "Session=1") + WriteLine(CCDFile, "Point=0xa1") + WriteLine(CCDFile, "ADR=0x01") + 'Control var is used here + WriteLine(CCDFile, Control:String) + WriteLine(CCDFile, "TrackNo=0") + WriteLine(CCDFile, "AMin=0") + WriteLine(CCDFile, "ASec=0") + WriteLine(CCDFile, "AFrame=0") + WriteLine(CCDFile, "ALBA=-150") + WriteLine(CCDFile, "Zero=0") + WriteLine(CCDFile, "PMin=" + (TrackCount:Int + 1)) 'Number of total tracks, why this is stored in PMin I have no idea. It was listed in a Google book thing. + WriteLine(CCDFile, "PSec=0") 'CDDA always 0 + WriteLine(CCDFile, "PFrame=0") 'CDDA always 0 + WriteLine(CCDFile, "PLBA=" + (MSFToSector(TrackCount:Int + 1, 0, 0) - 150)) 'MSF of above, but take -150 (ALBA) + + 'Entry 2 + WriteLine(CCDFile, "[Entry 2]") + WriteLine(CCDFile, "Session=1") + WriteLine(CCDFile, "Point=0xa2") + WriteLine(CCDFile, "ADR=0x01") + 'Control var is used here too + WriteLine(CCDFile, Control) + WriteLine(CCDFile, "TrackNo=0") + WriteLine(CCDFile, "AMin=0") + WriteLine(CCDFile, "ASec=0") + WriteLine(CCDFile, "AFrame=0") + WriteLine(CCDFile, "ALBA=-150") + WriteLine(CCDFile, "Zero=0") + WriteLine(CCDFile, "PMin=" + TotalMSFLeadin[0]) 'Minutes of the PLBA underneath, although 2 second leadin + WriteLine(CCDFile, "PSec=" + TotalMSFLeadin[1]) 'Seconds of the PLBA underneath, although 2 second leadin + WriteLine(CCDFile, "PFrame=" + TotalMSFLeadin[2]) 'Frames of the PLBA underneath, although 2 second leadin + WriteLine(CCDFile, "PLBA=" + TotalSectors) 'It's just the total Sector count + + 'Always the same + WriteLine(CCDFile, "[Entry 3]") + WriteLine(CCDFile, "Session=1") + WriteLine(CCDFile, "Point=0x01") + WriteLine(CCDFile, "ADR=0x01") + WriteLine(CCDFile, "Control=0x04") + WriteLine(CCDFile, "TrackNo=0") + WriteLine(CCDFile, "AMin=0") + WriteLine(CCDFile, "ASec=0") + WriteLine(CCDFile, "AFrame=0") + WriteLine(CCDFile, "ALBA=-150") + WriteLine(CCDFile, "Zero=0") + WriteLine(CCDFile, "PMin=0") + WriteLine(CCDFile, "PSec=2") + WriteLine(CCDFile, "PFrame=0") + WriteLine(CCDFile, "PLBA=0") + + 'Loop through all the audio tracks + Local CurrentEntry:Int = 4 + For Local CueFile:CUE = EachIn CUE.List:TList + 'Is current track in the loop an AUDIO type with index 1? + If(CueFile.TrackType = "AUDIO" And CueFile.Index = 1) + WriteLine(CCDFile, "[Entry " + CurrentEntry:Int + "]") + WriteLine(CCDFile, "Session=1") + 'Increases everytime, starts off at 2. It's a byte in HEX. Lowercase + 'BMX's HEX function makes a 8 character string, so we have to cut it + WriteLine(CCDFile, "Point=0x" + Lower(Right(Hex(CurrentEntry:Int - 2), 2))) + WriteLine(CCDFile, "ADR=0x01") + WriteLine(CCDFile, "Control=0x00") + WriteLine(CCDFile, "TrackNo=0") + WriteLine(CCDFile, "AMin=0") + WriteLine(CCDFile, "ASec=0") + WriteLine(CCDFile, "AFrame=0") + WriteLine(CCDFile, "ALBA=-150") + WriteLine(CCDFile, "Zero=0") + + 'MSF for this is just PLBA conversion + 150 (2 second leadin once again) + Local EntryMSF[] = SectorToMSF(CueFile.Sector + 150) + + WriteLine(CCDFile, "PMin=" + EntryMSF[0]) + WriteLine(CCDFile, "PSec=" + EntryMSF[1]) 'PLBA conversion + 150 + WriteLine(CCDFile, "PFrame=" + EntryMSF[2]) 'PLBA conversion + 150 + WriteLine(CCDFile, "PLBA=" + CueFile.Sector) 'Seems to just be the sector count for TRACK AUDIO, INDEX 1 + CurrentEntry = CurrentEntry + 1 'Increase the Entry count + EndIf + Next + + 'Basically just reading the cuefile as sectors instead of MSF at this point + Print "Writing TRACK info" + Local CurrentTrack:Int = 0 + 'Loop through all the listings again + For Local CueFile:CUE = EachIn CUE.List:TList + 'Does this current loop have a different track number? + If(CueFile.Track <> CurrentTrack) + CurrentTrack = CueFile.Track + WriteLine(CCDFile, "[TRACK " + CurrentTrack:Int + "]") + 'Is this track an Audio track? + If(CueFile.TrackType = "AUDIO") + WriteLine(CCDFile, "MODE=0") + Else + 'Then it's probably the data track + WriteLine(CCDFile, "MODE=2") + EndIf + EndIf + WriteLine(CCDFile, "INDEX " + CueFile.Index + "=" + CueFile.Sector) + Next + CloseFile(CCDFile) + Print "Done writing CCD!" + + 'Alright, now lets make a modified cue + Print "Creating modified CUE" + CUE.EditBinPath(CuePath, BaseName:String + ".img", "CCD\" + BaseName:String + "\" + BaseName:String + ".cue") + Print "Done writing CUE!" + + 'Copy the image under the new name + Print "Copying image (This will take a moment)" + CopyFile(CUE.BinPath:String + CUE.BinaryFN, "CCD\" + BaseName:String + "\" + BaseName:String + ".img") + + 'Time to run psxt001z + GenSub("CCD\" + BaseName:String + "\" + BaseName:String + ".sub", TotalSectors:Int) + + 'LibCrypt + CDDA audio Patching + 'SBI file found + If(FileType(CUE.BinPath + BaseName + ".sbi")) 'SBI file found + Print "LibCrypt patch '" + BaseName + ".sbi' was found! Patching subchannel..." + 'Load the subchannel + Local Subchannel:TBank = LoadBank("CCD\" + BaseName + "\" + BaseName + ".sub") + If Not(Subchannel) Then RuntimeError("Error loading subchannel!") 'Did it load? + SBIToSub(CUE.BinPath + BaseName + ".sbi", Subchannel) 'Run the patching function + SaveBank(Subchannel, "CCD\" + BaseName + "\" + BaseName:String + ".sub") 'Save the modified SUB + 'LSD file found + ElseIf(FileType(CUE.BinPath + BaseName + ".lsd")) + Print "LibCrypt patch '" + BaseName + ".lsd' was found! Patching subchannel..." + 'Load the subchannel + Local Subchannel:TBank = LoadBank("CCD\" + BaseName + "\" + BaseName + ".sub") + If Not(Subchannel) Then RuntimeError("Error loading subchannel!") 'Did it load? + LSDToSub(CUE.BinPath + BaseName:String + ".lsd", Subchannel) 'Run the patching function + SaveBank(Subchannel, "CCD\" + BaseName + "\" + BaseName + ".sub") 'Save the modified SUB + Else 'No patches found... + Print "LibCrypt .SBI/.LSD patches not found in CUE directory! Ignoring patching..." + + 'Load the subchannel + Local Subchannel:TBank = LoadBank("CCD\" + BaseName + "\" + BaseName + ".sub") + If Not(Subchannel) Then RuntimeError("Error loading subchannel!") 'Did it load? + GenSubCDDA(Subchannel) 'Run the SUB audio tracks function + SaveBank(Subchannel, "CCD\" + BaseName:String + "\" + BaseName + ".sub") 'Save the modified SUB + EndIf + + + Print "Done converting!" + Print "Everything exported to: '" + "CCD\" + BaseName + "\'" + Delay 2000 Default 'No arguments Print "Missing command line!" Print "" - Print "SBITools.exe -sbi image.(bin/img) subchannel.sbi subchannel.sub" - Print "SBITools.exe -lsd image.(bin/img) subchannel.lsd subchannel.sub" + Print "SBITools.exe -sbi cuefile subchannel.sbi" + Print "SBITools.exe -lsd cuefile subchannel.lsd" + Print "SBITools.exe -cue2ccd cuefile" + Print "" + Print "Definitions:" + Print "-sbi: Patches an images subchannel with a .SBI file." + Print "-lsd: Patches an images subchannel with a .LSD file." + Print "-cue2ccd: Converts a 'BIN/CUE/SBI|LSD' setup into a 'IMG/CCD/CUE/SUB' setup." + Print " This makes burning LibCrypt games easily possible with software such" + Print " as CloneCD" + Print "" + Print "Examples:" + Print "SBITools.exe -sbi " + Chr(34) + "C:\CoolGameRips\VRally2.CUE" + Chr(34) + " " + Chr(34) + "C:\CoolGameRips\sbipatches\V-Rally - Championship Edition 2 (Europe) (En,Fr,De).sbi" + Chr(34) + Print "" + Print "SBITools.exe -lsd " + Chr(34) + "C:\CoolGameRips\MediEvil.CUE" + Chr(34) + " " + Chr(34) + "C:\CoolGameRips\sbipatches\MediEvil (Europe).lsd" + Chr(34) Print "" - Print "Example:" - 'Chr:String(34) is a double quote. - Print "SBITools.exe -sbi MediEvil.bin " + Chr:String(34) + "MediEvil (Europe).sbi" + Chr:String(34) + " MediEvil.sub" + Print "SBITools.exe -cue2ccd " + Chr(34) + "C:\CoolGameRips\MediEvil.CUE" + Chr(34) Delay 1000 End Select