Skip to content

Instantly share code, notes, and snippets.

@zaksabeast
Created January 19, 2021 00:50
Show Gist options
  • Save zaksabeast/600fdd9579aaa4dde5b93f7207ea6550 to your computer and use it in GitHub Desktop.
Save zaksabeast/600fdd9579aaa4dde5b93f7207ea6550 to your computer and use it in GitHub Desktop.
A heavily commented script to find Wishmaker Jirachi seeds
// This code is meant to act as documentation for calculating Wishmaker Jirachi seeds.
// As a result, it's not the most efficient code, but can hopefully serve as a guide for anyone interested in the mechanics.
//
// Admittedly, I wrote this fairly quickly, so it's not quite as clean as I'd like it either, and I haven't tested it too much.
// If you have any questions or updates, please leave them in the comments below.
//
// This code assumes some knowledge of Wishmaker, as well as a disabled RTC.
// ----------------------------------------
// Jirachi seed logic
// ----------------------------------------
/**
* Calculates the checksum of a block
* @param {DataView} blockView
*/
const getBlockChecksum = (blockView) => {
let checksum = 0;
for (let i = 0; i < blockView.byteLength; i += 4) {
checksum += blockView.getUint32(i, true);
}
return ((checksum >>> 16) + checksum) & 0xffff;
};
/**
* Calculates the checksum of the constant u32s in a block
* @param {DataView} blockView
*/
const getChecksumConstant = (blockView) => {
let checksum = 0;
for (let i = 0; i < blockView.byteLength; i += 4) {
// Skip time bytes - we want to manipulate those
if (i === 0xc || i === 0x10) {
continue;
}
checksum += blockView.getUint32(i, true);
}
return checksum & 0xffffffff;
};
// Tests for finding seed parameters:
// - Test `carry` does not give the wrong result if if `frame` is 0
// - Test `carry` does not give the wrong result if `frame` and `hourLow` are 0
// - Test what happens if hour = 999 (max)
// - Test what happens if hour = 889 (999 - 111 + 1)
// - Test what happens if hour = 888 (safe)
// - Test what happens if hour = 0 (safest)
// - Test 0 <= hour < 1000, 0 <= minute < 60, 0 <= second < 60, 0 <= frame < 60
// - Test results are always in the future
/**
* Finds parameters that will produce a given seed
* @param {number} currentHour The save's current u16 hour between 0-999
* @param {number} currentMinute The save's current u8 minute between 0-59
* @param {number} chkConst The u32 block checksum for all constant values that won't be modified
* @param {number} seed The desired seed
*/
const findParamForSeed = (currentHour, currentMinute, chkConst, seed) => {
// This is used later to check if a carry will occur and needs to be accounted for
const chkConstLow = chkConst & 0xffff;
const chk = (chkConst >>> 16) + chkConstLow;
// Our controlled parameters need to create this value
const params = ((seed - chk) >>> 0) & 0xffff;
// Split the params to calculate the variables we control
const paramsHigh = params >>> 8;
const paramsLow = params & 0xff;
// Calculate the max value that can be added to (hour & 0xff)
// 0xff - 59 - 59 = 144
const maxHourLow = Math.min(999 - currentHour, 144);
// Check for an impossible value
// frame and minute have a max of 59
// 59 + 59 = 111
// If (hour & 0xff) cannot be manipulated to be <= 111, this is an impossible seed
if (paramsLow - maxHourLow > 111) {
return null;
}
// This will be added to the current hour later
let hourAddLow = Math.max(paramsLow - maxHourLow, 0);
let minute = Math.max(paramsLow - hourAddLow - 59, 0);
let frame = paramsLow - hourAddLow - minute;
// Set minute as low as possible for faster RNG
// I'd rather wait 10 minutes, 50 frames than 50 minutes, 10 frames
[minute, frame] = [minute, frame].sort();
// This will be added to the current hour later
const hourAddHigh = Math.max(paramsHigh - 59, 0);
const second = paramsHigh - hourAddHigh;
// Because the u32 checksum is split into two u16s and added
// checksum & 0xffff + (second << 8) + frame might be greater than 0xffff
// The carry needs to be accounted for
const carry = ((second << 8) + frame + chkConstLow) >>> 16;
// Subtract the carry from the hour is the hour < 0
const hourCarry = hourAddLow > 0 ? carry : 0;
// Subtract the carry from the frame if hour === 0
const frameCarry = carry - hourCarry;
hourAddLow -= hourCarry;
frame -= frameCarry;
// Carry creates impossible scenario
if (frame < 0) {
return null;
}
// If the time is in the past, shift the hour and frame (hourLow and frame are interchangeable for the checksum)
if (hourAddLow === 0 && minute < currentMinute) {
// Can't shift - impossible time
if (frame <= 0) {
return null;
}
hourAddLow += 1;
frame -= 1;
}
const hour = (hourAddHigh << 8) + hourAddLow + currentHour;
if (hour > 999 || second > 59) {
return null;
}
return {
seed: seed.toString(16),
hour,
minute,
second,
frame,
};
};
const BLOCK_0_SIZE = 0x890;
/**
* Get block 0 of a save file
* @param {Uint8Array} save
*/
const getBlock0View = (save) => {
// Get the index of the first save block tbat appears in the save file
const firstBlockIndex = save[0xff4];
// There are 14 blocks total, each 0x1000 in size
// Find where block 0 is in the save file
const block0StartOffset = (14 - firstBlockIndex) * 0x1000;
const block0EndOffset = block0StartOffset + BLOCK_0_SIZE;
// Create a data view to access LE u16 and u32 values
return new DataView(Uint8Array.from(save.slice(block0StartOffset, block0EndOffset)).buffer);
};
/**
* Find parameters for multiple seeds
* @param {DataView} block0View The save to calculate seeds for
* @param {number[]} seeds The seeds to find params for
*/
const findParamsForSeeds = (block0View, seeds) => {
// A save block's checksum is made by adding all the u32s in the block together
// These values are part of the u32s we can modify to influence the seed
// The SID is part of a u32 we can modify, but we can't modify the SID by itself
const sid = block0View.getUint16(0xc, true);
const hour = block0View.getUint16(0xe, true);
const shiftedHour = hour << 16;
const minute = block0View.getUint8(0x10);
// These are here for documentation
// We don't need to account for them since their values can be reset
// const second = block0View.getUint8(0x11) << 8;
// const frame = block0View.getUint8(0x12) << 16;
const optionLR = block0View.getUint8(0x13) << 24;
// This is the current checksum of the constant values in the save file
const chkConst = getChecksumConstant(block0View) + sid + shiftedHour + optionLR;
return seeds
.map((seed) => findParamForSeed(hour, minute, chkConst, seed))
.filter((param) => param !== null);
};
// ----------------------------------------
// Testing the logic with a save file
// ----------------------------------------
const fs = require('fs');
// Open the save file
const save = fs.readFileSync('./wishmaker.sav', { encoding: null });
const block0View = getBlock0View(save);
const SHINY_SEEDS = [0x353d, 0xf500, 0xecdd, 0x9359, 0xcf37, 0x7236, 0xa030, 0x7360, 0x3d60];
findParamsForSeeds(block0View, SHINY_SEEDS).forEach((param) => {
block0View.setUint16(0xe, param.hour, true);
block0View.setUint8(0x10, param.minute);
block0View.setUint8(0x11, param.second);
block0View.setUint8(0x12, param.frame);
block0View.setUint8(0x13, param.optionLR);
// Calulate the new checksum to verify the result
console.log(getBlockChecksum(block0View).toString(16), param);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment