Curious discovery regarding Sega Smash Pack for PC

Discussion in 'General Sega Discussion' started by MarzSyndrome, Oct 26, 2009.

  1. MarzSyndrome

    MarzSyndrome

    Everything is going to the beat. Member
    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...

    <b>GoodGen</b>
    Code (Text):
    1. Columns III - Revenge of Columns (U) [!]
    2. Dr. Robotnik's Mean Bean Machine (U) [!]
    <b>No-Intro</b>
    Code (Text):
    1. Columns III - Revenge of Columns (USA)
    2. Dr. Robotnik's Mean Bean Machine (USA)
     
  2. SpeedStarTMQ

    SpeedStarTMQ

    Here for The Hedgehog. Member
    2,393
    25
    28
    UK
    Any videos of Revenge of Shinobi's beta? That would be sweet.
     
  3. MarzSyndrome

    MarzSyndrome

    Everything is going to the beat. Member
    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...)
     
  4. Glisp

    Glisp

    That one weird guy that does stuff. Member
    1,278
    0
    16
    Bloomington, IN
    None at the moment I'm afraid.
    <!--quoteo(post=459122:date=May 25 2010, 02:40 PM:name=SpeedStarTMQ)--><div class='quotetop'>QUOTE (SpeedStarTMQ @ May 25 2010, 02:40 PM) <a href="index.php?act=findpost&pid=459122"><img src="public/style_images/retro/snapback.png"></a></div><div class='quotemain'><!--quotec-->Any videos of Revenge of Shinobi's beta? That would be sweet.<!--QuoteEnd--></div><!--QuoteEEnd-->

    I could make one but its going to be really really bad. I suck at ROS even though I love the game.
     
  5. Glisp

    Glisp

    That one weird guy that does stuff. Member
    1,278
    0
    16
    Bloomington, IN
    None at the moment I'm afraid.
    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.
     
  6. ZaedYhen

    ZaedYhen

    Member
    8
    1
    3
    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
    Code (Text):
    1. import sys
    2.  
    3. kvqFile = bytearray(open(sys.argv[2], 'rb').read())
    4.  
    5. romSize = len(kvqFile)-8
    6.  
    7. decodedRom = bytearray(romSize)
    8.  
    9. encodeStringPack1 = bytearray('Encoded for KGen Ultra / Sega Smash Pack / Snake KML 1999! ', 'ascii')
    10. encodeStringPack2 = bytearray('Encoded for KGen Ultra / Sega Smash Pack II / Snake KML 1999! ', 'ascii')
    11.  
    12. encodeString = ''
    13.  
    14. if sys.argv[1] == '-1':
    15.     encodeString = encodeStringPack1
    16. else:
    17.     encodeString = encodeStringPack2
    18.  
    19. encodeStringOffset = 0
    20. scramble = 6
    21.  
    22. for index in range(romSize):
    23.     encodedByte = kvqFile[8+index]
    24.     encodeStringCharacter = encodeString[encodeStringOffset]
    25.     decodedByte = ((encodedByte ^ encodeStringCharacter ^ 0x80) - scramble) & 0xFF
    26.     decodedRom[index] = decodedByte
    27.     scramble += 3
    28.     if encodeStringOffset < len(encodeString) - 1:
    29.         encodeStringOffset += 1
    30.     else:
    31.         encodeStringOffset = 0
    32.  
    33. 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

    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.
     
    Last edited: May 28, 2020
  7. ZaedYhen

    ZaedYhen

    Member
    8
    1
    3
    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
    Code (Text):
    1.  
    2. import sys
    3. import struct
    4.  
    5. inputFile = bytearray(open(sys.argv[3], 'rb').read())
    6.  
    7. outFile = None
    8.  
    9. encodeStringPack1 = bytearray('Encoded for KGen Ultra / Sega Smash Pack / Snake KML 1999! ', 'ascii')
    10. encodeStringPack2 = bytearray('Encoded for KGen Ultra / Sega Smash Pack II / Snake KML 1999! ', 'ascii')
    11.  
    12. encodeString = None
    13.  
    14. if sys.argv[2] == '-1':
    15.     encodeString = encodeStringPack1
    16. else:
    17.     encodeString = encodeStringPack2
    18.  
    19. encodeStringOffset = 0
    20. scramble = 6
    21.  
    22. if sys.argv[1] == '-d':
    23.     romSize = len(inputFile)-8
    24.     outFile = bytearray(romSize)
    25.     for index in range(romSize):
    26.         encodedByte = inputFile[8+index]
    27.         encodeStringCharacter = encodeString[encodeStringOffset]
    28.         decodedByte = ((encodedByte ^ encodeStringCharacter ^ 0x80) - scramble) & 0xFF
    29.         outFile[index] = decodedByte
    30.         scramble += 3
    31.         if encodeStringOffset < len(encodeString) - 1:
    32.             encodeStringOffset += 1
    33.         else:
    34.             encodeStringOffset = 0
    35. else:
    36.     romSize = len(inputFile)
    37.     outFile = bytearray(romSize+8)
    38.     check = 0
    39.     for index in range(romSize):
    40.         romByte = inputFile[index]
    41.         encodeStringCharacter = encodeString[encodeStringOffset]
    42.         encodedByte = ((romByte + scramble) & 0xFF) ^ 0x80 ^ encodeStringCharacter
    43.         outFile[8+index] = encodedByte
    44.         scramble += 3
    45.         check = (check + encodedByte + romByte) & 0xFFFFFFFF
    46.         if encodeStringOffset < len(encodeString) - 1:
    47.             encodeStringOffset += 1
    48.         else:
    49.             encodeStringOffset = 0
    50.     checkBytes1 = check.to_bytes(32, 'little')
    51.     checkBytes2 = ((~check) & 0xFFFFFFFF).to_bytes(32, 'little')
    52.     for index in range(4):
    53.         outFile[index] = checkBytes1[index]
    54.     for index in range(4):
    55.         outFile[4+index] = checkBytes2[index]
    56.  
    57. open(sys.argv[4], 'wb').write(outFile)
    58.  

    Here's Ristar running in Sega Smash Pack II.
    It has some issues...
    smashPackRistar.png

    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.
     
    Last edited: May 29, 2020
  8. ZaedYhen

    ZaedYhen

    Member
    8
    1
    3
    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.
    comixzonebug.png
    In contrast, if you encode the patched Comix Zone as a kvq and load it, everything is fine.
    patchedcomixzone.png
     
  9. ICEknight

    ICEknight

    Researcher Researcher
    Wait a minute. I've just remembered that there are supposed to be two kinds of Vectorman cartridges:
    Hmm, I wonder...
     
  10. MarkeyJester

    MarkeyJester

    ♡ ! Resident Jester
    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.
     
    • Like Like x 1
    • Informative Informative x 1
    • List
  11. The Claw

    The Claw

    Member
    68
    0
    6
    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
     
  12. ICEknight

    ICEknight

    Researcher Researcher
    Oh well... At least we know its exact purpose now.
     
  13. ZaedYhen

    ZaedYhen

    Member
    8
    1
    3
    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
     
    Last edited: Jun 1, 2020
  14. ZaedYhen

    ZaedYhen

    Member
    8
    1
    3
    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.
    Code (Text):
    1. from PIL import Image
    2. import sys
    3. import wave
    4. import collections
    5. from bitarray import bitarray
    6. import os
    7.  
    8. ImageSectionMetadata = collections.namedtuple('ImageSectionMetadata', 'dimensions mode palette name')
    9. AudioMetadata = collections.namedtuple('AudioSectionMetadata', 'sampleRate name')
    10.  
    11. def main():
    12.     kmlArchivePath = sys.argv[1]
    13.     kmlArchiveName = os.path.split(kmlArchivePath)[-1];
    14.  
    15.     kmlArchive = bytearray(open(kmlArchivePath, 'rb').read())
    16.     sections = kmlArchiveToSections(kmlArchive)
    17.     if kmlArchiveName == 'SoundBlk.kml':
    18.         sectionsToWavFiles(sections)
    19.     elif kmlArchiveName ==  'MainBlok.kml' and len(sys.argv) > 2:
    20.         smashExePath = sys.argv[2]
    21.         smashExeName = os.path.split(smashExePath)[-1];
    22.         smashMetadata = None
    23.         if smashExeName == 'Smash.exe':
    24.             smashMetadata = smashMainBlokMetaData(smashExePath)
    25.         elif smashExeName == 'Smash2.exe':
    26.             smashMetadata = smash2MainBlokMetaData(smashExePath)
    27.         images = sectionsToImages(smashMetadata, sections)
    28.         for image, sectionMetaData in zip(images, smashMetadata):
    29.             image.save(f'{sectionMetaData.name}.png')
    30.     else:
    31.         index = 0
    32.         while index < len(sections):
    33.             open(f'section{index}.data', 'wb').write(sections[index])
    34.             index += 1
    35.  
    36. def kmlArchiveToSections(kmlArchive):
    37.     hasRunFlag = lambda byte: byte & 0b11000000 == 0b11000000
    38.     byteToRunLength = lambda byte: byte & 0b00111111
    39.  
    40.     sections = []
    41.     kmlIndex = 0
    42.     while kmlIndex < len(kmlArchive):
    43.         kmlSectionMagicString = kmlArchive[kmlIndex:kmlIndex+4].decode('ascii')
    44.         kmlIndex += 4
    45.         kmlSectionNumber = int.from_bytes((kmlArchive[kmlIndex:kmlIndex+4]), 'little')
    46.         kmlIndex += 4
    47.         kmlSectionSize = int.from_bytes((kmlArchive[kmlIndex:kmlIndex+4]), 'little')
    48.         kmlIndex += 4
    49.         kmlDecompressedSize = int.from_bytes((kmlArchive[kmlIndex:kmlIndex+4]), 'little')
    50.         kmlIndex += 4
    51.         outData = bytearray(kmlDecompressedSize)
    52.         kmlSectionStartIndex = kmlIndex
    53.         outDataIndex = 0
    54.         while kmlIndex - kmlSectionStartIndex < kmlSectionSize:
    55.             byte1 = kmlArchive[kmlIndex]
    56.             kmlIndex += 1
    57.             dataByte = byte1
    58.             runLength = 1
    59.             if hasRunFlag(byte1):
    60.                 runLength = byteToRunLength(byte1)
    61.                 dataByte = kmlArchive[kmlIndex]
    62.                 kmlIndex += 1
    63.             runIndex = 0
    64.             while runIndex < runLength:
    65.                 outData[outDataIndex] = dataByte
    66.                 outDataIndex += 1
    67.                 runIndex += 1
    68.         sections.append(outData)
    69.     return sections
    70.  
    71. def sectionsToWavFiles(sections):
    72.     soundBlokMetadata = [
    73.         AudioMetadata(44100, 'sega44'),
    74.         AudioMetadata(22050, 'sega22'),
    75.         AudioMetadata(11025, 'sega11'),
    76.         AudioMetadata(44100, 'menu navigation44'),
    77.         AudioMetadata(22050, 'menu navigation22'),
    78.         AudioMetadata(11025, 'menu navigation11'),
    79.         AudioMetadata(44100, 'menu selection44'),
    80.         AudioMetadata(22050, 'menu selection22'),
    81.         AudioMetadata(11025, 'menu selection11'),
    82.     ]
    83.     for section, sectionMetadata in zip(sections, soundBlokMetadata):
    84.         with wave.open(f'{sectionMetadata.name}.wav', 'wb') as wavfile:
    85.             wavfile.setparams((1, 1, sectionMetadata.sampleRate, 0, 'NONE', 'NONE'))
    86.             wavfile.writeframes(section)
    87.  
    88. def sectionsToImages(sectionMetaDataList, sections):
    89.     images = []
    90.     for sectionMetadata, section in zip(sectionMetaDataList, sections):
    91.         image = Image.new(sectionMetadata.mode, sectionMetadata.dimensions)
    92.         if sectionMetadata.mode == '1':
    93.             image.putdata(bitarray(''.join(format(byte, '08b') for byte in section)))
    94.         else:
    95.             image.putpalette(sectionMetadata.palette)
    96.             image.putdata(section)
    97.         images.append(image)
    98.     return images
    99.  
    100. def smash2MainBlokMetaData(smashPack2ExePath):
    101.     menuPalette = None
    102.     otherPalette = None
    103.     titleGraphicPalette = None
    104.     cursorPalette = None
    105.     menuBackgroundPalette = None
    106.     comixZonePalette = None
    107.     flickyPalette = None
    108.     kidChameleonPalette = None
    109.     shiningPalette = None
    110.     sonic2Palette = None
    111.     superHangOnPalette = None
    112.     vectorman2Palette = None
    113.     controllerPalette = None
    114.     sonic2ModeSelectPalette = None
    115.     segaLogoPalette = None
    116.     cursorAndArrowPalette = None
    117.     menuBackgroundPalette = None
    118.     titleGraphicPalette = None
    119.     controllerPalette = None
    120.  
    121.     with open(smashPack2ExePath, 'rb') as exeFile:
    122.         exeFile.seek(0x8ca28)
    123.         comixZonePalette = list(exeFile.read(0x123))
    124.         exeFile.seek(0x8CB4B)
    125.         flickyPalette = list(exeFile.read(0x123))
    126.         exeFile.seek(0x8cc6E)
    127.         kidChameleonPalette = list(exeFile.read(0x123))
    128.         exeFile.seek(0x8cd91)
    129.         shiningPalette = list(exeFile.read(0x123))
    130.         exeFile.seek(0x8ceb4)
    131.         sonic2Palette = list(exeFile.read(0x123))
    132.         exeFile.seek(0x8cfd7)
    133.         superHangOnPalette = list(exeFile.read(0x123))
    134.         exeFile.seek(0x8D0Fa)
    135.         vectorman2Palette = list(exeFile.read(0x123))
    136.         exeFile.seek(0x8d21d)
    137.         sonic2ModeSelectPalette = list(exeFile.read(0x123))
    138.         exeFile.seek(0x8d340)
    139.         sonic2ModeSelectPalette = list(exeFile.read(0x123))
    140.         exeFile.seek(0x8d464)
    141.         cursorAndArrowPalette = list(exeFile.read(0x18))
    142.         exeFile.seek(0x8d47C)
    143.         segaLogoPalette = list(exeFile.read(0x300))
    144.         exeFile.seek(0x8d77c)
    145.         menuBackgroundPalette = list(exeFile.read(0xA2))
    146.         exeFile.seek(0x8D820)
    147.         titleGraphicPalette = list(exeFile.read(0x240))
    148.         exeFile.seek(0x8da60)
    149.         controllerPalette = list(exeFile.read(0x120))
    150.  
    151.     return [
    152.         ImageSectionMetadata((160, 112), 'P', comixZonePalette, 'comix zone'),
    153.         ImageSectionMetadata((160, 112), 'P', flickyPalette, 'flicky'),
    154.         ImageSectionMetadata((160, 112), 'P', kidChameleonPalette, 'kid chameleon'),
    155.         ImageSectionMetadata((160, 112), 'P', shiningPalette, 'shining force'),
    156.         ImageSectionMetadata((160, 112), 'P', sonic2Palette, 'sonic 2'),
    157.         ImageSectionMetadata((160, 112), 'P', superHangOnPalette, 'super hang-on'),
    158.         ImageSectionMetadata((160, 112), 'P', vectorman2Palette, 'vectorman 2'),
    159.         ImageSectionMetadata((160, 112), 'P', sonic2ModeSelectPalette, 'sonic 2 arcade mode'),
    160.         ImageSectionMetadata((160, 112), 'P', sonic2ModeSelectPalette, 'sonic 2 fullscreen mode'),
    161.         ImageSectionMetadata((320, 240), 'P', segaLogoPalette, 'sega logo'),
    162.         ImageSectionMetadata((12, 12), 'P', cursorAndArrowPalette, 'cursor'),
    163.         ImageSectionMetadata((17, 136), 'P', cursorAndArrowPalette, 'navigation arrow sheet'),
    164.         ImageSectionMetadata((256, 80), '1', None, 'bitmap font'),
    165.         ImageSectionMetadata((320, 240), 'P', menuBackgroundPalette, 'menu background'),
    166.         ImageSectionMetadata((256, 160), 'P', titleGraphicPalette, 'title graphic'),
    167.         ImageSectionMetadata((64, 288), 'P', controllerPalette, 'controller sheet'),
    168.         ImageSectionMetadata((8, 128), 'P', controllerPalette, 'controller button label sheet')
    169.     ]
    170.  
    171. def smashMainBlokMetaData(smashExePath):
    172.     alteredBeastPalette = None
    173.     columnsPalette = None
    174.     goldenAxePalette = None
    175.     outrunPalette = None
    176.     phantasyStartIIPalette = None
    177.     superShinobiPalette = None
    178.     sonicSpinballPalette = None
    179.     vectormanPalette = None
    180.     segaLogoPalette = None
    181.     cursorAndArrowPalette = None
    182.     menuBackgroundPalette = None
    183.     titleGraphicPalette = None
    184.     controllerPalette = None
    185.  
    186.     with open(smashExePath, 'rb') as exeFile:
    187.         exeFile.seek(0x7F994)
    188.         alteredBeastPalette = list(exeFile.read(0x123))
    189.         exeFile.seek(0x7FAB7)
    190.         columnsPalette = list(exeFile.read(0x123))
    191.         exeFile.seek(0x7FBDA)
    192.         goldenAxePalette = list(exeFile.read(0x123))
    193.         exeFile.seek(0x7FCFD)
    194.         outrunPalette = list(exeFile.read(0x123))
    195.         exeFile.seek(0x7FE20)
    196.         phantasyStartIIPalette = list(exeFile.read(0x123))
    197.         exeFile.seek(0x7FF43)
    198.         superShinobiPalette = list(exeFile.read(0x123))
    199.         exeFile.seek(0x80066)
    200.         sonicSpinballPalette = list(exeFile.read(0x123))
    201.         exeFile.seek(0x80189)
    202.         vectormanPalette = list(exeFile.read(0x123))
    203.         exeFile.seek(0x802AC)
    204.         cursorAndArrowPalette = list(exeFile.read(0x18))
    205.         exeFile.seek(0x802C4)
    206.         segaLogoPalette = list(exeFile.read(0x6c))
    207.         exeFile.seek(0x80330)
    208.         menuBackgroundPalette = list(exeFile.read(0xA2))
    209.         exeFile.seek(0x803D4)
    210.         titleGraphicPalette = list(exeFile.read(0x240))
    211.         exeFile.seek(0x80614)
    212.         controllerPalette = list(exeFile.read(0x120))
    213.  
    214.     return [
    215.         ImageSectionMetadata((160, 112), 'P', alteredBeastPalette, 'altered beast'),
    216.         ImageSectionMetadata((160, 112), 'P', columnsPalette, 'columns'),
    217.         ImageSectionMetadata((160, 112), 'P', goldenAxePalette, 'golden axe'),
    218.         ImageSectionMetadata((160, 112), 'P', outrunPalette, 'outrun'),
    219.         ImageSectionMetadata((160, 112), 'P', phantasyStartIIPalette, 'phantasy start II'),
    220.         ImageSectionMetadata((160, 112), 'P', superShinobiPalette, 'super shinobi'),
    221.         ImageSectionMetadata((160, 112), 'P', sonicSpinballPalette, 'sonic spinball'),
    222.         ImageSectionMetadata((160, 112), 'P', vectormanPalette, 'vectorman'),
    223.         ImageSectionMetadata((320, 240), 'P', segaLogoPalette, 'sega logo'),
    224.         ImageSectionMetadata((12, 12), 'P', cursorAndArrowPalette, 'cursor'),
    225.         ImageSectionMetadata((17, 136), 'P', cursorAndArrowPalette, 'navigation arrow sheet'),
    226.         ImageSectionMetadata((256, 80), '1', None, 'bitmap font'),
    227.         ImageSectionMetadata((320, 240), 'P', menuBackgroundPalette, 'menu background'),
    228.         ImageSectionMetadata((256, 160), 'P', titleGraphicPalette, 'title graphic'),
    229.         ImageSectionMetadata((64, 288), 'P', controllerPalette, 'controller sheet'),
    230.         ImageSectionMetadata((8, 128), 'P', controllerPalette, 'controller button label sheet')
    231.     ]
    232.  
    233. if __name__=="__main__":
    234.     main()
    235.  
    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.
    sonic2arcadeMode.png

    It seems if you want to save games, you must replace a game in the collection that had saving.
     
    Last edited: Jun 27, 2020
    • Informative Informative x 1
    • List
  15. ZaedYhen

    ZaedYhen

    Member
    8
    1
    3
    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:
    vectorMan2SmashPack.png
    Correct:
    vectorMan2Correct.png

    Smash Pack 2 (forced to load unpatched ROM as different title):
    comixzonebug.png

    Correct:
    comix000.png

    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)
    Code (Text):
    1. 0x001e5c54      06450071       addi.w 0x71, d5
    2. 0x001e5c58      383c0080       move.w 0x80, d4
    3. 0x001e5c5c      303c0d00       move.w 0xd00, d0
    4. 0x001e5c60      7409           moveq 0x9, d2
    5. 0x001e5c62      4eb9001cd2a4   jsr 0x1cd2a4.l  
    6. 0x001e5c68      06440020       addi.w 0x20, d4
    7. 0x001e5c6c      51cafff4       dbra d2, 0x1e5c62
    8. 0x001e5c70      0c450105       cmpi.w 0x105, d5
    9. 0x001e5c74      6f1e           ble.b 0x1e5c94
    10. x001e5c76      3f05           move.w d5, -(a7)
    11. x001e5c78      3a03           move.w d3, d5
    12. x001e5c7a      06450080       addi.w 0x80, d5
    13. x001e5c7e      303c0300       move.w 0x300, d0
    14. x001e5c82      7801           moveq 0x1, d4
    15. x001e5c84      4eb9001cd2a4   jsr 0x1cd2a4.l  
    16. x001e5c8a      7800           moveq 0x0, d4
    17. x001e5c8c      4eb9001cd2a4   jsr 0x1cd2a4.l  
    18. x001e5c92      3a1f           move.w (a7)+, d5
    19. 0x001e5c94      383c0080       move.w 0x80, d4
    20. 0x001e5c98      06450012       addi.w 0x12, d5
    21. 0x001e5c9c      303c0f00       move.w 0xf00, d0
    22. 0x001e5ca0      5041           addq.w 0x8, d1
    23. 0x001e5ca2      7409           moveq 0x9, d2
    24. 0x001e5ca4      4eb9001cd2a4   jsr 0x1cd2a4.l  
    25. 0x001e5caa      06440020       addi.w 0x20, d4
    26. 0x001e5cae      51cafff4       dbra d2, 0x1e5ca4
    27. 0x001e5cb2      4eb9001cd254   jsr 0x1cd254.l  
    28.  
    Code (Text):
    1. 0x001e5c54      605c           bra.b 0x1e5cb2            
    2. 0x001e5c56      0071383c0080   ori.w 0x383c, -0x80(a1, d0.w)
    3. 0x001e5c5c      303c0d00       move.w 0xd00, d0
    4. 0x001e5c60      7409           moveq 0x9, d2
    5. 0x001e5c62      4eb9001cd2a4   jsr 0x1cd2a4.l            
    6. 0x001e5c68      06440020       addi.w 0x20, d4
    7. 0x001e5c6c      51cafff4       dbra d2, 0x1e5c62
    8. 0x001e5c70      0c450105       cmpi.w 0x105, d5
    9. 0x001e5c74      6f1e           ble.b 0x1e5c94
    10. 0x001e5c76      3f05           move.w d5, -(a7)
    11. 0x001e5c78      3a03           move.w d3, d5
    12. 0x001e5c7a      06450080       addi.w 0x80, d5
    13. 0x001e5c7e      303c0300       move.w 0x300, d0
    14. 0x001e5c82      7801           moveq 0x1, d4
    15. 0x001e5c84      4eb9001cd2a4   jsr 0x1cd2a4.l
    16. 0x001e5c8a      7800           moveq 0x0, d4
    17. 0x001e5c8c      4eb9001cd2a4   jsr 0x1cd2a4.l
    18. 0x001e5c92      3a1f           move.w (a7)+, d5
    19. 0x001e5c94      383c0080       move.w 0x80, d4
    20. 0x001e5c98      06450012       addi.w 0x12, d5
    21. 0x001e5c9c      303c0f00       move.w 0xf00, d0
    22. 0x001e5ca0      5041           addq.w 0x8, d1
    23. 0x001e5ca2      7409           moveq 0x9, d2
    24. 0x001e5ca4      4eb9001cd2a4   jsr 0x1cd2a4.l            
    25. 0x001e5caa      06440020       addi.w 0x20, d4
    26. 0x001e5cae      51cafff4       dbra d2, 0x1e5ca4
    27. 0x001e5cb2      4eb9001cd254   jsr 0x1cd254.l            
    28.  
     
  16. ZaedYhen

    ZaedYhen

    Member
    8
    1
    3
    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.
     
    Last edited: Jun 30, 2020
    • Informative Informative x 1
    • List