Envision, Create, Share

Welcome to HBGames, a leading amateur game development forum and Discord server. All are welcome, and amongst our ranks you will find experts in their field from all aspects of video game design and development.

Game Progress Password System

Game Progress Password System
Version: 1.0.2

Introduction

This is a script that generates game passwords based on what type of data you give it.

Features
  • Easy to use
  • Quick

Demo

A small demo which includes a small password input screen.

Script

Code:
#==============================================================================
# ** Game Progress Password System
#------------------------------------------------------------------------------
#  * Scripted by: Yeyinde
#  * Version 1.0.2
#  * Date: 09/28/07
#------------------------------------------------------------------------------
#  This is the core for a password saved game.  It provides no method of input,
#  so that is left up to you as the scripter.  This system provides the base
#  password tools that you can use to make your own old-school NESesque game.
#
#  HOW TO USE:
#   1) Initialize a new instance of Password
#   2) Add switches, self_switches, variables, location, and time to the
#      allocation
#   3) Compile the password
#   4) Read the password to restore game data
#------------------------------------------------------------------------------

#==============================================================================
#  ** Password
#------------------------------------------------------------------------------
#  Holds the data structure that is passwords
#==============================================================================

class Password
  # Decryption hash.  Change only the characters!
  DECRYPT = {
  '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, 'I' => 18, 'J' => 19,
  'K' => 20, 'L' => 21, 'M' => 22, 'N' => 23, 'O' => 24,
  'P' => 25, 'Q' => 26, 'R' => 27, 'S' => 28, 'T' => 29,
  'U' => 30, 'V' => 31, 'W' => 32, 'X' => 33, 'Y' => 34,
  'Z' => 35, 'a' => 36, 'b' => 37, 'c' => 38, 'd' => 39,
  'e' => 40, 'f' => 41, 'g' => 42, 'h' => 43, 'i' => 44,
  'j' => 45, 'k' => 46, 'l' => 47, 'm' => 48, 'n' => 49,
  'o' => 50, 'p' => 51, 'q' => 52, 'r' => 53, 's' => 54,
  't' => 55, 'u' => 56, 'v' => 57, 'w' => 58, 'x' => 59,
  'y' => 60, 'z' => 61, '?' => 62, '-' => 63
  }
  # The encrypt hash.  It is the opposite of the decrypt hash.
  ENCRYPT = DECRYPT.dup.invert
  #--------------------------------------------------------------------------
  # * Object initialization
  #     bytes           : how many bytes the password will be
  #     shift_length    : how many bytes to use for encryption
  #     checksum_length : how many bytes to use for verification
  #--------------------------------------------------------------------------
  def initialize(bytes, shift_length, checksum_length)
    raise('Number of bits must be divisible by 6.') if bytes * 8 % 6 != 0
    raise('Not enough bytes for shift and checksum') if shift_length + 
          checksum_length >= bytes
    @total_bytes = bytes
    @shift_length = shift_length
    @checksum_length = checksum_length
    @bytes = Array.new(bytes - shift_length - checksum_length)
    @allocation = []
    @index = 0
  end
  #--------------------------------------------------------------------------
  # * Return password
  #--------------------------------------------------------------------------
  def password
    return @password || '0' * (@total_bytes * 8 / 6)
  end
  #--------------------------------------------------------------------------
  # * Add Switches
  #     switches : switch ids to save (maximum of 8)
  #--------------------------------------------------------------------------
  def add_switches(id1, *other)
    @allocation << [0, id1, *other]
  end
  #--------------------------------------------------------------------------
  # * Add Variable
  #     id       : variable id
  #     bytes    : how many bytes to use
  #     sign = 0 : positive or negative
  #--------------------------------------------------------------------------
  def add_variable(id, bytes, sign = 0)
    @allocation << [1, id, bytes, [[sign, -1].max, 1].min]
  end
  #--------------------------------------------------------------------------
  # * Add Self_Switches
  #     keys : self_switch keys to save (maximum of 8)
  #--------------------------------------------------------------------------
  def add_selfswitches(key1, *other)
    @allocation << [2, key1, *other]
  end
  #--------------------------------------------------------------------------
  # * Add Location
  #--------------------------------------------------------------------------
  def add_location
    @allocation << [3]
  end
  #--------------------------------------------------------------------------
  # * Add Time
  #     seconds = false : Use seconds instead of frames
  #--------------------------------------------------------------------------
  def add_time(seconds = false)
    @allocation << [4, seconds]
  end
  #--------------------------------------------------------------------------
  # * Compile Password
  #--------------------------------------------------------------------------
  def compile
    size = allocated_bytes
    raise("Allocated bytes do not match initial bytes.
#{size} allocated, #{@total_bytes} initialized.") if size != @total_bytes
    @index = 0
    @password = nil
    @bytes.collect!{nil}
    compile_bytes
    shift = @shift_length > 0 ? rand(2**(@shift_length * 8 - 1) - 1).to_i : -1
    checksum = calculate_checksum(@bytes, shift)
    password_bytes = shift_bytes(@bytes, shift, 1)
    shift = shift.to_s(2)
    shift = '0' + shift while shift.size < @shift_length * 8
    @shift_length.times do |i|
      password_bytes << shift[(i*8)...(i*8+8)].to_i(2)
    end
    checksum = checksum.to_s(2)
    checksum = '0' + checksum while checksum.size < @checksum_length * 8
    @checksum_length.times do |i|
      password_bytes << checksum[(i*8)...(i*8+8)].to_i(2)
    end
    password_string = bit_string(password_bytes, 8)
    password = ''
    password_string.unpack('a6' * (@total_bytes * 8 / 6)).each do |byte|
      byte = byte.to_i(2)
      password += ENCRYPT[byte]
    end
    @password = password
    return true
  end
  #--------------------------------------------------------------------------
  # * Read Password
  #     string : aString to read  Must be same amount of characters as a
  #              generated password
  #--------------------------------------------------------------------------
  def read(string)
    raise("Number of characters do not match.
#{string.size} given, #{@total_bytes * 8 / 6} needed.") if string.size != 
@total_bytes * 8 / 6
    password = []
    string.unpack('a' * string.size).each do |char|
      password << DECRYPT[char]
    end
    password = bit_string(password, 6).unpack('a8' * @total_bytes)
    checksum = ''
    @checksum_length.times do
      checksum = password.pop + checksum
    end
    checksum = @checksum_length > 0 ? checksum.to_i(2) : -1
    shift = ''
    @shift_length.times do
      shift = password.pop + shift
    end
    shift = @shift_length > 0 ? shift.to_i(2) : -1
    password.collect!{|byte| byte.to_i(2)}
    password = shift_bytes(password, shift, -1)
    raise('Invalid Password') if checksum != calculate_checksum(password, shift)
    assemble_bytes(password)
    return true
  end
  
  private
  #--------------------------------------------------------------------------
  # * Return number of allocated bytes
  #--------------------------------------------------------------------------
  def allocated_bytes
    bytes = @shift_length + @checksum_length
    @allocation.each do |data|
      case data[0]
      when 0 
        bytes += 1
      when 1
        bytes += data[2]
      when 2
        bytes += 1
      when 3
        bytes += 4
      when 4
        bytes += 4
      end
    end
    return bytes
  end
  #--------------------------------------------------------------------------
  # * Compile Bytes
  #--------------------------------------------------------------------------
  def compile_bytes
    @allocation.each do |data|
      case data[0]
      when 0
        comp_switches(*data[1...data.size])
      when 1
        comp_variable(*data[1...data.size])
      when 2
        comp_selfswitches(*data[1...data.size])
      when 3
        comp_location
      when 4
        comp_time(data[1])
      end
    end
  end
  #--------------------------------------------------------------------------
  # * Assemble Bytes
  #     bytes : password byte data
  #--------------------------------------------------------------------------
  def assemble_bytes(bytes)
    @allocation.each do |data|
      case data[0]
      when 0
        ass_switches(data[1...data.size], bytes.shift)
      when 1
        pass_data = []
        data[2].times {pass_data << bytes.shift}
        ass_variable(data[1...data.size], pass_data)
      when 2
        ass_selfswitches(data[1...data.size], bytes.shift)
      when 3
        ass_location(data[1...data.size], [bytes.shift, bytes.shift, bytes.shift, 
            bytes.shift])
      when 4
        ass_time(data[1...data.size], [bytes.shift, bytes.shift, bytes.shift, 
            bytes.shift])
      end
    end
  end
  #--------------------------------------------------------------------------
  # * Checksum Calculation
  #     shift : the shift character(s)
  #--------------------------------------------------------------------------
  def calculate_checksum(bytes, shift)
    string = ''
    bytes.each {|byte|string += byte.chr}
    if shift >= 0
      shift_string = shift.to_s(2)
      shift_string = '0' + shift_string while shift_string.size < @shift_length * 8
      shift_string.unpack('a8' * @shift_length).each do |byte|
        string += byte.to_i(2).chr
      end
    end
    order = @checksum_length * 8
    poly = 2**order - 1
    checksum = CRC.new(order, poly, 0, 0, false)
    checksum.update(string)
    sum = checksum.final
    return @checksum_length > 0 ? sum : -1
  end
  #--------------------------------------------------------------------------
  # * Byte Shift
  #     bytes : password byte data
  #     shift : how many times to shift
  #     dir   : direction - negative is left, positive is right
  #--------------------------------------------------------------------------
  def shift_bytes(bytes, shift, dir)
    password_bits = bit_string(bytes, 8)
    password_bits = password_bits.unpack('a' * password_bits.size)
    shift.times do
      if dir == 1
        password_bits.unshift password_bits.pop
      else
        password_bits.push password_bits.shift
      end
    end
    password_bits = password_bits.to_s.unpack('a8' * bytes.size)
    password_bits.collect!{|bit| bit.to_i(2)}
    return password_bits
  end
  #--------------------------------------------------------------------------
  # * Bit String
  #     bytes : password byte data
  #     bits  : number of bits per number
  #--------------------------------------------------------------------------
  def bit_string(bytes, bits)
    string = ''
    bytes.each do |byte|
      byte = byte.to_s(2)
      byte = '0' + byte while byte.size < bits
      string += byte
    end
    return string
  end
  #--------------------------------------------------------------------------
  # * Switch compiling
  #--------------------------------------------------------------------------
  def comp_switches(id1, id2=0, id3=0, id4=0, id5=0, id6=0, id7=0, id8=0)
    byte = 0
    byte += 1<<7 if $game_switches[id1]
    byte += 1<<6 if $game_switches[id2]
    byte += 1<<5 if $game_switches[id3]
    byte += 1<<4 if $game_switches[id4]
    byte += 1<<3 if $game_switches[id5]
    byte += 1<<2 if $game_switches[id6]
    byte += 1<<1 if $game_switches[id7]
    byte += 1<<0 if $game_switches[id8]
    @bytes[@index] = byte
    @index += 1
  end
  #--------------------------------------------------------------------------
  # * Variable compiling
  #--------------------------------------------------------------------------
  def comp_variable(id, bytes, sign)
    max = sign == 0 ? (2**(bytes*(8-1))-1) : sign == 1 ? 2**(bytes*8) : 0
    min = sign == 0 ? -(2**(bytes*(8-1))-1) : sign == -1 ? -2**(bytes*8) : 0
    max_bits = bytes * 8
    max_bits -= 1 if sign == 0
    var = [[$game_variables[id], min].max, max].min
    var = var.to_s(2)
    var = '0' + var while var.size < max_bits
    var = ($game_variables[id] < 0 ? '1' : '0') + var if sign == 0
    bytes.times do |i|
      @bytes[@index] = var[(i*8)...(i*8+8)].to_i(2)
      @index += 1
    end
  end
  #--------------------------------------------------------------------------
  # * Self_Switch compiling
  #--------------------------------------------------------------------------
  def comp_selfswitches(key1, key2=[0,0,'A'], key3=[0,0,'A'], key4=[0,0,'A'],
      key5=[0,0,'A'], key6=[0,0,'A'], key7=[0,0,'A'], key8=[0,0,'A'])
    byte = 0
    byte += 1<<7 if $game_self_switches[key1]
    byte += 1<<6 if $game_self_switches[key2]
    byte += 1<<5 if $game_self_switches[key3]
    byte += 1<<4 if $game_self_switches[key4]
    byte += 1<<3 if $game_self_switches[key5]
    byte += 1<<2 if $game_self_switches[key6]
    byte += 1<<1 if $game_self_switches[key7]
    byte += 1<<0 if $game_self_switches[key8]
    @bytes[@index] = byte
    @index += 1
  end
  #--------------------------------------------------------------------------
  # * Location compiling
  #--------------------------------------------------------------------------
  def comp_location
    map = $game_map.map_id.to_s(2)
    x = $game_player.x.to_s(2)
    y = $game_player.y.to_s(2)
    dir = ($game_player.direction / 2).to_s(2)
    map = '0' + map while map.size < 10
    x = '0' + x while x.size < 9
    y = '0' + y while y.size < 9
    dir = '0' + dir while dir.size < 4
    bits = map + x + y + dir
    4.times do |i|
      @bytes[@index] = bits[(i*8)...(i*8+8)].to_i(2)
      @index += 1
    end
  end
  #--------------------------------------------------------------------------
  # * Time compiling
  #--------------------------------------------------------------------------
  def comp_time(seconds)
    ticks = Graphics.frame_count
    ticks %= Graphics.frame_rate if seconds
    ticks = [ticks, 2**32-1].min
    ticks = ticks.to_s(2)
    ticks = '0' + ticks while ticks.size < 32
    ticks.reverse!
    4.times do |i|
      @bytes[@index] = ticks[(i*8)...(i*8+8)].to_i(2)
      @index += 1
    end
  end
  #--------------------------------------------------------------------------
  # * Switch assembly
  #--------------------------------------------------------------------------
  def ass_switches(allocation, data)
    data = bit_string([data], 8)
    allocation.each_with_index do |switch_id, i|
      $game_switches[switch_id] = data[i, 1] == '1'
    end
    $game_map.refresh
  end
  #--------------------------------------------------------------------------
  # * Variable assembly
  #--------------------------------------------------------------------------
  def ass_variable(allocation, data)
    data = bit_string(data, 8)
    mult = 1
    if allocation[2] == 0
      mult = data[0, 1] == '1' ? -1 : 1
      data = data[1...data.size]
    elsif allocation[2] == -1
      mult = -1
    end
    $game_variables[allocation[0]] = mult * data.to_i(2)
    $game_map.refresh
  end
  #--------------------------------------------------------------------------
  # * Self_Switch assembly
  #--------------------------------------------------------------------------
  def ass_selfswitches(allocation, data)
    data = bit_string([data], 8)
    allocation.each_with_index do |key, i|
      $game_self_switches[key] = data[i, 1] == '1'
    end
    $game_map.refresh
  end
  #--------------------------------------------------------------------------
  # * Location assembly
  #--------------------------------------------------------------------------
  def ass_location(allocation, data)
    bits = bit_string(data, 8)
    map_id = bits[0, 10].to_i(2)
    x = bits[10, 9].to_i(2)
    y = bits[19, 9].to_i(2)
    dir = bits[28, 4].to_i(2) * 2
    if map_id == 0
      map_id = $data_system.start_map_id
      x, y = $data_system.start_x, $data_system.start_y
      dir = 2
    end
    Graphics.freeze
    $game_map.setup(map_id)
    $game_player.moveto(x, y)
    $game_player.direction = dir
    $game_map.autoplay
    $scene = Scene_Map.new
  end
  #--------------------------------------------------------------------------
  # * Time assembly
  #--------------------------------------------------------------------------
  def ass_time(allocation, data)
    data = bit_string(data, 8).reverse.to_i(2)
    data * Graphics.frame_rate if allocation[0]
    Graphics.frame_count = data
  end
end
class Game_Character
  attr_writer :direction
end
#=============================================================================
#  ** Cyclic Redundancy Check
#=============================================================================

class CRC
  def initialize(order, poly, iv, xor, reflect)
    @order = order
    @poly = poly
    @iv = iv
    @xor = xor
    @reflect = reflect
    if @reflect
      @mask = (2 ** (@order - 8)) - 1
    end
    @lookup = build_lookup
    @crc = init
  end

  def init
    crc = @iv
    if @reflect
      crc = reflect(crc, @order)
    end
    crc
  end

  def update(str)
    topbit = 1 << (@order - 1)
    widthmask = (topbit << 1) - 1
    str.each_byte do |byte|
      if @reflect
        @crc = ((@crc >> 8) & @mask) ^ @lookup[(@crc ^ byte) & 0xFF]
      else
        @crc = (@crc << 8) ^ @lookup[((@crc >> (@order - 8)) ^ byte) & 0xFF]
      end
      @crc &= widthmask
    end
  end

  def final
    @crc ^= @xor
    bytes = (@order + 7) >> 3
    @crc
  end

private

  def build_lookup
    lookup = []
    topbit = 1 << (@order - 1)
    widthmask = (topbit << 1) - 1
    for idx in 0..255
      v = idx
      v = reflect(v, 8) if @reflect
      v <<= (@order - 8)
      8.times do
        if (v & topbit).nonzero?
          v = (v << 1) ^ @poly
        else
          v <<= 1
        end
      end
      v = reflect(v, @order) if @reflect
      v &= widthmask
      lookup << v
    end
    lookup
  end

  def reflect(crc, bits)
    base = crc
    for idx in 0...bits
      bitmask = 1 << ((bits - 1) - idx)
      if (base & 1).nonzero?
        crc |= bitmask;
      else
        crc &= ~bitmask;
      end
      base >>= 1
    end
    crc
  end
end

Instructions

Install the script above main and below the SDK, if using. Now you have to create the password data. The following is just an example:
Code:
PasswordGen = Password.new(12, 1, 1)
PasswordGen.add_switches(1, 2, 4, 3, 5, 7, 8, 6)
PasswordGen.add_variable(1, 1, 0)
PasswordGen.add_time(true)
PasswordGen.add_location
PasswordGen.compile
my_password = PasswordGen.password

Methods are:
  • add_switches(ids) Up to 8 IDs. Uses one byte.
  • add_variable(id, bytes, sign) Id is the variable ID, bytes is the number of bytes to use, sign is -1, 0 or 1.
  • add_selfswitches(keys) Up to 8 KEYs. Uses one byte.
  • add_location Saves the player's location and direction. Uses four bytes.
  • add_time(seconds) Saves the game time in frames, or seconds. Uses four bytes.

FAQ

<no questions>

Compatibility

Compatible with the SDK, as no methods were modified.

Credits and Thanks

Thanks to crc.rb, which I got the Cyclic Redundancy Check algorithm from.

Author's Notes

This script does NOT include an input window to input passwords. You as the scripter will have to do that, or request one(but not here!) If one has any questions on how to operate this, please ask!
 
On it's own: Nothing. This script is a tool used to make passwords which store game data which can be read later. Give me some time and I can construct a sample game which uses this. The current demo just doesn't do justice ;p
 
So this is a password maker so that other people can't play your games unless they have your passwords....I'm working on an event system like this x_x
 
No, that's not what this is. This system generates passwords based on what switches are on, what selfswitches are on, what variables hold and your game time. An incredible example of this is the original NES Metroid.
 
Fixed an error which popped up if you tried to read a password without compiling. And the demo will be a while longer. Damn school and such.
 
The new demo is up. Now for a challenge! The first one to post a screenshot with a 100% completion with 0 restarts and with the smallest time, I will script you something for free!
 

Thank you for viewing

HBGames is a leading amateur video game development forum and Discord server open to all ability levels. Feel free to have a nosey around!

Discord

Join our growing and active Discord server to discuss all aspects of game making in a relaxed environment. Join Us

Content

  • Our Games
  • Games in Development
  • Emoji by Twemoji.
    Top