Guest

ketti

By: a guest on Sep 15th, 2009  |  syntax: Java  |  size: 6.80 KB  |  hits: 106  |  expires: Never
download  |  raw  |  embed  |  report abuse
Copied
  1. /**
  2.  * Java implementation of the secret password check and the You Win code generation algorithm
  3.  * in the NES game "Treasure Master"
  4.  *
  5.  * Note: The password check is possibly incomplete.
  6.  *
  7.  * @author ketti
  8.  * @see http://www.reddit.com/r/TreasureMaster/
  9.  */
  10. public class TreasureMaster
  11. {
  12.         private static final int YOUWINCODE_LENGTH = 0x18;
  13.         private static final int PASSWORD_LENGTH = 0x18;
  14.         private static final int BUFFER_LENGTH = 0x0F;
  15.         private static final int CHECK_LENGTH_1 = 0x0D;
  16.         private static final int CHECK_LENGTH_2 = 0x08;
  17.         private static final int SERIAL_LENGTH = 0x08;
  18.  
  19.         private static final byte[] PPU = new byte[] {(byte)0xFD, (byte)0x22, (byte)0x3C, (byte)0x40};
  20.        
  21.         private static char[] CONVERSION_TABLE = new char[] {
  22.                         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
  23.                         'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N',
  24.                         'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z', '!'};
  25.        
  26.         public static void main(String[] args)
  27.         {
  28.                 String secret = "3HDJL9DNQV2WYTV4S91RXR86";
  29.                 System.out.println("Secret password \"" + secret + "\" is " +
  30.                                 (checkSecretPassword(secret) ? "" : "in") + "valid.\n");
  31.                
  32.                
  33.                
  34.                 String serial = "12345678";
  35.                 int score = 79900;
  36.                 int lives = 4;
  37.                
  38.                 System.out.println("Serial: " + serial);
  39.                 System.out.println("Score: " + score);
  40.                 System.out.println("Lives: " + lives);
  41.                
  42.                 System.out.println("\nYou Win codes:");
  43.                 for (int random = 0; random < 0x10; random++)
  44.                 {
  45.                         System.out.println(computeYouWinCode(serial, score, lives, random));
  46.                 }
  47.         }
  48.  
  49.        
  50.         public static boolean checkSecretPassword(String string)
  51.         {
  52.                 if (string.length() != PASSWORD_LENGTH)
  53.                 {
  54.                         throw new IllegalArgumentException("Secret password must be " +
  55.                                         PASSWORD_LENGTH + " characters long.");
  56.                 }
  57.                
  58.                 // Convert the secret password string to the "Treasure Master format".
  59.                 byte[] password = new byte[PASSWORD_LENGTH];
  60.                 char[] chars = string.toCharArray();
  61.                 for (int i = 0; i < PASSWORD_LENGTH; i++)
  62.                 {
  63.                         boolean found = false;
  64.                         for (int j = 0; j < CONVERSION_TABLE.length; j++)
  65.                         {
  66.                                 if (chars[i] == CONVERSION_TABLE[j])
  67.                                 {
  68.                                         password[i] = (byte)j;
  69.                                         found = true;
  70.                                         break;
  71.                                 }
  72.                         }
  73.                         if (!found)
  74.                         {
  75.                                 throw new IllegalArgumentException("Illegal character in secret password found: " + chars[i]);
  76.                         }
  77.                 }
  78.                
  79.  
  80.                 // Compute buffer contents for the first check (somewhere around $B0BE)
  81.                 byte[] buffer = new byte[BUFFER_LENGTH];
  82.                 for (int i = 0; i < PASSWORD_LENGTH; i++)
  83.                 {
  84.                         byte b = password[i];
  85.                        
  86.                         CarryContainer c = new CarryContainer();
  87.                         b = c.shiftLeftWithCarry(b);
  88.                         b = c.shiftLeftWithCarry(b);
  89.                         b = c.shiftLeftWithCarry(b);
  90.  
  91.                         for (int k = 0; k < 5; k++)
  92.                         {
  93.                                 b = c.rotateLeftThroughCarry(b);
  94.                                
  95.                                 for (int j = BUFFER_LENGTH - 1; j >= 0; j--)
  96.                                 {
  97.                                         buffer[j] = c.rotateLeftThroughCarry(buffer[j]);
  98.                                 }
  99.                         }
  100.                 }
  101.                
  102.                 int sum = computeChecksum(buffer, CHECK_LENGTH_1);
  103.  
  104.                 byte lowByte = (byte)(sum & 0xFF);
  105.                 byte highByte = (byte)((sum >> 8) & 0xFF);
  106.                
  107.                 // Compare computed checksum with checksum bytes in the buffer
  108.                 if ((lowByte != buffer[CHECK_LENGTH_1]) || (highByte != buffer[CHECK_LENGTH_1 + 1]))
  109.                 {
  110.                         return false;
  111.                 }
  112.  
  113.                 // For the second check to execute the first byte of the buffer has to be smaller than 0x20
  114.                 if (buffer[0] >= 0x20)
  115.                 {
  116.                         return false;
  117.                 }
  118.                
  119.                 // Compute buffer contents for the second check (somewhere around $B0EB)
  120.                 CarryContainer c = new CarryContainer();
  121.                 for (int i = buffer[0]; i > 0; i--)
  122.                 {
  123.                         for (int j = buffer.length - 1; j >= 0; j--)
  124.                         {
  125.                                 buffer[j] = c.rotateLeftThroughCarry(buffer[j]);
  126.                         }
  127.                 }
  128.  
  129.                 sum = computeChecksum(buffer, CHECK_LENGTH_2);
  130.                
  131.                 return ((byte)(sum & 0xFF)) == buffer[8];
  132.         }
  133.        
  134.         public static String computeYouWinCode(final String serial, final int score,
  135.                         final int lives, final int random)
  136.         {
  137.                 byte[] serialBytes = new byte[SERIAL_LENGTH];
  138.                 byte[] scoreBytes = new byte[SERIAL_LENGTH];
  139.                 byte[] buffer = new byte[BUFFER_LENGTH];
  140.  
  141.                 if (serial.length() != SERIAL_LENGTH)
  142.                 {
  143.                         throw new IllegalArgumentException("Serial must be " + SERIAL_LENGTH + " characters long.");
  144.                 }
  145.  
  146.                 // Convert serial to byte array
  147.                 char[] serialChars = serial.toCharArray();
  148.                 for (int i = 0; i < SERIAL_LENGTH; i++)
  149.                 {
  150.                         char c = serialChars[i];
  151.                         if ((c < '0') || (c > '9'))
  152.                         {
  153.                                 throw new IllegalArgumentException("Invalid character in serial: " + c);
  154.                         }
  155.                        
  156.                         serialBytes[i] = (byte)(c - '0');
  157.                 }
  158.  
  159.                 // Copy PPU bytes to the buffer
  160.                 for (int i = 0; i < PPU.length; i++)
  161.                 {
  162.                         buffer[i] = PPU[i];
  163.                 }
  164.                
  165.                 // Convert score to "Treasure Master format"
  166.                 int s = score / 10;
  167.                 for (int i = SERIAL_LENGTH - 1; i >= 0 ; i--)
  168.                 {
  169.                         scoreBytes[i] = (byte)(s % 10);
  170.                         s /= 10;
  171.                 }
  172.                
  173.                 // Put score and serial into the buffer
  174.                 byte b;
  175.                 CarryContainer c = new CarryContainer();
  176.                 for (int i = 0; i < SERIAL_LENGTH; i++)
  177.                 {
  178.                         b = scoreBytes[i];
  179.                         b = c.shiftLeftWithCarry(b);
  180.                         b = c.shiftLeftWithCarry(b);
  181.                         b = c.shiftLeftWithCarry(b);
  182.                         b = c.shiftLeftWithCarry(b);
  183.                         buffer[i + 4] = (byte)(serialBytes[i] | b);
  184.                 }
  185.  
  186.                 // Put lives and random number into the buffer
  187.                 b = (byte)lives;
  188.                 b = c.shiftLeftWithCarry(b);
  189.                 b = c.shiftLeftWithCarry(b);
  190.                 b = c.shiftLeftWithCarry(b);
  191.                 b = c.shiftLeftWithCarry(b);
  192.                 buffer[4 + SERIAL_LENGTH] = (byte)((random & 0x0F) | b);
  193.                
  194.                 // Compute checksum for that buffer and append it to the buffer
  195.                 int sum = computeChecksum(buffer, CHECK_LENGTH_1);
  196.                 buffer[CHECK_LENGTH_1] = (byte)(sum & 0xFF);
  197.                 buffer[CHECK_LENGTH_1 + 1] = (byte)((sum >> 8) & 0xFF);
  198.  
  199.                 // Create You Win code string
  200.                 StringBuffer sb = new StringBuffer(YOUWINCODE_LENGTH);
  201.                 c.setCarry(false);
  202.                 for (int i = 0; i < YOUWINCODE_LENGTH; i++)
  203.                 {
  204.                         b = 0;
  205.                        
  206.                         for (int j = 0; j < 5; j++)
  207.                         {
  208.                                 for (int k = BUFFER_LENGTH - 1; k >= 0; k--)
  209.                                 {
  210.                                         buffer[k] = c.rotateLeftThroughCarry(buffer[k]);
  211.                                 }
  212.                                 b = c.rotateLeftThroughCarry(b);
  213.                         }
  214.                        
  215.                         sb.insert(i, CONVERSION_TABLE[b]);
  216.                 }
  217.                
  218.                 return sb.toString();
  219.         }
  220.        
  221.         private static int computeChecksum(final byte[] buffer, final int length)
  222.         {
  223.                 int sum = 0;
  224.                 for (int i = 0; i < length; i++)
  225.                 {
  226.                         sum += buffer[i] & 0xFF;
  227.                 }
  228.                
  229.                 return sum;
  230.         }
  231.        
  232.         static class CarryContainer
  233.         {
  234.                 private boolean carry;
  235.  
  236.                 public CarryContainer()
  237.                 {
  238.                         this(false);
  239.                 }
  240.                
  241.                 public CarryContainer(final boolean carry)
  242.                 {
  243.                         this.carry = carry;
  244.                 }
  245.  
  246.                 public byte shiftLeftWithCarry(final byte b)
  247.                 {
  248.                         // ASL
  249.                         carry = ((b & 0x80) != 0);
  250.                         return (byte)(b << 1);
  251.                 }
  252.                
  253.                 public byte rotateLeftThroughCarry(byte b)
  254.                 {
  255.                         // ROL
  256.                         boolean newCarry = ((b & 0x80) != 0);
  257.                         b = (byte)((b << 1) | ((carry) ? 1 : 0));
  258.                         carry = newCarry;
  259.                        
  260.                         return b;
  261.                 }
  262.                
  263.                 public void setCarry(boolean carry)
  264.                 {
  265.                         this.carry = carry;
  266.                 }
  267.         }
  268. }