cv2r - Castlevania II: Simon's Quest Randomizer by BloodSweatAndCode. Come find me running this and other retro games on Twitch!
WARNING This is still pre-alpha
Castlevania II: Simon's Quest Randomizer by BloodSweatAndCode Usage cv2r [options] [vanilla_rom_file] Examples # Show this help node .\bin\cv2r --help # Generate a rom with the seed "BSAC" at standard difficulty node .\bin\cv2r --seed BSAC cv2.nes # Generate a rom with the seed "BurbAndSath" at hard difficulty node .\bin\cv2r --seed BurbAndSath --difficulty hard cv2.nes # Generate a rom with a random seed using the "Rondo of Burb" palette node .\bin\cv2r --palette rondo-of-burb cv2.nes Options -V, --version output the version number -d, --difficulty <difficulty> Difficulty setting: easy, standard, hard -j, --json output patch and spoiler as json, disables all other output -o, --output <output> filepath for randomized rom output -p, --palette <palette> palette selection: rondo-of-burb, simons-quest -r, --run immediately run with emulator after randomizing (fceux or OpenEmu must be in PATH) -s, --seed <seed> seed to use for randomization -z, --debug enable debug output
I plan to make executables so this installation isn't necessary, but until then, follow these steps:
- Install node.js
- Download the latest build of cv2r
- Extract cv2r to a folder
- Copy a vanilla cv2 rom into the cv2r folder as "cv2.nes"
- In a command prompt:
cd /path/to/cv2r npm install # on Windows, "BSAC" is replaced by your seed (any string) node --no-warnings .\bin\cv2r --all --seed BSAC cv2.nes # on Mac/Linux (I've done ZERO Linux testing), "BSAC" is replaced by your seed (any string) ./bin/cv2r --all --seed BSAC cv2.nes
You can now find your new cv2 rando rom in the
Set value $1C to the number of the bank then execute a JSR at the following location. If you want to set it based on the accumulator, subtract 2 from the RAM address, which will in turn set $1C to the value of the accumulator.
$4F is selected quest item $90 is selected weapon/carry item
progression logic decisions
- Whips will be upgraded progressively. No quick jump to morning star or flame whip.
- Crystals will also be encountered progressively to prevent potential super boring back tracking.
- The 3 block whip jump in Camilla's Cemetery is NOT in logic. This means red crystal is required for Bodley, Laruba, Doina, etc...
- Laurels and Holy Water are considered required for Laruba Mansion
- Laurels are NOT considered required to cross the swamp in Belasco Marsh
code locations for all actors that can hold items
$7F value is set after the code executes for the given item/weapon/whatever.
|merchant (weapon/item)||EDD8||1EDE8||values start at $EE12 RAM, $1EE22 ROM|
|merchant (whip)||EDF4||1EE04||$7F is #33 (thorn), #34 (chain), or #35 (morning star)||($40 AND #1F) - #7 = $434|
|crystal dude (blue)||906F||507F||$7F is 0x55 on accept, 0x6B on reject (text???)|
|crystal dude (red)||9088||5098||$7F is 0x56 on accept, 0x6B on reject (text???)|
|orb||8794||47A4||$3BA = #25, $4C2,X=???, $8632,Y=($91 values)|
|laurel dude (laruba)||9347||5357||$7F = #78|
|flame whip dude||8C72||4C82||$7F = #75|
|diamond dude||AA3A||6A4A||$7F = #12|
|secret merchant (silver knife)||AE12||6E22||$7F = #10|
|secret merchant (silk bag)||AE07||6E17||$7F = #0F|
|Death||87C7||47D7||$3BA = #44 (#49 for knife)||still appears as knife no matter what item it actually gives you|
|Camilla||87BF||47CF||$3BA = #42 (#?? for cross)||still appears as cross no matter what item it actually gives you|
|sacred flame||87CD||47DD||still appears as flame no matter what item it actually gives you (bank 1)|
progressive whips and crystals
$D0will track in each bit whether or not you've already received a progressive upgrade from a particular actor.
unused but interesting values
(ROM) 0x1EE25 is 0x092010, which seems to indicate it's a carryable item, but 0x10 doesn't match up with the bit flags currently mapped.
0xCC84 is where all the text starts in the ROM. First one is "What a horrible night to have a curse".
- Dead River - Part 2 and Dead River to Brahm share the same actors, so this needs to be accounted for during any randomizing that involves the actors in those screens. Essentially, unless otherwise handled, Dead River - Part 2 will take precedence since it is defined after Dead River to Brahm in the
npc.crystalDudeis the same as the (secret) merchant in the non-town pattern tables
pattern table re-mapping
I had to store new 1 byte values (high 4 bits are bg table, low 4 bits are sprite table) for every screen in the game. These needed to stored in a space that could be accessed mathematically based on 3 values: objset ($30), area ($50), and submap ($51 AND #$0xF). Those 3 values together compose a unique reference to every screen. The table below shows how I allocated free bytes in the ROM to essentially store a multi-dimensional array.
|objset||# of areas||# of submaps||map storage|
3, 5, 3
101 11000 101
0 + 0 + 0 + 0x35
So I'm mapping 167 bytes, only using 93 of those bytes in total, thereby wasting 74 bytes as unused space between the valid values of the mult-dimensional array. The pseduo-code calculation below shows roughly how, stored in this manner, I can access any bg/sprite table index at any time with the objset, area, and submap values:
// OBJ_OFFSET is 0x30, as there's equal space between each objset in the stored bytes. (objset * OBJ_OFFSET) + (area * num_of_submaps_for_this_area) + submap
With the values stored, we then hijack the typical rudimentary pattern table mapping code. We write to unused ROM space at
0x7860 the compiled opcodes of the 6502 asm contained in the file at
data/pattern.asm. This code is responsible for executing the calculation above and then properly selecting a sprite table index based on it. All we need to do after that is overwrite 3 particular bytes of code to point to our new code. Essentially we replace the code at
0x1CCDF with the following:
JSR $B850: 20 50 B8
and voila! Each screen now has it's own pattern table mapping! Here's the original mapping, which shows there were only 6 options (instead of the 93 possible options we have now). Here we can see what background and sprite table indexes correspond to what enemy groupings.
sale icons and prices
Code for determining the sale icons and prices is at
0x1ED46 ROM (
07:ED36 in RAM). Mapped bank is 3 for these calls.
red crystal tornado at 01:A956 ram, 6966 rom core function 07:C0E7
// merchant purchasable items: 0x1ee22-0x1ee39
// code for blue crystal guy at 1:906F(RAM) 0x507F(ROM)
// code for red crystal guy at 1:9088(RAM) 0x5098(ROM)
// 07:C08A function for red crystal
// F7 is dpad
// 0x1F6AD is where the dialog starts, 0x0E 0x00 means 0x00 14 times
// 0x83CC code specific to merchants? // 0x58,0x59 map -- 0x5E,0x5F ??? // 0x80EC - code that stores npc "type", value at 0x9140 for thorn
// 0x3ba is 0x2E which is the lower 7 bits of 0xAE which is the actor "type" // 0x4da is the actor "value" 0x08 // 0x34e is 0xC0 // 0x32a is 0xC0 // 0x3de is 0x08 // 0x438 is 0x01
// $1F6F7 is the price line in dialog ROM
// 0x414 is 0xOB after a pointer chek on 0x3ba when entering room // 0x414 times 3 (0x21) goes into 0x94, // then ised to point to 0xDDC3 = (01 1e 0c) // 1e goes into 0x306, 0c goes into 3f0 //
// $40 is 0x28, gets set to 0x93 // 0x888D is where whip is checked for merchant chat
// $1ED3D in ROM is 0x5B, which is the whip icon, gets stored at $703 // the format is 3 bytes for each entry. The first byte is the // icon for sale (index for background PPU table). The second and third // bytes are the price. PRICE RANDO! // prices: 0x1ED25-0x1ED45 // price high byte gets stored at $29, $93 // price low byte gets stored at $28
// 0xE09C is where 0x3de AND 0xf7, which excludes only values with the 4th bit // set, meaning merchants that sell whips. This gets put in 0x3de, or zero
// $f6e7 for line in dialog for dashes and hearts // 0x1EFE2 is where yes/no dialog choice happens (0xEFD2 RAM)
// $E08C (RAM) watch me whip?
// $40 and $93 determine icon for sale // $1D is 0x2E, $18 is 0x05
// $7A // * 0-3 == presenting dialog background // * 4 == printing dialog text // * 5 == non-interactive text end, waiting for B press // * 6 == waiting for yes/no choice
// $7E // * 0 == start menu // * 1 == no interaction dialog // * 2 == yes/no dialog
// $F1 is dpad
// ECC7 is where we move to the whip icon, C5D0 is where pointer is set // EE9D is the dialog text loop
// 7A increments after dialog completes (5), i think it managed dialog state // does $164 hold this value at any stage?
// C058 checks $1A, if Z flag not set, skips all merchant logic!
// $7A needs to be 0x01 for normal dialog
// $103???? at C)FB
// Change $3BA (or whatever near location has the merchant 0x2E) to the value 0x35. // The merchant then moves like a normal NPC and will deliver an npc dialog with no // yes/no interaction. Dialog now points to the unused "you level has increased..." // messages, which should be easy to change.
// Using $7F as a unique ID for actors doesn't work because the multiple // laurel and garlic merchants share the same value. Additionally, laurel // merchants have a $7F value of 0x00, which is intended to be an empty value // in the current progressive whip upgrade logic, so that will have to // change.
// whip sale code 0xEDF4 RAM, 0x1EE04 ROM
// palette hacking // $82 day/night? 1/0 // $C7E7 is where day/night is checked