Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
386 lines (344 sloc) 11.5 KB
#Lets Randomize Relics
#Made by setz
#@splixel on twitter
#twitch.tv/skiffain
#too lazy for licenses, pretend I attached WTFPL
#(do what the fuck you want with this)
#error_recalc comes from https://www.romhacking.net/utilities/1264/
#Conditions for flight are one of the following
#Soul of Bat (ez mode)
#Gravity Boots + Leap Stone (chaining gravity jumps)
#Form of Mist + Power of Mist (fly as mist)
#Requirements for accessing castle 2 are
#Flight
#Jewel of Open
#Mist
import random
from subprocess import call
from binascii import hexlify
from datetime import datetime
FileName = "Castlevania - Symphony of the Night (USA) (Track 1).bin"
#RandomizeBossRelics = False #Not Supported
ShowItemPlacements = True #Print out a log of when items are placed
#RandoSeed = 1234567890
RandoSeed = datetime.time(datetime.now())
#Ability Checks
HasLeapStone = False
HasGravityBoots = False
HasJewelOfOpen = False
HasMist = False
HasPowerOfMist = False
HasBat = False
HasWolf = False
HasSonar = False
HasMermanStatue = False
#TODO
#accept args
#args: Player Seed input
#args for other options
#Options to not randomize boss relics
#Copy files instead of just overwriting
#do a hash check to ensure its editing the right file
#possibly make a small frontend for it?
#Known Bugs
#need to trace relics from left/right as well as up/down because of how sotn loads entities
#List of known delinquents
#Cube Of Zoe
#Spirit Orb
#Farie Scroll
#Leap Stone
#Some relics have doubles, so..
#Relic ID/Name #RelicLocation ID #Cant Be Behind RL ID No-Gos
#00 Soul of Bat 00
#01 Fire of Bat 01
#02 Echo of Bat 02 Castle 2 18 19 1a 1b 1c
#03 Force of Echo 03
#04 Soul of Wolf 04
#05 Power of Wolf 05
#05 Power of Wolf 05
#06 Skill of Wolf 06
#07 Form of Mist 07 Castle 2, Mist Gates 18 19 1a 1b 1c 00
#08 Power of Mist 08
#09 Gas Cloud 09
#0A Cube of Zoe 0a
#0A Cube of Zoe 0a
#0B Spirit Orb 0b
#0C Gravity Boots 0c
#0D Leap Stone 0d
#0E Holy Symbol 0e
#0F Faerie Scroll 0f
#10 Jewel of Open 10 Castle 2, Jewel Doors 18 19 1a 1b 1c 0d 0e 11 15
#11 Merman Statue 11 Holy Snorkel Location 0e
#12 Bat Card 12
#13 Ghost Card 13
#14 Faerie Card 14
#15 Demon Card 15
#16 Sword Card 16
#17 Sprite Card --
#18 Nosedevil Card --
#19 Heart of Vlad 17
#19 Heart of Vlad 17
#1A Tooth of Vlad 18
#1A Tooth of Vlad 18
#1B Rib of Vlad 19
#1B Rib of Vlad 19
#1C Ring of Vlad 1a
#1C Ring of Vlad 1a
#1D Eye of Vlad 1b
#1D Eye of Vlad 1b
RelicLocation = [0x047a5b66, 0x0557535e, 0x04aa4156, 0x0526e6a8, 0x049d6596, 0x04b6b9b4, 0x054b1d5a, 0x043c578a,
0x05610db8, 0x04cfcb16, 0x04b6b946, 0x048fd1fe, 0x048fc9ba, 0x05610dc2, 0x04c34ee6, 0x047a5720,
0x047a321c, 0x04c35174, 0x054b1d58, 0x05611958, 0x047a5784, 0x045ea95e, 0x04aa3f76, 0x06306ab2,
0x05051d52, 0x069d2b1e, 0x059bdb30, 0x04da65f2]
DoubleLocation = [0, 0, 0, 0, 0, 0x053f971c, 0, 0,
0x0561142C, 0, 0x053F969A, 0, 0, 0, 0, 0,
0, 0, 0, 0x0561127c, 0, 0, 0, 0x04e335b4,
0x067d1630, 0x050fa914, 0x059ee2e4, 0x0662263a]
TripleLocation = [0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0x04b6b08a, 0x048fe280, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0]
QuadrupleLocation = [0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0x053f8e2e, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0]
RelicList = []
RelicList = [bytearray([0x00]),
bytearray([0x01]),
bytearray([0x02]),
bytearray([0x03]),
bytearray([0x04]),
bytearray([0x05]),
bytearray([0x06]),
bytearray([0x07]),
bytearray([0x08]),
bytearray([0x09]),
bytearray([0x0a]),
bytearray([0x0b]),
bytearray([0x0c]),
bytearray([0x0d]),
bytearray([0x0e]),
bytearray([0x0f]),
bytearray([0x10]),
bytearray([0x11]),
bytearray([0x12]),
bytearray([0x13]),
bytearray([0x14]),
bytearray([0x15]),
bytearray([0x16]),
bytearray([0x19]),
bytearray([0x1a]),
bytearray([0x1b]),
bytearray([0x1c]),
bytearray([0x1d])]
RelicsUsed = []
LocationsUsed = []
for i in range(0, len(RelicList)):
RelicsUsed.append(False)
LocationsUsed.append(False)
def ReplaceByte(ByteLocation, NewByte):
with file(FileName, "r+b") as HackThisRom:
HackThisRom.seek(ByteLocation)
HackThisRom.write(NewByte)
return True
def PlaceItem(Item, Location):
if ShowItemPlacements:
print("Placing Item: "+hexlify(RelicList[Item]))
#print("Location a: "+str(hex(RelicLocation[Location])))
ReplaceByte(RelicLocation[Location], RelicList[Item])
if (DoubleLocation[Location] != 0):
#if ShowItemPlacements:
# print("Location b: "+str(hex(DoubleLocation[Location])))
ReplaceByte(DoubleLocation[Location], RelicList[Item])
if (TripleLocation[Location] != 0):
#if ShowItemPlacements:
# print("Location b: "+str(hex(DoubleLocation[Location])))
ReplaceByte(TripleLocation[Location], RelicList[Item])
if (QuadrupleLocation[Location] != 0):
#if ShowItemPlacements:
# print("Location b: "+str(hex(DoubleLocation[Location])))
ReplaceByte(QuadrupleLocation[Location], RelicList[Item])
#Check abilities if possible
global HasJewelOfOpen
global HasLeapStone
global HasMist
global HasPowerOfMist
global HasGravityBoots
global HasBat
global HasWolf
global HasSonar
global HasMermanStatue
if Item == 0x10:
HasJewelOfOpen = True
elif Item == 0xd:
HasLeapStone = True
elif Item == 0x7:
HasMist = True
elif Item == 0x8:
HasPowerOfMist = True
elif Item == 0xc:
HasGravityBoots = True
elif Item == 0x4:
HasWolf = True
elif Item == 0x0:
HasBat = True
elif Item == 0x2:
HasSonar = True
elif Item == 0x11:
HasMermanStatue = True
#Mark as used
RelicsUsed[Item] = True
LocationsUsed[Location] = True
return True
def FindUnplacedRelic():
#print(len(RelicList))
RandIndex = random.randint(0, len(RelicList)-1)
if RelicsUsed[RandIndex]:
return FindUnplacedRelic()
else:
return RandIndex
def FindUnplacedLocation(InputArray):
RandIndex = InputArray[random.randint(0, len(InputArray)-1)]
if LocationsUsed[RandIndex]:
return FindUnplacedLocation(InputArray)
else:
return RandIndex
def SoftUnlock():
#print(str(HasJewelOfOpen)+" | "+str(HasLeapStone)+" | "+str(HasMist)+" | "+str(HasPowerOfMist)+" | "+str(HasGravityBoots)+" | "+str(HasBat)+" | "+str(HasSonar)+" | "+str(HasMermanStatue))
#List of available locations
LocationsAvailable = []
#Starting Areas
if LocationsUsed[0x04] == False:
LocationsAvailable.append(0x04)
if LocationsUsed[0x0a] == False:
LocationsAvailable.append(0x0a)
if LocationsUsed[0x0b] == False:
LocationsAvailable.append(0x0b)
if LocationsUsed[0x0f] == False:
LocationsAvailable.append(0x0f)
if LocationsUsed[0x10] == False:
LocationsAvailable.append(0x10)
#Restricted Areas
if HasMist and (HasLeapStone or HasGravityBoots or HasBat):
#Soul of Bat Vanilla
if LocationsUsed[0x00] == False:
LocationsAvailable.append(0x00)
if HasBat or (HasGravityBoots and HasLeapStone) or (HasMist and HasPowerOfMist):
#Flight only
if LocationsUsed[0x01] == False:
LocationsAvailable.append(0x01)
if LocationsUsed[0x05] == False:
LocationsAvailable.append(0x05)
if LocationsUsed[0x08] == False:
LocationsAvailable.append(0x08)
if LocationsUsed[0x0c] == False:
LocationsAvailable.append(0x0c)
if LocationsUsed[0x13] == False:
LocationsAvailable.append(0x13)
if LocationsUsed[0x16] == False:
LocationsAvailable.append(0x16)
if (HasBat or (HasMist and HasPowerOfMist) or (HasGravityBoots and HasLeapStone)) and (HasMist or HasWolf or HasBat):
if LocationsUsed[0x02] == False: #Olrox's Prize
LocationsAvailable.append(0x02)
if HasGravityBoots or HasBat or (HasMist and HasPowerOfMist):
#Gravity Boots or better
if LocationsUsed[0x06] == False:
LocationsAvailable.append(0x06)
if LocationsUsed[0x12] == False:
LocationsAvailable.append(0x12)
if LocationsUsed[0x14] == False:
LocationsAvailable.append(0x14)
if HasLeapStone or HasGravityBoots or HasBat or (HasMist and HasPowerOfMist):
#Leapstone or better
if LocationsUsed[0x07] == False:
LocationsAvailable.append(0x07)
if LocationsUsed[0x0d] == False:
LocationsAvailable.append(0x0d) #Colosseum - only required if leap stone?
if HasJewelOfOpen:
#Bottom of the castle
if LocationsUsed[0x11] == False:
LocationsAvailable.append(0x11)
if HasJewelOfOpen and HasLeapStone or HasBat or (HasMist and HasPowerOfMist):
if LocationsUsed[0x15] == False: #Demon card a bitch
LocationsAvailable.append(0x15)
if HasMermanStatue and HasJewelOfOpen:
#holy snorkel vanilla
if LocationsUsed[0x0e] == False:
LocationsAvailable.append(0x0e)
if HasJewelOfOpen and HasMist and (HasBat or HasPowerOfMist or (HasLeapStone and HasGravityBoots)) and (HasPowerOfMist or HasSonar):
#Castle 2 - Flight, Mist, Jewel of Open, and sonar or power of mist
if LocationsUsed[0x03] == False:
LocationsAvailable.append(0x03)
if LocationsUsed[0x09] == False:
LocationsAvailable.append(0x09)
if LocationsUsed[0x17] == False:
LocationsAvailable.append(0x17)
if LocationsUsed[0x18] == False:
LocationsAvailable.append(0x18)
if LocationsUsed[0x19] == False:
LocationsAvailable.append(0x19)
if LocationsUsed[0x1a] == False:
LocationsAvailable.append(0x1a)
if LocationsUsed[0x1b] == False:
LocationsAvailable.append(0x1b)
ThisRel = FindUnplacedRelic()
if len(LocationsAvailable) == 1:
#Only one location left?
#Check to see if its the last item in the game
#If not, give an item that will unlock more items
#I need to actually think this through and place the correct items, but this will do for now
if HasJewelOfOpen == False:
ThisRel = 0x10
elif HasLeapStone == False:
ThisRel = 0x0D
elif HasGravityBoots == False:
ThisRel = 0x0C
elif HasBat == False:
ThisRel = 0x00
elif HasMist == False:
ThisRel = 0x07
elif HasMermanStatue == False:
ThisRel = 0x11
else:
ThisRel = FindUnplacedRelic()
ThisLoc = FindUnplacedLocation(LocationsAvailable)
#Items are never allowed in these locations
if ThisRel == 0x02:
if ThisLoc == 0x18 or ThisLoc == 0x19 or ThisLoc == 0x1a or ThisLoc == 0x1b or ThisLoc == 0x1c:
return SoftUnlock()
elif ThisRel == 0x07:
if ThisLoc == 0x18 or ThisLoc == 0x19 or ThisLoc == 0x1a or ThisLoc == 0x1b or ThisLoc == 0x1c or ThisLoc == 0x00:
return SoftUnlock()
elif ThisRel == 0x10:
if ThisLoc == 0x18 or ThisLoc == 0x19 or ThisLoc == 0x1a or ThisLoc == 0x1b or ThisLoc == 0x1c or ThisLoc == 0x0d or ThisLoc == 0x0e or ThisLoc == 0x11 or ThisLoc == 0x15:
return SoftUnlock()
elif ThisRel == 0x11:
if ThisLoc == 0x0e:
return SoftUnlock()
retval = [ThisRel, ThisLoc]
return retval
def main():
print("Sotn Relic Randomizer")
print("Your file name should be \""+FileName+"\"")
print("To show spoilers, edit the script and set ShowItemPlacements to True")
print("If this is your first time running, you will need to download error_recalc.exe and put it in the same directory as this script. You can grab it here: https://www.romhacking.net/utilities/1264/")
print("")
random.seed(RandoSeed)
print("Seed is \""+str(RandoSeed)+"\"")
#Do some shuffling things, make sure things arent impossible to access
#Make things always possible later
print("Shuffling Relics..")
#Place the rest of the items
for i in range(0, len(RelicList)):
PlsNoSoftlock = SoftUnlock()
ThisRelic = PlsNoSoftlock[0]
ThisLocation = PlsNoSoftlock[1]
PlaceItem(ThisRelic, ThisLocation)
print("Bytes Written, Fixing ECC..")
#Windows
#call(["error_recalc.exe", FileName, "1"])
#Not Windows
call(["wine", "error_recalc.exe", FileName, "1"])
print("Done")
if __name__ == '__main__':
main()
You can’t perform that action at this time.