Just a little heads-up to let you know that after checking the Sega Puzzle Pack roms (Dr. Robotnik's MBN and Columns III) I can reveal....... zero changes as usual. So, for reference... GoodGen Code (Text): Columns III - Revenge of Columns (U) [!] Dr. Robotnik's Mean Bean Machine (U) [!] No-Intro Code (Text): Columns III - Revenge of Columns (USA) Dr. Robotnik's Mean Bean Machine (USA)
I guess it hasn't quite got the attention of YouTubers yet..... ....is there any particular reason why you can't just run the rom through an emulator and see for yourself? (Presumably you're at work or something...)
Sorry for the bump but I wanted to know if anyone still had these dumps lying around since all the old links are down and I lost most of the data on the PC they were on. If anyone still has them and would upload them, that would be great. Thanks.
Huge bump, hopefully not frowned upon. This thread shows up fairly high in search results related to the smash pack roms, so it seems appropriate to put this here. This python 3 code can decode the roms from the .kvq files in the pc smash packs (stored in the MyGames directory of the install). The packs use the same algorithm, only a string used for the encoding differs. Tool Usage: .\smashpackdecode.py -1 '.\Sonic Spinball.kvq' sonicSpin.bin .\smashpackdecode.py -2 '.\Sonic II.kvq' sonic2.bin Spoiler: pythonDecoder Code (Text): import sys kvqFile = bytearray(open(sys.argv[2], 'rb').read()) romSize = len(kvqFile)-8 decodedRom = bytearray(romSize) encodeStringPack1 = bytearray('Encoded for KGen Ultra / Sega Smash Pack / Snake KML 1999! ', 'ascii') encodeStringPack2 = bytearray('Encoded for KGen Ultra / Sega Smash Pack II / Snake KML 1999! ', 'ascii') encodeString = '' if sys.argv[1] == '-1': encodeString = encodeStringPack1 else: encodeString = encodeStringPack2 encodeStringOffset = 0 scramble = 6 for index in range(romSize): encodedByte = kvqFile[8+index] encodeStringCharacter = encodeString[encodeStringOffset] decodedByte = ((encodedByte ^ encodeStringCharacter ^ 0x80) - scramble) & 0xFF decodedRom[index] = decodedByte scramble += 3 if encodeStringOffset < len(encodeString) - 1: encodeStringOffset += 1 else: encodeStringOffset = 0 open(sys.argv[3], 'wb').write(decodedRom) Smash.exe sha1 061B94DE3E38B62D1063764051E652A16AF30932 Smash2.exe sha1 D47EB94F98F745AC4C3D30C6EDC2467937765D27 The encode string locations are: 0x7F59C in smash.exe 0x8C5D0 in smash2.exe The decoding loop is exactly the same in both smash packs. ebp contains the current memory address. esi contains the bytes remaining. The local esp+0xd4 contains the decode string. 0x00403b4c - 0x00403b9a in smash.exe for smash pack 1 0x00403b5f - 0x00403bad in smash2.exe for smash pack 2 Spoiler: Roms in the collections according to No Intro (after trimming) Altered Beast (USA, Europe) Columns (USA, Europe) Golden Axe (World) (Rev B) (Sega Smash Pack) OutRun (USA, Europe) (Sega Smash Pack) Phantasy Star II (USA, Europe) (Rev A) Sonic Spinball (USA) Super Shinobi, The (Japan) (En) (Beta) (1989-xx-xx) (Sega Smash Pack) Vectorman (USA, Europe) (Sega Smash Pack) Comix Zone (USA) (Virtual Console) Flicky (USA, Europe) Kid Chameleon (USA, Europe) Shining Force (USA) Sonic The Hedgehog 2 (World) (Rev A) Super Hang-On (World) (Sega Smash Pack) Vectorman 2 (USA) Phantasy Star II has 0x40000 bytes extra data at the end of the ROM (the extra data 0xC0000-0xFFFFF is a duplication of 0x80000-0xBFFFF) Flicky has a second copy of all the data at the end of the ROM Shining Force has 0x80000 of 0xFF bytes at the end of the ROM As noted earlier in the thread, Vectorman is Vectorman (USA, Europe) except at 0x1C8, where A3 95 97 91 7D 93 98 91 9E 9E 95 9C is randomly present instead of 20 20 20 20 20 20 20 20 20 20 20 20. Comix Zone is strange. It is altered by Smash Pack II after decode, before running in the emulator. If you break right after the decode loop, the RAM content matches what you get from my decoder. When the game is running though, two bytes have been changed at 0x1E5C54. 06 45 is changed to 60 5C. This patching is why the checksum is wrong for the cut from RAM version, and fusion complains, as noted earlier in the thread. Switching those bytes, No Intro identifies the ROM as Comix Zone (USA) (Sega Smash Pack). Perhaps working around some emulator bug? Haven't looked into it.
The 8 bytes at the start of the file are a sort of checksum as one would guess. Updated script tool can encode roms now. Usage: encode with pack 2 encoding .\smashpackkvqutil.py -e -2 '.\Ristar (UE) (Aug 1994) [!].bin' 'Shining Force.kvq' decode with pack 1 encoding .\smashpackkvqutil.py -d -1 '.\Phantasy Star II.kvq' ps2.bin Spoiler: EncoderDecoder Code (Text): import sys import struct inputFile = bytearray(open(sys.argv[3], 'rb').read()) outFile = None encodeStringPack1 = bytearray('Encoded for KGen Ultra / Sega Smash Pack / Snake KML 1999! ', 'ascii') encodeStringPack2 = bytearray('Encoded for KGen Ultra / Sega Smash Pack II / Snake KML 1999! ', 'ascii') encodeString = None if sys.argv[2] == '-1': encodeString = encodeStringPack1 else: encodeString = encodeStringPack2 encodeStringOffset = 0 scramble = 6 if sys.argv[1] == '-d': romSize = len(inputFile)-8 outFile = bytearray(romSize) for index in range(romSize): encodedByte = inputFile[8+index] encodeStringCharacter = encodeString[encodeStringOffset] decodedByte = ((encodedByte ^ encodeStringCharacter ^ 0x80) - scramble) & 0xFF outFile[index] = decodedByte scramble += 3 if encodeStringOffset < len(encodeString) - 1: encodeStringOffset += 1 else: encodeStringOffset = 0 else: romSize = len(inputFile) outFile = bytearray(romSize+8) check = 0 for index in range(romSize): romByte = inputFile[index] encodeStringCharacter = encodeString[encodeStringOffset] encodedByte = ((romByte + scramble) & 0xFF) ^ 0x80 ^ encodeStringCharacter outFile[8+index] = encodedByte scramble += 3 check = (check + encodedByte + romByte) & 0xFFFFFFFF if encodeStringOffset < len(encodeString) - 1: encodeStringOffset += 1 else: encodeStringOffset = 0 checkBytes1 = check.to_bytes(32, 'little') checkBytes2 = ((~check) & 0xFFFFFFFF).to_bytes(32, 'little') for index in range(4): outFile[index] = checkBytes1[index] for index in range(4): outFile[4+index] = checkBytes2[index] open(sys.argv[4], 'wb').write(outFile) Here's Ristar running in Sega Smash Pack II. It has some issues... Make sure when replacing a game, you don't use Comix Zone. The post decode patching is likely to make the game crash. EDIT: messing around with other games out of curiosity Sonic 3 works fine. Saves. Dunno if saving working is related to me having replaced Shining Force or not.
The Comix Zone post decode patch is to work around an emulator bug. If you load Comix Zone as a different game so no patching happens the title screen roll is glitched. In contrast, if you encode the patched Comix Zone as a kvq and load it, everything is fine.
Wait a minute. I've just remembered that there are supposed to be two kinds of Vectorman cartridges: Hmm, I wonder...
The values "A3 95 97 91 7D 93 98 91 9E 9E 95 9C" when shifted towards a readable ASCII "53 45 47 41 2D 43 48 41 4E 4E 45 4C" read "SEGA-CHANNEL", not sure if that was known in this earlier thread spoken of.
It's news to me. I can't think of many other games that tried anything like that. For a comparable scenario, though, Fire Emblem 6 springs to mind. Nearly mythical extra battle maps from a contest are said to be floating around somewhere. https://fireemblemwiki.org/wiki/List_of_Trial_Maps_in_Fire_Emblem:_The_Binding_Blade
It was not and that's very interesting. Is this magic string some kind of standard for sega channel roms? I wonder if the binary reads from that area of memory to version check itself at any point... EDIT: To see it for yourself, use this site https://www.dcode.fr/ascii-shift-cipher A39597917D9398919E9E959C, offset 80, decode to printable ascii Input will be detected as extended ascii
Reverse engineered the kml archive format used to store graphics, sounds, and saves. The archives just store raw data. I have also located the palette offsets for the images within the smash and smash2 executables and figured out the audio rates. Except for the bitmap font, which is a 1bpp black and white image, all images are 8 bpp paletted. Every sound is stored at 3 sample rates. Probably to playback on different settings without need for resampling code. convert save file .\kmlConverter.py 'GameSav3.KML' png images .\kmlConverter.py 'MainBlok.kml' 'Smash2.exe' wav files .\kmlConverter.py 'SoundBlk.kml' Script uses Pillow and bitarray. Spoiler: kmlConverter.py Code (Text): from PIL import Image import sys import wave import collections from bitarray import bitarray import os ImageSectionMetadata = collections.namedtuple('ImageSectionMetadata', 'dimensions mode palette name') AudioMetadata = collections.namedtuple('AudioSectionMetadata', 'sampleRate name') def main(): kmlArchivePath = sys.argv[1] kmlArchiveName = os.path.split(kmlArchivePath)[-1]; kmlArchive = bytearray(open(kmlArchivePath, 'rb').read()) sections = kmlArchiveToSections(kmlArchive) if kmlArchiveName == 'SoundBlk.kml': sectionsToWavFiles(sections) elif kmlArchiveName == 'MainBlok.kml' and len(sys.argv) > 2: smashExePath = sys.argv[2] smashExeName = os.path.split(smashExePath)[-1]; smashMetadata = None if smashExeName == 'Smash.exe': smashMetadata = smashMainBlokMetaData(smashExePath) elif smashExeName == 'Smash2.exe': smashMetadata = smash2MainBlokMetaData(smashExePath) images = sectionsToImages(smashMetadata, sections) for image, sectionMetaData in zip(images, smashMetadata): image.save(f'{sectionMetaData.name}.png') else: index = 0 while index < len(sections): open(f'section{index}.data', 'wb').write(sections[index]) index += 1 def kmlArchiveToSections(kmlArchive): hasRunFlag = lambda byte: byte & 0b11000000 == 0b11000000 byteToRunLength = lambda byte: byte & 0b00111111 sections = [] kmlIndex = 0 while kmlIndex < len(kmlArchive): kmlSectionMagicString = kmlArchive[kmlIndex:kmlIndex+4].decode('ascii') kmlIndex += 4 kmlSectionNumber = int.from_bytes((kmlArchive[kmlIndex:kmlIndex+4]), 'little') kmlIndex += 4 kmlSectionSize = int.from_bytes((kmlArchive[kmlIndex:kmlIndex+4]), 'little') kmlIndex += 4 kmlDecompressedSize = int.from_bytes((kmlArchive[kmlIndex:kmlIndex+4]), 'little') kmlIndex += 4 outData = bytearray(kmlDecompressedSize) kmlSectionStartIndex = kmlIndex outDataIndex = 0 while kmlIndex - kmlSectionStartIndex < kmlSectionSize: byte1 = kmlArchive[kmlIndex] kmlIndex += 1 dataByte = byte1 runLength = 1 if hasRunFlag(byte1): runLength = byteToRunLength(byte1) dataByte = kmlArchive[kmlIndex] kmlIndex += 1 runIndex = 0 while runIndex < runLength: outData[outDataIndex] = dataByte outDataIndex += 1 runIndex += 1 sections.append(outData) return sections def sectionsToWavFiles(sections): soundBlokMetadata = [ AudioMetadata(44100, 'sega44'), AudioMetadata(22050, 'sega22'), AudioMetadata(11025, 'sega11'), AudioMetadata(44100, 'menu navigation44'), AudioMetadata(22050, 'menu navigation22'), AudioMetadata(11025, 'menu navigation11'), AudioMetadata(44100, 'menu selection44'), AudioMetadata(22050, 'menu selection22'), AudioMetadata(11025, 'menu selection11'), ] for section, sectionMetadata in zip(sections, soundBlokMetadata): with wave.open(f'{sectionMetadata.name}.wav', 'wb') as wavfile: wavfile.setparams((1, 1, sectionMetadata.sampleRate, 0, 'NONE', 'NONE')) wavfile.writeframes(section) def sectionsToImages(sectionMetaDataList, sections): images = [] for sectionMetadata, section in zip(sectionMetaDataList, sections): image = Image.new(sectionMetadata.mode, sectionMetadata.dimensions) if sectionMetadata.mode == '1': image.putdata(bitarray(''.join(format(byte, '08b') for byte in section))) else: image.putpalette(sectionMetadata.palette) image.putdata(section) images.append(image) return images def smash2MainBlokMetaData(smashPack2ExePath): menuPalette = None otherPalette = None titleGraphicPalette = None cursorPalette = None menuBackgroundPalette = None comixZonePalette = None flickyPalette = None kidChameleonPalette = None shiningPalette = None sonic2Palette = None superHangOnPalette = None vectorman2Palette = None controllerPalette = None sonic2ModeSelectPalette = None segaLogoPalette = None cursorAndArrowPalette = None menuBackgroundPalette = None titleGraphicPalette = None controllerPalette = None with open(smashPack2ExePath, 'rb') as exeFile: exeFile.seek(0x8ca28) comixZonePalette = list(exeFile.read(0x123)) exeFile.seek(0x8CB4B) flickyPalette = list(exeFile.read(0x123)) exeFile.seek(0x8cc6E) kidChameleonPalette = list(exeFile.read(0x123)) exeFile.seek(0x8cd91) shiningPalette = list(exeFile.read(0x123)) exeFile.seek(0x8ceb4) sonic2Palette = list(exeFile.read(0x123)) exeFile.seek(0x8cfd7) superHangOnPalette = list(exeFile.read(0x123)) exeFile.seek(0x8D0Fa) vectorman2Palette = list(exeFile.read(0x123)) exeFile.seek(0x8d21d) sonic2ModeSelectPalette = list(exeFile.read(0x123)) exeFile.seek(0x8d340) sonic2ModeSelectPalette = list(exeFile.read(0x123)) exeFile.seek(0x8d464) cursorAndArrowPalette = list(exeFile.read(0x18)) exeFile.seek(0x8d47C) segaLogoPalette = list(exeFile.read(0x300)) exeFile.seek(0x8d77c) menuBackgroundPalette = list(exeFile.read(0xA2)) exeFile.seek(0x8D820) titleGraphicPalette = list(exeFile.read(0x240)) exeFile.seek(0x8da60) controllerPalette = list(exeFile.read(0x120)) return [ ImageSectionMetadata((160, 112), 'P', comixZonePalette, 'comix zone'), ImageSectionMetadata((160, 112), 'P', flickyPalette, 'flicky'), ImageSectionMetadata((160, 112), 'P', kidChameleonPalette, 'kid chameleon'), ImageSectionMetadata((160, 112), 'P', shiningPalette, 'shining force'), ImageSectionMetadata((160, 112), 'P', sonic2Palette, 'sonic 2'), ImageSectionMetadata((160, 112), 'P', superHangOnPalette, 'super hang-on'), ImageSectionMetadata((160, 112), 'P', vectorman2Palette, 'vectorman 2'), ImageSectionMetadata((160, 112), 'P', sonic2ModeSelectPalette, 'sonic 2 arcade mode'), ImageSectionMetadata((160, 112), 'P', sonic2ModeSelectPalette, 'sonic 2 fullscreen mode'), ImageSectionMetadata((320, 240), 'P', segaLogoPalette, 'sega logo'), ImageSectionMetadata((12, 12), 'P', cursorAndArrowPalette, 'cursor'), ImageSectionMetadata((17, 136), 'P', cursorAndArrowPalette, 'navigation arrow sheet'), ImageSectionMetadata((256, 80), '1', None, 'bitmap font'), ImageSectionMetadata((320, 240), 'P', menuBackgroundPalette, 'menu background'), ImageSectionMetadata((256, 160), 'P', titleGraphicPalette, 'title graphic'), ImageSectionMetadata((64, 288), 'P', controllerPalette, 'controller sheet'), ImageSectionMetadata((8, 128), 'P', controllerPalette, 'controller button label sheet') ] def smashMainBlokMetaData(smashExePath): alteredBeastPalette = None columnsPalette = None goldenAxePalette = None outrunPalette = None phantasyStartIIPalette = None superShinobiPalette = None sonicSpinballPalette = None vectormanPalette = None segaLogoPalette = None cursorAndArrowPalette = None menuBackgroundPalette = None titleGraphicPalette = None controllerPalette = None with open(smashExePath, 'rb') as exeFile: exeFile.seek(0x7F994) alteredBeastPalette = list(exeFile.read(0x123)) exeFile.seek(0x7FAB7) columnsPalette = list(exeFile.read(0x123)) exeFile.seek(0x7FBDA) goldenAxePalette = list(exeFile.read(0x123)) exeFile.seek(0x7FCFD) outrunPalette = list(exeFile.read(0x123)) exeFile.seek(0x7FE20) phantasyStartIIPalette = list(exeFile.read(0x123)) exeFile.seek(0x7FF43) superShinobiPalette = list(exeFile.read(0x123)) exeFile.seek(0x80066) sonicSpinballPalette = list(exeFile.read(0x123)) exeFile.seek(0x80189) vectormanPalette = list(exeFile.read(0x123)) exeFile.seek(0x802AC) cursorAndArrowPalette = list(exeFile.read(0x18)) exeFile.seek(0x802C4) segaLogoPalette = list(exeFile.read(0x6c)) exeFile.seek(0x80330) menuBackgroundPalette = list(exeFile.read(0xA2)) exeFile.seek(0x803D4) titleGraphicPalette = list(exeFile.read(0x240)) exeFile.seek(0x80614) controllerPalette = list(exeFile.read(0x120)) return [ ImageSectionMetadata((160, 112), 'P', alteredBeastPalette, 'altered beast'), ImageSectionMetadata((160, 112), 'P', columnsPalette, 'columns'), ImageSectionMetadata((160, 112), 'P', goldenAxePalette, 'golden axe'), ImageSectionMetadata((160, 112), 'P', outrunPalette, 'outrun'), ImageSectionMetadata((160, 112), 'P', phantasyStartIIPalette, 'phantasy start II'), ImageSectionMetadata((160, 112), 'P', superShinobiPalette, 'super shinobi'), ImageSectionMetadata((160, 112), 'P', sonicSpinballPalette, 'sonic spinball'), ImageSectionMetadata((160, 112), 'P', vectormanPalette, 'vectorman'), ImageSectionMetadata((320, 240), 'P', segaLogoPalette, 'sega logo'), ImageSectionMetadata((12, 12), 'P', cursorAndArrowPalette, 'cursor'), ImageSectionMetadata((17, 136), 'P', cursorAndArrowPalette, 'navigation arrow sheet'), ImageSectionMetadata((256, 80), '1', None, 'bitmap font'), ImageSectionMetadata((320, 240), 'P', menuBackgroundPalette, 'menu background'), ImageSectionMetadata((256, 160), 'P', titleGraphicPalette, 'title graphic'), ImageSectionMetadata((64, 288), 'P', controllerPalette, 'controller sheet'), ImageSectionMetadata((8, 128), 'P', controllerPalette, 'controller button label sheet') ] if __name__=="__main__": main() Spoiler: kmlInformation 16 byte headers. 4 dwords, little endian for sizes first is magic string second is section number, starting index 0 third is size of section fourth is decompressed size. Run length encoding with run flag bits SECTIONS smash pack 2 mainblok.kml 0: 160 112 comix zone 1: 160 112 flicky 2: 160 112 kid chameleon 3: 160 112 shining force 4: 160 112 sonic 2 5: 160 112 super hang on 6: 160 112 vectorman 2 7: 160 112 sonic 2 arcade setting 8: 160 112 sonic 2 fullscreen setting 9: 320 240 sega logo 10: 12 12 cursor 11: 17 36 navigation arrow graphics sprite sheet 12: 256 80 B&W 1 bit binary image. Bitmap font. 13: 320 240 title screen background 14: 256 160 smash pack title graphic 15: 64 288 controller sprite sheet 16: 8 128 controller button label sprite sheet SECTIONS smash pack mainblock.kml 0: 160 112 altered beast 1: 160 112 columns 2: 160 112 golden axe 3: 160 112 outrun 4: 160 112 phantasy star II 5: 160 112 super shinobi 6: 160 112 sonic spinball 7: 160 112 vectorman 8: 320 240 sega logo 9: 12 12 cursor 10: 17 36 navigation arrow sprite sheet 11: 256 80 B&W 1 bit binary image. bitmap font. Identical to smash2 bitmap font section 12: 320 240 title background image 13: 256 160 smash pack title graphic 14: 64 288 controller sprite sheet 15: 8 128 controller button label sprite sheet SoundBlk unsigned 8 bit pcm little endian byte order 1 channel sample rate varies SECTIONS smash pack 1 soundblk.kml 0: 44100hz sega cheer 1: 22050hz sega cheer 2: 11025hz sega cheer 3: 44100hz menu navigation sound 4: 22050hz menu navigation sound 5: 11025hz menu navigation sound 6: 44100hz menu change menu sound 7: 22050hz menu change menu sound 8: 11025hz menu change menu sound SECTIONS smash pack 2 soundblk.kml 0: 44100hz sega shout 1: 22050hz sega shout 2: 11025hz sega shout sections 3-8 in soundblk.kml are identical in the two packs Also discovered that when smash pack 2 is in dual player mode, Sonic 2 has a graphics option that displays the genesis' interlaced mode as two full resolution screens, rather than the usual squished output. Interesting. Never played the collection with two players before, but I noticed there were graphics for something sonic 2 related in the kml. They were for selecting between display modes. It seems if you want to save games, you must replace a game in the collection that had saving.
Comix Zone was patched because the smash pack emulator does not handle the VDP highlight and shadow correctly. From what I have read, that part of the VDP is tricky and was not well understood for some time. This problem is clearly visible in Vectorman 2, in the first level, but the output isn't as painfully bugged looking. Smash Pack 2: Correct: Smash Pack 2 (forced to load unpatched ROM as different title): Correct: Notice for both Comix Zone and Vectorman areas that should be highlighted are replaced with a solid color. The Comix Zone patch replaces an addi.w with a bra.b (branch always). It skips over several subroutine calls, which must be responsible for the effect. The patched ROM has no shadowing or highlight in the roll. (see image in prior post) Spoiler: Original Code (Text): 0x001e5c54 06450071 addi.w 0x71, d5 0x001e5c58 383c0080 move.w 0x80, d4 0x001e5c5c 303c0d00 move.w 0xd00, d0 0x001e5c60 7409 moveq 0x9, d2 0x001e5c62 4eb9001cd2a4 jsr 0x1cd2a4.l 0x001e5c68 06440020 addi.w 0x20, d4 0x001e5c6c 51cafff4 dbra d2, 0x1e5c62 0x001e5c70 0c450105 cmpi.w 0x105, d5 0x001e5c74 6f1e ble.b 0x1e5c94 x001e5c76 3f05 move.w d5, -(a7) x001e5c78 3a03 move.w d3, d5 x001e5c7a 06450080 addi.w 0x80, d5 x001e5c7e 303c0300 move.w 0x300, d0 x001e5c82 7801 moveq 0x1, d4 x001e5c84 4eb9001cd2a4 jsr 0x1cd2a4.l x001e5c8a 7800 moveq 0x0, d4 x001e5c8c 4eb9001cd2a4 jsr 0x1cd2a4.l x001e5c92 3a1f move.w (a7)+, d5 0x001e5c94 383c0080 move.w 0x80, d4 0x001e5c98 06450012 addi.w 0x12, d5 0x001e5c9c 303c0f00 move.w 0xf00, d0 0x001e5ca0 5041 addq.w 0x8, d1 0x001e5ca2 7409 moveq 0x9, d2 0x001e5ca4 4eb9001cd2a4 jsr 0x1cd2a4.l 0x001e5caa 06440020 addi.w 0x20, d4 0x001e5cae 51cafff4 dbra d2, 0x1e5ca4 0x001e5cb2 4eb9001cd254 jsr 0x1cd254.l Spoiler: Patched Code (Text): 0x001e5c54 605c bra.b 0x1e5cb2 0x001e5c56 0071383c0080 ori.w 0x383c, -0x80(a1, d0.w) 0x001e5c5c 303c0d00 move.w 0xd00, d0 0x001e5c60 7409 moveq 0x9, d2 0x001e5c62 4eb9001cd2a4 jsr 0x1cd2a4.l 0x001e5c68 06440020 addi.w 0x20, d4 0x001e5c6c 51cafff4 dbra d2, 0x1e5c62 0x001e5c70 0c450105 cmpi.w 0x105, d5 0x001e5c74 6f1e ble.b 0x1e5c94 0x001e5c76 3f05 move.w d5, -(a7) 0x001e5c78 3a03 move.w d3, d5 0x001e5c7a 06450080 addi.w 0x80, d5 0x001e5c7e 303c0300 move.w 0x300, d0 0x001e5c82 7801 moveq 0x1, d4 0x001e5c84 4eb9001cd2a4 jsr 0x1cd2a4.l 0x001e5c8a 7800 moveq 0x0, d4 0x001e5c8c 4eb9001cd2a4 jsr 0x1cd2a4.l 0x001e5c92 3a1f move.w (a7)+, d5 0x001e5c94 383c0080 move.w 0x80, d4 0x001e5c98 06450012 addi.w 0x12, d5 0x001e5c9c 303c0f00 move.w 0xf00, d0 0x001e5ca0 5041 addq.w 0x8, d1 0x001e5ca2 7409 moveq 0x9, d2 0x001e5ca4 4eb9001cd2a4 jsr 0x1cd2a4.l 0x001e5caa 06440020 addi.w 0x20, d4 0x001e5cae 51cafff4 dbra d2, 0x1e5ca4 0x001e5cb2 4eb9001cd254 jsr 0x1cd254.l
As Wikipedia used to state, and has anybody who owns it knows, the Twin Pack release of Sega Smash Pack is bugged, and returns to the main menu after playing any title for a few minutes. This is because Sega screwed up a no-cd crack of their own software. They put a mov instruction over the call to the initial cd check routine. Good so far. However, they forgot that a second check happens on game load. This second check depends on the first check running, because that earlier code stores the drive letter the collection was found in. Without that, the second check always fails to find the cd. Games load, and after load, this second cd check takes place. If it fails, it deletes the kvq file with a DeleteFileA win32 api call (assuming the process has permissions to really do this, it will be deleted). While the emulator is running some logic related to this seems to be triggered again periodically. I didn't dig into it the exact mechanisms of failure. At any rate, the second time, this is fatal, and the collection returns to the main menu. Naturally, the game cannot even be loaded after this if the process was able to delete the kvq file. The original Smash.exe can be found with i5comp tools in the /SP1/Data1.cab. The patched exe is /SP1/PATCH.exe on the cd. The only difference is at 0x47F3, E8 68 C8 FF FF, the call instruction, is replaced with B8 01 00 00 00 (mov eax, 1). This makes me suspect whoever did it lacked the source code to the collection. Probably saw the cd check dialog went away with their assembly edit, successfully loaded a game, and called it a day. The original pack 1 cd check routine will always fail with the Twin Pack disc. It seems to look at the Setup.ini and Autorun.ini files for particular strings. In the Twin Pack, these files match what Smash Pack 2's cd check expects, so some of the checks fail. You can ignore these parts, by changing 0x117d to 31 from 85 and 0x124A to 31 from 85. These edits xor eax with itself, 0-ing it out before branches that need to see the 0 flag set to pass. The collection works fine with the cd check routine re-enabled and edited. I guess this is an inverted crack, partially re-enabling DRM... I don't know what Sega's PC division was doing for quality control in the late 90s to early 00s, but it wasn't enough. I know there are versions of Sonic R with no music, and broken versions of Sonic CD as well.
A user on the Sega 16 Discord server asked if someone could post the NOCD patch for the game http://krimsky.net/patchers/segapcx.html