12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306 |
- #!/bin/sh
- #\
- for x in 8.7 8.6 8.5 ""; do \
- command -v "tclsh$x" >/dev/null && exec "tclsh$x" "$0" "$@"; \
- command -v "wish$x" >/dev/null && exec "wish$x" "$0" -- "$@"; \
- done
- # Permission to use, copy, modify, and/or distribute this software for
- # any purpose with or without fee is hereby granted.
- #
- # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
- # WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
- # OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
- # FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
- # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
- # AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
- # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- # NAME
- # bokpass - decode/encode passwords from/for boktai games
- #
- # SYNOPSIS
- # bokpass [-gui] [-kbd] [-invalid] [-columns columns] [-fontsize size]
- # bokpass [-cli] [-verbose] -game game password ...
- #
- # DESCRIPTION
- # bokpass is a tool to decode and encode passwords from/for the Boktai
- # series of games, including Lunar Knights.
- #
- # OPTIONS
- # -gui Open the GUI, this is the default when there are no
- # arguments provided.
- #
- # -columns columns
- # Arrange the gui in specified number of columns,
- # the default is 2.
- #
- # -fontsize size
- # Set the default font size to `size', the password entry and
- # virtual keyboard font sizes are an additional 20% bigger.
- # The default is 10.
- #
- # -cli Decode the provided passwords, this is the default when
- # arguments are provided.
- #
- # -verbose
- # If a value has a single string representation, print that
- # next to its numerical representation.
- #
- # -invalid
- # Do not validate the data fields.
- #
- # -game game
- # The game that the provided password(s) are for, `game' can
- # be a number (1-4), the title of the game, or the short-name
- # for the game (zoktai/shinbok).
- # This option is required for decoding passwords.
- #
- # GUI
- # The tabs at the top select the game.
- #
- # Under that is the encode button "↱", the password text box, the
- # cycle button "↻", and the decode button "↴".
- #
- # The cycle button "↻" edits the password to change the `offset'
- # field and change how it would be decoded. For if one of the first
- # letters was a typo. Though note that the `offset' is not included in
- # Boktai 1's checksum.
- #
- # Then columns (changed by the `-columns' option) of labeled dropdown
- # menus, number-boxes, checkboxes, and text fields that correspond to
- # the various fields of the selected game's password.
- #
- # Then there is an error line, usually containing "✓", that will show
- # an error preceded by "⚠" if there was an encoding error, a decoding
- # error, or if the password may be considered invalid. For example,
- # Boktai 2 (US) will not accept Boktai 1 passwords that have 0 playtime
- # or clears, and will not accept Japanese characters in the player's
- # name.
- #
- # If the `-kbd' flag is given then at the bottom there are tabs
- # containing grids of buttons that function as a virtual keyboard.
- # When clicking a text-entry field it will automatically switch to
- # the appropriate keyboard tab, and lock it out of any invalid tabs.
- #
- # BOKTAI PASSWORD
- # JP EN
- # u3 u3 region
- # u16 u16 checksum
- # u2 u2 offset [0x0203C800, ([0x03004620]+1 & 0x3FF)*2] & 3
- # u9 u9 sol/4 [0x0203D8BC] >> 12
- # u6 u7 timezone [0x0203D82C]
- # u6 u6 hours [0x0203D8FC] / 60 / 60 / 60
- # u6 u6 minutes [0x0203D8FC] / 60 / 60 % 60
- # u3 u3 difficulty [0x0203D838]
- # u5 u5 dungeons [0x0203D8A6]h
- # u3 u3 clears [0x0203D8B6]h
- # u4 u4 continues [0x0203D902]h
- # u5 u5 caught [0x0203DB0C]h
- # u7 u7 kills [0x0203D8B0]h
- # u4 u4 rank [0x0203D8C6]h
- # u5 u5 title [0x0203D828]h
- # u8[5] u8[9] name [0x0203D830]...
- # u6 u6 link-battles [0x0203DD2E]
- # u6 u6 link-trades [0x0203DD32]
- # u8 u8 loan [0x0203D878]
- # u0 u15 padding 0
- #
- # The checksum offset is 24-bits and uses the constant 0x1021.
- # The encoding offset is 24-bits and uses the initializer 0x8C159.
- #
- # BOKTAI 2 PASSWORD
- # JP EN
- # u16 u16 checksum
- # u2 u2 padding 0
- # u3 u3 region
- # u3 u3 offset [0x0203B400, ([0x030046B8]+1 & 0x3FF)*2] & 7
- # u6 u7 timezone [[0x030047A8], 0x20]
- # u3 u3 side [[0x030046A0], 0x2F8]h
- # u4 u4 style [[0x030046A0], 0x2FA]h
- # s8 s8 kills/8 [[0x030046A0], 0x1F0]h / 8
- # u7 u7 forge [[0x030046A0], 0x2FC]h
- # u6 u6 link-battles [[0x030046A0], 0x914]h
- # u6 u6 link-shops [[0x030046A0], 0x918]h + [[0x030047A8], 0x91A]h
- # s10 s10 sol/4096 [[0x030046A0], 0x1D8] / 4096
- # s8 s8 loan/256 [[0x030046A0], 0x1D4] / 256
- # u6 u6 hours [[0x030046A0], 0x2B4] / 60 / 60 / 60
- # u16 u16 titles [[0x030046A0], 0x1EE]h
- # u8[5] u8[9] name [[0x030046A0], 0x3C0]b...
- # u0 u15 padding 0
- # [0x030046A0] is heap allocated, typically 0x0203C400.
- #
- # The checksum offset is 18-bits and uses the constant 0x1021.
- # The encoding offset is 24-bits and uses the initializer 0xB8E6E.
- #
- # BOKTAI 3 PASSWORD
- # u16 checksum
- # u2 padding 0
- # u3 region
- # u3 offset [0x203B400, ([0x03005308] + 1 & 0x3FF) * 2] / 256 & 7
- # u6 timezone [[0x30053F8], 0x18]
- # u8 kills/8 [[0x2000710], 0x538]h / 8
- # u7 forges [[0x2000710], 0x664]h
- # u9 races [[0x2000710], 0x75A]h
- # u7 link-races [[0x2000710], 0x7B4]h
- # u1 cross-linked [[0x2000710], 0x75F]b
- # u6 endings [[0x2000710], 0x75E]b
- # u10 sol/4096 [[0x2000710], 0x520]
- # u8 loan/256 [[0x2000710], 0x51C]
- # u6 hours [[0x2000710], 0x614] / 60 / 60 / 60
- # u12 titles [[0x2000710], 0x536]h
- # u8[5] name [[0x2000710], 0x781]b...
- # u0 padding 0
- # [0x02000710] is heap allocated, typically 0x0203C400.
- #
- # The checksum offset is 18-bits and uses the constant 0x8005.
- # The encoding offset is 24-bits and uses the initializer 0x8C159.
- #
- # CAVEATS
- # I was unable to find a Shinbok password with what I labeled as the
- # "Cross-Linked" flag set. I labeled it that way because it seems to
- # only be modified in bytecode function 1657 at file offset 0xDF2D81
- # which references a string 9797 that translates as a wireless-adapter
- # communication error, and it seems as though the wireless-adapter is
- # only used for the crossover battles.
- #
- # DISASSEMBLY NOTES
- # Password generation functions:
- # Boktai 1 JP put 1: 0x12D2E4
- # Boktai 1 NA put 1: 0x12E7D4
- # Boktai 1 EU put 1: 0x12B244
- # Boktai 2 JP put 2: 0x12DC0
- # Boktai 2 NA put 2: 0x12C14
- # Boktai 2 EU put 2: 0x12ABC
- # Boktai 3 JP put 3: 0x44E6C
- # Boktai 3 JP put 2: 0x451BC
- # Password reading functions:
- # Boktai 2 JP get 1: 0x112B4
- # Boktai 2 NA get 1: 0x111E0
- # Boktai 2 EU get 1: 0x10FC8
- # Boktai 3 JP get 3: 0x450D8
- # Boktai 3 JP get 2: 0x453DC
- #
- # Boktai 1 uses a static random number table of 1024 numbers at 0x203C800;
- # the index stored at 0x3004620 with an inital value of 837. The index
- # is only incremented when used (particle effects and enemies, mostly),
- # and is not stored in the save file.
- #
- # This means, for example, you can just choose what emblem you want at the
- # Azure Sky Tower. Entering the outside screen while there is still the
- # ice/fire increments the RNG by 1, entering Azure Sky Tower itself
- # increments it by two, and the steam/smoke increments it by 85.
- # So before collecting an emblem repeatedly running between the outside
- # of the tower and the 'Byroad of the Beasts' will increment the RNG by 1,
- # and repeatedly exiting/entering the tower will increment the RNG by 3
- # each cycle. The following table tells you what actions after loading a
- # file saved on the 'Byroad of the Beasts' will get which emblem:
- # RNG Index Byroad in/outs AZT in/outs Emblem
- # 925 0 0 Earth
- # 928 0 1 Cloud
- # 930 2 1 Frost
- # 931 0 2 Cloud
- # 932 1 2 Frost
- # 934 0 3 Cloud
- # 937 0 4 Frost
- # 939 2 4 Flame
- # 940 0 5 Earth
- # 943 0 6 Earth
- # 946 0 7 Flame
- #
- # Boktai 1's titles are checked in bytecode function 37607 in this order:
- # 0 Trigger of Sol S Rank and all 42 parts
- # 1 Gun Master Level 3 lenses and all 42 parts
- # 6 Death 100 or more Continues
- # 5 Berserker Caught 400 or more times
- # 3 Bishop 500 or more Kills
- # 4 Queen 100 or less Kills
- # 2 Gladiator A win rate of 70% or more with at least 50 Link Battles
- # 10 Running Boy Punished 8 or more times
- # 9 Solar Merchant 50 or more Link Trades
- # 7 Solar Boy 600 or more Sol
- # 8 Dark Boy 100 or less Sol
- # 11 King S Rank
- # 12 Rook A-/A/A+ Rank
- # 13 Knight B-/B/B+ Rank
- # 14 Pawn C-/C/C+ Rank
- # 14 Pawn Default
- #
- # Boktai 2's "side" is calculated as follows:
- # 128*r/max(r+b, 1) > 70 ? 0 : 128*r/max(r+b, 1) > 58 ? 2 : 1
- # Where `r' is the number of frames of "red" Django action and `b' is
- # the number of frames of "black" Django action.
- #
- # Boktai 2's "style" is based on which weapon had the highest number of
- # frames in use rounded down to the nearest 15 minutes, or "No Style"
- # if there was a tie.
- #
- # Boktai 3's endings should be:
- # 1 Otenko lives.
- # 2 Both Otenko and Sabata live.
- # 4 Neither Otenko or Sabata live.
- # 8 Sabata lives.
- #
- # The checksums are CRC-16s initialized to 0xFFFF and with the result
- # inverted. The polynomial 0x1021 is used in the first two games, 0x8005
- # is used in the third, and 0x180D is used in the fourth game.
- #
- # The encoding algorithm is to initialize a value depending on the game,
- # and initialize the an accumulator variable to 0xFFFF. For each iteration
- # the current value by 0x6262C05D and increment it by 1, then xor the result
- # into the xor accumulator. This iteration in run a number of times
- # depending on the offset and 4 additional times, then before each iteration
- # starting at offset 4 until the end of the list the last 6-bits of the xor
- # accumulator are xored with the current 6-bit value.
- # Because of this truncation only the last 6 bits of any constant matters.
- # The initialization constants are 0x8C159 for games 1 and 3, 0xB8E6E for
- # game 2, and 0x5BB15 for game 4.
- #
- # BYTECODE NOTES
- # In Boktai 3 most things about the bytecode are the same as described
- # in github.com/Prof9/SolDec, though there are more 16-bit (control)
- # operators. The Boktai 3 function that executes them is at 0x21AC6C.
- #
- # The following are tables some useful addresses and where they are
- # found in RAM.
- #
- # note: Addresses starting with 0x08 indicate a location in the ROM.
- # RAM-Address Func-Table Func-Count BC-Offset BC-Start
- # JP1 0x03000648 0x08E1D738 0x13F6 0x08E52328 0x08EE9F17
- # NA1 0x03000648 0x08E0DA88 0x140A 0x08E41D4C 0x08EDA36F
- # EU1 0x03000650 0x08DE2FBC 0x140F 0x08EC1350 0x08F5D001
- # JP2 0x03000748 0x08CBF1A0 0x2D13 0x08D1337C 0x08DA9DB4
- # NA2 0x03000748 0x08C8B56C 0x390A 0x08CE4B9C 0x08D7BBF5
- # EU2 0x03000748 0x08CA1E8C 0x3F48 0x08E2F0EC 0x08ECC13C
- # JP3 0x02000438 0x08D5A860 0x4260 0x08DD6BA0 0x08E5F048
- #
- # In Boktai 1 the Func-Table is indexed like:
- # for (i = 0; i < Func-Count; i++)
- # if (Func-Table[i*2] == index)
- # return BC-Offset + (Func-Table[i*2+1] & 0x00FFFFFF)
- #
- # In others the Func-Table is indexed like:
- # BC-Offset + (Func-Table[(index & 0x7FFFFFFF) - 1] & 0x00FFFFFF)
- #
- # RAM-Address ??? String-Table String-Data
- # JP1 0x03000658 0x08E276F0 0x08E27700 0x08E2B72C
- # NA1 0x03000658 0x08E17AE0 0x08E17AF0 0x08E1BE1C
- # EU1 0x03000660 0x08DED03C 0x08DED04C 0x08E00E64
- # JP2 0x03000758 0x08CCA5F0 0x08CCA600 0x08CD1594
- # NA2 0x03000758 0x08C99998 0x08C999A8 0x08CA0A80
- # EU2 0x03000758 0x08CB1BB0 0x08CB1BC0 0x08CD3A58
- # JP3 0x02000448 0x08D6B1E4 0x08D6B1F8 0x08D74DDC
- #
- # The String-Table is indexed like:
- # String-Data + (String-Table[index] & 0x7FFFFFFF)
- # The strings are nul-terminated.
- #
- # The current floor of the Azure Sky Tower is stored at memory location
- # 0x203E8DD, and the number of clears at 0x203E8E0. So building it up
- # can be skipped by changing 0x203E8E0 to 29+, and most of the climb up
- # can be skipped by changing 0x203E8DD to 99 after entering floor 1.
- #
- # Boktai 1 -> 2 password effects (byte-code function JP #25 and NA/EU #28):
- # Trigger of Sol Start with Stats +1, a Healer, and a Magic Potion
- # Gun Master Start with Stats +1, a Healer, and a Magic Potion
- # Gladiator Start with Stats +1, and 2 Healers
- # Bishop Start with Vitl +2, Strg +2, an Antidote, and an Elixer
- # Queen Start with Sprt +2, Agil +2, a Speed, and Tiptoe Nut
- # Berserker Start with Strg +2, a Tasty, and Rotten Meat
- # Death Start with Vitl +2, a Solar, and Rotten Nut
- # Solar Boy Start with Vitl +2, Sprt +2, a Solar Nut, and a Drop of Sun
- # Dark Boy Start with Strg +2, Agil +2, a Power Nut, and a Bearnut
- # Solar Merchant Start with Sprt +2, Strg +2, a Redshroom, and a Blueshroom
- # Running Boy Start with Vitl +2, Agil +2, and 2 Magic Potions
- # King Start with Stats +1, an Elixer, and a Healer
- # Rook Start with Vitl +1, Sprt +1, a Healer, and an Antidote
- # Knight Start with Strg +1, Agil +1, an Earthly, and Solar Nut
- # Pawn Start with an Earthly and Solar Nut
- # Invalid Start with 2 Earthly Nuts
- #
- # Boktai 2 -> 3 password effects (byte-code function #546):
- # Sword Master Starting Strength + 1
- # Spear Master Starting Spirit + 1
- # Hammer Master Starting Vitality + 1
- # Fist Master Starting Strength + 1
- # Gun Master Starting Spirit + 1
- # Adept Starting Vitality + 1
- # Day Walker Start with a Sun card
- # Adventurer Start with a Speed Nut
- # Agent Start with a Tiptoe Nut
- # Collector Start with a See-All Nut
- # Dark Hunter Start with a Tasty Meat
- # Grand Master Start with a Moon card
- # Red (Side) Solar Trance time + 5000 frames
- # Black (Side) Dark Trance time + 5000 frames
- # No Password Start with 2 extra Earthly Nuts
- #
- # ACKNOWLEDGMENTS
- # Most of the Lunar Knight / Boktai DS information, and the character
- # tables were copied from github.com/Prof9/LKPassDecode.
- #
- # github.com/Prof9/SolDec was very useful in understanding bytecode.
- #
- # The Boktai 3 translations and Japanese timezone names are from
- # github.com/moozilla/boktai3trans.
- # lmap shim for Tcl 8.5
- if {[info commands lmap] == ""} {
- proc lmap {args} {
- if {[llength $args] < 3 || [llength $args] % 2 != 1} {
- return -code error \
- {wrong # args: should be "lmap vars list ?vars list ...? body"}
- }
- set i 0
- for {set i 0} {$i < [llength $args] - 1} {incr i 2} {
- for {set j 0; set l {}} {$j < [llength [lindex $args $i]]} {incr j} {
- upvar [lindex $args $i $j] var$i,$j
- lappend l var$i,$j
- }
- lset args $i $l
- }
- set r {}
- foreach {*}[lrange $args 0 end-1] {
- lappend r [uplevel 1 [lindex $args end]]
- }
- return $r
- }
- }
- # dict map shim for Tcl 8.5
- if {[namespace which -command ::tcl::dict::map] == ""} {
- proc ::tcl::dict::map {vars dict body} {
- if {[llength $vars] != 2} {
- return -code error {must have exactly two variable names}
- }
- upvar 1 [lindex $vars 0] key [lindex $vars 1] value
- ::set r {}
- dict for {key value} $dict {
- ::set value [uplevel 1 $body]
- set r $key $value
- }
- return $r
- }
- namespace ensemble configure ::dict -map [dict replace \
- [namespace ensemble configure ::dict -map] map ::tcl::dict::map]
- }
- namespace eval bok {namespace export *}
- set bok::strings {en {
- games {
- jp1 {Bokura no Taiyō}
- jp2 {Zoku Bokura no Taiyō: Taiyō Shōnen Jango}
- jp3 {Shin Bokura no Taiyō: Gyakushū no Sabata}
- jp4 {Bokura no Taiyō: Django & Sabata}
- en1 {Boktai: The Sun Is in Your Hand}
- en2 {Boktai 2: Solar Boy Django}
- en3 {Boktai 3: Sabata's Counterattack}
- en4 {Lunar Knights}
- }
- labels {
- game-tabs {
- 1 Boktai 2 {Boktai 2 / Zoktai}
- 3 {Boktai 3 / Shinbok} 4 {Lunar Knights / Boktai DS}
- }
- keyboard-tabs {
- en64 Base64 lk64 {Base64 LK} jp64 {Base64 JP}
- en English ext {Latin Ext.}
- hiri Hirigana kata Katakana
- }
- padding Padding header-padding Padding
- region Region region-name Region
- checksum Checksum offset Offset
- sol Sol sol/4 Sol sol/4096 Sol
- loan Loan loan/256 Loan
- timezone Timezone timezone-name Timezone
- hours Hours minutes Minutes
- difficulty Difficulty difficulty-name Difficulty
- dungeons Dungeons clears Clears
- continues Continues caught Caught
- kills Kills kills/8 Kills
- rank Rank rank-name Rank
- forges Forges races Races
- link-battles Link-Battles link-trades Link-Trades
- link-shopping Link-Shopping
- link-races Link-Races cross-linked Cross-Linked
- title Title title-name Title
- titles Titles title-list Titles
- side Side side-name Side
- style Style style-name Style
- sword Sword sword-name Sword
- gun Gun gun-name Gun
- terrennial Terrennial terrennial-name Terrennial
- climate Climate climate-name Climate
- endings Endings ending-list Survivors
- name-dark Name(dark) name-solar Name(sol) name Name
- }
- regions {{Unknown Region #0} Japan {North America} Europe}
- timezones {jp {
- {Unknown Timezone #0}
- Ibaraki Tochigi Gunma Saitama
- Chiba Tokyo Kanagawa Ogasawara
- Niigata Toyama Ishikawa Fukui
- Yamanashi Nagano Gifu Shizuoka
- Aichi Mie Shiga Kyoto
- Osaka Hyougo Nara Wakayama
- Tottori Shimane Okayama Hiroshima
- Yamaguchi Tokushima Kagawa Ehime
- Kouchi Fukuoka Saga Nagasaki
- Kumamoto Ooita Miyazaki Kagoshima
- Okinawa Ishigakijima Sapporo Hakodate
- Asahikawa Kushiro Obihiro Aomori
- Iwate Miyagi Akita Yamagata
- Fukushima
- } na {
- {Unknown Timezone #0}
- {St. John's} {Labrador City} Halifax Quebec
- Montreal Ottawa Toronto Timmins
- Boston Albany Syracuse {New York}
- Philadelphia Pittsburgh {Washington D.C.} Norfolk
- Raleigh Charlotte Atlanta Jacksonville
- Tampa Miami Detroit Cleveland
- Columbus Lexington {Thunder Bay} Winnipeg
- Regina Thompson Indianapolis Chicago
- Milwaukee Minneapolis Bismark {St. Louis}
- Nashville Memphis Montgomery Jackson
- {New Orleans} {Des Moines} Lincoln {Kansas City}
- Topeka Springfield {Little Rock} {Oklahoma City}
- Dallas Houston Edmonton Calgary
- Yellowknife Denver Albuquerque Phoenix
- Boise {Salt Lake City} Vancouver Whitehorse
- Spokane Seattle Salem Reno
- {Las Vegas} {Los Angeles} {San Diego} {San Francisco}
- Anchorage Fairbanks Ketchikan Honolulu
- } eu {
- {Unknown Timezone #0}
- Reykjavik Dublin Cork London
- Cardiff Edinburgh Belfast Liverpool
- Lisbon Porto Valletta Madrid
- Barcelona Valencia {La Coruna} Seville
- Paris Brest Lyons Bordeaux
- Marseilles Brussels Bastogne Amsterdam
- Rotterdam Luxembourg Berlin Hamburg
- Essen Frankfurt Munich Bern
- Geneve Vaduz Wien Innsbruck
- Rome Genova Venezia Palermo
- Sassari Oslo Bergen Trondheim
- Copenhagen Odense Stockholm Gothenburg
- Helsinki Turku Mikkeli Warszawa
- Gdansk Poznan Wroclaw Krakow
- Praha Bratislava Kosice Budapest
- Bucuresti Ljubljana Zagreb Sarajevo
- Beograd Skopje Tirane Sofiya
- Athinai Thessaloniki Iraklion Ankara
- Istanbul Izmir Konya Adana
- Jerusalem Pretoria {Cape Town} Durban
- Wellington Auckland Dunedin Sydney
- Melbourne Adelaide Perth Brisbane
- }}
- ranks {S A+ A A- B+ B B- C+ C C- D+ D D- F+ F F-}
- difficulties {
- 1 {Easy {Normal 1} {Normal 2} Hard}
- 2 {}
- 3 {}
- 4 {Normal Hard Nightmare}
- }
- titles {1 {
- {Trigger of Sol} {Gun Master} Gladiator Bishop
- Queen Berserker Death {Solar Boy}
- {Dark Boy} {Solar Merchant} {Running Boy} King
- Rook Knight Pawn
- } 2 {
- {Sword Master} {Spear Master} {Hammer Master} {Fist Master}
- {Gun Master} Adept {Day Walker} {Adventurer}
- Agent Collector {Dark Hunter} {Grand Master}
- } 3 {
- Adept Gladiator {SP Agent} Champion
- {Dark Hunter} Alchemist Collector {Doll Master}
- Storyteller Grandmaster
- } 4 {
- {Dark Knight} {Sol Gunner} {Sword Master} {Gun Master}
- Guardian {Treasure Hunter} Collector Huntmaster
- {Shooting Star} Gladiator {Special Agent} Wanderer
- Adventurer {Grand Master}
- }}
- sides {Red Black Grey}
- styles {Sword Spear Hammer Gun Fists {No Style}}
- endings {Otenko Everybody Nobody Sabata}
- swords {Vanargand Jormungandr Hel}
- guns {Knight Dragoon Bomber Witch Ninja}
- terrennials {Toasty Nero Ursula Ezra Alexander Tove {War Rock}}
- climates {
- {Balmy Sub-Tropical} {Arid Desert} {Tropical Rainforest}
- {Humind-Continental} {Frigid Arctic}
- }
- }}
- # bok::@ is a shortcut to lookup a string in bok::strings. Arguments starting
- # with "#" are used as list indices, other arguments are used as dict keys.
- proc bok::@ {locale args} {
- variable strings
- set current [dict get $strings $locale]
- foreach arg $args {
- set current [expr {[string index $arg 0] == "#" ?
- [lindex $current [string range $arg 1 end]] : [dict get $current $arg]
- }]
- }
- return $current
- }
- # The character table is stored as a dictionary of lists indexed by the table's
- # number formatted as "0x%X". {0x0 0x1F 0x80 0x81 0x81}
- set bok::ctable {0x0 {
- � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � �
- { } ! \" # ÷ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
- @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ × ] ^ _
- ` a b c d e f g h i j k l m n o p q r s t u v w x y z \{ | \} ¯ ⋅
- } 0x1F {
- � Ä � Ç É Ñ Ö Ü á à â ä � å ç é è ê ë í ì î ï ñ ó ò ô ö � ú ù û
- ü � ° � � � � � ß � � � � � � � � � � � � � � � � � � � � � � �
- � ¿ ¡ � � � � � « » � � À � � Œ œ � � � � � � � ý ÿ Ý � � � � �
- � � � � � � Â Ê Á Ë È Í Î Ï Ì Ó Ô � Ò Ú Û Ù � � � � � � � � � �
- } 0x80 {
- � あ い う え お か き く け こ さ し す せ そ
- よ た ち つ て と な に ぬ ね の は ひ ふ へ ほ
- 下 ま み む め も や ゆ よ ら り る れ ろ わ を
- 左 ん ぁ ぃ ぅ ぇ ぉ っ ゃ ゅ ょ が ぎ ぐ げ ご
- 右 ざ じ ず ぜ ぞ だ ぢ づ で ど ば び ぶ べ ぼ
- 東 ぱ ぴ ぷ ぺ ぽ 。 、 ~ ー … � � � � �
- 西 ア イ ウ エ オ カ キ ク ケ コ サ シ ス セ ソ
- 南 タ チ ツ テ ト ナ ニ ヌ ネ ノ ハ ヒ フ ヘ ホ
- 北 マ ミ ム メ モ ラ リ ル レ ロ ヤ ユ ヨ ワ ヲ
- 大 ン ァ ィ ゥ ェ ォ ッ ャ ュ ョ ガ ギ グ ゲ ゴ
- 中 ザ ジ ズ ゼ ゾ ダ ヂ ヅ デ ド バ ビ ブ ベ ボ
- 小 パ ピ プ ペ ポ ・ : ; 「 」 + × ℃ ℉ �
- � ↑ ↓ → ← ★ ♥ ♪ ヴ Ⅰ Ⅱ Ⅲ � � � �
- 風 白 黒 赤 青 黄 緑 金 銀 紫 � 火 炎 災 水 氷
- 永 太 陽 年 月 日 時 分 秒 春 夏 秋 冬 之 ヶ 々
- = 丈 片 己 凶 歯 � � � � � � � � � �
- } 0x180 {
- � あ い う え お か き く け こ さ し す せ そ
- よ た ち つ て と な に ぬ ね の は ひ ふ へ ほ
- 下 ま み む め も や ゆ よ ら り る れ ろ わ を
- 左 ん ぁ ぃ ぅ ぇ ぉ っ ゃ ゅ ょ が ぎ ぐ げ ご
- 右 ざ じ ず ぜ ぞ だ ぢ づ で ど ば び ぶ べ ぼ
- 東 ぱ ぴ ぷ ぺ ぽ 。 、 ~ ー … � � � � �
- 西 ア イ ウ エ オ カ キ ク ケ コ サ シ ス セ ソ
- 南 タ チ ツ テ ト ナ ニ ヌ ネ ノ ハ ヒ フ ヘ ホ
- 北 マ ミ ム メ モ ラ リ ル レ ロ ヤ ユ ヨ ワ ヲ
- 大 ン ァ ィ ゥ ェ ォ ッ ャ ュ ョ ガ ギ グ ゲ ゴ
- 中 ザ ジ ズ ゼ ゾ ダ ヂ ヅ デ ド バ ビ ブ ベ ボ
- 小 パ ピ プ ペ ポ ・ : ; 「 」 + × ℃ ℉ �
- � ↑ ↓ → ← ★ ♥ ♪ ヴ Ⅰ Ⅱ Ⅲ � � � �
- 風 白 黒 赤 青 黄 緑 金 銀 紫 � 火 炎 災 水 氷
- 永 太 陽 年 月 日 時 分 秒 春 夏 秋 { } 之 ヶ 々
- = 丈 片 己 凶 歯 � � � � � � � � � �
- } 0x81 {
- � 地 均 坂 塔 境 塊 填 場 増 堀 堤 壊 塚 域 城
- � 現 理 球 環 � � � � 切 � 功 攻 項 崎 靖
- 端 化 代 付 何 仕 任 仗 仲 伯 件 作 伝 休 体 仮
- 住 佐 他 使 便 信 倍 借 価 値 低 侮 個 保 係 供
- 侵 依 偉 備 偽 似 俊 傷 像 優 候 修 例 側 倒 働
- 健 併 佳 倫 停 傲 儀 � � � � � � � � �
- 衝 行 往 彼 役 徐 復 後 待 得 徳 術 街 御 徴 徹
- 衛 打 払 押 択 技 抜 投 抗 持 担 指 捨 排 抵 挑
- 推 提 携 授 接 掘 操 揮 捕 探 換 振 掛 援 拠 損
- 拡 把 握 掃 撤 � � � � � � � � � � �
- 状 牧 物 特 犠 牲 独 狙 猛 狂 狩 猫 狐 狼 獲 猟
- 獄 性 快 悦 怪 悟 怖 情 慎 慢 燐 憶 � � � �
- � 粒 料 粗 精 � � � � 灯 灼 焼 煙 燥 燃 爆
- 燼 札 材 林 杯 村 析 相 枚 板 松 根 格 槍 横 株
- 様 棺 桶 桿 植 橋 構 機 械 � 欄 樹 樋 椛 椿 模
- 根 標 � � � 和 利 科 称 程 種 移 秘 積 稼 稲
- } 0x82 {
- � 礼 祈 社 祝 神 視 福 � � � � 初 裕 複 被
- 捕 紅 紀 約 紋 紙 細 組 統 終 純 練 級 緒 経 絵
- 給 絃 納 紹 絡 結 続 継 絶 編 縁 博 織 総 縦 綾
- 締 績 網 縮 絆 幻 郷 � � 次 冷 凍 � � 議 論
- 訳 計 討 記 許 訓 詳 説 話 証 読 設 語 談 試 調
- 誤 課 誠 誘 護 認 謙 誕 識 謝 泡 汚 浪 液 涙 汰
- 沙 江 況 沢 泊 河 注 洋 泣 治 活 浴 浩 池 波 洗
- 流 法 決 油 消 温 浮 海 � � � � � � � �
- }}
- # bok::decstr converts a list of 8-bit integers to a string, mapping to
- # bok::ctable. If $tab is specified then the decoding only uses table $tab,
- # essentially assuming that each 8-bit integer is preceded by $tab.
- proc bok::decstr {src {tab ""}} {
- variable ctable
- if {$tab != ""} {
- set tab [format 0x%X $tab]
- if {![dict exists $ctable $tab]} {
- return -code error "invalid table $tab"
- }
- }
- while {[lindex $src end] == 0} {
- set src [lreplace $src end end]
- }
- set r ""
- set state ""
- foreach c $src {
- if {$state == ""} {
- if {$tab == ""} {
- set state [format 0x%X $c]
- if {[dict exists $ctable $state]} {continue}
- set state [format 0x%X [expr {$c >> 8}]]
- } else {
- set state $tab
- }
- }
- if {[dict exists $ctable $state]} {
- append r [lindex [dict get $ctable $state] [expr {$c & 0xFF}]]
- } else {
- append r �
- }
- set state ""
- }
- return $r
- }
- # bok::encstr converts a string to a list of 8-bit integers, mapping from
- # bok::ctable. If $tab is specified the encoder only uses the $tab table,
- # essentially omitting the table-change characters.
- proc bok::encstr {src {tab ""}} {
- variable ctable
- if {$tab != ""} {
- set tab [format 0x%X $tab]
- if {![dict exists $ctable $tab]} {
- return -code error "invalid table $tab"
- }
- }
- set k 0
- set r {}
- foreach c [split $src {}] {
- if {$c == "�"} {break}
- if {$tab != ""} {
- set k 0
- set t [lsearch -exact [dict get $ctable $tab] $c]
- } else {
- foreach {k s} $ctable {
- if {[set t [lsearch -exact $s $c]] >= 0} {break}
- }
- }
- if {$t < 0} {break}
- if {$k != "0"} {lappend r $k}
- lappend r $t
- }
- return $r
- }
- set bok::b64table {jp {
- あ い う え お か き く け こ さ し す せ そ た
- ち つ て と な に ぬ ね の は ひ ふ へ ほ ま み
- む め も や ゆ よ ら り る れ ろ わ を が ぎ ぐ
- げ ご ざ じ ず ぜ ぞ だ ぢ づ で ど ば び ぶ べ ぼ
- } en {
- B C D F G H J K L M N P Q R S T V W X Y Z
- b c d f g h j k l m n p q r s t v w x y z
- 0 1 2 3 4 5 6 7 8 9 ? ! @ # = ^ > / - _ + : .
- } lk {
- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
- a b c d e f g h i j k l m n o p q r s t u v w x y z
- 1 2 3 4 5 6 7 8 9 0 ? = .
- }}
- # bok::decb64 decodes the base64 string $pass using the above tables into a list
- # of 6-bit integers, ignoring any [:space:] characters. If the $game is 4 the
- # "jp" & "lk" tables are used, otherwise the "jp" & "en" tables are used.
- proc bok::decb64 {pass {game 1} {lang ""}} {
- variable b64table
- set tables [list [dict get $b64table jp] \
- [dict get $b64table [expr {$game < 4 ? "en" : "lk"}]]]
- return [lmap c [split $pass {}] {
- foreach table $tables {
- if {[set j [lsearch -exact $table $c]] >= 0} {break}
- }
- if {[string is space $c] || $j >= 64} {continue}
- if {$j < 0} {return -code error "Invalid character: $c"}
- set j
- }]
- }
- # bok::encb64 encodes a base64 string from list, which is list of 6-bit integers
- # using the appropriate alphabet for the $game / $lang pair. $lang is set based
- # on the region if it is set to an empty string.
- proc bok::encb64 {list {game 1} {lang ""}} {
- variable b64table
- if {$lang == ""} {
- set lang [getlang $list $game 6]
- }
- set table [dict get $b64table [expr {
- $lang == "jp" ? "jp" : $game < 3 ? "en" : "lk"}]]
- return [join [lmap c $list {lindex $table $c}] {}]
- }
- # bok::spaceout inserts spaces every few characters for readability. It uses
- # the common amount for a particular $game and $lang combination. 5 if $game
- # is 4, 6 if $lang is "jp", and finally 8 if $lang is "en". Otherwise the
- # number guessed based on the length of $str.
- proc bok::spaceout {str {game ""} {lang ""}} {
- if {$game == "" && $lang == ""} {
- variable passwordlengths
- set k [lindex [dict keys [dict filter $passwordlengths value \
- [string length $str]]] 0]
- } else {
- set k $lang$game
- }
- switch -exact -- $k {
- 4 - en4 - jp4 {set n 5}
- jp - jp1 - jp2 - jp3 {set n 6}
- en - en1 - en2 - en3 {set n 8}
- default {return $str}
- }
- for {set i $n} {$i < [string length $str]} {incr i; incr i $n} {
- set str "[string range $str 0 $i-1] [string range $str $i end]"
- }
- return $str
- }
- # bok::getint gets an integer of width $count at bit-offset $offset from the
- # list $list of $width wide integers, interpreting it as a two's complement
- # signed integer if $sign is true. The bits are read and written LSb first.
- proc bok::getint {list offset count {sign 0} {width 6}} {
- set count [expr {min($count, [llength $list] * $width - $offset)}]
- for {set v 0; set i 0} {$i < $count} {incr i; incr offset} {
- set j [expr {$offset / $width}]
- set k [expr {$offset % $width}]
- set v [expr {$v | ([lindex $list $j] >> $k & 1) << $i}]
- }
- if {$sign && $count > 0 && ($v >> $count - 1 & 1)} {
- set v [expr {-(~$v + 1 & (1 << $count) - 1)}]
- }
- return $v
- }
- # bok::putint puts the integer $v of width $count into list $listvar at
- # bit-offset $offset. $listvar is a list of $width wide integers. Zeros are
- # added to the list as needed. $v is clamped to [0,2^$count) for unsigned
- # integers ($sign is false), and [-2^($count-1),2^($count-1)) for signed
- # integers ($sign is true). Signed numbers are converted to two's-complement
- # formatted $width wide unsigned integers before writing. The bits are written
- # and read LSb first.
- proc bok::putint {listvar v offset count {sign 0} {width 6}} {
- if {$listvar != ""} {upvar $listvar list} {set list {}}
- set m [expr {(1 << $count - ($sign && $count > 0 ? 1 : 0)) - 1}]
- set v [expr {$sign ? min(max($v, -($m + 1)), $m) + $m + 1 ^ $m + 1 :
- min(max($v, 0), $m)}]
- for {set i 0} {$i < $count} {incr i; incr offset} {
- set j [expr {$offset / $width}]
- set k [expr {$offset % $width}]
- while {[llength $list] <= $j} {lappend list 0}
- lset list $j [expr {
- [lindex $list $j] & ~(1 << $k) | ($v >> $i & 1) << $k}]
- }
- return $list
- }
- # bok::getstr gets the string converted from $count octets from the $list of
- # $width wide integers starting at bit-offset $offset. $table is passed to
- # bok::decstr along with the octets.
- proc bok::getstr {list offset count {table ""} {width 6}} {
- if {$table == "-"} {set table ""}
- if {$offset + $count * 8 > [llength $list] * $width} {
- set count [expr {[llength $list] * $width - $offset >> 3}]
- }
- for {set r {}; set i 0} {$i < $count} {incr i} {
- lappend r [getint $list [expr {$offset + $i * 8}] 8 0 $width]
- }
- return [decstr $r $table]
- }
- # bok::putstr puts $count octets from the encoded $str into $listvar at
- # bit-offset $offset. $listvar is a list of $width wide integers and $table
- # is passed to bok::encstr along with $str.
- proc bok::putstr {listvar str offset count {table ""} {width 6}} {
- if {$listvar != ""} {upvar $listvar list} {set list {}}
- if {$table == "-"} {set table ""}
- set l [encstr $str $table]
- for {set i 0} {$i < $count} {incr i} {
- set c [expr {$i < [llength $l] ? [lindex $l $i] : 0}]
- putint list $c [expr {$offset + $i * 8}] 8 0 $width
- }
- return $list
- }
- # bok::bitmaps is a dictionary of bitmaps with the keys being the password
- # language and the game number: {jp1 jp2 jp3 jp4 en1 en2 en3 en4}
- # each bitmap is a dictionary with list values: {type offset count modifier}
- # The type is either "int" or "str". The offset is the bit that the value
- # starts at. The count how long the value is in either bits (for type "int")
- # or octets (for type "str"). The modifier for type "int" is a boolean meaning
- # unsigned for false values or signed for true values; for type "str" it is the
- # character table to use with "-" indicating the default (""). The list items
- # after the type can be used directly as arguments to bok::getint/bok::putint
- # for type "int", or bok::getstr/bok::putstr for type "str".
- set bok::bitmaps {
- jp1 {
- region {int 0 3 0} checksum {int 3 16 0}
- offset {int 19 2 0} sol/4 {int 21 9 0}
- timezone {int 30 6 0} hours {int 36 6 0}
- minutes {int 42 6 0} difficulty {int 48 3 0}
- dungeons {int 51 5 0} clears {int 56 3 0}
- continues {int 59 4 0} caught {int 63 5 0}
- kills {int 68 7 0} rank {int 75 4 0}
- title {int 79 5 0} name {str 84 5 0x180}
- link-battles {int 124 6 0} link-trades {int 130 6 0}
- loan {int 136 8 0} padding {int 144 0 0}
- } jp2 {
- checksum {int 0 16 0} header-padding {int 16 2 0}
- region {int 18 3 0} offset {int 21 3 0}
- timezone {int 24 6 0} side {int 30 3 0}
- style {int 33 4 0} kills/8 {int 37 8 0}
- forges {int 45 7 0} link-battles {int 52 6 0}
- link-shopping {int 58 6 0} sol/4096 {int 64 10 1}
- loan/256 {int 74 8 1} hours {int 82 6 0}
- titles {int 88 16 0} name {str 104 5 0x180}
- padding {int 144 0 0}
- } jp3 {
- checksum {int 0 16 0} header-padding {int 16 2 0}
- region {int 18 3 0} offset {int 21 3 0}
- timezone {int 24 6 0} kills/8 {int 30 8 0}
- forges {int 38 7 0} races {int 45 9 0}
- link-races {int 54 7 0} cross-linked {int 61 1 0}
- endings {int 62 6 0} sol/4096 {int 68 10 0}
- loan/256 {int 78 8 0} hours {int 86 6 0}
- titles {int 92 12 0} name {str 104 5 0x180}
- padding {int 144 0 0}
- } jp4 {
- checksum {int 0 16 0} header-padding {int 16 2 0}
- region {int 18 3 0} offset {int 21 3 0}
- titles {int 24 14 0} difficulty {int 38 2 0}
- hours {int 40 7 0} sol/4096 {int 47 15 0}
- sword {int 62 3 0} gun {int 65 3 0}
- terrennial {int 68 3 0} climate {int 71 3 0}
- name-dark {str 74 10 -} name-solar {str 154 10 -}
- padding {int 234 6 0}
- } en1 {
- region {int 0 3 0} checksum {int 3 16 0}
- offset {int 19 2 0} sol/4 {int 21 9 0}
- timezone {int 30 7 0} hours {int 37 6 0}
- minutes {int 43 6 0} difficulty {int 49 3 0}
- dungeons {int 52 5 0} clears {int 57 3 0}
- continues {int 60 4 0} caught {int 64 5 0}
- kills {int 69 7 0} rank {int 76 4 0}
- title {int 80 5 0} name {str 85 9 0}
- link-battles {int 157 6 0} link-trades {int 163 6 0}
- loan {int 169 8 0} padding {int 177 15 0}
- } en2 {
- checksum {int 0 16 0} header-padding {int 16 2 0}
- region {int 18 3 0} offset {int 21 3 0}
- timezone {int 24 7 0} side {int 31 3 0}
- style {int 34 4 0} kills/8 {int 38 8 0}
- forges {int 46 7 0} link-battles {int 53 6 0}
- link-shopping {int 59 6 0} sol/4096 {int 65 10 1}
- loan/256 {int 75 8 1} hours {int 83 6 0}
- titles {int 89 16 0} name {str 105 9 0}
- padding {int 177 15 0}
- } en3 {
- checksum {int 0 16 0} header-padding {int 16 2 0}
- region {int 18 3 0} offset {int 21 3 0}
- timezone {int 24 7 0} kills/8 {int 31 8 0}
- forges {int 39 7 0} races {int 46 9 0}
- link-races {int 55 7 0} cross-linked {int 62 1 0}
- endings {int 63 6 0} sol/4096 {int 69 10 0}
- loan/256 {int 79 8 0} hours {int 87 6 0}
- titles {int 93 12 0} name {str 105 9 0}
- padding {int 177 15 0}
- } en4 {
- checksum {int 0 16 0} header-padding {int 16 2 0}
- region {int 18 3 0} offset {int 21 3 0}
- titles {int 24 14 0} difficulty {int 38 2 0}
- hours {int 40 7 0} sol/4096 {int 47 15 0}
- sword {int 62 3 0} gun {int 65 3 0}
- terrennial {int 68 3 0} climate {int 71 3 0}
- name-dark {str 74 10 -} name-solar {str 154 10 -}
- padding {int 234 6 0}
- }
- }
- # bok::getdict returns a dictionary created by reading values from the $list of
- # $width wide integers for each key in $args, or every key if $args is empty,
- # as specified in bok::bitmaps for the $game / $lang pair.
- proc bok::getdict {list game lang width args} {
- variable bitmaps
- if {[llength $args] == 0} {
- set args [dict keys [dict get $bitmaps $lang$game]]
- }
- set dict {}
- foreach key $args {
- if {![dict exists $bitmaps $lang$game $key]} {continue}
- lassign [dict get $bitmaps $lang$game $key] type offset count mod
- switch -exact -- $type str {
- dict set dict $key [getstr $list $offset $count $mod $width]
- } int {
- dict set dict $key [getint $list $offset $count $mod $width]
- }
- }
- return $dict
- }
- # bok::getkeys returns the list of values from calling bok::getdict.
- proc bok::getkeys {list game lang width args} {
- return [dict values [getdict $list $game $lang $width {*}$args]]
- }
- # bok::getkey returns only the value from calling bok::getdict with $key.
- proc bok::getkey {list game lang width key} {
- return [lindex [getkeys $list $game $lang $width $key] 0]
- }
- # bok::putdict inserts the values from each key/value pair into the $listvar of
- # $width wide integers as specified with the corresponding key in bok::bitmaps
- # for the $game / $lang pair.
- proc bok::putdict {listvar game lang width args} {
- variable bitmaps
- if {$listvar != ""} {upvar $listvar list} {set list {}}
- if {[llength $args] == 1} {set args [concat {*}$args]}
- if {[llength $args] % 2 != 0} {
- set usage {putdict listvar game lang width ?dict | key value ...?}
- return -code error "wrong # args: should be \"$usage\""
- }
- foreach {key value} $args {
- if {![dict exists $bitmaps $lang$game $key]} {continue}
- lassign [dict get $bitmaps $lang$game $key] type offset count mod
- switch -exact -- $type str {
- putstr list $value $offset $count $mod $width
- } int {
- putint list $value $offset $count $mod $width
- }
- }
- return $list
- }
- # bok::modkeys executes the last argument for each key given in $args, with
- # the key, value, and count values set to variables specified in $varnames.
- # If $varnames only contains one member then it is used for the storing the
- # value. The key and count values are from the bitmap specified in bok::bitmaps
- # for the $game / $lang pair, and the value itself is extracted from the list of
- # $width wide integers in $listvar. The result of the executed body is then
- # put back into $listvar in place of what was extracted. Non-existent or out
- # of range keys are ignored, continues skip overwriting the value, and breaks
- # exit early. This proc returns the final value of $listvar.
- proc bok::modkeys {listvar game lang width varnames args} {
- if {$listvar == "" || $varnames == "" || [llength $args] < 1} {
- set usage {modify listvar game lang width varnames ?key ...? body}
- return -code error "wrong # args: should be \"$usage\""
- }
- variable bitmaps
- upvar $listvar list
- if {[llength $varnames] > 1} {
- lassign $varnames keyname varname countvar
- if {$keyname != ""} {upvar $keyname key}
- if {$varname != ""} {upvar $varname var}
- if {$countvar != ""} {upvar $countvar count}
- } elseif {$varnames != ""} {
- upvar $varnames var
- }
- set body [lindex $args end]
- if {[llength $args] == 1} {
- set args [dict keys [dict get $bitmaps $lang$game]]
- } else {
- set args [lrange $args 0 end-1]
- }
- set max [expr {[llength $list] * $width}]
- foreach key $args {
- if {![dict exists $bitmaps $lang$game $key]} {continue}
- lassign [dict get $bitmaps $lang$game $key] type offset count mod
- if {$offset >= $max} {continue}
- switch -exact -- $type int {
- set var [getint $list $offset $count $mod $width]
- putint list [uplevel 1 $body] $offset $count $mod $width
- } str {
- set var [getstr $list $offset $count $mod $width]
- putstr list [uplevel 1 $body] $offset $count $mod $width
- }
- }
- return $list
- }
- # bok::getregion reads the region bits from the $list of $width wide integers
- # and returns a short region code, or an error.
- proc bok::getregion {list {game 1} {width 6}} {
- switch -exact -- [set t [getkey $list $game jp $width region]] {
- 1 {return jp} 2 {return na} 3 {return eu}
- }
- return -code error "Invalid region #$t"
- }
- # bok::getlang reads the region bits from the $list of $width wide integers and
- # returns a short language code or an error.
- proc bok::getlang {list {game 1} {width 6}} {
- switch -exact -- [set t [getkey $list $game jp $width region]] {
- 1 {return jp} 2 - 3 {return en}
- }
- return -code error "Invalid region #$t"
- }
- # bok::whichgame searches $dict for keys specific to a particular game, and
- # returns the game number of the first one found.
- proc bok::whichgame {dict} {
- foreach {key game} {
- dungeons 1 clears 1 caught 1 rank 1 title 1 link-trades 1
- rank-name 1 title-name 1
- side 2 style 2 link-shopping 2
- side-name 2 style-name 2
- races 3 link-races 3 cross-linked 3 endings 3
- sword 4 gun 4 terrennial 4 climate 4 name-dark 4 name-solar 4
- sword-name 4 gun-name 4 terrennial-name 4 climate-name 4
- } {
- if {[dict exists $dict $key]} {return $game}
- }
- return -code error "Unknown game"
- }
- # bok::checksum calculates the CRC-16 for the $list of 6-bit integers with an
- # initializer of 0xFFFF, the $game appropriate polynomial from bok::sumconsts,
- # and the output inverted, and starting at the offset from bok::sumoffsets.
- set bok::sumoffsets {4 3 3 3}
- set bok::sumconsts {0x1021 0x1021 0x8005 0x180D}
- proc bok::checksum {list {game 1}} {
- variable sumoffsets
- variable sumconsts
- for {
- set c [lindex $sumconsts $game-1]
- set v 0xFFFF
- set i [lindex $sumoffsets $game-1]
- } {$i < [llength $list]} {incr i} {
- set v [expr {$v ^ [lindex $list $i] << 8}]
- for {set j 0} {$j < 8} {incr j} {
- set v [expr {($v << 1 ^ (($v & 0x8000) ? $c : 0)) & 0xFFFF}]
- }
- }
- return [expr {~$v & 0xFFFF}]
- }
- # bok::xor encodes/decodes the password bits from the $list of 6-bit integers
- # using bok::xorconst and the $game appropriate initalizer from bok::xorconsts. # $m is the initialized value, $t is initialized to ~0, and the offset is
- # read from the $list. For each iteration $m is multiplied by bok::xorconst
- # then incremented by 1, and $t is xored by the result. After $offset plus 4
- # iterations, starting at index 4 until the end of the $list, each $list item
- # is xored by the last 6 bits of $t before the next iteration.
- # The encoded list is returned.
- # Using the full sized constants can casue unnecessary integer promotion,
- # and only the last 6 bits are used, so smaller constants are used here.
- #set bok::xorconsts {0x8C159 0xB8E6E 0x8C159 0x5BB15}
- set bok::xorconsts {0x19 0x2E 0x19 0x15}
- #set bok::xorconst 0x6262C05D
- set bok::xorconst 0x1D
- proc bok::xor {list {game 1}} {
- variable xorconsts
- variable xorconst
- for {
- set m [lindex $xorconsts $game-1]
- set t 0x3F
- set i -[getkey $list $game jp 6 offset]
- } {$i < [llength $list]} {incr i} {
- if {$i >= 4} {
- lset list $i [expr {[lindex $list $i] ^ $t}]
- }
- set m [expr {$xorconst * $m + 1 & 0x3F}]
- set t [expr {$t ^ $m}]
- }
- return $list
- }
- set bok::passwordlengths {
- jp1 24 jp2 24 jp3 24 jp4 40 en1 32 en2 32 en3 32 en4 40
- }
- # bok::decpass decodes the base64 string $password into a dictionary.
- proc bok::decpass {password {game 1} {lang ""}} {
- variable passwordlengths
- set l [decb64 $password $game]
- if {$lang == ""} {
- set lang [getlang $l $game]
- }
- set passlen [dict get $passwordlengths $lang$game]
- if {[llength $l] != $passlen} {
- return -code error "Invalid length: [llength $l] != $passlen"
- }
- set sum [checksum $l $game]
- set l [xor $l $game]
- set dict [getdict $l $game $lang 6]
- if {[dict get $dict checksum] != $sum} {
- dict append dict checksum "!=$sum"
- }
- return $dict
- }
- # bok::encpass encodes $dict into a base64 password.
- proc bok::encpass {dict {lang ""}} {
- if {$lang == ""} {
- set lang [lindex {jp jp en en} [dict get $dict region]]
- }
- set game [whichgame $dict]
- set l [xor [putdict {} $game $lang 6 $dict] $game]
- putdict l $game $lang 6 checksum [checksum $l $game]
- return [encb64 $l $game $lang]
- }
- # bok::normalizelist sets {*}$skey in $dictvar based on the value of {*}$vkey
- # based on the strings in $list, or vice versa. $name is used for error
- # messages, and defaults to the last element of $vkey. If $robust is set then
- # no errors are returned for invalid values, or known invalid strings.
- proc bok::normalizelist {dictvar list vkey skey {name ""} {robust 0}} {
- upvar $dictvar dict
- if {$name == ""} {
- set name [lindex $vkey end]
- }
- set inval "Invalid $name #"
- set unknown "Unknown $name #"
- if {[dict exists $dict {*}$vkey]} {
- set v [dict get $dict {*}$vkey]
- set s "$unknown$v"
- if {$v >= 0 && $v < [llength $list]} {
- set s [lindex $list $v]
- }
- if {!$robust && $s == "$unknown$v"} {
- return -code error $s
- }
- dict set dict {*}$skey $s
- } elseif {[dict exists $dict {*}$skey]} {
- set s [dict get $dict {*}$skey]
- set v [lsearch -exact -nocase $list $s]
- if {$v < 0 && [string first $inval $s] == 0} {
- set v [string range $s [string length $inval] end]
- if {![string is integer $v]} {set v -1}
- if {!$robust} {
- return -code error $s
- }
- } elseif {$v < 0 && [string first $unknown $s] == 0} {
- set v [string range $s [string length $unknown] end]
- if {![string is integer $v]} {set v -1}
- if {!$robust} {
- return -code error $s
- }
- }
- if {$v < 0} {
- return -code error "Invalid [lindex $skey end]: $s"
- }
- dict set dict {*}$vkey $v
- }
- }
- # bok::normalizebits sets {*}$lkey in $dictvar based on the value of {*}$vkey
- # based on the strings in $list, or vice versa. $name is used for error
- # messages, and defaults to the last element of $vkey. If $robust is set then
- # invalid values and items are ignored.
- proc bok::normalizebits {dictvar list vkey lkey {name ""} {robust 0}} {
- upvar $dictvar dict
- if {$name == ""} {
- set name [lindex $vkey end]
- }
- if {[dict exists $dict {*}$vkey]} {
- dict set dict {*}$vkey [format 0x%0[expr {
- [llength $list] + 3 >> 2
- }]X [dict get $dict {*}$vkey]]
- set v [dict get $dict {*}$vkey]
- for {set i 0; set l {}} {$i < [llength $list]} {incr i} {
- if {$v & 1 << $i} {
- set v [expr {$v ^ 1 << $i}]
- lappend l [lindex $list $i]
- }
- }
- if {!$robust && $v != 0} {
- for {} {!($v & 1 << $i)} {incr i} {}
- return -code error "Unknown $name #$i"
- }
- dict set dict {*}$lkey $l
- } elseif {[dict exists $dict {*}$lkey]} {
- set v 0
- foreach item [dict get $dict {*}$lkey] {
- set i [lsearch -exact -nocase $list $item]
- if {!$robust && $i < 0} {
- return -code error "Unknown $name: $item"
- }
- set v [expr {$v | ($i >= 0 ? 1 << $i : 0)}]
- }
- dict set dict {*}$vkey $v
- }
- }
- # bok::normalize normalizes the provided dictionary using $locale strings
- # such that if "sol/4096" or "timezone-name" exists then "sol" and "timezone"
- # will contain the equivalent value in the result. If $robust is set then no
- # ignorable errors are returned.
- proc bok::normalize {dict {locale en} {robust 0}} {
- # Identify game, language, and region
- set game [whichgame $dict]
- normalizelist dict [@ $locale regions] region region-name \
- [@ $locale labels region] $robust
- set region [expr {[dict exists $dict region] ? [dict get $dict region] : -1}]
- if {!$robust && ($region < 1 || $region > 3)} {
- return -code error "Unknown region"
- }
- set lang [lindex {?? jp en en ?? ?? ?? ??} $region]
- set region [lindex {?? jp na eu ?? ?? ?? ??} $region]
- # Normalize
- dict set dict game $game
- dict set dict game-name [@ $locale games $lang$game]
- if {![dict exists $dict header-padding]} {dict set dict header-padding 0}
- if {![dict exists $dict padding]} {dict set dict padding 0}
- if {![dict exists $dict offset]} {dict set dict offset 0}
- # Sol
- if {[dict exists $dict sol]} {
- dict set dict sol/4 [expr {int([dict get $dict sol] / 4)}]
- dict set dict sol/4096 [expr {int([dict get $dict sol] / 4096)}]
- } elseif {[dict exists $dict sol/4]} {
- dict set dict sol [expr {[dict get $dict sol/4] * 4}]
- dict set dict sol/4096 [expr {int([dict get $dict sol/4] / 1024)}]
- } elseif {[dict exists $dict sol/4096]} {
- dict set dict sol [expr {[dict get $dict sol/4096] * 4096}]
- dict set dict sol/4 [expr {[dict get $dict sol/4096] * 1024}]
- }
- # Kills
- if {[dict exists $dict kills]} {
- dict set dict kills/8 [expr {int([dict get $dict kills] / 8)}]
- } elseif {[dict exists $dict kills/8]} {
- dict set dict kills [expr {[dict get $dict kills/8] * 8}]
- }
- # Loan
- if {[dict exists $dict loan]} {
- dict set dict loan/256 [expr {int([dict get $dict loan] / 256)}]
- } elseif {[dict exists $dict loan/256]} {
- dict set dict loan [expr {[dict get $dict loan/256] * 256}]
- }
- # Timezone
- set l [@ $locale timezones]
- if {[dict exists $l $region]} {
- normalizelist dict [dict get $l $region] timezone timezone-name \
- [@ $locale labels timezone] $robust
- }
- # Boktai 1
- normalizelist dict [@ $locale ranks] rank rank-name [@ $locale labels rank] \
- $robust
- normalizelist dict [@ $locale titles 1] title title-name \
- [@ $locale labels title] $robust
- normalizelist dict [@ $locale difficulties $game] difficulty difficulty-name \
- [@ $locale labels difficulty] $robust
- # Boktai 2
- normalizelist dict [@ $locale sides] side side-name [@ $locale labels side] \
- $robust
- normalizelist dict [@ $locale styles] style style-name \
- [@ $locale labels style] $robust
- # Boktai 2, Boktai 3, and Lunar Knight's Titles
- normalizebits dict [@ $locale titles $game] titles title-list \
- [@ $locale labels titles] $robust
- # Boktai 3's Endings
- normalizebits dict [@ $locale endings] endings ending-list \
- [@ $locale labels endings] $robust
- # Lunar Knights favorites
- normalizelist dict [@ $locale swords] sword sword-name \
- [@ $locale labels sword] $robust
- normalizelist dict [@ $locale guns] gun gun-name [@ $locale labels gun] \
- $robust
- normalizelist dict [@ $locale terrennials] terrennial terrennial-name \
- [@ $locale labels terrennial] $robust
- normalizelist dict [@ $locale climates] climate climate-name \
- [@ $locale labels climate] $robust
- return $dict
- }
- namespace eval bok::ui {
- namespace export *
- namespace import [namespace parent]::*
- }
- # bok::ui::info returns a list of ui element types, values, and defaults
- # depending on the values of $game, $region, and $locale. Most values and
- # defaults can be dictionaries keyed by region. A key of "-" makes the next
- # widget always start on a new row.
- # key {int {min ?max?} ?defaults? ?step?}
- # key {string {max ?maxbytes?} ?defaults? ?restrict?}
- # key {list ?values? ?defaults?}
- # key {boolean ?default?}
- # key {bits values ?default?}
- # - {}
- proc bok::ui::info {game {region na} {locale en}} {
- set region [expr {max([region $region $locale num] - 1, 0)}]
- set timezone [expr {
- $region == 0 ? 5 : $region == 2 ? 21 : $region == 1 ? 14 : 0
- }]
- set timezonelists [dict map {key value} [@ $locale timezones] {
- lrange $value 1 end
- }]
- set regions [lrange [@ $locale regions] 1 end]
- switch -exact -- $game 1 {
- return [dict create \
- region-name [list list $regions $region] \
- offset {int {0 3}} \
- sol {int {0 0x7FC 4}} \
- timezone-name [list regionlist $timezonelists {jp 5 na 14 eu 21}] \
- - {} hours {int {0 63}} minutes {int {0 59} 1} \
- difficulty-name [list list [@ $locale difficulties 1] 2] \
- dungeons {int {0 29}} \
- clears {int {1 7}} continues {int {0 15}} \
- caught {int {0 31}} kills {int {0 127}} \
- rank-name [list list [@ $locale ranks]] \
- title-name [list list [@ $locale titles 1]] \
- name {string {jp 5 na 9 eu 9} {jp ジャンゴ na Django eu Django} 1} \
- link-battles {int {0 63}} link-trades {int {0 63}} \
- loan {int {0 255}} \
- padding hidden \
- ]
- } 2 {
- return [dict create \
- region-name [list list $regions $region] \
- offset {int {0 7}} \
- timezone-name [list regionlist $timezonelists {jp 5 na 14 eu 21}] \
- side-name [list list [@ $locale sides]] \
- style-name [list list [@ $locale styles]] \
- kills {int {0 0x7F8 8}} \
- forges {int {0 127}} \
- link-battles {int {0 63}} link-shopping {int {0 63}} \
- sol {int {0 0x1FF000 0x1000}} loan {int {0 0x7F00 0x100}} \
- hours {int {0 63}} \
- name {string {jp 5 na 9 eu 9} {jp ジャンゴ na Django eu Django} 1} \
- title-list [list bits [@ $locale titles 2] 0x40] \
- header-padding hidden padding hidden \
- ]
- } 3 {
- return [dict create \
- region-name [list list [lindex $regions 0] 0] \
- offset {int {0 7}} \
- timezone-name [list regionlist $timezonelists {jp 5 na 14 eu 21}] \
- kills {int {0 0x7F8 8}} forges {int {0 127}} races {int {0 511}} \
- link-races {int {0 127}} cross-linked {boolean} \
- sol {int {0 0x3FF000 0x1000}} loan {int {0 0xFF00 0x100}} \
- hours {int {0 63}} \
- name {string {jp 5 na 9 eu 9} {jp ジャンゴ na Django eu Django} 1} \
- ending-list [list bits [@ $locale endings] 1] \
- title-list [list bits [@ $locale titles 3]] \
- header-padding hidden padding hidden \
- ]
- } 4 {
- return [dict create \
- region-name [list list $regions $region] \
- offset {int {0 7}} \
- difficulty-name [list list [@ $locale difficulties 4]] \
- hours {int {0 127}} sol {int {0 0x7FFF000 0x1000}} \
- - {} \
- sword-name [list list [@ $locale swords]] \
- gun-name [list list [@ $locale guns]] \
- terrennial-name [list list [@ $locale terrennials]] \
- climate-name [list list [@ $locale climates]] \
- - {} \
- name-dark {string {jp {5 10} na 10 eu 10}
- {jp サバタ na Lucian eu Lucian}} \
- name-solar {string {jp {5 10} na 10 eu 10}
- {jp ジャンゴ na Aaron eu Aaron}} \
- title-list [list bits [@ $locale titles 4]] \
- header-padding hidden padding hidden \
- ]
- }
- }
- # bok::ui::region translates the $str to the region "number", two-letter "code",
- # "name", or a list of all three depending on $type. $locale selects which
- # region list from bok::strings to use.
- proc bok::ui::region {str {locale en} {type code}} {
- set dict {}
- switch -glob -nocase -- $str {
- jp - japan - japanese {dict set dict region 1}
- na - north?america - northamerica - america {dict set dict region 2}
- us - usa - united?states - unitedstates {dict set dict region 2}
- en - english {dict set dict region 2}
- eu - europe {dict set dict region 3}
- default {dict set dict region-name $str}
- }
- normalizelist dict [@ $locale regions] region region-name \
- [@ $locale labels region] 1
- set region [dict get $dict region]
- set str [dict get $dict region-name]
- set code [lindex {?? jp na eu ?? ?? ?? ??} $region]
- switch -exact -- $type {
- short - code {
- return $code
- } string - str - name {
- return $str
- } number - num {
- return $region
- } default {
- return [list $region $code $str]
- }}
- }
- # bok::ui::init initializes a grid of widgets in frame $window based on the
- # values from bok::ui::info for region $region. Valid options are -locale to
- # set the locale to use, -columns for the number of columns of label/entry pairs
- # to use, -bind for a list of bindings for matching elements, -style for a list
- # of styles for matching elements, -fontsize for the base font size, and
- # -validate to pass when creating widgets.
- # -bind and -style keys can match either the widget type, the widget subtype,
- # the widget path, or "all". For example button $widget.password.cycleoffset
- # matches the widget path, button, password.button, or "all".
- proc bok::ui::init {window region game args} {
- set usage {init path {jp|na|eu} {1|2|3|4} ?-option value ...?}
- if {[llength $args] % 2 != 0} {
- return -code error "wrong # args: should be \"$usage\""
- }
- set options [dict create \
- -locale en -columns 2 -bind {} -style {} -fontsize 10 -validate all]
- foreach {opt arg} $args {
- switch -exact -- $opt -locale - -columns - -fontsize - -validate {
- dict set options $opt $arg
- } -bind - -style {
- dict lappend options $opt {*}$arg
- } default {
- return -code error "bad option: $opt"
- }
- }
- set columns [expr {[dict get $options -columns] * 2}]
- set locale [dict get $options -locale]
- set region [region $region $locale]
- if {$region == "??" || $game < 1 || $game > 4} {
- return -code error "wrong args: should be \"$usage\""
- }
- if {![winfo exists $window]} {
- ttk::frame $window
- }
- set row 0
- set col $columns
- grid [ttk::frame $window.password] \
- -row $row -column 0 -columnspan $columns -sticky nsew
- pack [ttk::button $window.password.encode -text ↱ -width 2 -takefocus 0 \
- -command [list [namespace current]::encode $window $game $locale]
- ] -padx 2p -side left
- pack [ttk::entry $window.password.entry \
- -font "mono [expr {int([dict get $options -fontsize]*1.2)}]"] \
- -expand 1 -side left -fill x
- pack [ttk::button $window.password.cycleoffset -text ↻ -width 2 -takefocus 0 \
- -command [list [namespace current]::cycleoffset $window $game]
- ] -padx 2p -side left
- pack [ttk::button $window.password.decode -text ↴ -width 2 -takefocus 0 \
- -command [list [namespace current]::decode $window $game $locale]
- ] -padx 2p -side left
- set elements [list \
- frame password $window.password \
- button password.button $window.password.encode \
- entry password.entry $window.password.entry \
- button password.button $window.password.cycleoffset \
- button password.button $window.password.decode
- ]
- set sublabellengths {}
- foreach {key list} [info $game $locale] {
- if {$list == ""} {
- set col $columns
- continue
- }
- if {[lindex $list 0] == "hidden"} {
- if {[llength $list] < 2} {set list {hidden 0}}
- set default [lindex $list 1]
- if {[llength $default] > 1} {set default [dict get $defalut $region]}
- set ::$window.$key $default
- continue
- }
- if {[lindex $list 0] == "bits"} {
- set col $columns
- }
- if {[incr col] >= $columns} {
- set col 0
- incr row
- }
- grid [ttk::label $window.$key\-label -anchor e \
- -text [@ $locale labels $key]:
- ] -row $row -column $col -sticky new
- if {[lindex $list 0] != "bits"} {
- bind $window.$key\-label <ButtonPress> [list focus $window.$key]
- }
- incr col
- lappend elements label $key.label $window.$key\-label
- switch -exact -- [lindex $list 0] string {
- lassign [lrange $list 1 end] l default type
- grid [ttk::entry $window.$key -validate [dict get $options -validate] \
- -validatecommand [list [namespace current]::validatestring \
- $window %W %V %s %P $l $default $locale $type] \
- -font "sans [dict get $options -fontsize]"
- ] -row $row -column $col -sticky new
- if {[llength $default] > 1} {
- set default [dict get $default $region]
- }
- $window.$key insert 0 $default
- lappend elements entry string $window.$key
- } int {
- lassign [lrange $list 1 end] l default
- if {$default == ""} {set default [lindex $l 0]}
- if {[llength $l] == 1} {lappend l $l}
- lappend l 1
- grid [ttk::spinbox $window.$key \
- -font "sans [dict get $options -fontsize]" \
- -from [lindex $l 0] -to [lindex $l 1] -increment [lindex $l 2] \
- -validate [dict get $options -validate] \
- -validatecommand [list \
- [namespace current]::validateint $window %W %V %s %P $default]
- ] -row $row -column $col -sticky new
- if {[llength $default] > 1} {set default [dict get $default $region]}
- $window.$key set $default
- lappend elements spinbox int $window.$key
- } boolean {
- set default [lindex $list 1]
- grid [ttk::checkbutton $window.$key] -row $row -column $col -sticky new
- $window.$key state !alternate
- if {[llength $default] > 1} {set default [dict get $default $region]}
- if {$default != "" && !!$default} {
- $window.$key state selected
- }
- bind $window.$key\-label <ButtonPress> [list $window.$key invoke]
- lappend elements checkbutton boolean $window.$key
- } regionlist - list {
- lassign [lrange $list 1 end] l default
- if {[lindex $list 0] == "regionlist"} {set l [dict get $l $region]}
- grid [ttk::combobox $window.$key -state readonly -values $l \
- -font "sans [dict get $options -fontsize]" \
- ] -row $row -column $col -sticky new
- if {[llength $default] > 1} {set default [dict get $default $region]}
- $window.$key current [expr {$default == "" ? 0 : $default}]
- lappend elements combobox [lindex $list 0] $window.$key
- } bits {
- lassign [lrange $list 1 end] l default
- if {[llength $default] > 1} {set default [dict get $default $region]}
- grid [ttk::frame $window.$key -borderwidth 1] \
- -column $col -row $row -columnspan [expr {$columns - $col}] -sticky new
- set i 0
- set x 0
- set y 0
- foreach k $l {
- lappend elements label bits.label $window.$key.l$i
- grid [ttk::label $window.$key.l$i -text $k: -anchor e \
- -font "sans [dict get $options -fontsize]"] \
- -column $x -row $y -sticky new
- bind $window.$key.l$i <ButtonPress> [list $window.$key.$i invoke]
- if {![dict exists $sublabellengths $x] ||
- [dict get $sublabellengths $x] < [string length $k:]} {
- dict set sublabellengths $x [string length $k:]
- }
- incr x
- grid [ttk::checkbutton $window.$key.$i] \
- -column $x -row $y -sticky new
- $window.$key.$i state !alternate
- if {$default != "" && ($default & 1 << $i) != 0} {
- $window.$key.$i state selected
- }
- if {[incr x] >= $columns * 2} {
- set x 0
- incr y
- }
- lappend elements checkbutton bits.checkbutton $window.$key.$i
- incr i
- }
- if {$x > 0} {
- set x 0
- incr y
- }
- grid $window.$key -rowspan $y
- set col $columns
- incr row [expr {$y - 1}]
- lappend elements frame bits $window.$key
- }
- }
- if {$col > 0} {
- incr row
- }
- grid [ttk::label $window.error -text ✓] -column 0 -row $row -sticky sew \
- -columnspan $columns
- for {set col 1} {$col < $columns} {incr col 2} {
- grid columnconfigure $window $col -weight 1
- }
- for {set col 0} {$col < $columns} {incr col} {
- grid columnconfigure $window $col -pad 4p
- }
- for {set y 0} {$y < $row} {incr y} {
- grid rowconfigure $window $y -pad 2p
- }
- bind $window.region-name <<ComboboxSelected>> \
- [list [namespace current]::updateregion $window $game $locale]
- foreach {type key w} $elements {
- switch -exact -- $key bits {
- dict for {col width} $sublabellengths {
- foreach label [grid slaves $w -column $col] {
- $label configure -width $width
- }
- }
- }
- foreach {k binding} [dict get $options -bind] {
- if {[string equal -nocase $k all] ||
- [string equal -nocase $k $type] ||
- [string equal -nocase $k $key]} {
- foreach {pat body} $binding {
- bind $w $pat $body
- }
- }
- }
- foreach {k style} [dict get $options -style] {
- if {[string equal -nocase $k all] ||
- [string equal -nocase $k $type] ||
- [string equal -nocase $k $key]} {
- $w configure -style $style
- }
- }
- }
- return $window
- }
- # bok::ui::updateregion updates any region dependant entries/lists in $window
- # based on the value of $window.region-name and $locale.
- proc bok::ui::updateregion {window game {locale en}} {
- set region [region [$window.region-name get] $locale]
- if {$region == "??"} {
- $window.timezone-name set [@ $locale timezones \#0]
- $window.timezone-name configure state disabled
- return
- }
- set lang [expr {$region == "jp" ? "jp" : "en"}]
- set pass [decb64 [$window.password.entry get] $game]
- modkeys pass $game $lang 6 {k v} region {
- expr {$region == "jp" ? 1 : $region == "na" ? 2 : 3}
- }
- foreach {key list} [info $game $region $locale] {
- switch -exact -- [lindex $list 0] regionlist {
- $window.$key configure -values [dict get [lindex $list 1] $region]
- if {[llength [lindex $list 2]] > 1} {
- $window.$key current [dict get [lindex $list 2] $region]
- } elseif {[lindex $list 2] != ""} {
- $window.$key current [lindex $list 2]
- } else {
- $window.$key current 0
- }
- } string {
- $window.$key validate
- }
- }
- if {[llength $pass] > 0} {
- $window.password.entry delete 0 end
- $window.password.entry insert 0 [spaceout [
- encb64 $pass $game $lang] $game $lang]
- }
- }
- # bok::ui::validatestring validates the string in entry $window.
- # $root is the frame initialized by bok::ui::init, $window is the entry, $type
- # is the event type, $old is the old value, $new is the new value, $max is
- # {max ?maxbytes?} which limits the length of the string, default is either a
- # default string or a dictionary of default strings where the region in $root
- # is used as the key, $locale is the locale to use for the region name, and
- # $restrict if true restricts the character table to either 0x180 if the region
- # is Japan, or 0 otherwise.
- proc bok::ui::validatestring {root window type old {new {}} {max 0} {default {}}
- {locale en} {restrict 0}} {
- set region [region [$root.region-name get] $locale]
- set tab [expr {
- $restrict == "" || !$restrict ? "" : $region == "jp" ? 0x180 : 0
- }]
- if {[llength $max] > 2} {set max [dict get $max $region]}
- if {[llength $max] == 1} {lappend max $max}
- lassign $max max bytemax
- switch -exact -- $type key {
- set list [encstr $new $tab]
- return [expr {
- ($max <= 0 || [string length $new] <= $max) &&
- ($bytemax <= 0 || [llength $list] <= $bytemax) &&
- [decstr $list $tab] == $new
- }]
- } default {
- if {$old != ""} {
- set list [encstr $new $tab]
- if {$bytemax > 0 && [llength $list] > $bytemax} {
- set list [lrange $list 0 $bytemax-1]
- }
- set new [decstr $list $tab]
- if {$max > 0 && [string length $new] > $max} {
- set new [string range $new 0 $max-1]
- }
- if {$new == $old} {return 1}
- $window delete 0 end
- if {$new != ""} {
- $window insert 0 $new
- return 0
- }
- }
- if {$default != ""} {
- if {[llength $default] > 1} {set default [dict get $default $region]}
- $window insert 0 $default
- }
- return 0
- }
- }
- # bok::ui::validateint validates the value of spinbox $window is an integer.
- # $root is the frame initialized by bok::ui::init $window is the spinbox, $type
- # is the event type, $old is the old value, $new is the new value, and $default
- # is the default value.
- proc bok::ui::validateint {root window type old {new {}} {default {}}} {
- switch -exact -- $type key {
- if {![string is integer $new]} {
- return 0
- } elseif {$new != "" && $new < [$window cget -from]} {
- $window set [expr {int([$window cget -from])}]
- return 0
- } elseif {$new != "" && $new > [$window cget -to]} {
- $window set [expr {int([$window cget -to])}]
- return 0
- }
- return 1
- } default {
- if {$old == "" || ![string is integer $old]} {
- if {![string equal $old $default]} {
- $window set $default
- }
- return 0
- }
- return 1
- }
- }
- # bok::ui::encode encodes the widget values into the password entry.
- # $window is the window initialized with bok::ui::init, $game is the game that
- # $window is for, and $locale is the locale of bok::strings to use.
- proc bok::ui::encode {window game {locale en}} {
- if {$game < 1 || $game > 4} {
- return -code error \
- {wrong args: should be "encode path game ?locale?"}
- }
- set region [region [$window.region-name get] $locale]
- set info [info $game $region $locale]
- set dict {}
- foreach {key list} $info {
- switch -exact -- [lindex $list 0] {
- {} {continue}
- string - int - regionlist - list {
- dict set dict $key [$window.$key get]
- } boolean {
- dict set dict $key [expr {"selected" in [$window.$key state]}]
- } bits {
- dict set dict $key {}
- for {set i 0} {$i < [llength [lindex $list 1]]} {incr i} {
- if {"selected" in [$window.$key.$i state]} {
- dict lappend dict $key [lindex $list 1 $i]
- }
- }
- } hidden {
- dict set dict $key [set ::$window.$key]
- }}
- }
- $window.error configure -text ✓
- if {[catch {normalize $dict $locale} error]} {
- $window.error configure -text "⚠ $error"
- }
- set dict [normalize $dict $locale 1]
- if {[catch {set pass [encpass $dict]} error]} {
- $window.error configure -text "⚠ $error"
- return
- }
- switch -exact -- [dict get $dict game] 1 {
- if {[dict get $dict minutes] == 0 && [dict get $dict hours] == 0} {
- $window.error configure \
- -text "⚠ [@ $locale labels hours] or [
- @ $locale labels minutes] should be set"
- } elseif {[dict get $dict minutes] >= 60} {
- $window.error configure \
- -text "⚠ [@ $locale labels minutes] should be less than 60"
- } elseif {[dict get $dict clears] == 0} {
- $window.error configure \
- -text "⚠ [@ $locale labels clears] should be greater than 0"
- }
- } 2 {
- if {([dict get $dict titles] & (1 << 6)) == 0} {
- $window.error configure \
- -text "⚠ [@ $locale titles 2 \#6] should be set"
- }
- } 3 {
- } 4 {
- if {[dict get $dict name-solar] == ""} {
- $window.error configure \
- -text "⚠ [@ $locale labels name-solar] should be set"
- } elseif {[dict get $dict name-dark] == ""} {
- $window.error configure \
- -text "⚠ [@ $locale labels name-dark] should be set"
- }
- }
- if {[dict exists $dict name] && [dict get $dict name] == ""} {
- $window.error configure -text "⚠ [@ $locale labels name] should be set"
- }
- $window.password.entry delete 0 end
- $window.password.entry insert 0 [spaceout $pass]
- }
- # bok::ui::decode decodes the current password into the appropriate widgets.
- # $window is the window initialized with bok::ui::init, $game is the game that
- # $window is for, and $locale is the locale of bok::strings to use.
- proc bok::ui::decode {window game {locale en}} {
- set region [region [$window.region-name get] $locale]
- set lang [expr {$region == "jp" ? "jp" : "en"}]
- if {[catch {
- set dict [decpass [$window.password.entry get] $game $lang]
- } error]} {
- $window.error configure -text "⚠ $error"
- return
- }
- if {[string match *!=* [dict get $dict checksum]]} {
- $window.error configure -text "⚠ checksum [dict get $dict checksum]"
- } elseif {[catch {normalize $dict $locale} error]} {
- $window.error configure -text "⚠ $error"
- } else {
- $window.error configure -text ✓
- }
- set dict [normalize $dict $locale 1]
- foreach {key list} [info $game $region $locale] {
- switch -exact -- [lindex $list 0] {} {continue} string {
- $window.$key delete 0 end
- $window.$key insert 0 [dict get $dict $key]
- } int - regionlist - list {
- $window.$key set [dict get $dict $key]
- } boolean {
- $window.$key state "[expr {[dict get $dict $key] ? "" : "!"}]selected"
- } bits {
- for {set i 0} {$i < [llength [lindex $list 1]]} {incr i} {
- $window.$key.$i state "[expr {
- [lindex $list 1 $i] in [dict get $dict $key] ? "" : "!"
- }]selected"
- }
- } hidden {
- set ::$window.$key [dict get $dict $key]
- }
- }
- }
- # bok::ui::cycleoffset increments the offset bit in the current password by
- # $count, if it is present. $window is the window initialized with
- # bok::ui::init and $game is the game that $window is for.
- proc bok::ui::cycleoffset {window game {count 1}} {
- set list [decb64 [$window.password.entry get] $game]
- set ok 0
- modkeys list $game jp 6 {k v n} offset region {
- if {$k == "region"} {set ok 1; return}
- expr {$v + $count & (1 << $n) - 1}
- }
- if {!$ok} {return}
- $window.password.entry delete 0 end
- $window.password.entry insert 0 [spaceout [encb64 $list $game]]
- }
- # bok::ui::keyboards is a dictionary of lists. The lists contain lists of rows
- # of keys, with the keys being in the form {display ?value? ?expand? ?width?}.
- # The value defaults to the same as the display value, if the display is empty
- # then that button is skipped, if "expand" is present then that key is expanded
- # to take up horizontal space, and if "expand" has a width then that key is
- # expanded to the specified width.
- set bok::ui::keyboards {en64 {
- {B C D F G H J @ # ^ {⌫ <<DeletePrevChar>>} {⌦ <<DeleteNextChar>>}}
- {K L M N P Q R 0 1 2 3 4}
- {S T V W X Y Z 5 6 7 8 9}
- {b c d f g h j + - / .}
- {k l m n p q r = _ ? {← <<PrevChar>>} {→ <<NextChar>>}}
- {s t v w x y z : > ! {␣ { } expand}}
- } lk64 {
- {1 2 3 4 5 6 7 8 9 0 . {⌫ <<DeletePrevChar>>} {⌦ <<DeleteNextChar>>}}
- {A B C D E F G H I J K L M}
- {N O P Q R S T U V W X Y Z}
- {a b c d e f g h i j k l m}
- {n o p q r s t u v w x y z}
- {? = {␣ { } expand} {← <<PrevChar>>} {→ <<NextChar>>}}
- } jp64 {
- {あ い う え お ま み む め も {⌫ <<DeletePrevChar>>} {⌦ <<DeleteNextChar>>}}
- {か き く け こ や ゆ よ わ を ば び}
- {さ し す せ そ ら り る れ ろ ぶ べ}
- {た ち つ て と が ぎ ぐ げ ご ぼ}
- {な に ぬ ね の ざ じ ず ぜ ぞ {← <<PrevChar>>} {→ <<NextChar>>}}
- {は ひ ふ へ ほ だ ぢ づ で ど {␣ { } expand}}
- } en {
- {A B C D E F G H I {⌫ <<DeletePrevChar>>} {⌦ <<DeleteNextChar>>}}
- {J K L M N O P Q R . ,}
- {S T U V W X Y Z {} ' -}
- {a b c d e f g h i /}
- {j k l m n o p q r {← <<PrevChar>>} {→ <<NextChar>>}}
- {s t u v w x y z {} {␣ { } expand}}
- } ext {
- {& * ; + - × ÷ = {⌫ <<DeletePrevChar>>} {⌦ <<DeleteNextChar}}
- {à á â ä è é ê ë ( )}
- {ì í î ï ò ó ô ö [ ]}
- {ù ú û ü ç ñ œ ß ! ?}
- {À Á Â Ä È É Ê Ë ¡ ¿}
- {Ì Í Î Ï Ò Ó Ô Ö {← <<PrevChar>>} {→ <<NextChar>>}}
- {Ù Ú Û Ü Ç Ñ Œ {⋅ ⋅} {␣ { } expand}}
- } hiri {
- {あ い う え お ら り る れ ろ {⌫ <<DeletePrevChar>>} {⌦ <<DeleteNextChar>>}}
- {か き く け こ が ぎ ぐ げ ご ん ・}
- {さ し す せ そ ざ じ ず ぜ ぞ ー ~}
- {た ち つ て と だ ぢ づ で ど}
- {な に ぬ ね の ば び ぶ べ ぼ}
- {は ひ ふ へ ほ ぱ ぴ ぷ ぺ ぽ}
- {ま み む め も ぁ ぃ ぅ ぇ ぉ {← <<PrevChar>>} {→ <<NextChar>>}}
- {や ゆ よ わ を っ ゃ ゅ ょ {} {␣ { } expand}}
- } kata {
- {ア イ ウ エ オ ラ リ ル レ ロ {⌫ <<DeletePrevChar>>} {⌦ <<DeleteNextChar>>}}
- {カ キ ク ケ コ ガ ギ グ ゲ ゴ ン ・}
- {サ シ ス セ ソ ザ ジ ズ ゼ ゾ ー ~}
- {タ チ ツ テ ト ダ ヂ ヅ デ ド}
- {ナ ニ ヌ ネ ノ バ ビ ブ ベ ボ}
- {ハ ヒ フ ヘ ホ パ ピ プ ペ ポ}
- {マ ミ ム メ モ ァ ィ ゥ ェ ォ {← <<PrevChar>>} {→ <<NextChar>>}}
- {ヤ ユ ヨ ワ ヲ ッ ャ ュ ョ ヴ {␣ { } expand}}
- }}
- # bok::ui::keyboardinit initializes a grid of buttons that send
- # <<VirtualKeyPress>> events to the frame $window with %d set to the value.
- # The default binding is to call bok::ui::keypress with %d. Supported options
- # are -style and -command. -style sets the style to use for the buttons, and
- # -command replaces the default binding for <<VirtualKeyPress>>.
- proc bok::ui::keyboardinit {window keyboard args} {
- variable keyboards
- set usage {keyboardinit path keyboard ?option value ...?}
- set options [dict create -style {} \
- -command [list [namespace current]::keypress %W %d]
- ]
- if {[llength $args] % 2 != 0} {
- return -code error "wrong # args: should be \"$usage\""
- }
- if {![winfo exists $window]} {
- ttk::frame $window
- } elseif {[winfo exists $window.0]} {
- return
- }
- foreach {opt arg} $args {
- switch -exact -- $opt -keys - -command {
- dict set options $opt $arg
- } -style {
- dict lappend options $opt {*}$arg
- } default {
- return -code error "Invalid option: $opt"
- }
- }
- if {[dict exists $keyboards $keyboard]} {
- set keyboard [dict get $keyboards $keyboard]
- }
- bind $window <<VirtualKeyPress>> [dict get $options -command]
- set einfo {}
- set winfo {}
- foreach list $keyboard {
- set e 0
- set j 0
- foreach key $list {
- set width 1
- foreach opt [lrange $key 2 end] {
- if {[lindex $opt 0] != "expand"} {continue}
- set width [lindex $opt 1]
- }
- if {$width == ""} {
- set width 1
- incr e
- }
- incr j $width
- }
- lappend einfo $e
- lappend winfo $j
- }
- set max [::tcl::mathfunc::max {*}$winfo]
- set button 0
- for {set row 0} {$row < [llength $keyboard]} {incr row} {
- set list [lindex $keyboard $row]
- set n [lindex $einfo $row]
- set e [expr {max($max - [lindex $winfo $row] + $n - 1, 0)}]
- set elist {}
- if {$n > 0} {
- for {set i 0; set j [expr {int(($n - $e % $n) / 2)}]} {$i < $n} {incr i} {
- lappend elist [expr {
- int($e / $n) + ($i >= $j && $i < $n - $j ? 1 : 0)
- }]
- }
- }
- for {set e 0; set i 0; set col 0} {$i < [llength $list]} {incr i} {
- set key [lindex $list $i]
- if {$key == ""} {
- incr col
- continue
- }
- if {[llength $key] < 2} {
- set text $key
- set value $key
- } else {
- lassign $key text value
- }
- set width 1
- foreach opt [lrange $key 2 end] {
- switch -nocase -exact -- [lindex $opt 0] expand {
- set width [lindex $opt 1]
- }
- }
- if {$width == ""} {
- set width [lindex $elist $e]
- incr e
- }
- grid [ttk::button $window.$button -text $text -command \
- [list event generate $window <<VirtualKeyPress>> -data $value \
- -root [winfo toplevel $window] -subwindow $window.$button] \
- -style [dict get $options -style] -width 2 -takefocus 0
- ] -ipadx 0 -row $row -column $col -columnspan $width -sticky nsew
- incr button
- incr col $width
- }
- }
- for {set row 0} {$row < [llength $keyboard]} {incr row} {
- grid rowconfigure $window $row -weight 1
- }
- for {set col 0} {$col < $max} {incr col} {
- grid columnconfigure $window $col -weight 1
- }
- return $window
- }
- # bok::ui::keypress inserts $value into Entry/TEntry currently in focus for the
- # toplevel window for $window. If value is a virtual event then that action is
- # performed instead. Supported virtual events are PrevChar, NextChar,
- # DeletePrevChar, and DeleteNextChar.
- proc bok::ui::keypress {window value} {
- set window [focus -lastfor [winfo toplevel $window]]
- switch -glob -nocase -- [winfo class $window] *Entry {
- switch -glob -nocase -- $value {
- <<PrevChar>> {$window icursor [expr {[$window index i] - 1}]}
- <<NextChar>> {$window icursor [expr {[$window index i] + 1}]}
- <<DeletePrevChar>> {$window delete [expr {[$window index i] - 1}]}
- <<DeleteNextChar>> {$window delete i}
- <<*>> {return -code error "Unsupported event: $value"}
- default {
- $window insert i $value
- }}
- }
- }
- # bok::ui::keyboardallow disables/enables all keyboards in $window depending on
- # if they are in $keyboards. The tab is set to the default (or first valid
- # keyboard) if the current keyboard is not in $keyboards.
- proc bok::ui::keyboardallow {window keyboards {default ""}} {
- if {$default == "" || $default ni $keyboards} {
- set default [lindex $keyboards 0]
- }
- set current [winfo name [$window select]]
- foreach keyboard [$window tabs] {
- set name [winfo name $keyboard]
- $window tab $keyboard \
- -state [expr {$name in $keyboards ? "normal" : "disabled"}]
- if {$name == $default && $current ni $keyboards} {
- $window select $keyboard
- }
- }
- }
- proc usage {} {
- puts stderr [join {
- {usage: bokpass [-gui] [-kbd] [-invalid] [-columns columns]}
- { [-fontsize size]}
- { bokpass [-cli] [-verbose] -game game password ...}
- } "\n"]
- exit 1
- }
- set options {
- -locale en
- -mode cli -verbose 0
- -columns 2 -fontsize 10 -kbd 0 -validate all
- }
- for {set optind 0} {$optind < [llength $argv]} {incr optind} {
- switch -glob -- [lindex $argv $optind] -game - -columns - -fontsize {
- if {$optind + 1 >= [llength $argv]} {usage}
- dict set options {*}[lrange $argv $optind $optind+1]
- incr optind
- } -cli - -gui {
- dict set options -mode [string range [lindex $argv $optind] 1 end]
- } -verbose - -kbd {
- dict set options [lindex $argv $optind] 1
- } -invalid {
- dict set options -validate none
- } -- {
- incr optind
- break
- } -?* {
- usage
- } default {
- break
- }
- }
- if {$optind == [llength $argv]} {
- dict set options -mode gui
- }
- if {[dict get $options -mode] == "cli" && ![dict exists $options -game]} {
- usage
- }
- switch -exact -- [dict get $options -mode] gui {
- package require Tk
- wm title . bokpass
- ttk::style configure Keyboard.TButton \
- -font "mono [expr {int([dict get $options -fontsize] * 1.2)}]"
- ttk::style configure TLabel \
- -font "sans [dict get $options -fontsize]"
- ttk::style configure TNotebook.Tab \
- -font "sans [dict get $options -fontsize]"
- ttk::style configure TSpinBox -font "sans [dict get $options -fontsize]"
- ttk::style configure TComboBox -font "sans [dict get $options -fontsize]"
- option add *TCombobox*Listbox.font "sans [dict get $options -fontsize]"
- # Adds ^W and ^U functionality to Entry and TEntry widgets.
- proc betterediting {args} {
- foreach arg $args {
- bind $arg <Control-KeyPress-w> {
- apply {old {
- event generate %W <<PrevWord>>
- %W delete [%W index i] $old
- }} [%W index i]
- }
- bind $arg <Control-KeyPress-u> {
- %W delete 0 end
- }
- bind $arg <KeyPress-Up> {%W icursor 0}
- bind $arg <KeyPress-Down> {%W icursor e}
- }
- }
- betterediting Entry TEntry
- # Lazily initializes game tabs.
- proc loadgametab {window} {
- if {[dict get $::options -kbd]} {
- foreach keyboard [.keyboard tabs] {
- .keyboard tab $keyboard -state normal
- }
- }
- if {[winfo exists $window.password]} {return}
- if {![string match {*.*[1234]} $window]} {return}
- set game [string index $window end]
- set bindings {}
- if {[dict get $::options -kbd]} {
- lappend bindings password.entry {<FocusIn> {+
- if {[bok::ui::region \
- [[winfo parent [winfo parent %W]].region-name get] \
- [dict get $::options -locale]] == "jp"} {
- bok::ui::keyboardallow .keyboard jp64
- } elseif {[string index [.games select] end] == "4"} {
- bok::ui::keyboardallow .keyboard lk64
- } else {
- bok::ui::keyboardallow .keyboard en64
- }
- }} string {<FocusIn> {+
- switch -glob -- "[bok::ui::region [[winfo parent %W].region-name get] \
- [dict get $::options -locale]][string index [.games select] end]" {
- jp[123] {
- bok::ui::keyboardallow .keyboard {hiri kata} hiri
- } jp4 {
- bok::ui::keyboardallow .keyboard {en ext hiri kata} hiri
- } na[123] - eu[123] {
- bok::ui::keyboardallow .keyboard {en}
- } na4 - eu4 {
- bok::ui::keyboardallow .keyboard {en ext hiri kata} en
- }}
- }} entry {<FocusOut> {
- bok::ui::keyboardallow .keyboard {en64 lk64 jp64 en ext kata hiri}
- }}
- }
- if {$game != 3} {
- switch -exact -- [dict get $::options -locale] {
- en {set region na}
- jp {set region jp}
- default {set region eu}
- }
- } else {
- set region jp
- }
- set options [dict map {key value} $::options {
- if {$key ni {-fontsize -columns -validate}} {continue}
- set value
- }]
- bok::ui::init $window $region $game -bind $bindings {*}$options
- }
- pack [ttk::notebook .games] -expand 1 -fill both
- bind .games <<NotebookTabChanged>> {loadgametab [%W select]}
- foreach {tab name} [bok::@ [dict get $options -locale] labels game-tabs] {
- .games add [ttk::frame .games.$tab] -text $name
- }
- if {[dict get $options -kbd]} {
- # Lazily initializes keyboard tabs.
- pack [ttk::notebook .keyboard -takefocus 0] -fill both
- bind .keyboard <<NotebookTabChanged>> {
- if {![winfo exists [%W select].0]} {
- bok::ui::keyboardinit [%W select] [winfo name [%W select]] \
- -style Keyboard.TButton
- }
- }
- bind Entry <FocusIn> {+ set ::currententry %W}
- bind TEntry <FocusIn> {+ set ::currententry %W}
- bind .games <<NotebookTabChanged>> {+ set ::currententry {}}
- bind .keyboard <<NotebookTabChanged>> {+
- if {[winfo exists $::currententry]} {focus $::currententry}
- }
- foreach {tab name} \
- [bok::@ [dict get $options -locale] labels keyboard-tabs] {
- .keyboard add [ttk::frame .keyboard.$tab] -text $name
- }
- }
- } cli {
- # Forgivingly match -game.
- switch -exact -nocase -- [regsub {[^[:alnum:]]+} \
- [dict get $options -game] {}] {
- boktai - tsiiyh - boktai1 - boktaitsiiyh - boktaithesunisinyourhand -
- bokura - bokura1 - bokuranotaiyou - bokuranotaiyou1 -
- jp1 - en1 - na1 - eu1 - us1 - 1 {
- dict set options -game 1
- }
- zoktai - boktai2 - boktai2solarboydjango - solarboydjango -
- zokubokura - zokubokuranotaiyou - bokura2 - bokuranotaiyou2 -
- jp2 - en2 - na2 - eu2 - us2 - 2 {
- dict set options -game 2
- }
- shinbok - boktai3 - boktai3sabatascounterattack - sabatascounterattack -
- shinbokura - shinbokuranotaiyou - bokura3 - bokuranotaiyou3 -
- jp3 - en3 - na3 - eu3 - us3 - 3 {
- dict set options -game 3
- }
- lk - lunarknigths - boktai4 - boktaids - bokura4 - bokurads -
- bokuranotaiyouds - bokuranotaiyou4 - boktaidjangosabata - bokuradjangosabata -
- bokuranotaiyoudjangosabata - djangosabata - ds -
- jp4 - en4 - na4 - eu4 - us4 - 4 {
- dict set options -game 4
- } default {
- puts stderr "Unknown game: [dict get $options -game]"
- exit 1
- }}
- # Decodes passwords for -game and print them in columns.
- set dict [dict create]
- for {} {$optind < [llength $argv]} {incr optind} {
- if {[catch {
- set width [::tcl::mathfunc::max 0 {*}[lmap value [dict values $dict] {
- string length $value
- }]]
- set tdict [bok::decpass [lindex $argv $optind] \
- [dict get $options -game]]
- set vdict {}
- catch {set vdict [bok::normalize $tdict [dict get $options -locale] 1]}
- dict for {key value} $tdict {
- set pad {}
- if {[dict exists $dict $key]} {
- set pad [string repeat { } [expr {
- $width - [string length [dict get $dict $key]]
- }]]
- }
- if {[dict exists $vdict $key]} {
- set value [dict get $vdict $key]
- }
- if {[dict get $options -verbose] && [dict exists $vdict $key\-name]} {
- append value " \"[dict get $vdict $key\-name]\""
- }
- dict append dict $key " $pad$value"
- }
- } error]} {
- puts stderr "bokpass: $error"
- }
- }
- set width [::tcl::mathfunc::max 0 {*}[lmap key [dict keys $dict] {
- string length $key
- }]]
- dict for {key value} $dict {
- set pad [string repeat { } [expr {$width - [string length $key]}]]
- puts $pad$key:$value
- }
- }
|