/* Guardian Legend 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
 *
 * Reverse engineering by TNSe
 */
function glinput(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 }
  
  /* 24 * 8-bit values {1,2,3,4,5,6,7,8}{A,B,C} */
  this.bytes = [0,0,0, 0,0,0, 0,0,0, 0,0,0,
                0,0,0, 0,0,0, 0,0,0, 0,0,0]

  /* 32 * 6-bit encoded values */
  this.chars = [0,0,0,0, 0,0,0,0,  0,0,0,0, 0,0,0,0,
                0,0,0,0, 0,0,0,0,  0,0,0,0, 0,0,0,0]
  
  /* Length: 64 characters                                    */
  /*           0123456789ABCDEF   0123456789ABCDEF   0123456789ABCDEF   0123456789ABCDEF */
  this.cset = 'ABCDEFGHIJKLMNOP'+'QRSTUVWXYZ0123ab'+'cdefghijklmnopqr'+'stuvwxyz456789!?'
  
  this.swap2 = [0,        2,
                1,        3]
  this.swap3 = [0,  4,    2,   6,
                1,  5,    3,   7]
  this.swap4 = [0,8,4,12, 2,10,6,14,
                1,9,5,13, 3,11,7,15]

  this.inputs =
  [
    /*1F*/ 1, 'b00', 1, 'b01', 1, 'b02', 1, 'b03',1, 'b04', 1, 'b05',1, 'b06', 1, 'b07',
    /*1G*/ 1, 'b08', 1, 'b09', 1, 'b0A', 1, 'b0B',1, 'b0C', 1, 'b0D',1, 'b0E', 1, 'b0F',
    /*1H*/ 1, 'b10', 1, 'b11', 1, 'b12', 1, 'b13',1, 'b14', 1, 'b15',1, 'b16', 1, 'b17',
    /*2F*/ 1, 'b18', 1, 'b19', 1, 'b1A', 1, 'b1B',1, 'b1C', 1, 'b1D',1, 'b1E', 1, 'b1F',
    /*2G*/ 1, 'b20', 1, 'b21', 1, 'b22', 1, 'b23',1, 'b24', 1, 'b25',1, 'b26', 1, 'b27',
    /*2H*/ 1, 'b28', 1, 'b29', 1, 'b2A', 1, 'b2B',1, 'b2C', 1, 'b2D',1, 'b2E', 1, 'b2F',
    /*3F*/ 1, 'b30', 1, 'b31', 1, 'b32', 1, 'b33',1, 'b34', 1, 'b35',1, 'b36', 1, 'b37',
    /*3G*/ 1, 'b38', 1, 'b39', 1, 'b3A', 1, 'b3B',1, 'b3C', 1, 'b3D',1, 'b3E', 1, 'b3F',
    /*3H*/ 4, 'chip',15, 0,1,
           1, 'b44', 1, 'b45', 1, 'b46', 1, 'b47',
    /*4F*/ 1, 'c1',  1, 'c2',  1, 'c3',  1, 'c4', 1, 'c5',  1, 'c6', 1, 'c7',  1, 'c8',
    /*4G*/ 1, 'c9',  1, 'c10', 1, 'c11', 1, 'c12',1, 'c13', 1, 'c14',1, 'c15', 1, 'c16',
    /*4H*/ 2, 'score_low', 3, 8,0,
           2, 'score_high',3, 8,0,
           1, 'c17', 1, 'c18', 1, 'c19', 1, 'c20',
    /*5F*/ 2, 'w0',3,0,0,  2, 'w1',3,0,0,  2, 'w2',3,0,0,  2, 'w3',3,0,0,
    /*5G*/ 2, 'w4',3,0,0,  2, 'w5',3,0,0,  2, 'w6',3,0,0,  2, 'w7',3,0,0,
    /*5H*/ 2, 'w8',3,0,0,  2, 'w9',3,0,0,  2,'w10',3,0,0,  2,'w11',1,0,0,
    /*6F*/ 8,'ammo',255, 0,0,
    /*6G*/ 1, 'k1',  1, 'k2',  1, 'k3',  1, 'k4', 1, 'k5',  1, 'k6', 1, 'k7',  1, 'k8',
    /*6H*/ 3, 'fr',5,  0,1, /* fire rate     0..5 */
           5, 'sa',31, 0,0, /* shield amount 0..31 (0 appears as 32) */
    /*7F*/ 3, 'ws',7,  0,1, /* weapon strength 0..7, all appear as +1 */
           5, 'lx',23, 0,0, /* x coordinate 0..23 */
    /*7G*/ 3, 'ss',7,  0,1, /* shield strength 0..7, 0 can't technically happen */
           5, 'ly',23, 0,0, /* y coordinate 0..23 */
    /*7H*/ 8, 'score_high',255, 0,0,
    /*8F*/ 8, 'score_low', 255, 0,0
  ]
  
  this.xorvalue = 0

  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 |= input[n] << cachelen
      cachelen += ib
      while(cachelen >= ob)
      {
        result.push(cache & obm)
        cachelen -= ob
        cache >>= ob
      }
    }
    return result
  }
  this.bhex = function(ch)
  {
    tmp = '0123456789ABCDEF'
    return tmp[ch >> 4] + tmp[ch & 15]
  }
  this.showpass = function()
  {
    s = ''
    t = this.cset
    for(n=0; n<32; ++n)
    {
      i = this.chars[n]
      ch = t.charAt(i)
      s += ch
      
      /* Support for combining characters appears to be generally bad. */
      /* X11 can't render them properly.
       * They overflow the inputbox length limit. And so on...
       */
      if(0 && ch >= 'a' && ch <= 'z') s += '̈'; /* combining " character U+0308 */
      
      if((n%4)==3) s += ' ';
      if((n%16)==7) s += ' ';
      if(n==15) { this.txs('l1', s); s=''; }
      if(n==31) { this.txs('l2', s); s=''; }
    }
  }
  this.decode = function()
  {
    error   = 0
    message = ''

    /* Get user input (32 6-bit characters) */
    l1 = this.tx('l1')
    l2 = this.tx('l2')
    
    t = this.cset
    
    this.chars = []
    for(n=0; n<l1.length; ++n)
    {
      ch = t.indexOf(l1.charAt(n)); if(ch >= 0) this.chars.push(ch & 63)
    }
    for(n=0; n<l2.length; ++n)
    {
      ch = t.indexOf(l2.charAt(n)); if(ch >= 0) this.chars.push(ch & 63)
    }

    /* convert 6-bit to 8-bit */
    this.bytes = this.convert_bits(this.chars, 6, 8)
    
    this.xorvalue = this.bytes[23]
    this.do_xorring() /* dexor */
    chksum = this.bytes[22]
    
    chksum_shouldbe = this.calc_checksum()
    if(chksum != chksum_shouldbe)
    {
      error = 1
      message += '(chsum '+chksum+' should be '+chksum_shouldbe+')'
    }
    
    score_low  = 0
    score_high = 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)
      {
        this.chs(fname, value)
      }
      else
      {
        maxvalue = this.inputs[a++]
        if(value > maxvalue)
        {
          error = 1
          message += '(range)'
          value = maxvalue
        }
        
        value <<= this.inputs[a++]
        
        if(this.inputs[a++])
          switch(thisbits)
          {
            case 2: value = this.swap2[value]; break;
            case 3: value = this.swap3[value]; break;
            case 4: value = this.swap4[value]; break;
          }
        
        if(fname == 'score_low')
          score_low  |= value;
        else if(fname == 'score_high')
          score_high |= value;
        else
          this.ids(fname, value)
      }
      bitpos += thisbits
      if(bitpos == 8) { bitpos=0; ++bytepos; }
    }
    
    this.txs('score', 10 * (score_low + score_high*1000))
    this.ids('xor', this.xorvalue)
    
    if(error) message = '(error)' + message
    this.txs('msg', message)
  }
  this.calc_checksum = function()
  {
    a=0;
    for(n=0; n<22; ++n) { a += this.bytes[n]; if(a>255) a -= 255; }
    return (a + this.xorvalue) & 255
  }
  this.do_xorring = function()
  {
    for(n=0; n<23; ++n) this.bytes[n] ^= (this.xorvalue + 0x1D - n) & 255;
  }
  this.setup = function()
  {
    for(n=0; n<24; ++n) this.bytes[n] = 0
    
    this.xorvalue = this.id('xor')
    
    score = 0 + this.tx('score')
    
    /* strip the last digit */
    score = (score - (score%10)) / 10
    
    score_low  = score % 1000
    score_high = (score - score_low) / 1000
    
    bytepos=0
    bitpos =0
    
    b=this.inputs.length
    for(a=0; a<b; )
    {
      thisbits = this.inputs[a++]
      fname    = this.inputs[a++]
      if(thisbits == 1)
      {
        this.bytes[bytepos] |= this.ch(fname) << bitpos
      }
      else
      {
        if(fname == 'score_low')
          value = score_low;
        else if(fname == 'score_high')
          value = score_high;
        else
          value = this.id(fname)
        
        ignore_maxvalue = this.inputs[a++]
        
        value >>= this.inputs[a++]
        
        value &= (1 << thisbits) - 1;
        
        if(this.inputs[a++])
          switch(thisbits)
          {
            case 2: value = this.swap2[value]; break;
            case 3: value = this.swap3[value]; break;
            case 4: value = this.swap4[value]; break;
          }
        this.bytes[bytepos] |= value << bitpos
      }
      bitpos += thisbits
      if(bitpos == 8) { bitpos=0; ++bytepos; }
    }
    
    this.bytes[23] = this.xorvalue
    this.bytes[22] = this.calc_checksum()
    
    this.do_xorring()
    
    /* convert 8-bit to 6-bit */
    this.chars = this.convert_bits(this.bytes, 8, 6)

    this.showpass()
    this.decode()
  }
}
