/* Simon's Quest password generator / decoder
 * Copyright (C) 1992,2004 Joel Yliluoma (http://iki.fi/bisqwit/)
 * Written for http://tasvideos.org/PasswordGenerators.html
 * Permission to copy and modify is granted under the following terms:
 *   The copyright notice is kept unmodified
 *   No attempts are made to prevent anyone downloading the source code
 *
 * Initial work on reverse engineering done by Hitek, completed by Bisqwit
 */
cv2u_do_crypt = 1
function cv2u_set_crypt(n) { cv2u_do_crypt = n }

function cv2uinput(name)
{
  this.gel = function(id)   { return document.getElementById(name+id) }
  this.id  = function(id)   { return this.gel(id).selectedIndex }
  this.ids = function(id,v) { this.gel(id).selectedIndex=v }
  this.ch  = function(id)   { return this.gel(id).checked?1:0 }
  this.chs = function(id,v) { this.gel(id).checked = v>0 }
  this.tx  = function(id)   { return this.gel(id).value }
  this.txs = function(id,v) { this.gel(id).value = v }
  
  /* 0..255 values */
  this.bytes = [0,0,0,0, 0,0,0, 0,0,0]

  /* 0..31 encoded values */
  this.chars = [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0]

  /* Length: 36 characters                                    */
  /*           0123456789ABCDEF     0123456789ABCDEF     0123 */
  this.ntsc = 'ABCDEFGHIJKLMNOP' + 'QRSTUVWXYZ012345' + '6789'
  this.pal  = 'BDGHJKLMNPQRTVWX' + 'YZ0123456789dsh@' + '*#!?'
  /*           - - - - - - - -      - - - - - - - -           */
  this.csets = [this.ntsc, this.pal]
  
  /* Which values to encode in which bytes */
  this.inputs =
  [
    /* 00 */ 8, 'LV',      6, /* level */
    /* 01 */ 4, 'day_low', 9,
             4, 'day_high',9,
    /* 02 */ 4, 'LN',      8, /* laurels */
             4, 'GN',      8, /* garlics */
    /* 03 */ 1, 'RB', 1, 'HE', 1, 'EY', 1, 'NA', 1, 'RG', /* body parts */
             3, 'C',       3,
    /* 04 */ 1, 'SB', 1, 'XS', 1, 'LF', 1, 'GF', /* misc */
             4, '-',0, /* unused */
    /* 05 */ 1, 'D1', 1, 'D2', 1, 'D3', 1, 'WA',
             1, 'DI', 1, 'FL', 1, 'ST', 1, '-',  /* msb unused */
    /* 06 */ 8, 'W',       4,
    /* 07 */ 8, '-',255, /* ignore, checksum_low */
    /* 08 */ 8, '-',255, /* ignore, checksum_high */
    /* 09 */ 4, 'xor',    15,
             4, 'add',    15
  ]

  this.xortables =
   [[14, 1,10,16, 4,15,24,27,22, 7,30,18,17,29],
    [ 4,15,24,27,22, 7,30,18,11,18, 2, 3,17,29],
    [10, 1,14,16, 4,12,24,27,22, 7,10,18,17,29],
    [14,13,10,28, 4,13,26,27, 6, 7,30,18,21,29],
    [14,17,10,16, 4, 3,24,27,22, 7,18,18,17,29],
    [19, 9,10,18, 4, 3,24,27,18, 7,22,10,16,21],
    [ 2,13,10,18, 4,15,10,27,18, 7,24,18,17,29],
    [ 0,29,10,24, 4,11,24,26,20, 7,30,20,17,29],
    [14,13,14,18,12,15,26,27,22, 7,30,18,21,29],
    [14, 1,10,16, 4,15,24,27,20, 7, 0,18,17,29],
    [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,17,29],
    [16,13,10,22, 4,15,24,27,22, 7,30,18,29,29],
    [ 2, 3,10,18, 4,15,24,11,22, 7, 6,22, 1,29],
    [24, 5,10,26, 4,13,24,27,22, 3,26,22,23,21],
    [ 3, 5,10, 2, 4,13, 8,15,30,11,18,22,17, 4],
    [30, 5,10,26, 7,13,26,31,23, 0,26,22,22,21]]
  this.add_tab = [0,1,2,3,4,1,2,3]

  this.convert_bits = function(input, ib, ob)
  {
    cache=0
    cachelen=0
    result = []
    nm = input.length
    obm = (1 << ob) - 1
    for(n=0; n<nm; ++n)
    {
      cache = (cache << ib) + input[n]
      for(cachelen += ib; cachelen >= ob; )
      {
        cachelen -= ob
        /* eat high bits */ result.push((cache >> cachelen) & obm)
        /* keep low bits */ cache &= (1 << cachelen)-1;
      }
    }
    return result
  }
  this.showpass = function()
  {
    s = ''
    t = this.csets[this.id('sys')]
    for(n=0; n<16; ++n)
    {
      i = this.chars[n]
      s += t.charAt(i)
    }
    this.txs('l1', s.substr(0, 4)+ ' ' + s.substr( 4,4))
    this.txs('l2', s.substr(8, 4)+ ' ' + s.substr(12,4))
  }
  this.try_decode = function(sys, act)
  {
    error   = 0
    message = ''

    /* Get user input (16 5-bit characters) */
    l1 = this.tx('l1')
    l2 = this.tx('l2')
    
    t = this.csets[sys]
    
    for(n=0; n<4; ++n)
    {
      this.chars[n+0 ] = t.indexOf(l1.charAt(n  )) & 31
      this.chars[n+4 ] = t.indexOf(l1.charAt(n+5)) & 31
      this.chars[n+8 ] = t.indexOf(l2.charAt(n  )) & 31
      this.chars[n+12] = t.indexOf(l2.charAt(n+5)) & 31
    }

    /* convert 5-bit to 8-bit to get the last byte */
    this.bytes = this.convert_bits(this.chars, 5, 8)

    /* read crypting settings from the last byte */
    use_xor = this.bytes[9] & 15
    use_add = this.bytes[9] >> 4
    
    /* then decrypt the 5-bit string */
    xortable = this.xortables[use_xor]
    
    /* first undo the adding, then undo xorring */
    for(n=0; n<14; ++n)
    {
      this.chars[n] = ((this.chars[n] - use_add) ^ xortable[n]) & 31
    }

    /* convert the decrypted 5-bit string to 8-bit */
    this.bytes = this.convert_bits(this.chars, 5, 8)
    
    /* checksum */
    chksum = this.bytes[8] | (this.bytes[7] << 8)
    
    for(n=0; n<7; ++n) chksum -= this.bytes[n]
    if(chksum != 0)
    {
      message += 'Checksum failure; '
      error=1
    }

    day_high = 0
    day_low  = 0
    
    bytepos=0
    bitpos =0
    
    b=this.inputs.length
    for(a=0; a<b; )
    {
      thisbits = this.inputs[a++]
      fname    = this.inputs[a++]
      
      value = (this.bytes[bytepos] >> bitpos) & ((1 << thisbits)-1)
      
      if(thisbits == 1)
      {
        if(fname!='-' && act)
          this.chs(fname, value)
      }
      else
      {
        maxvalue = this.inputs[a++]
        if(value > maxvalue)
        {
          error = 1;
          value = maxvalue
        }
        if(fname!='-' && act)
        {
          if(fname == 'day_low')
            day_low = value;
          else if(fname == 'day_high')
            day_high = value;
          else
            this.ids(fname, value)
        }
      }
      bitpos += thisbits
      if(bitpos == 8) { bitpos=0; ++bytepos; }
    }
    
    if(act) this.ids('D', day_low + 10 * day_high)
    
    if(act)
    {
      if(use_add != this.add_tab[use_xor & 7])
      {
        /* Passwords the game can decode but will never generate */
        if(!error)
        {
          message += 'Valid but not authentic.'
        }
      }
      
      if(error)message = 'Error: ' + message
      this.txs('msg', message)
    }
    
    return error
  }
  this.decode = function()
  {
    /* guess whether it's ntsc or pal */
    ntsc_error = this.try_decode(0, 0)
    pal_error  = this.try_decode(1, 0)
    
    /* if one was better than the other, use it */
    if(ntsc_error != pal_error)
      this.ids('sys', ntsc_error ? 1 : 0)
    
    this.try_decode(this.id('sys'), 1)
  }
  this.modified = function()
  {
    this.decode()
    /*this.setup()*/
  }
  this.setupxor = function()
  {
    /* automatically select an add value in the way the game does */
    this.ids('add', this.add_tab[this.id('xor') & 7])
    this.setup()
  }
  this.setup = function()
  {
    /* No extra data can be added. The game verifies all bytes
     * for the maximum values and validity of all BCD values.
     */
    for(n=0; n<10; ++n) this.bytes[n] = 0
    
    day_low   = this.id('D') % 10
    day_high  = (this.id('D') - day_low) / 10
    
    bytepos=0
    bitpos =0
    
    b=this.inputs.length
    for(a=0; a<b; )
    {
      thisbits = this.inputs[a++]
      fname    = this.inputs[a++]
      if(thisbits == 1)
      {
        if(fname=='-')
          value = 0;
        else
          value = this.ch(fname);
      }
      else
      {
        ignore_maxvalue = this.inputs[a++]
        
        if(fname=='-')
          value = 0;
        else if(fname == 'day_low')
          value = day_low;
        else if(fname == 'day_high')
          value = day_high;
        else
          value = this.id(fname)
      }
      this.bytes[bytepos] |= value << bitpos
      bitpos += thisbits
      if(bitpos == 8) { bitpos=0; ++bytepos; }
    }
    
    chksum=0
    for(n=0; n<7; ++n) chksum += this.bytes[n]
    
    this.bytes[7] = (chksum >> 8) & 255
    this.bytes[8] = (chksum     ) & 255
    
    /* convert 8-bit to 5-bit */
    this.chars = this.convert_bits(this.bytes, 8, 5)

    /* First do some xorring, then do some adding */
    if(cv2u_do_crypt)
    {
      xortable = this.xortables[this.id('xor')]
      for(n=0; n<14; ++n)
      {
        this.chars[n] = ((this.chars[n] ^ xortable[n]) + this.id('add')) & 31
      }
    }
    
    this.showpass()
    if(cv2u_do_crypt)
      this.decode()
  }
}
