satoshinm / nes-game-genie

Game Genie decoder/encoder for Nintendo Entertainment System (npmjs module)
https://satoshinm.github.io/nescode/
MIT License
4 stars 0 forks source link

Famicom Pro Action Rocky code encoding/decoding support #1

Open satoshinm opened 6 years ago

satoshinm commented 6 years ago

http://chrismcovell.com/fcrocky.html

Comparable to Game Genie, but the Pro Action Rocky is for Famicom, and uses a different code format. Would be nice to support, maybe as a separate module.

Unfortunately, the link from http://chrismcovell.com/fcrocky.html describing the technical details of the code format seems to be broken: http://chrismcovell.com/fcrocky_codes.txt 500 Internal Server Error, and it may not be available on the Wayback Machine either. But there are sample codes available at http://chrismcovell.com/texts/RockyDump-chris.html and new codes at http://chrismcovell.com/NewRockyCodes.htm which have corresponding decoded codes, example:

WARWOLF 超人狼戦記 ウォーウルフ    
infinite lives 残機減らない 15C90F12 (B606:DE->BD)

https://gamehacking.org/system/nes has a code converter, raw/gg/par

satoshinm commented 6 years ago

http://forums.nesdev.com/viewtopic.php?f=3&t=13455&p=158623 ran into the same problem (dead link), but notes that http://www.chrismcovell.com/CheatConverter.html has working JavaScript for Pro Action Rocky encoding/decoding. Copying the complete source here so it doesn't get lost again:

/*
Because this is in javascript and javascript has trouble using bytes with the highest bit
set (try doing alert(0x80000000>>1 == 0x40000000)) I had to do things in a weird way.

You may use snippits of this code in any program you want, if it is the game genie or pro action rockey decode or encode. Include this file with the program or put a thanks to Blue Hawk and this script title.

Coded by Blue Hawk.
*/

// Bit descrambling arrays
var rocky_shifts = new Array(
    3,13,14,1,6,9,5,0,12,7,2,8,10,11,4, // addr
    19,21,23,22,20,17,16,18,        // compare
    29,31,24,26,25,30,27,28         // replace
);

//%Addr (15 bits) %Val8 compare in special var
var gg_shift = new Array(
    0,   1,   2,  7,
    4,   5,   6,  15,
    12,  13,  14, 23,
    20,  21,  22, 11,
    8,    9,  10, 19,
    16,  17,  18,  3
);

var hexlet = "0123456789ABCDEF";
var GG_letters = "APZLGITYEOXUKSVN";
var cmp, addr, val, ggCode;

// Everything is shifted one bit right to avoid the high bit being set
// which certain browser doesn't handle well.

var rocky_key = 0x7e5ee93a;
var rocky_xor = 0x5c184b91;

function filter()
{
  var n, ggCode2, ch ;
  ggCode = document.frm.ggCode.value;

  ggCode  = ggCode.toUpperCase();
  ggCode2 = "";

  for (n=0; n < ggCode.length; n++)
  {
    ch = ggCode.charAt(n)
    if (GG_letters.indexOf(ch) != -1)
        ggCode2 += ch;
  }
  document.frm.ggCode.value = ggCode2;
}

function toHex(val, textSize)
{
  var hex = "";

  for(var i=0; i < textSize; i++)
  {
      n = (val >> (i * 4)) & 15;
      hex = hexlet.charAt(n) + hex;
  }
  return hex;
}

function fromHex(hexText)
{
  var decVal = 0;
  hexText = hexText.toUpperCase();
  for(var i=0; i < hexText.length; i++)
  {
    decVal <<= 4;

    n = hexText.charAt(i);
    n = hexlet.indexOf(n);
    if (n == -1)
      return "bad values";
    else
      decVal += n;
  }
  return decVal;
}

function decodeGG()
{
  var n, num = new Array, ch;

  filter();
  ggCode = document.frm.ggCode.value;

  if ((ggCode.length != 6) && (ggCode.length != 8))
  {
    alert("Bad Game Genie size");
    return;
  }

  for (n=0; n < ggCode.length; n++)
  {
    ch = ggCode.charAt(n);
    num[n] = GG_letters.indexOf(ch) - 0;
  }

  ch = 0;
  if ((num[2] & 8) && (ggCode.length == 8))
  {
    ggDecode(num)
    ggDecode8(num);
    document.frm.cmp.value  = toHex(cmp,  2);
    ch = 1;
  }
  else if (!(num[2] & 8) && (ggCode.length == 6))
  {
    ggDecode(num);
    document.frm.cmp.value   = "";
    document.frm.rocky.value = "";
  }
  else
  {
    alert("Bad game genie code");
    return;
  }

  document.frm.addr.value = toHex(addr, 4);
  document.frm.val.value  = toHex(val,  2);
  if (ch)
    paRockyEncode();
}

function encodeVal()
{
  var eightLetters = 0;

  addr = document.frm.addr.value;
  addr = fromHex(addr);

  if (addr == "bad values")
  {
    document.frm.addr.value = addr;
    return;
  }
  else
  {
    addr |= 0x8000;
    document.frm.addr.value = toHex(addr, 4);
  }

  val = document.frm.val.value;
  val = fromHex(val);
  document.frm.val.value = val;
  if (val == "bad values")
  {
    document.frm.val.value = val;
    return;
  }
  else
    document.frm.val.value = toHex(val, 2);

  cmp = document.frm.cmp.value;
  if (cmp.length)
  {
    cmp = fromHex(cmp);
    document.frm.cmp.value = cmp;
    if (cmp == "bad values")
    {
      document.frm.cmp.value = cmp;
      return;
    }
    else
      document.frm.cmp.value = toHex(cmp, 2);

    eightLetters = 1;
  }
  else
    cmp = 0;

  encodeGG(eightLetters);

  if (eightLetters)
    paRockyEncode();
  else
    document.frm.rocky.value = "";
}

function encodeGG(eightLetters)
{
  var n, num = new Array;

  ggEncode(num);

  if (eightLetters)
  {
    ggEncode8(num);
    num[2] |= 8;
  }
  else
  {
    ggEncode6(num);
    num[2] &= 255 - 8;
  }

  ggCode = "";
  for (n=0; n < (eightLetters ? 8 :6) ; n++)
    ggCode += GG_letters.charAt(num[n]);

  document.frm.ggCode.value = ggCode;
}

function ggDecode8(num)
{
    val = val & (255 - 8);
    val |= num[7] & 8 ? 0x08 : 0x00;

    cmp  = num[6] & 8 ? 0x80 : 0x00;
    cmp |= num[7] & 4 ? 0x40 : 0x00;
    cmp |= num[7] & 2 ? 0x20 : 0x00;
    cmp |= num[7] & 1 ? 0x10 : 0x00;

    cmp |= num[5] & 8 ? 0x08 : 0x00;
    cmp |= num[6] & 4 ? 0x04 : 0x00;
    cmp |= num[6] & 2 ? 0x02 : 0x00;
    cmp |= num[6] & 1 ? 0x01 : 0x00;
}

function ggDecode(num)
{
    var n, i;
    val = 0;

    for (n=0; n<6; n++) {
        for (i=0; i<4; i++) {
            if (num[n] & 1<<i)
                val |=  1<<gg_shift[n*4 + i];
        }
    }

    addr = (val>>8) | 0x8000;
    val &= 0xFF;
}

function ggEncode(num)
{
    var n, i;

    val &= 0xFF;
    addr|= 0x8000;
    val |= addr<<8;

    num[0] = num[1] = num[2] = num[3] = 0;
    num[4] = num[5] = num[6] = num[7] = 0;

    for (n=0; n<6; n++) {
        for (i=0; i<4; i++) {
            if (val & 1<<gg_shift[n*4 + i])
                num[n] |= 1<<i;
        }
    }
}

function ggEncode8(num)
{
    num[2] |= 8;
    num[7] |= val & 0x08 ? 8 : 0;

    num[6]  = cmp & 0x80 ? 8 : 0;
    num[7] |= cmp & 0x40 ? 4 : 0;
    num[7] |= cmp & 0x20 ? 2 : 0;
    num[7] |= cmp & 0x10 ? 1 : 0;

    num[5] &= 255 - 8;
    num[5] |= cmp & 0x08 ? 8 : 0;
    num[6] |= cmp & 0x04 ? 4 : 0;
    num[6] |= cmp & 0x02 ? 2 : 0;
    num[6] |= cmp & 0x01 ? 1 : 0;
}

function ggEncode6(num)
{
    num[5] |= val & 0x08 ? 8 : 0;
}

function paRockyDecode()
{
    encoded = document.frm.rocky.value;
    encoded = fromHex( encoded.substr(0, 8) );
    document.frm.rocky.value = toHex( encoded, 8 );

    encoded >>= 1;
    key = rocky_key;
    decoded = 0;
    for ( i = 31; i--; )
    {
        if ( (key ^ encoded) & 0x40000000 ) {
            decoded |= 1 << rocky_shifts [i];
            key ^= rocky_xor;
        }
        encoded <<= 1;
        key <<= 1;
    }

    addr = (decoded & 0x7fff) | 0x8000;
    cmp  = (decoded >> 16) & 0xff;
    val  = (decoded >> 24) & 0xff;

    document.frm.addr.value  = toHex( addr, 4 );
    document.frm.cmp.value   = toHex( cmp, 2 );
    document.frm.val.value   = toHex( val, 2 );

    encodeGG(1);
}

function paRockyEncode()
{
    decoded  = fromHex(document.frm.addr.value) & 0x7fff;
    decoded |= fromHex(document.frm.cmp.value) << 16;
    decoded |= fromHex(document.frm.val.value) << 24;

    key = rocky_key;
    encoded = 0;
    for ( i = 31; i--; )
    {
        bit = decoded >> rocky_shifts [i];

        if ( ((key >> 30) ^ bit) & 1 )
            encoded |= 2 << i;

        if ( bit & 1 )
            key ^= rocky_xor;

        key <<= 1;
    }

    document.frm.rocky.value = toHex(encoded , 8);
}
satoshinm commented 6 years ago

Added in https://github.com/satoshinm/famicom-pro-action-rocky https://www.npmjs.com/package/famicom-pro-action-rocky but would be perhaps worth adding to cli and web examples of https://github.com/satoshinm/nes-game-genie/tree/master/examples