Last active
July 16, 2017 01:08
-
-
Save inmatarian/450a9a258e49d0aca9b9 to your computer and use it in GitHub Desktop.
80s Style Video Game Passcode Encoder/Decoder
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[=[ | |
Silly little 80s nostalgia game passcode generator. | |
Of course, the JUSTIN BAILEY password was hardcoded in and not decoded, | |
but having a convincing password entry form that works is the piece that | |
makes hard coded passwords all the more magic. | |
The codes do embed an inverse checksum at the end, which means newbies | |
have slightly more math to do in order to hack ur gaems. When you Decode | |
a passcode, the second return value is a true/false for the passcode | |
having been valid. It's on you if you care about that or not, as an object | |
will still have been created. | |
A note about usage, the ordering of the fields is significant. If you have | |
HP be 7 bits and XP following it be 15 bits, then you can't swap them, change | |
their bit lengths, or insert a field before or inbetween them. If you do | |
then all of the passcodes generated previously become invalid. | |
UI Note: Its usually friendly to insert a space after every 5th code symbol. | |
The Encode method returns a table, not a string. You can table.concat it | |
if you want a string. Likewise, the Decode method takes a table-form, not | |
a string. DecodeString is provided for convenience. | |
-- | |
This is free and unencumbered software released into the public domain. | |
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. | |
In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
For more information, please refer to http://unlicense.org/ | |
--]=] | |
local PassCodec = {} | |
do | |
PassCodec.__index = PassCodec | |
local encode_symbols = { | |
[ 0]='0',[ 1]='1',[ 2]='2',[ 3]='3',[ 4]='4',[ 5]='5',[ 6]='6',[ 7]='7', | |
[ 8]='8',[ 9]='9',[10]='A',[11]='B',[12]='C',[13]='D',[14]='E',[15]='F', | |
[16]='G',[17]='H',[18]='J',[19]='K',[20]='M',[21]='N',[22]='P',[23]='Q', | |
[24]='R',[25]='S',[26]='T',[27]='V',[28]='W',[29]='X',[30]='Y',[31]='Z' | |
} | |
local decode_symbols = { | |
['0']= 0,['1']= 1,['2']= 2,['3']= 3,['4']= 4,['5']= 5,['6']= 6,['7']= 7, | |
['8']= 8,['9']= 9,['A']=10,['B']=11,['C']=12,['D']=13,['E']=14,['F']=15, | |
['G']=16,['H']=17,['J']=18,['K']=19,['M']=20,['N']=21,['P']=22,['Q']=23, | |
['R']=24,['S']=25,['T']=26,['V']=27,['W']=28,['X']=29,['Y']=30,['Z']=31, | |
['O']= 0,['I']= 1,['L']= 1 | |
} | |
local function add_code_to_bits(bits, code) | |
for i = 0, 4 do bits[#bits+1] = math.floor(code/(2^i)) % 2 end | |
end | |
local function compute_checksum(bits) | |
local checksum = 0 | |
local shift = 1 | |
for i = 1, #bits do | |
checksum = checksum + (bits[i] * shift) | |
shift = shift * 2 | |
if shift >= 32 then shift = 1 end | |
end | |
return checksum % 32 | |
end | |
local function bits_to_base32(bits) | |
local symbols = {} | |
for i = 1, #bits, 5 do | |
local code = bits[i]+(bits[i+1]*2)+(bits[i+2]*4)+(bits[i+3]*8)+(bits[i+4]*16) | |
symbols[#symbols+1] = encode_symbols[code] | |
end | |
return symbols | |
end | |
local function base32_to_bits(symbols) | |
local bits = {} | |
for i = 1, #symbols do | |
add_code_to_bits(bits, decode_symbols[string.upper(symbols[i])]) | |
end | |
return bits | |
end | |
-- very simple scramble process is to rotate whole patten right twice, and | |
-- then switch every second of five bit with the 4th of five bits. | |
local function scramble(bits) | |
for i = 1, 2 do | |
table.insert(bits, 1, table.remove(bits)) | |
end | |
for i = 1, #bits, 5 do | |
bits[i+1], bits[i+3] = bits[i+3], bits[i+1] | |
end | |
return bits | |
end | |
local function unscramble(bits) | |
for i = 1, #bits, 5 do | |
bits[i+1], bits[i+3] = bits[i+3], bits[i+1] | |
end | |
for i = 1, 2 do | |
table.insert(bits, table.remove(bits, 1)) | |
end | |
return bits | |
end | |
function PassCodec.Encode(fields, object) | |
local bits = {} | |
for fieldnum = 1, #fields do | |
local field = fields[fieldnum] | |
local value = math.floor(object[field.key]) | |
for i = 1, field.bits do | |
bits[#bits+1] = math.floor(value % 2) | |
value = math.floor(value / 2) | |
end | |
end | |
while ((#bits)%5) > 0 do bits[#bits+1] = 0 end | |
local checksum = compute_checksum(bits) | |
add_code_to_bits(bits, math.abs(checksum-31)) | |
return bits_to_base32(scramble(bits)) | |
end | |
function PassCodec.Decode(fields, symbols) | |
local object = {} | |
local bits = unscramble(base32_to_bits(symbols)) | |
local checksum = compute_checksum(bits) | |
local bit_idx = 1 | |
for fieldnum = 1, #fields do | |
local field = fields[fieldnum] | |
local value = 0 | |
local shift = 1 | |
for i = 1, field.bits do | |
value = value + (bits[bit_idx] * shift) | |
shift = shift * 2 | |
bit_idx = bit_idx + 1 | |
end | |
object[field.key] = value | |
end | |
return object, ((checksum % 32) == 31) | |
end | |
function PassCodec.DecodeString(fields, symbolStr) | |
local symbols = {} | |
string.gsub(symbolStr, ".", function(ch) symbols[#symbols+1] = ch end) | |
return PassCodec.Decode(fields, symbols) | |
end | |
function PassCodec.new() | |
return setmetatable({}, PassCodec) | |
end | |
function PassCodec.AddField(fields, key, bits) | |
fields[#fields+1] = { key = key, bits = bits } | |
end | |
end | |
----------------------------------------------------------------------------- | |
do | |
local tests = {} | |
tests["Encoded and Decodes an object"] = function() | |
local fields = { | |
{ key="hitpoints", bits=7 }, | |
{ key="experience", bits=12 }, | |
{ key="magic", bits=4 } | |
} | |
local player = { | |
hitpoints = 100, | |
experience = 2040, | |
magic = 9 | |
} | |
local symbols = PassCodec.Encode(fields, player) | |
assert(type(symbols)=="table") | |
assert(#symbols > 0) | |
local loaded, passed = PassCodec.Decode(fields, symbols) | |
assert(passed) | |
assert(loaded.hitpoints == player.hitpoints) | |
assert(loaded.experience == player.experience) | |
assert(loaded.magic == player.magic) | |
end | |
tests["Encoded everything differently"] = function() | |
local adjustment_fields = { | |
{ key="hp", bits = 5 }, | |
} | |
local object = { hp = 0 } | |
local output = {} | |
for i = 0, 31 do | |
object.hp = i | |
local cypher = table.concat(PassCodec.Encode(adjustment_fields, object)) | |
assert(output[cypher]==nil) | |
output[cypher] = 1 | |
end | |
end | |
tests["Decode String"] = function() | |
local object = PassCodec.DecodeString({{key="hi", bits=1}}, "00") | |
assert(type(object)=="table") | |
assert(object.hi ~= nil) | |
end | |
tests["Object Oriented"] = function() | |
local module = PassCodec.new() | |
module:AddField("energy", 7) | |
module:AddField("missiles", 8) | |
module:AddField("tanks", 3) | |
local samus = { | |
energy = 99, | |
missiles = 200, | |
tanks = 5 | |
} | |
local passcode = module:Encode(samus) | |
assert(type(passcode)=="table") | |
assert(#passcode>0) | |
end | |
for name, fn in pairs(tests) do | |
local status, message = pcall(fn) | |
io.write(status and "Passed" or "FAILED", ": ", name, '\n') | |
if status == false then | |
io.write(message, '\n\n') | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment