879 lines
31 KiB
Python
879 lines
31 KiB
Python
# -*- coding: UTF-8 -*-
|
||
text_addresses = {'Pedestal': (0x180300, 256),
|
||
'Triforce': (0x180400, 256),
|
||
'Uncle': (0x180500, 256),
|
||
'Ganon1': (0x180600, 256),
|
||
'Ganon2': (0x180700, 256),
|
||
'Blind': (0x180800, 256),
|
||
'TavernMan': (0x180C00, 256),
|
||
'Sahasrahla1': (0x180A00, 256),
|
||
'Sahasrahla2': (0x180B00, 256),
|
||
'BombShop1': (0x180E00, 256),
|
||
'BombShop2': (0x180D00, 256),
|
||
'PyramidFairy': (0x180900, 256),
|
||
'EtherTablet': (0x180F00, 256),
|
||
'BombosTablet': (0x181000, 256),
|
||
'Ganon1Invincible': (0x181100, 256),
|
||
'Ganon2Invincible': (0x181200, 256)}
|
||
|
||
|
||
Uncle_texts = [
|
||
'Good Luck!\nYou will need it.',
|
||
'Forward this message to 10 other people or this seed will be awful.',
|
||
'I hope you like your seeds bootless and fluteless.',
|
||
'10\n9\n8\n7\n6\n5\n4\n3\n2\n1\nGo!',
|
||
'I\'m off to visit cousin Fritzl.'
|
||
] * 2 + [
|
||
"We're out of\nWeetabix. To\nthe store!",
|
||
"This seed is\nbootless\nuntil boots.",
|
||
"Why do we only\nhave one bed?",
|
||
"This is the\nonly textbox.",
|
||
"I'm going to\ngo watch the\nMoth tutorial.",
|
||
"This seed is\nthe worst.",
|
||
"Chasing tail.\nFly ladies.\nDo not follow.",
|
||
"I feel like\nI've done this\nbefore...",
|
||
"Magic cape can\npass through\nthe barrier!",
|
||
"If this is a\nKanzeon seed,\nI'm quitting.",
|
||
"I am not your\nreal uncle.",
|
||
"You're going\nto have a very\nbad time.",
|
||
"Today you\nwill have\nbad luck.",
|
||
"I am leaving\nforever.\nGoodbye.",
|
||
"Don't worry.\nI got this\ncovered.",
|
||
"Race you to\nthe castle!",
|
||
"\n hi",
|
||
"I'M JUST GOING\nOUT FOR A\nPACK OF SMOKES",
|
||
"It's dangerous\nto go alone.\nSee ya!",
|
||
"ARE YOU A BAD\nENOUGH DUDE TO\nRESCUE ZELDA?",
|
||
"\n\n I AM ERROR",
|
||
"This seed is\nsub 2 hours,\nguaranteed.",
|
||
"The chest is\na secret to\neverybody.",
|
||
"I'm off to\nfind the\nwind fish.",
|
||
"The shortcut\nto Ganon\nis this way!",
|
||
"THE MOON IS\nCRASHING! RUN\nFOR YOUR LIFE!",
|
||
"Time to fight\nhe who must\nnot be named.",
|
||
"RED MAIL\nIS FOR\nCOWARDS.",
|
||
"HEY!\n\nLISTEN!",
|
||
"Well\nexcuuuuuse me,\nprincess!",
|
||
"5,000 Rupee\nreward for >\nYou're boned",
|
||
"Welcome to\nStoops Lonk's\nHoose",
|
||
"Erreur de\ntraduction.\nsvp reessayer",
|
||
"I could beat\nit in an hour\nand one life",
|
||
"I thought this\nwas open mode?",
|
||
]
|
||
Triforce_texts = [
|
||
'Product has Hole in center. Bad seller, 0 out of 5.',
|
||
'Who stole the fourth triangle?',
|
||
'Trifource?\nMore Like Tritrice, am I right?'
|
||
'\n Well Done!',
|
||
'You just wasted 2 hours of your life.',
|
||
'This was meant to be a trapezoid'
|
||
] * 2 + [
|
||
"\n G G",
|
||
"All your base\nare belong\nto us.",
|
||
"You have ended\nthe domination\nof dr. wily",
|
||
" thanks for\n playing!!!",
|
||
"\n You Win!",
|
||
" Thank you!\n your quest\n is over.",
|
||
" A winner\n is\n you!",
|
||
"\n WINNER!!",
|
||
"\n I'm sorry\n\nbut your\nprincess is in\nanother castle",
|
||
"\n success!",
|
||
" Whelp…\n that just\n happened",
|
||
" Oh hey…\n it's you",
|
||
"\n Wheeeeee!!",
|
||
" Time for\n another one?",
|
||
"and\n\n scene",
|
||
"\n GOT EM!!",
|
||
"\nTHE VALUUUE!!!",
|
||
"Cool seed,\n\nright?",
|
||
"\n We did it!",
|
||
" Spam those\n emotes in\n wilds chat",
|
||
"\n O M G",
|
||
" Hello. Will\n you be my\n friend?",
|
||
" Beetorp\n was\n here!",
|
||
"The Wind Fish\nwill wake\nsoon. Hoot!",
|
||
"meow meow meow\nmeow meow meow\n oh my god!",
|
||
"Ahhhhhhhhh\nYa ya yaaaah\nYa ya yaaah",
|
||
".done\n\n.comment lol",
|
||
]
|
||
BombShop2_texts = ['Bombs!\nBombs!\nBiggest!\nBestest!\nGreatest!\nBoomest!']
|
||
PyramidFairy_texts = ['May I talk to you about our lord and savior, Ganon?']
|
||
Sahasrahla2_texts = ['You already got my item, idiot.', 'Why are you still talking to me?', 'This text won\'t change.', 'Have you met my brother, Hasarahshla?']
|
||
Blind_texts = [
|
||
"I hate insect\npuns, they\nreally bug me.",
|
||
"I haven't seen\nthe eye doctor\nin years",
|
||
"I don't see\nyou having a\nbright future",
|
||
"Are you doing\na blind run\nof this game?",
|
||
"pizza joke? no\nI think it's a\nbit too cheesy",
|
||
"A novice skier\noften jumps to\ncontusions.",
|
||
"the beach?\nI'm not shore\nI can make it.",
|
||
"Rental agents\noffer quarters\nfor dollars.",
|
||
"I got my tires\nfixed for a\nflat rate.",
|
||
"New lightbulb\ninvented?\nEnlighten me.",
|
||
"A baker's job\nis a piece of\ncake.",
|
||
"My optometrist\nsaid I have\nvision!",
|
||
"when you're a\nbaker, don't\nloaf around",
|
||
"mire requires\nether quake,\nor bombos",
|
||
"Broken pencils\nare pointless.",
|
||
"The food they\nserve guards\nlasts sentries",
|
||
"being crushed\nby big objects\nis depressing.",
|
||
"A tap dancer's\nroutine runs\nhot and cold.",
|
||
"A weeknight is\na tiny\nnobleman",
|
||
"The chimney\nsweep wore a\nsoot and tye.",
|
||
"Gardeners like\nto spring into\naction.",
|
||
"bad at nuclear\nphysics. I\nGot no fission",
|
||
]
|
||
Ganon1_texts = [
|
||
"Start your day\nsmiling with a\ndelicious\nwholegrain\nbreakfast\ncreated for\nyour\nincredible\ninsides.",
|
||
"You drove\naway my other\nself, Agahnim\ntwo times…\nBut, I won't\ngive you the\nTriforce.\nI'll defeat\nyou!",
|
||
"Impa says that\nthe mark on\nyour hand\nmeans that you\nare the hero\nchosen to\nawaken Zelda.\nyour blood can\nresurrect me.",
|
||
"Don't stand,\n\ndon't stand so\nDon't stand so\n\nclose to me\nDon't stand so\nclose to me\nback off buddy",
|
||
"So ya\nThought ya\nMight like to\ngo to the show\nTo feel the\nwarm thrill of\nconfusion\nThat space\ncadet glow.",
|
||
"Like other\npulmonate land\ngastropods,\nthe majority\nof land slugs\nhave two pairs\nof 'feelers'\nor tentacles\non their head.",
|
||
"If you were a\nburrito, what\nkind of a\nburrito would\nyou be?\nMe, I fancy I\nwould be a\nspicy barbacoa\nburrito.",
|
||
"I am your\nfather's\nbrother's\nnephew's\ncousin's\nformer\nroommate. What\ndoes that make\nus, you ask?",
|
||
"I'll be more\neager about\nencouraging\nthinking\noutside the\nbox when there\nis evidence of\nany thinking\ninside it.",
|
||
"If we're not\nmeant to have\nmidnight\nsnacks, then\nwhy is there\na light in the\nfridge?\n",
|
||
"I feel like we\nkeep ending up\nhere.\n\nDon't you?\n\nIt's like\ndeja vu\nall over again",
|
||
"Did you know?\nThe biggest\nand heaviest\ncheese ever\nproduced\nweighed\n57,518 pounds\nand was 32\nfeet long.",
|
||
"Now there was\na time, When\nyou loved me\nso. I couldn't\ndo wrong,\nAnd now you\nneed to know.\nSo How you\nlike me now?",
|
||
"Did you know?\nNutrition\nexperts\nrecommend that\nat least half\nof our daily\ngrains come\nfrom whole\ngrain products",
|
||
"The Hemiptera\nor true bugs\nare an order\nof insects\ncovering 50k\nto 80k species\nlike aphids,\ncicadas, and\nshield bugs.",
|
||
"Thanks for\ndropping in,\nthe first\npassengers\nin a hot\nair balloon.\nwere a duck,\na sheep,\nand a rooster.",
|
||
"You think you\nare so smart?\n\nI bet you\ndidn't know\nYou can't hum\nwhile holding\nyour nose\nclosed.",
|
||
"grumble,\n\ngrumble…\ngrumble,\n\ngrumble…\nSeriously you\nwere supposed\nto bring food",
|
||
"Join me hero,\nand I shall\nmake your face\nthe greatest\nin the dark\nworld!\n\nOr else you\nwill die!",
|
||
]
|
||
TavernMan_texts = [
|
||
"What do you\ncall a blind\ndinosaur?\nadoyouthink-\nhesaurus\n",
|
||
"A blind man\nwalks into\na bar.\nAnd a table.\nAnd a chair.\n",
|
||
"What do ducks\nlike to eat?\n\nQuackers!\n",
|
||
"How do you\nset up a party\nin space?\n\nYou planet!\n",
|
||
"I'm glad I\nknow sign\nlanguage,\nit's pretty\nhandy.\n",
|
||
"What did Zelda\nsay to Link at\na secure door?\n\nTRIFORCE!\n",
|
||
"I am on a\nseafood diet.\n\nEvery time\nI see food,\nI eat it.",
|
||
"I've decided\nto sell my\nvacuum.\nIt was just\ngathering\ndust.",
|
||
"Whats the best\ntime to go to\nthe dentist?\n\nTooth-hurtie!\n",
|
||
"Why can't a\nbike stand on\nits own?\n\nIt's two-tired!\n",
|
||
"If you haven't\nfound Quake\nyet…\nit's not your\nfault.",
|
||
"Why is Peter\nPan always\nflying?\nBecause he\nNeverlands!",
|
||
"I once told a\njoke to Armos.\n\nBut he\nremained\nstone-faced!",
|
||
"Lanmola was\nlate to our\ndinner party.\nHe just came\nfor the desert",
|
||
"Moldorm is\nsuch a\nprankster.\nAnd I fall for\nit every time!",
|
||
"Helmasaur is\nthrowing a\nparty.\nI hope it's\na masquerade!",
|
||
"I'd like to\nknow Arrghus\nbetter.\nBut he won't\ncome out of\nhis shell!",
|
||
"Mothula didn't\nhave much fun\nat the party.\nHe's immune to\nspiked punch!",
|
||
"Don't set me\nup with that\nchick from\nSteve's Town.\n\n\nI'm not\ninterested in\na Blind date!",
|
||
"Kholdstare is\nafraid to go\nto the circus.\nHungry kids\nthought he was\ncotton candy!",
|
||
"I asked who\nVitreous' best\nfriends are.\nHe said,\n'Me, Myself,\nand Eye!'",
|
||
"Trinexx can be\na hothead or\nhe can be an\nice guy. In\nthe end, he's\na solid\nindividual!",
|
||
"Bari thought I\nhad moved out\nof town.\nHe was shocked\nto see me!",
|
||
"I can only get\nWeetabix\naround here.\nI have to go\nto Steve's\nTown for Count\nChocula!",
|
||
"Don't argue\nwith a frozen\nDeadrock.\nHe'll never\nchange his\nposition!",
|
||
"I offered a\ndrink to a\nself-loathing\nGhini.\nHe said he\ndidn't like\nspirits!",
|
||
"I was supposed\nto meet Gibdo\nfor lunch.\nBut he got\nwrapped up in\nsomething!",
|
||
"Goriya sure\nhas changed\nin this game.\nI hope he\ncomes back\naround!",
|
||
"Hinox actually\nwants to be a\nlawyer.\nToo bad he\nbombed the\nBar exam!",
|
||
"I'm surprised\nMoblin's tusks\nare so gross.\nHe always has\nhis Trident\nwith him!",
|
||
"Don’t tell\nStalfos I’m\nhere.\nHe has a bone\nto pick with\nme!",
|
||
"I got\nWallmaster to\nhelp me move\nfurniture.\nHe was really\nhandy!",
|
||
"Wizzrobe was\njust here.\nHe always\nvanishes right\nbefore we get\nthe check!",
|
||
"I shouldn't\nhave picked up\nZora's tab.\nThat guy\ndrinks like\na fish!",
|
||
"I was sharing\na drink with\nPoe.\nFor no reason,\nhe left in a\nheartbeat!",
|
||
"Don’t trust\nhorsemen on\nDeath Mountain\nThey’re Lynel\nthe time!",
|
||
"Today's\nspecial is\nbattered bat.\nGot slapped\nfor offering a\nlady a Keese!",
|
||
"Don’t walk\nunder\npropellered\npineapples.\nYou may end up\nwearing\na pee hat!",
|
||
"My girlfriend\nburrowed under\nthe sand.\nSo I decided\nto Leever!",
|
||
"Geldman wants\nto be a\nBroadway star.\nHe’s always\npracticing\nJazz Hands!",
|
||
"Octoballoon\nmust be mad\nat me.\nHe blows up\nat the sight\nof me!",
|
||
"Toppo is a\ntotal pothead.\n\nHe hates it\nwhen you take\naway his grass",
|
||
"I lost my\nshield by\nthat house.\nWhy did they\nput up a\nPikit fence?!",
|
||
"Know that fox\nin Steve’s\nTown?\nHe’ll Pikku\npockets if you\naren't careful",
|
||
"Dash through\nDark World\nbushes.\nYou’ll see\nGanon is tryin\nto Stal you!",
|
||
"Eyegore!\n\nYou gore!\nWe all gore\nthose jerks\nwith arrows!",
|
||
"I like my\nwhiskey neat.\n\nSome prefer it\nOctoroks!",
|
||
"I consoled\nFreezor over a\ncup of coffee.\nHis problems\njust seemed to\nmelt away!",
|
||
"Magic droplets\nof water don’t\nshut up.\nThey just\nKyameron!",
|
||
"I bought hot\nwings for\nSluggula.\nThey gave him\nexplosive\ndiarrhea!",
|
||
"Hardhat Beetle\nwon’t\nLet It Be?\nTell it to Get\nBack or give\nit a Ticket to\nRide down\na hole!",
|
||
]
|
||
|
||
KingsReturn_texts = [
|
||
'Who is this even',
|
||
'The Harem'
|
||
] * 2 + [
|
||
"the return of the king",
|
||
"fellowship of the ring",
|
||
"the two towers",
|
||
]
|
||
Sanctuary_texts = [
|
||
'A Priest\'s love'
|
||
] * 2 + [
|
||
"the loyal priest",
|
||
"read a book",
|
||
"sits in own pew",
|
||
]
|
||
Sahasrahla_names = [
|
||
"sahasralah", "sabotaging", "sacahuista", "sacahuiste", "saccharase", "saccharide", "saccharify",
|
||
"saccharine", "saccharins", "sacerdotal", "sackcloths", "salmonella", "saltarelli", "saltarello",
|
||
"saltations", "saltbushes", "saltcellar", "saltshaker", "salubrious", "sandgrouse", "sandlotter",
|
||
"sandstorms", "sandwiched", "sauerkraut", "schipperke", "schismatic", "schizocarp", "schmalzier",
|
||
"schmeering", "schmoosing", "shibboleth", "shovelnose", "sahananana", "sarararara", "salamander",
|
||
"sharshalah", "shahabadoo", "sassafrass",
|
||
]
|
||
|
||
Kakariko_texts = ["{}'s homecoming"]
|
||
Blacksmiths_texts = [
|
||
'frogs for bread',
|
||
'That\'s not a sword',
|
||
'The Rupeesmiths'
|
||
] * 1 + [
|
||
"the dwarven breadsmiths"
|
||
]
|
||
DeathMountain_texts = [
|
||
"the lost old man",
|
||
"gary the old man",
|
||
"Your ad here"
|
||
]
|
||
LostWoods_texts = [
|
||
'thieves\' stump',
|
||
'He\'s got wood',
|
||
] * 2 + [
|
||
"the forest thief",
|
||
"dancing pickles",
|
||
"flying vultures",
|
||
]
|
||
WishingWell_texts = [
|
||
"venus. queen of faeries",
|
||
"Venus was her name",
|
||
"I'm your Venus",
|
||
"Yeah, baby, shes got it",
|
||
"Venus, I'm your fire",
|
||
"Venus, At your desire",
|
||
]
|
||
DesertPalace_texts = ['vultures rule the desert', 'literacy moves']
|
||
MountainTower_texts = ['the bully makes a friend', 'up up and away']
|
||
LinksHouse_texts = ['your uncle recovers', 'Home Sweet Home', 'Only one bed']
|
||
Lumberjacks_texts = [
|
||
'Chop Chop'
|
||
] * 2 + [
|
||
"twin lumberjacks",
|
||
"fresh flapjacks",
|
||
"two woodchoppers",
|
||
"double lumberman",
|
||
"lumberclones",
|
||
"woodfellas",
|
||
]
|
||
SickKid_texts = ['Next Time Stay Down']
|
||
Zora_texts = ['Splashes For Sale', 'Slippery when wet']
|
||
MagicShop_texts = ['Drug deal', 'Shrooms for days']
|
||
FluteBoy_texts = ['Stumped']
|
||
|
||
|
||
class Credits(object):
|
||
def __init__(self):
|
||
self.credit_scenes = {
|
||
'castle': [
|
||
SceneSmallCreditLine(19, 'The return of the King'),
|
||
SceneLargeCreditLine(23, 'Hyrule Castle'),
|
||
],
|
||
'sancturary': [
|
||
SceneSmallCreditLine(19, 'The loyal priest'),
|
||
SceneLargeCreditLine(23, 'Sanctuary'),
|
||
],
|
||
'kakariko': [
|
||
SceneSmallCreditLine(19, "Sahasralah's Homecoming"),
|
||
SceneLargeCreditLine(23, 'Kakariko Town'),
|
||
],
|
||
'desert': [
|
||
SceneSmallCreditLine(19, 'vultures rule the desert'),
|
||
SceneLargeCreditLine(23, 'Desert Palace'),
|
||
],
|
||
'hera': [
|
||
SceneSmallCreditLine(19, 'the bully makes a friend'),
|
||
SceneLargeCreditLine(23, 'Mountain Tower'),
|
||
],
|
||
'house': [
|
||
SceneSmallCreditLine(19, 'your uncle recovers'),
|
||
SceneLargeCreditLine(23, 'Your House'),
|
||
],
|
||
'zora': [
|
||
SceneSmallCreditLine(19, 'finger webs for sale'),
|
||
SceneLargeCreditLine(23, "Zora's Waterfall"),
|
||
],
|
||
'witch': [
|
||
SceneSmallCreditLine(19, 'the witch and assistant'),
|
||
SceneLargeCreditLine(23, 'Magic Shop'),
|
||
],
|
||
'lumberjacks': [
|
||
SceneSmallCreditLine(19, 'twin lumberjacks'),
|
||
SceneLargeCreditLine(23, "Woodsmen's Hut"),
|
||
],
|
||
'grove': [
|
||
SceneSmallCreditLine(19, 'ocarina boy plays again'),
|
||
SceneLargeCreditLine(23, 'Haunted Grove'),
|
||
],
|
||
'well': [
|
||
SceneSmallCreditLine(19, 'venus, queen of faeries'),
|
||
SceneLargeCreditLine(23, 'Wishing Well'),
|
||
],
|
||
'smithy': [
|
||
SceneSmallCreditLine(19, 'the dwarven swordsmiths'),
|
||
SceneLargeCreditLine(23, 'Smithery'),
|
||
],
|
||
'kakariko2': [
|
||
SceneSmallCreditLine(19, 'the bug-catching kid'),
|
||
SceneLargeCreditLine(23, 'Kakariko Town'),
|
||
],
|
||
'bridge': [
|
||
SceneSmallCreditLine(19, 'the lost old man'),
|
||
SceneLargeCreditLine(23, 'Death Mountain'),
|
||
],
|
||
'woods': [
|
||
SceneSmallCreditLine(19, 'the forest thief'),
|
||
SceneLargeCreditLine(23, 'Lost Woods'),
|
||
],
|
||
'pedestal': [
|
||
SceneSmallCreditLine(19, 'and the master sword'),
|
||
SceneSmallAltCreditLine(21, 'sleeps again...'),
|
||
SceneLargeCreditLine(23, 'Forever!'),
|
||
],
|
||
}
|
||
|
||
self.scene_order = ['castle', 'sancturary', 'kakariko', 'desert', 'hera', 'house', 'zora', 'witch',
|
||
'lumberjacks', 'grove', 'well', 'smithy', 'kakariko2', 'bridge', 'woods', 'pedestal']
|
||
|
||
def update_credits_line(self, scene, line, text):
|
||
scenes = self.credit_scenes
|
||
|
||
text = text[:32]
|
||
scenes[scene][line].text = text
|
||
|
||
def get_bytes(self):
|
||
pointers = []
|
||
data = bytearray()
|
||
for scene_name in self.scene_order:
|
||
scene = self.credit_scenes[scene_name]
|
||
pointers.append(len(data))
|
||
|
||
for part in scene:
|
||
data += part.as_bytes()
|
||
|
||
pointers.append(len(data))
|
||
return (pointers, data)
|
||
|
||
class CreditLine(object):
|
||
"""Base class of credit lines"""
|
||
|
||
def __init__(self, text, align='center'):
|
||
self.text = text
|
||
self.align = align
|
||
|
||
@property
|
||
def x(self):
|
||
x = 0
|
||
if self.align == 'left':
|
||
x = 0
|
||
elif self.align == 'right':
|
||
x = 32 - len(self.text)
|
||
else: # center
|
||
x = (32 - len(self.text)) // 2
|
||
return x
|
||
|
||
|
||
class SceneCreditLine(CreditLine):
|
||
"""Base class for credit lines for the scene portion of the credits"""
|
||
def __init__(self, y, text, align='center'):
|
||
self.y = y
|
||
super().__init__(text, align)
|
||
|
||
def header(self, x=None, y=None, length=None):
|
||
if x is None:
|
||
x = self.x
|
||
if y is None:
|
||
y = self.y
|
||
if length is None:
|
||
length = len(self.text)
|
||
header = (0x6000 | (y >> 5 << 11) | ((y & 0x1F) << 5) | (x >> 5 << 10) | (x & 0x1F)) << 16 | (length * 2 - 1)
|
||
return bytearray([header >> 24 & 0xFF, header >> 16 & 0xFF, header >> 8 & 0xFF, header & 0xFF])
|
||
|
||
|
||
class SceneSmallCreditLine(SceneCreditLine):
|
||
def as_bytes(self):
|
||
buf = bytearray()
|
||
buf.extend(self.header())
|
||
buf.extend(GoldCreditMapper.convert(self.text))
|
||
|
||
# handle upper half of apostrophe character if present
|
||
if "'" in self.text:
|
||
apos = "".join([',' if x == "'" else ' ' for x in self.text])
|
||
buf.extend(self.header(self.x + apos.index(','), self.y - 1, len(apos.strip())))
|
||
buf.extend(GoldCreditMapper.convert(apos.strip()))
|
||
|
||
# handle lower half of comma character if present
|
||
if ',' in self.text:
|
||
commas = "".join(["'" if x == ',' else ' ' for x in self.text])
|
||
buf.extend(self.header(self.x + commas.index("'"), self.y + 1, len(commas.strip())))
|
||
buf.extend(GoldCreditMapper.convert(commas.strip()))
|
||
|
||
return buf
|
||
|
||
|
||
class SceneSmallAltCreditLine(SceneCreditLine):
|
||
def as_bytes(self):
|
||
buf = bytearray()
|
||
buf += self.header()
|
||
buf += GreenCreditMapper.convert(self.text)
|
||
return buf
|
||
|
||
|
||
class SceneLargeCreditLine(SceneCreditLine):
|
||
def as_bytes(self):
|
||
buf = bytearray()
|
||
buf += self.header()
|
||
buf += LargeCreditTopMapper.convert(self.text)
|
||
|
||
buf += self.header(self.x, self.y + 1)
|
||
buf += LargeCreditBottomMapper.convert(self.text)
|
||
return buf
|
||
|
||
|
||
def string_to_alttp_text(s, maxbytes=256):
|
||
outbuf = string_to_alttp_core(s)
|
||
|
||
# check for max length
|
||
if len(outbuf) > maxbytes - 2:
|
||
outbuf = outbuf[:maxbytes - 2]
|
||
# Note: this could crash if the last byte is part of a two byte command
|
||
# depedning on how well the command handles a value of 0x7F.
|
||
# Should probably do something about this.
|
||
|
||
outbuf.append(0x7F)
|
||
outbuf.append(0x7F)
|
||
return outbuf
|
||
|
||
special_commands = {
|
||
"{SPEED0}": [0x7A, 0x00],
|
||
"{SPEED2}": [0x7A, 0x02],
|
||
"{SPEED6}": [0x7A, 0x06],
|
||
"{PAUSE1}": [0x78, 0x01],
|
||
"{PAUSE3}": [0x78, 0x03],
|
||
"{PAUSE5}": [0x78, 0x05],
|
||
"{PAUSE7}": [0x78, 0x07],
|
||
"{PAUSE9}": [0x78, 0x09],
|
||
"{INPUT}": [0x7E],
|
||
"{CHOICE}": [0x68],
|
||
"{ITEMSELECT}": [0x69],
|
||
"{CHOICE2}": [0x71],
|
||
"{CHOICE3}": [0x72],
|
||
"{HARP}": [0x79, 0x2D],
|
||
"{MENU}": [0x6D, 0x00],
|
||
"{BOTTOM}": [0x6D, 0x00],
|
||
"{NOBORDER}": [0x6B, 0x02],
|
||
"{CHANGEPIC}": [0x67, 0x67],
|
||
"{CHANGEMUSIC}": [0x67],
|
||
"{INTRO}": [0x6E, 0x00, 0x77, 0x07, 0x7A, 0x03, 0x6B, 0x02, 0x67],
|
||
"{NOTEXT}": [0x6E, 0x00, 0x6B, 0x04],
|
||
"{IBOX}": [0x6B, 0x02, 0x77, 0x07, 0x7A, 0x03],
|
||
}
|
||
|
||
def string_to_alttp_core(s, pause=True, wrap=14):
|
||
s = s.upper()
|
||
lines = s.split('\n')
|
||
outbuf = bytearray()
|
||
lineindex = 0
|
||
is_intro = '{INTRO}' in s
|
||
|
||
while lines:
|
||
linespace = wrap
|
||
line = lines.pop(0)
|
||
if line.startswith('{'):
|
||
outbuf.extend(special_commands[line])
|
||
continue
|
||
|
||
words = line.split(' ')
|
||
outbuf.append(0x74 if lineindex == 0 else 0x75 if lineindex == 1 else 0x76) # line starter
|
||
|
||
while words:
|
||
word = words.pop(0)
|
||
# sanity check: if the word we have is more than 14 characters, we take as much as we can still fit and push the rest back for later
|
||
if wordlen(word) > wrap:
|
||
if linespace < wrap:
|
||
word = ' ' + word
|
||
(word_first, word_rest) = splitword(word, linespace)
|
||
words.insert(0, word_rest)
|
||
lines.insert(0, ' '.join(words))
|
||
|
||
write_word(outbuf, word_first)
|
||
break
|
||
|
||
if wordlen(word) <= (linespace if linespace == wrap else linespace - 1):
|
||
if linespace < wrap:
|
||
word = ' ' + word
|
||
linespace -= wordlen(word)
|
||
write_word(outbuf, word)
|
||
else:
|
||
# ran out of space, push word and lines back and continue with next line
|
||
words.insert(0, word)
|
||
lines.insert(0, ' '.join(words))
|
||
break
|
||
|
||
if is_intro and lineindex < 3:
|
||
outbuf.extend([0xFF]*linespace)
|
||
|
||
has_more_lines = len(lines) > 1 or (lines and not lines[0].startswith('{'))
|
||
|
||
lineindex += 1
|
||
if pause and lineindex % 3 == 0 and has_more_lines:
|
||
outbuf.append(0x7E)
|
||
if lineindex >= 3 and has_more_lines:
|
||
outbuf.append(0x73)
|
||
return outbuf
|
||
|
||
two_byte_commands = [
|
||
0x6B, 0x6C, 0x6D, 0x6E,
|
||
0x77, 0x78, 0x79, 0x7A
|
||
]
|
||
specially_coded_commands = {
|
||
0x73: 0xF6,
|
||
0x74: 0xF7,
|
||
0x75: 0xF8,
|
||
0x76: 0xF9,
|
||
0x7E: 0xFA,
|
||
0x7A: 0xFC,
|
||
}
|
||
|
||
def string_to_alttp_text_compressed(s, pause=True, max_bytes_expanded=0x800, wrap=14):
|
||
inbuf = string_to_alttp_core(s, pause, wrap)
|
||
|
||
# Links name will need 8 bytes in the target buffer
|
||
# and two will be used by the terminator
|
||
# (Variables will use 2 bytes, but they start as 2 bytes)
|
||
bufsize = len(inbuf) + 7 * inbuf.count(0x6A) + 2
|
||
if bufsize > max_bytes_expanded:
|
||
raise ValueError("Uncompressed string too long for buffer")
|
||
inbuf.reverse()
|
||
outbuf = bytearray()
|
||
outbuf.append(0xfb) # terminator for previous record
|
||
while inbuf:
|
||
val = inbuf.pop()
|
||
if val == 0xFF:
|
||
outbuf.append(val)
|
||
elif val == 0x00:
|
||
outbuf.append(inbuf.pop())
|
||
elif val == 0x01: #kanji
|
||
outbuf.append(0xFD)
|
||
outbuf.append(inbuf.pop())
|
||
elif val >= 0x67:
|
||
if val in specially_coded_commands:
|
||
outbuf.append(specially_coded_commands[val])
|
||
else:
|
||
outbuf.append(0xFE)
|
||
outbuf.append(val)
|
||
if val in two_byte_commands:
|
||
outbuf.append(inbuf.pop())
|
||
else:
|
||
raise ValueError("Unexpected byte found in uncompressed string")
|
||
return outbuf
|
||
|
||
|
||
|
||
def wordlen(word):
|
||
l = 0
|
||
offset = 0
|
||
while offset < len(word):
|
||
c_len, offset = charlen(word, offset)
|
||
l += c_len
|
||
return l
|
||
|
||
def splitword(word, length):
|
||
l = 0
|
||
offset = 0
|
||
while True:
|
||
c_len, new_offset = charlen(word, offset)
|
||
if l+c_len > length:
|
||
break
|
||
l += c_len
|
||
offset = new_offset
|
||
return (word[0:offset], word[offset:])
|
||
|
||
def charlen(word, offset):
|
||
c = word[offset]
|
||
if c in ['>', '¼', '½', '♥']:
|
||
return (2, offset+1)
|
||
if c in ['@']:
|
||
return (4, offset+1)
|
||
if c in ['ᚋ', 'ᚌ', 'ᚍ', 'ᚎ']:
|
||
return (2, offset+1)
|
||
return (1, offset+1)
|
||
|
||
def write_word(buf, word):
|
||
for char in word:
|
||
res = char_to_alttp_char(char)
|
||
if isinstance(res, int):
|
||
buf.extend([0x00, res])
|
||
else:
|
||
buf.extend(res)
|
||
|
||
|
||
char_map = {' ': 0xFF,
|
||
'?': 0xC6,
|
||
'!': 0xC7,
|
||
',': 0xC8,
|
||
'-': 0xC9,
|
||
'…': 0xCC,
|
||
'.': 0xCD,
|
||
'~': 0xCE,
|
||
'~': 0xCE,
|
||
'@': [0x6A], # Links name (only works if compressed)
|
||
'>': [0x00, 0xD2, 0x00, 0xD3], # Link's face
|
||
"'": 0xD8,
|
||
'’': 0xD8,
|
||
'%': 0xDD, # Hylian Bird
|
||
'^': 0xDE, # Hylian Ankh
|
||
'=': 0xDF, # Hylian Wavy Lines
|
||
'↑': 0xE0,
|
||
'↓': 0xE1,
|
||
'→': 0xE2,
|
||
'←': 0xE3,
|
||
'≥': 0xE4, # Cursor
|
||
'¼': [0x00, 0xE5, 0x00, 0xE7], # ¼ heart
|
||
'½': [0x00, 0xE6, 0x00, 0xE7], # ½ heart
|
||
'¾': [0x00, 0xE8, 0x00, 0xE9], # ¾ heart
|
||
'♥': [0x00, 0xEA, 0x00, 0xEB], # full heart
|
||
'ᚋ': [0x6C, 0x00], # var 0
|
||
'ᚌ': [0x6C, 0x01], # var 1
|
||
'ᚍ': [0x6C, 0x02], # var 2
|
||
'ᚎ': [0x6C, 0x03], # var 3
|
||
'あ': 0x00,
|
||
'い': 0x01,
|
||
'う': 0x02,
|
||
'え': 0x03,
|
||
'お': 0x04,
|
||
'や': 0x05,
|
||
'ゆ': 0x06,
|
||
'よ': 0x07,
|
||
'か': 0x08,
|
||
'き': 0x09,
|
||
'く': 0x0A,
|
||
'け': 0x0B,
|
||
'こ': 0x0C,
|
||
'わ': 0x0D,
|
||
'を': 0x0E,
|
||
'ん': 0x0F,
|
||
'さ': 0x10,
|
||
'し': 0x11,
|
||
'す': 0x12,
|
||
'せ': 0x13,
|
||
'そ': 0x14,
|
||
'が': 0x15,
|
||
'ぎ': 0x16,
|
||
'ぐ': 0x17,
|
||
'た': 0x18,
|
||
'ち': 0x19,
|
||
'つ': 0x1A,
|
||
'て': 0x1B,
|
||
'と': 0x1C,
|
||
'げ': 0x1D,
|
||
'ご': 0x1E,
|
||
'ざ': 0x1F,
|
||
'な': 0x20,
|
||
'に': 0x21,
|
||
'ぬ': 0x22,
|
||
'ね': 0x23,
|
||
'の': 0x24,
|
||
'じ': 0x25,
|
||
'ず': 0x26,
|
||
'ぜ': 0x27,
|
||
'は': 0x28,
|
||
'ひ': 0x29,
|
||
'ふ': 0x2A,
|
||
'へ': 0x2B,
|
||
'ほ': 0x2C,
|
||
'ぞ': 0x2D,
|
||
'だ': 0x2E,
|
||
'ぢ': 0x2F,
|
||
'ま': 0x30,
|
||
'み': 0x31,
|
||
'む': 0x32,
|
||
'め': 0x33,
|
||
'も': 0x34,
|
||
'づ': 0x35,
|
||
'で': 0x36,
|
||
'ど': 0x37,
|
||
'ら': 0x38,
|
||
'り': 0x39,
|
||
'る': 0x3A,
|
||
'れ': 0x3B,
|
||
'ろ': 0x3C,
|
||
'ば': 0x3D,
|
||
'び': 0x3E,
|
||
'ぶ': 0x3F,
|
||
'べ': 0x40,
|
||
'ぼ': 0x41,
|
||
'ぱ': 0x42,
|
||
'ぴ': 0x43,
|
||
'ぷ': 0x44,
|
||
'ぺ': 0x45,
|
||
'ぽ': 0x46,
|
||
'ゃ': 0x47,
|
||
'ゅ': 0x48,
|
||
'ょ': 0x49,
|
||
'っ': 0x4A,
|
||
'ぁ': 0x4B,
|
||
'ぃ': 0x4C,
|
||
'ぅ': 0x4D,
|
||
'ぇ': 0x4E,
|
||
'ぉ': 0x4F,
|
||
'ア': 0x50,
|
||
'イ': 0x51,
|
||
'ウ': 0x52,
|
||
'エ': 0x53,
|
||
'オ': 0x54,
|
||
'ヤ': 0x55,
|
||
'ユ': 0x56,
|
||
'ヨ': 0x57,
|
||
'カ': 0x58,
|
||
'キ': 0x59,
|
||
'ク': 0x5A,
|
||
'ケ': 0x5B,
|
||
'コ': 0x5C,
|
||
'ワ': 0x5D,
|
||
'ヲ': 0x5E,
|
||
'ン': 0x5F,
|
||
'サ': 0x60,
|
||
'シ': 0x61,
|
||
'ス': 0x62,
|
||
'セ': 0x63,
|
||
'ソ': 0x64,
|
||
'ガ': 0x65,
|
||
'ギ': 0x66,
|
||
'グ': 0x67,
|
||
'タ': 0x68,
|
||
'チ': 0x69,
|
||
'ツ': 0x6A,
|
||
'テ': 0x6B,
|
||
'ト': 0x6C,
|
||
'ゲ': 0x6D,
|
||
'ゴ': 0x6E,
|
||
'ザ': 0x6F,
|
||
'ナ': 0x70,
|
||
'ニ': 0x71,
|
||
'ヌ': 0x72,
|
||
'ネ': 0x73,
|
||
'ノ': 0x74,
|
||
'ジ': 0x75,
|
||
'ズ': 0x76,
|
||
'ゼ': 0x77,
|
||
'ハ': 0x78,
|
||
'ヒ': 0x79,
|
||
'フ': 0x7A,
|
||
'ヘ': 0x7B,
|
||
'ホ': 0x7C,
|
||
'ゾ': 0x7D,
|
||
'ダ': 0x7E,
|
||
'マ': 0x80,
|
||
'ミ': 0x81,
|
||
'ム': 0x82,
|
||
'メ': 0x83,
|
||
'モ': 0x84,
|
||
'ヅ': 0x85,
|
||
'デ': 0x86,
|
||
'ド': 0x87,
|
||
'ラ': 0x88,
|
||
'リ': 0x89,
|
||
'ル': 0x8A,
|
||
'レ': 0x8B,
|
||
'ロ': 0x8C,
|
||
'バ': 0x8D,
|
||
'ビ': 0x8E,
|
||
'ブ': 0x8F,
|
||
'ベ': 0x90,
|
||
'ボ': 0x91,
|
||
'パ': 0x92,
|
||
'ピ': 0x93,
|
||
'プ': 0x94,
|
||
'ペ': 0x95,
|
||
'ポ': 0x96,
|
||
'ャ': 0x97,
|
||
'ュ': 0x98,
|
||
'ョ': 0x99,
|
||
'ッ': 0x9A,
|
||
'ァ': 0x9B,
|
||
'ィ': 0x9C,
|
||
'ゥ': 0x9D,
|
||
'ェ': 0x9E,
|
||
'ォ': 0x9F}
|
||
|
||
|
||
def char_to_alttp_char(char):
|
||
if 0x30 <= ord(char) <= 0x39:
|
||
return ord(char) + 0x70
|
||
|
||
if 0x41 <= ord(char) <= 0x5A:
|
||
return ord(char) + 0x69
|
||
|
||
return char_map.get(char, 0xFF)
|
||
|
||
class TextMapper(object):
|
||
number_offset = None
|
||
alpha_offset = 0
|
||
char_map = {}
|
||
@classmethod
|
||
def map_char(cls, char):
|
||
if cls.number_offset is not None:
|
||
if 0x30 <= ord(char) <= 0x39:
|
||
return ord(char) + cls.number_offset
|
||
if 0x61 <= ord(char) <= 0x7A:
|
||
return ord(char) + cls.alpha_offset
|
||
return cls.char_map.get(char, cls.char_map[' '])
|
||
|
||
@classmethod
|
||
def convert(cls, text):
|
||
buf = bytearray()
|
||
for char in text.lower():
|
||
buf.append(cls.map_char(char))
|
||
return buf
|
||
|
||
|
||
class GoldCreditMapper(TextMapper):
|
||
char_map = {' ': 0x9F,
|
||
',': 0x34,
|
||
'.': 0x37,
|
||
'-': 0x36,
|
||
"'": 0x35}
|
||
alpha_offset = -0x47
|
||
|
||
|
||
class GreenCreditMapper(TextMapper):
|
||
char_map = {' ': 0x9F,
|
||
'.': 0x52}
|
||
alpha_offset = -0x29
|
||
|
||
class RedCreditMapper(TextMapper):
|
||
char_map = {' ': 0x9F} #fixme
|
||
alpha_offset = -0x61
|
||
|
||
class LargeCreditTopMapper(TextMapper):
|
||
char_map = {' ': 0x9F,
|
||
"'": 0x77,
|
||
'!': 0x78,
|
||
'.': 0xA0,
|
||
'#': 0xA1,
|
||
'/': 0xA2,
|
||
':': 0xA3}
|
||
alpha_offset = -0x04
|
||
number_offset = 0x23
|
||
|
||
|
||
class LargeCreditBottomMapper(TextMapper):
|
||
char_map = {' ': 0x9F,
|
||
"'": 0x9D,
|
||
'!': 0x9E,
|
||
'.': 0xC0,
|
||
'#': 0xC1,
|
||
'/': 0xC2,
|
||
':': 0xC3}
|
||
alpha_offset = 0x22
|
||
number_offset = 0x49
|