Skip to content

Instantly share code, notes, and snippets.

@inmatarian
Last active July 16, 2017 01:08
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save inmatarian/450a9a258e49d0aca9b9 to your computer and use it in GitHub Desktop.
Save inmatarian/450a9a258e49d0aca9b9 to your computer and use it in GitHub Desktop.
80s Style Video Game Passcode Encoder/Decoder
--[=[
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