Skip to content

Commit

Permalink
Add SOS to A-OK converter
Browse files Browse the repository at this point in the history
  • Loading branch information
Karthik99999 committed Feb 23, 2024
1 parent 5e97aa8 commit 4d88ee2
Show file tree
Hide file tree
Showing 12 changed files with 335 additions and 117 deletions.
9 changes: 9 additions & 0 deletions src/lib/generators/bitstream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,12 @@ export class BitstreamWriter {
}
}
}

export function bitpack(bits: number[], oldSize: number, newSize: number) {
const newBits: number[] = [];
const reader = new BitstreamReader(bits, oldSize);
while (reader.remaining()) {
newBits.push(reader.read(newSize));
}
return newBits;
}
1 change: 1 addition & 0 deletions src/lib/generators/rt/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { generateWonderMail } from './wm/generate';
export { convertSOSToAOK, convertAOKToThankYou } from './sos/generate';
export { default as data } from './data';
88 changes: 88 additions & 0 deletions src/lib/generators/rt/sos/generate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { bitpack, BitstreamWriter } from '../../bitstream';
import { bitsToChars, checksum } from '../utils';
import { deserialize } from './read';
import Data from '../data';

function shuffle(password: string[]): string[] {
const newPassword: string[] = [];
const shuffledIndexes = [
23, 16, 37, 45, 4, 41, 52, 1, 8, 39, 25, 36, 47, 0, 12, 3, 33, 20, 28, 9, 49, 53, 51, 31, 11, 2, 13, 14, 34, 5, 46,
27, 17, 18, 19, 29, 38, 48, 22, 32, 42, 15, 6, 26, 30, 10, 44, 50, 35, 7, 40, 21, 43, 24,
];
for (let i = 0; i < shuffledIndexes.length; i++) {
newPassword[i] = password[shuffledIndexes[i]];
}
return newPassword;
}

interface PasswordData {
mailType: number;
dungeon: number;
floor: number;
unknown1?: [number, number, number];
pokemon: number;
mailID?: number;
nickname: number[];
itemReward?: number;
teamSeekingHelp: number;
teamGivingHelp?: number;
chancesRemaining: number;
}

function serialize(data: PasswordData) {
const writer = new BitstreamWriter(8);
writer.write(data.mailType, 4);
writer.write(data.dungeon, 7);
writer.write(data.floor, 7);
if (data.unknown1) {
for (const n of data.unknown1) writer.write(n, 8);
} else {
for (let i = 0; i < 3; i++) writer.write(0, 8);
}
writer.write(data.pokemon, 9);
writer.write(data.mailID || 0, 32);
for (let i = 0; i < 10; i++) {
if (i < data.nickname.length) {
writer.write(data.nickname[i], 8);
} else {
writer.write(0, 8);
}
}
writer.write(data.mailType === 5 ? 1 : 0, 8); // Unknown 2
writer.write(data.itemReward || 0, 8);
writer.write(0, 8); // Unknown 3
writer.write(data.teamSeekingHelp, 32);
writer.write(data.teamGivingHelp || 0, 32);
writer.write(data.chancesRemaining, 8);
writer.write(0, 1); // Unknown 4
writer.write(0, 4); // Pad to 33 bytes

const code = writer.finish();
code.unshift(checksum(code));
const bits = bitpack(code, 8, 5);
const chars = bitsToChars(bits);
const shuffled = shuffle(chars);
return shuffled.join('');
}

export function convertSOSToAOK(password: string) {
const data = deserialize(password);
if (data.inclChecksum !== data.calcChecksum) throw new Error('Invalid password');
if (data.mailType !== 1) throw new Error('The given password is not an SOS Mail');

data.mailType = 4;
data.teamGivingHelp = data.teamSeekingHelp;
data.chancesRemaining = Math.max(data.chancesRemaining - 1, 0);
return serialize(data);
}

export function convertAOKToThankYou(password: string, itemReward = 0) {
const data = deserialize(password);
if (data.inclChecksum !== data.calcChecksum) throw new Error('Invalid password');
if (data.mailType !== 4) throw new Error('The given password is not an A-OK Mail');
if (itemReward && !Data.items.get(itemReward).valid) throw new Error('Invalid item reward');

data.mailType = 5;
data.itemReward = itemReward;
return serialize(data);
}
83 changes: 83 additions & 0 deletions src/lib/generators/rt/sos/read.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { bitpack, BitstreamReader } from '../../bitstream';
import { sanitizePassword, charsToBits, checksum } from '../utils';

function unshuffle(password: string[]): string[] {
const newPassword: string[] = [];
const shuffledIndexes = [
23, 16, 37, 45, 4, 41, 52, 1, 8, 39, 25, 36, 47, 0, 12, 3, 33, 20, 28, 9, 49, 53, 51, 31, 11, 2, 13, 14, 34, 5, 46,
27, 17, 18, 19, 29, 38, 48, 22, 32, 42, 15, 6, 26, 30, 10, 44, 50, 35, 7, 40, 21, 43, 24,
];
for (let i = 0; i < shuffledIndexes.length; i++) {
newPassword[shuffledIndexes[i]] = password[i];
}
return newPassword;
}

interface PasswordData {
inclChecksum: number;
calcChecksum: number;
mailType: number;
dungeon: number;
floor: number;
unknown1: [number, number, number];
pokemon: number;
mailID: number;
nickname: number[];
unknown2: number;
itemReward: number;
unknown3: number;
teamSeekingHelp: number;
teamGivingHelp: number;
chancesRemaining: number;
unknown4: number;
}

export function deserialize(password: string): PasswordData {
const sanitized = sanitizePassword(password, 54);
const shuffled = unshuffle(sanitized);
const bits = charsToBits(shuffled);
const code = bitpack(bits, 5, 8);

const reader = new BitstreamReader(code.slice(1), 8);

const inclChecksum = code[0];
const calcChecksum = checksum(code.slice(1));

const mailType = reader.read(4);
const dungeon = reader.read(7);
const floor = reader.read(7);
const unknown1: [number, number, number] = [reader.read(8), reader.read(8), reader.read(8)];
const pokemon = reader.read(9);
const mailID = reader.read(32);
const nickname = [];
for (let i = 0; i < 10; i++) {
nickname.push(reader.read(8));
}
const unknown2 = reader.read(8);
const itemReward = reader.read(8);
const unknown3 = reader.read(8);
const teamSeekingHelp = reader.read(32);
const teamGivingHelp = reader.read(32);
const chancesRemaining = reader.read(8);
const unknown4 = reader.read(1);

const data: PasswordData = {
inclChecksum,
calcChecksum,
mailType,
dungeon,
floor,
unknown1,
pokemon,
mailID,
nickname,
unknown2,
itemReward,
unknown3,
teamSeekingHelp,
teamGivingHelp,
chancesRemaining,
unknown4,
};
return data;
}
32 changes: 31 additions & 1 deletion src/lib/generators/rt/utils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,41 @@
// prettier-ignore
export const characters = [
const characters = [
'?', '6', '7', 'N', 'P', 'R', '8', '9',
'F', '0', '+', '…', 'S', 'T', 'X', 'Y',
'4', '5', 'M', 'C', 'H', 'J', '-', 'K',
'1', '2', '!', '♀', '3', 'Q', '♂', 'W',
];

/**
* Convert password string to array, then
* validates that all characters of the password are valid
* and replaces common unicode characters with the less common ones
* (m,# -> ♂ | f,% -> ♀ | . -> …)
*/
export function sanitizePassword(password: string, length: number): string[] {
password = password.replace(/\s/g, '');
if (password.length !== length) throw new Error(`Password must be exatly ${length} characters long`);
const sanitized: string[] = [];
for (let char of password) {
char = char.replace(/\./g, '…').replace(/m|#/g, '♂').replace(/f|%/g, '♀');

if (characters.includes(char)) {
sanitized.push(char);
} else {
throw new Error(`Invalid character: ${char}`);
}
}
return sanitized;
}

export function charsToBits(password: string[]): number[] {
return password.map((char) => characters.indexOf(char));
}

export function bitsToChars(code: number[]): string[] {
return code.map((num) => characters[num]);
}

export function checksum(code: number[]): number {
let sum = 0;
for (let i = 0; i < code.length; i++) {
Expand Down
19 changes: 3 additions & 16 deletions src/lib/generators/rt/wm/generate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BitstreamReader, BitstreamWriter } from '../../bitstream';
import { characters, checksum } from '../utils';
import { bitpack, BitstreamWriter } from '../../bitstream';
import { bitsToChars, checksum } from '../utils';
import Data from '../data';

function shuffle(password: string[]): string[] {
Expand All @@ -11,19 +11,6 @@ function shuffle(password: string[]): string[] {
return newPassword;
}

function bitsToChars(password: number[]): string[] {
return password.map((num) => characters[num]);
}

function bitunpack(bits: number[]): number[] {
const unpacked: number[] = [];
const reader = new BitstreamReader(bits, 8);
while (reader.remaining()) {
unpacked.push(reader.read(5));
}
return unpacked;
}

interface WonderMailData {
missionType: number;
clientPokemon: number;
Expand Down Expand Up @@ -54,7 +41,7 @@ export function serialize(data: WonderMailData) {

const code = writer.finish();
code.unshift(checksum(code));
const bits = bitunpack(code);
const bits = bitpack(code, 8, 5);
const chars = bitsToChars(bits);
const shuffled = shuffle(chars);
return shuffled.join('');
Expand Down
55 changes: 4 additions & 51 deletions src/lib/generators/rt/wm/read.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,6 @@
import { BitstreamReader } from '../../bitstream';
import { characters, checksum } from '../utils';
import { bitpack, BitstreamReader } from '../../bitstream';
import { sanitizePassword, charsToBits, checksum } from '../utils';

/**
* Convert password string to array, then
* validates that all characters of the password are valid
* and replaces common unicode characters with the less common ones
* (m,# -> ♂ | f,% -> ♀ | . -> …)
*/
function sanitizePassword(password: string): string[] {
password = password.replace(/\s/g, '');
if (password.length !== 24) throw new Error('Password must be exatly 24 characters long');
const sanitized: string[] = [];
for (let char of password) {
char = char.replace('.', '…').replace('m', '♂').replace('#', '♂').replace('f', '♀').replace('%', '♀');

if (characters.includes(char)) {
sanitized.push(char);
} else {
throw new Error(`Invalid character: ${char}`);
}
}
return sanitized;
}

/**
* Unshuffles the 24 character password based on a predefined lookup table
*/
function unshuffle(password: string[]): string[] {
const newPassword: string[] = [];
const shuffledIndexes = [12, 20, 9, 17, 4, 15, 1, 23, 3, 7, 19, 14, 0, 5, 21, 6, 8, 18, 11, 2, 10, 13, 22, 16];
Expand All @@ -35,28 +10,6 @@ function unshuffle(password: string[]): string[] {
return newPassword;
}

/**
* Converts individual characters of the password to a 5 byte number
*/
function charsToBits(password: string[]): number[] {
return password.map((char) => characters.indexOf(char));
}

/**
* Bitpack bits into 8 bytes
*/
function bitpack(bits: number[]): number[] {
const newCode: number[] = [];
const reader = new BitstreamReader(bits, 5);
while (reader.remaining()) {
newCode.push(reader.read(8));
}
return newCode;
}

/**
* Data structure of a wonder mail
*/
interface WonderMailData {
inclChecksum: number;
calcChecksum: number;
Expand All @@ -76,10 +29,10 @@ interface WonderMailData {
}

function deserialize(password: string): WonderMailData {
const sanitized = sanitizePassword(password);
const sanitized = sanitizePassword(password, 24);
const unshuffled = unshuffle(sanitized);
const bits = charsToBits(unshuffled);
const code = bitpack(bits);
const code = bitpack(bits, 5, 8);

const reader = new BitstreamReader(code.slice(1), 8);

Expand Down
16 changes: 2 additions & 14 deletions src/lib/generators/rtdx/generate.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BitstreamReader, BitstreamWriter } from '../bitstream';
import { bitpack, BitstreamWriter } from '../bitstream';
import { symbols, checksum, RNG } from './utils';
import { read } from './read';
import Data from './data';
Expand All @@ -24,18 +24,6 @@ function toSymbols(indexes: number[]): string[] {
return indexes.map((i) => symbols[i]);
}

/**
* Unpacks the code
*/
function bitunpack(bits: number[]): number[] {
const unpacked: number[] = [];
const reader = new BitstreamReader(bits, 8);
while (reader.remaining()) {
unpacked.push(reader.read(6));
}
return unpacked;
}

/**
* Encrypt the code using rng
*/
Expand Down Expand Up @@ -105,7 +93,7 @@ function serialize(data: RescueData | RevivalData): string {
const indexes = writer.finish();
indexes.unshift(checksum(indexes));
const encrypted = encrypt(indexes);
const bitstream = bitunpack(encrypted);
const bitstream = bitpack(encrypted, 8, 6);
const symbols = toSymbols(bitstream);
const code = shuffle(symbols);
return code.join('');
Expand Down
Loading

0 comments on commit 4d88ee2

Please sign in to comment.