Coverage Report - uk.co.javagear.Memory
 
Classes in this File Line Coverage Branch Coverage Complexity
Memory
0% 
0% 
6.667
 
 1  
 /*
 2  
  * Memory.java
 3  
  *
 4  
  *
 5  
  * This file is part of JavaGear.
 6  
  *
 7  
  * JavaGear is free software; you can redistribute it and/or modify
 8  
  * it under the terms of the GNU General Public License as published by
 9  
  * the Free Software Foundation; either version 2 of the License, or
 10  
  * (at your option) any later version.
 11  
  *
 12  
  * JavaGear is distributed in the hope that it will be useful,
 13  
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14  
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15  
  * GNU General Public License for more details.
 16  
  *
 17  
  * You should have received a copy of the GNU General Public License
 18  
  * along with JavaGear; if not, write to the Free Software
 19  
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 20  
  */
 21  
 
 22  
 package uk.co.javagear;
 23  
 
 24  
 import java.io.BufferedInputStream;
 25  
 import java.io.IOException;
 26  
 import java.io.InputStream;
 27  
 import java.net.URL;
 28  
 import java.net.URLConnection;
 29  
 import java.util.zip.ZipEntry;
 30  
 import java.util.zip.ZipInputStream;
 31  
 import org.apache.log4j.Logger;
 32  
 
 33  
 /**
 34  
  * Emulates SMS/GG Memory and Paging Hardware.
 35  
  * Also Handles Reading ROMs and configuring the memory appropriately.
 36  
  *
 37  
  * @author Copyright (C) 2002-2003 Chris White
 38  
  * @author Jesús Adolfo García Pasquel
 39  
  * @version 18th January 2003
 40  
  * @see "JavaGear Final Project Report"
 41  
  */
 42  
 public final class Memory {
 43  
     
 44  
     /**
 45  
      * Array of memory frames.
 46  
      */
 47  
     private byte [][] frames;
 48  
     
 49  
     /**
 50  
      * Array of memory pages.
 51  
      */
 52  
     private byte [][] pages;
 53  
     
 54  
     /**
 55  
      * Array of cartridge RAM pages.
 56  
      */
 57  
     private byte [][] cartRamPages;
 58  
     
 59  
     /**
 60  
      * Stores whether frame 2 contains cartridge ROM or cartridge RAM.
 61  
      */
 62  
     private boolean frameTwoRom;
 63  
     
 64  
     /**
 65  
      * Stores which page of cartridge RAM to use.
 66  
      */
 67  
     private int cartRamPage;
 68  
     
 69  
     /**
 70  
      * Stores number of pages.
 71  
      */
 72  
     private int numberOfPages;
 73  
     
 74  
     /**
 75  
      * Pointer to general parameters.
 76  
      */
 77  
     private Setup setup;
 78  
     
 79  
     /**
 80  
      * Memory constructor.
 81  
      *
 82  
      * @param set pointer to general parameters.
 83  
      */
 84  0
     public Memory(Setup set) {
 85  0
         this.setup = set;
 86  
         // Create 4 x 16K Frames
 87  0
         frames = new byte[4][0x4000];
 88  
         // Create 2 x 16K RAM Cartridge Pages
 89  0
         cartRamPages = new byte[2][0x4000];
 90  
         // Create 4 x 16K Pages
 91  0
         pages = new byte[4][0x4000];
 92  
         // Default number of pages
 93  0
         numberOfPages = 2;
 94  
         // Reset to Default Values
 95  0
         reset();
 96  0
     }
 97  
     
 98  
     /**
 99  
      * Reset memory to default values.
 100  
      */
 101  
     public void reset() {
 102  0
         cartRamPage = 0;
 103  0
         frameTwoRom = true;
 104  
         
 105  
         // Default Mapping
 106  0
         if (pages != null) {
 107  0
             frames[0] = pages[0];
 108  0
             frames[1] = pages[1];
 109  
         }
 110  0
     }
 111  
     
 112  
     /**
 113  
      * Write to a memory location.
 114  
      *
 115  
      * @param address memory address
 116  
      * @param value value to write
 117  
      */
 118  
     public void write(int address, int value) {
 119  
         // Do not write over ROM (GG Bartman tries this)
 120  0
         if (address < 0x8000) {
 121  0
             return;
 122  0
         } else if (address < 0xC000) { // ROM Page 2
 123  0
             if (!frameTwoRom) {
 124  0
                 frames[2][address - 0x8000] = (byte) value;
 125  
             }
 126  0
         } else if (address < 0xE000) { // RAM
 127  0
             frames[3][address - 0xA000] = (byte) value;
 128  0
         } else if (address < 0xFFFC) { // Mirror RAM
 129  0
             frames[3][address - 0xC000] = (byte) value;
 130  0
         } else if (address < 0x10000) { // Paging registers!
 131  0
             page(address, value);
 132  
         }
 133  0
     }
 134  
     
 135  
     /**
 136  
      * Read from a memory location.
 137  
      *
 138  
      * @param address memory location.
 139  
      *
 140  
      * @return value from memory location.
 141  
      */
 142  
     
 143  
     public int read(int address) {
 144  
         // Elegant :
 145  
         // return unsigned(readSigned(address));
 146  
         
 147  
         // Dirty Fast Solution (Copy Code Twice!)
 148  0
         if (address < 0x400) {          // First 1K
 149  0
             return pages[0][address] & 0xFF;
 150  0
         } else if (address < 0x4000) {  // ROM Page 0
 151  0
             return frames[0][address] & 0xFF;
 152  0
         } else if (address < 0x8000) {  // ROM Page 1
 153  0
             return frames[1][address - 0x4000] & 0xFF;
 154  0
         } else if (address < 0xC000) {  // ROM Page 2
 155  0
             return frames[2][address - 0x8000] & 0xFF;
 156  0
         } else if (address < 0xE000) {  // RAM
 157  0
             return frames[3][address - 0xA000] & 0xFF;
 158  0
         } else if (address < 0x10000) { // Mirror RAM
 159  0
             return frames[3][address - 0xC000] & 0xFF;
 160  
         } else {
 161  0
             return 0;
 162  
         }
 163  
     }
 164  
     
 165  
     /**
 166  
      * Read a signed value from a memory location.
 167  
      *
 168  
      * @param address Memory address
 169  
      *
 170  
      * @return Value from memory location
 171  
      */
 172  
     public int readSigned(int address) {
 173  0
         if (address < 0x400) {          // First 1K
 174  0
             return pages[0][address];
 175  0
         } else if (address < 0x4000) {  // ROM Page 0
 176  0
             return frames[0][address];
 177  0
         } else if (address < 0x8000) {  // ROM Page 1
 178  0
             return frames[1][address - 0x4000];
 179  0
         } else if (address < 0xC000) {  // ROM Page 2
 180  0
             return frames[2][address - 0x8000];
 181  0
         } else if (address < 0xE000) {  // RAM
 182  0
             return frames[3][address - 0xA000];
 183  0
         } else if (address < 0x10000) { // Mirror RAM
 184  0
             return frames[3][address - 0xC000];
 185  
         } else {
 186  0
             return 0;
 187  
         }
 188  
     }
 189  
     
 190  
     /**
 191  
      * Read a word (two bytes) from a memory location.
 192  
      *
 193  
      * @param address memory address.
 194  
      *
 195  
      * @return value from memory location
 196  
      */
 197  
     public int readWord(int address) {
 198  0
         return ((read(address + 1) << 8) + read(address));
 199  
     }
 200  
     
 201  
     /**
 202  
      * Unsign an integer.
 203  
      *
 204  
      * @param b value to unsign.
 205  
      *
 206  
      * @return unsigned value.
 207  
      */
 208  
     private int unsigned(int b) {
 209  0
         return b & 0xFF;
 210  
     }
 211  
     
 212  
     /**
 213  
      * Write to a paging register.
 214  
      *
 215  
      * @param address memory location.
 216  
      * @param value value to write.
 217  
      */
 218  
     private void page(int address, int value) {
 219  0
         switch(address) {
 220  
             // RAM/ROM select register
 221  
             case 0xFFFC:
 222  
                 // Map RAM
 223  0
                 if ((value & 0x08) == 0x08) {
 224  0
                     frameTwoRom = false;
 225  0
                     if ((value & 0x04) == 0x04) {
 226  0
                         frames[2] = cartRamPages[1]; // Use second page
 227  0
                         cartRamPage = 1;
 228  
                     } else {
 229  0
                         frames[2] = cartRamPages[0]; // Use first page
 230  0
                         cartRamPage = 0;
 231  
                     }
 232  
                 } else { // Map ROM
 233  0
                     frameTwoRom = true;
 234  
                 }
 235  0
                 break;
 236  
                 // Page 0 ROM Bank
 237  
             case 0xFFFD:
 238  0
                 frames[0] = pages[value % numberOfPages];
 239  0
                 break;
 240  
                 // Page 1 ROM Bank
 241  
             case 0xFFFE:
 242  0
                 frames[1] = pages[value % numberOfPages];
 243  0
                 break;
 244  
                 // Page 2 ROM Bank
 245  
             case 0xFFFF:
 246  0
                 if (frameTwoRom) {
 247  0
                     frames[2] = pages[value % numberOfPages]; // Map ROM
 248  
                 } else {
 249  0
                     frames[2] = pages[cartRamPage]; // Map RAM
 250  
                 }
 251  0
                 break;
 252  
             default:
 253  0
                 Logger.getLogger(this.getClass()).warn("Possible illegal frame: 0x"
 254  
                         + Integer.toHexString(address).toUpperCase());
 255  
         }
 256  
         
 257  0
         frames[3][address - 0xC000] = (byte) value;
 258  0
     }
 259  
     
 260  
     /**
 261  
      * Read a cartridge into memory from the specified URL. The file it points to must be
 262  
      * a ROM for the SMS or GG, or a zip file containing one.
 263  
      *
 264  
      * @param url URL of cartridge image.
 265  
      * @throws IOException if an error occurs while reading the file.
 266  
      * @throws IllegalArgumentException if the file is neither a ROM, nor a Zip file containing one.
 267  
      */
 268  
     public void readCart(URL url) throws IOException, IllegalArgumentException {
 269  0
         Logger.getLogger(this.getClass()).trace("Reading cart from: " + url);
 270  
         
 271  0
         URLConnection connection = url.openConnection(); // A connection to the resource pointed to.
 272  0
         int size = connection.getContentLength(); // The length of the file's content.
 273  0
         InputStream is = connection.getInputStream(); // The stream to read the data from.
 274  
         
 275  
         // Find out what sort of file we're dealing with
 276  0
         String extension = getExtension(url.toString());
 277  0
         if (extension.equals(Setup.System.SMS.getRomFileExtension())) {
 278  0
             setup.setSystem(Setup.System.SMS);
 279  0
         } else if (extension.equals(Setup.System.GG.getRomFileExtension())) {
 280  0
             setup.setSystem(Setup.System.GG);
 281  0
         } else if (extension.equals(Setup.ZIP_FILE_EXTENSION)) {
 282  0
             is = new ZipInputStream(is);
 283  0
             ZipEntry entry = getRomEntry((ZipInputStream) is);
 284  0
             if (entry == null) {
 285  0
                 throw new IllegalArgumentException("The Zip file does not contain a GG or "
 286  
                         + "SMS ROM.");
 287  
             }
 288  0
             size = (int) entry.getSize();
 289  0
             extension = getExtension(entry.getName());
 290  0
             if (extension.equals(Setup.System.SMS.getRomFileExtension())) {
 291  0
                 setup.setSystem(Setup.System.SMS);
 292  0
             } else if (extension.equals(Setup.System.GG.getRomFileExtension())) {
 293  0
                 setup.setSystem(Setup.System.GG);
 294  
             } else {
 295  0
                 Logger.getLogger(this.getClass()).fatal("Unsupported ROM file: "
 296  
                         + extension);
 297  0
                 throw new IllegalArgumentException("Unsupported ROM file: "
 298  
                         + extension);
 299  
             }
 300  0
         } else {
 301  0
             throw new IllegalArgumentException("The URL does not point to a Zip file, "
 302  
                     + "GG or SMS ROM");
 303  
         }
 304  
         
 305  
         try {
 306  0
             readCart(is, size);
 307  0
         } catch (IllegalArgumentException iae) {
 308  0
             throw new IOException(iae);
 309  
         } finally {
 310  0
             try {
 311  0
                 is.close();
 312  0
                 Logger.getLogger(this.getClass()).trace("Closed stream for: " + url);
 313  0
             } catch (IOException ioe) {
 314  0
                 Logger.getLogger(this.getClass()).error("Unable to close input stream.", ioe);
 315  0
             }
 316  0
         }
 317  0
     }
 318  
     
 319  
     /**
 320  
      * Returns the first entry in the <code>ZipInputStream</code> that contains an SMS or GG ROM or
 321  
      * <code>null</code> if none is found. If the stream does not include a ROM or an
 322  
      * <code>IOException</code> occurs while looking for a valid entry, the stream will be closed.
 323  
      * Otherwise it is left open, pointing at the ROM's contents.
 324  
      *
 325  
      * @param zis an instance of <code>ZipInputStream</code>, may be <code>null</code>.
 326  
      * @return an SMS or GG ROM in the form of a <code>ZipEntry</code> or <code>null</code> if none
 327  
      *    were found.
 328  
      * @throws IOException if an error occurs while reading from the stream.
 329  
      */
 330  
     private static ZipEntry getRomEntry(ZipInputStream zis) throws IOException {
 331  0
         if (zis == null) {
 332  0
             return null;
 333  
         }
 334  0
         ZipEntry entry = null;
 335  
         try {
 336  0
             entry = zis.getNextEntry();
 337  0
             while ((entry != null)) {
 338  0
                 String extension = getExtension(entry.getName());
 339  0
                 if (extension.equals(Setup.System.SMS.getRomFileExtension())
 340  
                 || extension.equals(Setup.System.GG.getRomFileExtension())) {
 341  0
                     break;
 342  
                 }
 343  0
                 entry = zis.getNextEntry();
 344  0
             }
 345  0
         } catch (IOException ioe) { // Try to close the stream
 346  
             try {
 347  0
                 zis.close();
 348  0
                 Logger.getLogger(Memory.class).trace("Closed stream for Zip file.");
 349  0
             } catch (IOException ioe2) {
 350  0
                 Logger.getLogger(Memory.class).error("Unable to close ZipInputStream "
 351  
                         + "after exception.", ioe2);
 352  0
             }
 353  0
             throw ioe;
 354  0
         }
 355  0
         if (entry == null && zis != null) { // Try to close the stream
 356  
             try {
 357  0
                 zis.close();
 358  0
                 Logger.getLogger(Memory.class).trace("Closed stream for Zip file");
 359  0
             } catch (IOException ioe) {
 360  0
                 Logger.getLogger(Memory.class).error("Unable to close ZipInputStream "
 361  
                         + "after exception.", ioe);
 362  0
             }
 363  
         }
 364  0
         return entry;
 365  
     }
 366  
     
 367  
     /**
 368  
      * Read a cartridge's data from an <code>InputStream</code>. The method <b>does not
 369  
      * close</b> when it finishes reading.
 370  
      *
 371  
      * @param is the stream to read the ROM's data from.
 372  
      * @param size the size of the stream's content.
 373  
      * @throws IllegalArgumentException if <code>is</code> is <code>null</code> or
 374  
      *     <code>size</code>.
 375  
      * @throws IOException if an error occurs while reading the file.
 376  
      */
 377  
     private void readCart(InputStream is, int size) throws IOException, IllegalArgumentException {
 378  0
         if (is == null) {
 379  0
             throw new IllegalArgumentException("The input stream cannot be null.");
 380  
         }
 381  0
         if (size < 0) {
 382  0
             throw new IllegalArgumentException("Ivalid file size: " + size);
 383  
         }
 384  0
         BufferedInputStream bis = new BufferedInputStream(is);
 385  
         
 386  
         // Strip 512 Byte File Headers
 387  0
         if ((size % 1024) != 0) {
 388  0
             bis.skip(512); // skip 512 bytes
 389  0
             size -= 512;
 390  
         }
 391  
         
 392  
         // Calculate number of pages from file size and create array appropriately
 393  0
         numberOfPages = (int) (size / 0x4000);
 394  0
         pages = new byte[numberOfPages][0x4000];
 395  
         
 396  
         // Read file into pages array
 397  0
         for (int x = 0; x < size; x += 0x4000) {
 398  
             // second value is offset, third is length
 399  0
             bis.read(pages[x / 0x4000], 0x0000, 0x4000);
 400  
         }
 401  
         
 402  
         // Default Mapping (Needed or Shinobi doesn't work)
 403  0
         frames[0] = pages[0];
 404  0
         frames[1] = pages[1];
 405  0
     }
 406  
     
 407  
     /**
 408  
      * Strip an extension from a file.
 409  
      *
 410  
      * @param filename filename to strip.
 411  
      * @return extension of file in lowercase characters.
 412  
      */
 413  
     private static String getExtension(String filename) {
 414  0
         int i = filename.lastIndexOf('.');
 415  0
         if (i > 0 && i < filename.length() - 1) {
 416  0
             return filename.substring(i + 1).toLowerCase();
 417  
         }
 418  0
         return "";
 419  
     }
 420  
     
 421  
 }