/* JPC: An x86 PC Hardware Emulator for a pure Java Virtual Machine Release Version 2.4 A project from the Physics Dept, The University of Oxford Copyright (C) 2007-2010 The University of Oxford This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Details (including contact information) can be found at: jpc.sourceforge.net or the developer website sourceforge.net/projects/jpc/ Conceived and Developed by: Rhys Newman, Ian Preston, Chris Dennis End of licence header */ package org.jpc.emulator.memory; import java.io.*; import java.util.HashSet; import java.util.Set; import org.jpc.emulator.*; import org.jpc.emulator.execution.codeblock.*; import org.jpc.emulator.processor.Processor; import org.jpc.j2se.Option; /** * Class that emulates the 32bit physical address space of the machine. Mappings * between address and blocks are performed either using a single stage lookup on * the RAM area for speed, or a two stage lookup for the rest of the address space * for space-efficiency. * <p> * All addresses are initially mapped to an inner class instance that returns * <code>-1</code> on all reads (as all data lines float high). * @author Chris Dennis */ public final class PhysicalAddressSpace extends AddressSpace implements HardwareComponent { private static final int GATEA20_MASK = 0xffefffff; private static final int QUICK_INDEX_SIZE = PC.SYS_RAM_SIZE >>> INDEX_SHIFT; private static final int TOP_INDEX_BITS = (32 - INDEX_SHIFT) / 2; private static final int BOTTOM_INDEX_BITS = 32 - INDEX_SHIFT - TOP_INDEX_BITS; private static final int TOP_INDEX_SHIFT = 32 - TOP_INDEX_BITS; private static final int TOP_INDEX_SIZE = 1 << TOP_INDEX_BITS; private static final int TOP_INDEX_MASK = TOP_INDEX_SIZE - 1; private static final int BOTTOM_INDEX_SHIFT = 32 - TOP_INDEX_BITS - BOTTOM_INDEX_BITS; private static final int BOTTOM_INDEX_SIZE = 1 << BOTTOM_INDEX_BITS; private static final int BOTTOM_INDEX_MASK = BOTTOM_INDEX_SIZE - 1; private static final Memory UNCONNECTED = new UnconnectedMemoryBlock(); private boolean gateA20MaskState; private Memory[] quickNonA20MaskedIndex, quickA20MaskedIndex, quickIndex; private Memory[][] nonA20MaskedIndex, a20MaskedIndex, index; private LinearAddressSpace linearAddr; private CodeBlockManager manager = null; public static final boolean track_page_writes = Option.track_writes.value(); private Set<Integer> dirtyPages = new HashSet(); /** * Constructs an address space which is initially empty. All addresses are * mapped to an instance of the inner class <code>UnconnectedMemoryBlock</code> * whose data lines float high. */ public PhysicalAddressSpace(CodeBlockManager manager) { this.manager = manager; quickNonA20MaskedIndex = new Memory[QUICK_INDEX_SIZE]; clearArray(quickNonA20MaskedIndex, UNCONNECTED); quickA20MaskedIndex = new Memory[QUICK_INDEX_SIZE]; clearArray(quickA20MaskedIndex, UNCONNECTED); nonA20MaskedIndex = new Memory[TOP_INDEX_SIZE][]; a20MaskedIndex = new Memory[TOP_INDEX_SIZE][]; initialiseMemory(); setGateA20State(false); } public void saveState(DataOutput output) throws IOException { output.writeBoolean(gateA20MaskState); dumpMemory(output, quickNonA20MaskedIndex, nonA20MaskedIndex); } private static void dumpMemory(DataOutput output, Memory[] quick, Memory[][] full) throws IOException { byte[] temp = new byte[0]; output.writeInt(quick.length); for (Memory block : quick) { int blockLength = (int) block.getSize(); if (block.isAllocated()) { try { if (block instanceof MapWrapper) { output.writeInt(0); } else { if (temp.length < blockLength) { temp = new byte[blockLength]; } block.copyContentsIntoArray(0, temp, 0, blockLength); output.writeInt(blockLength); output.write(temp); } } catch (IllegalStateException e) { output.writeInt(0); } } else { output.writeInt(0); } } output.writeInt(full.length); for (Memory[] chunk : full) { if (chunk == null) { output.writeInt(0); continue; } output.writeInt(chunk.length); for (Memory block : chunk) { if ((block == null) || (!block.isAllocated())) { output.writeInt(0); continue; } int blockLength = (int) block.getSize(); if (block.isAllocated()) { try { if (block instanceof MapWrapper) { output.writeInt(0); } else { if (temp.length < blockLength) { temp = new byte[blockLength]; } block.copyContentsIntoArray(0, temp, 0, blockLength); output.writeInt(blockLength); output.write(temp); } } catch (IllegalStateException e) { output.writeInt(0); } } else { output.writeInt(0); } } } } private static void loadMemory(DataInput input, Memory[] quick, Memory[][] full, CodeBlockManager manager) throws IOException { byte[] temp = new byte[0]; int quickLength = input.readInt(); for (int i = 0; i < quickLength; i++) { Memory block = quick[i]; int blockLength = input.readInt(); if (blockLength > 0) { if (blockLength > temp.length) { temp = new byte[blockLength]; } input.readFully(temp, 0, blockLength); block.loadInitialContents(0, temp, 0, blockLength); } } int fullLength = input.readInt(); for (int i = 0; i < fullLength; i++) { int chunkLength = input.readInt(); if (chunkLength == 0) continue; full[i] = new Memory[chunkLength]; Memory[] chunk = full[i]; for (int j = 0; j < chunkLength; j++) { int blockLength = input.readInt(); if (blockLength == 0) continue; chunk[j] = new LazyCodeBlockMemory(blockLength, manager); Memory block = chunk[j]; if (blockLength > 0) { if (blockLength > temp.length) { temp = new byte[blockLength]; } input.readFully(temp, 0, blockLength); block.loadInitialContents(0, temp, 0, blockLength); } } } } private void initialiseMemory() { for (int i = 0; i < PC.SYS_RAM_SIZE; i += AddressSpace.BLOCK_SIZE) { mapMemory(i, new LazyCodeBlockMemory(AddressSpace.BLOCK_SIZE, manager)); } // memory hole, the last 64 K of this is replaced by BIOS shadow ram if the BIOS ROM is 128 K or greater for (int i = 0xD0000; i < 0xF0000; i += AddressSpace.BLOCK_SIZE) { mapMemory(i, new PhysicalAddressSpace.UnconnectedMemoryBlock()); } } public void loadState(DataInput input, CodeBlockManager manager) throws IOException { clearArray(quickA20MaskedIndex, UNCONNECTED); clearArray(quickNonA20MaskedIndex, UNCONNECTED); linearAddr = null; this.manager = manager; initialiseMemory(); setGateA20State(input.readBoolean()); loadMemory(input, quickNonA20MaskedIndex, nonA20MaskedIndex, manager); for (int a = 0; a < TOP_INDEX_SIZE; a++) { if (nonA20MaskedIndex[a] == null) continue; for (int b = 0; b < BOTTOM_INDEX_SIZE; b++) try { a20MaskedIndex[a][b] = nonA20MaskedIndex[a][b]; } catch (NullPointerException n) { a20MaskedIndex[a] = new Memory[BOTTOM_INDEX_SIZE]; a20MaskedIndex[a][b] = nonA20MaskedIndex[a][b]; } } //fill in a20 masked full array for (int a = 0; a < TOP_INDEX_SIZE; a++) { if (nonA20MaskedIndex[a] == null) continue; for (int b = 0; b < BOTTOM_INDEX_SIZE; b++) { int i = (a << BOTTOM_INDEX_BITS) | b; if ((i & (GATEA20_MASK >>> INDEX_SHIFT)) == i) { int modi = i | (~GATEA20_MASK >>> INDEX_SHIFT); int moda = modi >>> BOTTOM_INDEX_BITS; int modb = modi & BOTTOM_INDEX_BITS; try { a20MaskedIndex[moda][modb] = nonA20MaskedIndex[a][b]; } catch (NullPointerException n) { a20MaskedIndex[moda] = new Memory[BOTTOM_INDEX_SIZE]; a20MaskedIndex[moda][modb] = nonA20MaskedIndex[a][b]; } } } } //fill in a20 masked quick array System.arraycopy(quickNonA20MaskedIndex, 0, quickA20MaskedIndex, 0, quickA20MaskedIndex.length); for (int i = 0; i < QUICK_INDEX_SIZE; i++) { if ((i & (GATEA20_MASK >>> INDEX_SHIFT)) == i) { quickA20MaskedIndex[i] = quickNonA20MaskedIndex[i]; int modi = i | (~GATEA20_MASK >>> INDEX_SHIFT); quickA20MaskedIndex[modi] = quickNonA20MaskedIndex[i]; } } } public void loadState(DataInput in) {} public void setEpromWritable(int address, boolean w) { Memory m = getMemoryBlockAt(address); if (m instanceof EPROMMemory) { ((EPROMMemory) m).setWritable(w); } else System.out.printf("Tried to set non eprom writable at %x\n", address); } public void setEpromReadable(int address, boolean r) { Memory m = getMemoryBlockAt(address); if (m instanceof EPROMMemory) { ((EPROMMemory) m).setReadable(r); } else System.out.printf("Tried to set non eprom readable at %x\n", address); } public void setBIOSWritable(boolean w) { // set Eprom at FFFE0000 writable or not for (int page = 0xFFFE0000; page < -1; page += 0x1000) ((EPROMMemory) getMemoryBlockAt(page)).setWritable(w); } public CodeBlockManager getCodeBlockManager() { return manager; } /** * Enables or disables the 20th address line. * <p> * If set to <code>true</code> then the 20th address line is enabled and memory * access works conventionally. If set to <code>false</code> then the line * is held low, and therefore a memory wrapping effect emulating the behaviour * of and original 8086 is acheived. * @param value status of the A20 line. */ public void setGateA20State(boolean value) { gateA20MaskState = value; if (value) { quickIndex = quickNonA20MaskedIndex; index = nonA20MaskedIndex; } else { quickIndex = quickA20MaskedIndex; index = a20MaskedIndex; } if ((linearAddr != null) && linearAddr.isPagingEnabled()) { linearAddr.flush(); } } /** * Returns the status of the 20th address line.<p> * <p> * A <code>true</code> return indicates the 20th address line is enabled. A * <code>false</code> return indicates that the 20th address line is held low * to emulate an 8086 memory system. * @return status of the A20 line. */ public boolean getGateA20State() { return gateA20MaskState; } private void logWrite(int address) { if (track_page_writes) dirtyPages.add(address >>> 12); } public void getDirtyPages(Set<Integer> res) { res.addAll(dirtyPages); dirtyPages.clear(); } protected Memory getReadMemoryBlockAt(int offset) { return getMemoryBlockAt(offset); } public byte getByte(int offset) { return getReadMemoryBlockAt(offset).getByte(offset & BLOCK_MASK); } public void setByte(int offset, byte data) { getWriteMemoryBlockAt(offset).setByte(offset & BLOCK_MASK, data); } public short getWord(int offset) { try { return getReadMemoryBlockAt(offset).getWord(offset & BLOCK_MASK); } catch (ArrayIndexOutOfBoundsException e) { return super.getWord(offset); } } public void setWord(int offset, short data) { try { getWriteMemoryBlockAt(offset).setWord(offset & BLOCK_MASK, data); } catch (ArrayIndexOutOfBoundsException e) { super.setWord(offset, data); } } public int getDoubleWord(int offset) { try { return getReadMemoryBlockAt(offset).getDoubleWord(offset & BLOCK_MASK); } catch (ArrayIndexOutOfBoundsException e) { return super.getDoubleWord(offset); } } public void setDoubleWord(int offset, int data) { try { getWriteMemoryBlockAt(offset).setDoubleWord(offset & BLOCK_MASK, data); } catch (ArrayIndexOutOfBoundsException e) { super.setDoubleWord(offset, data); } } protected Memory getWriteMemoryBlockAt(int offset) { logWrite(offset); return getMemoryBlockAt(offset); } public int executeReal(Processor cpu, int offset) { try { // if (PC.HISTORY) // { // try { // Memory m = getReadMemoryBlockAt(offset); // PC.logBlock(((LazyCodeBlockMemory)m).getRealBlock(offset & AddressSpace.BLOCK_MASK)); // } catch (Exception e) {} // } return getReadMemoryBlockAt(offset).executeReal(cpu, offset & AddressSpace.BLOCK_MASK); } catch (SpanningDecodeException e) { SpanningCodeBlock block = e.getBlock(); // if (PC.HISTORY) // PC.logBlock(block.decode(cpu)); int length = block.decode(cpu).getX86Length(); // add block to subsequent page to allow invalidation upon a write getReadMemoryBlockAt(offset+0x1000).addSpanningBlock(block, length-(0x1000-(offset & AddressSpace.BLOCK_MASK))); return getReadMemoryBlockAt(offset).executeReal(cpu, offset & AddressSpace.BLOCK_MASK); } } public int executeProtected(Processor cpu, int offset) { throw new IllegalStateException("Cannot execute protected mode block in physical memory"); } public int executeVirtual8086(Processor cpu, int offset) { throw new IllegalStateException("Cannot execute protected mode block in physical memory"); } protected void replaceBlocks(Memory oldBlock, Memory newBlock) { for (int i = 0; i < quickA20MaskedIndex.length; i++) { if (quickA20MaskedIndex[i] == oldBlock) { quickA20MaskedIndex[i] = newBlock; } } for (int i = 0; i < quickNonA20MaskedIndex.length; i++) { if (quickNonA20MaskedIndex[i] == oldBlock) { quickNonA20MaskedIndex[i] = newBlock; } } for (Memory[] subArray : a20MaskedIndex) { if (subArray == null) { continue; } for (int j = 0; j < subArray.length; j++) { if (subArray[j] == oldBlock) { subArray[j] = newBlock; } } } for (Memory[] subArray : nonA20MaskedIndex) { if (subArray == null) { continue; } for (int j = 0; j < subArray.length; j++) { if (subArray[j] == oldBlock) { subArray[j] = newBlock; } } } } private static class MapWrapper implements Memory { private Memory memory; private int baseAddress; MapWrapper(Memory mem, int base) { baseAddress = base; memory = mem; } public void lock(int addr) {} public void unlock(int addr) {} public void addSpanningBlock(SpanningCodeBlock b, int lengthRemaining) {} public long getSize() { return BLOCK_SIZE; } public boolean isAllocated() { return memory.isAllocated(); } public void clear() { memory.clear(baseAddress, (int) getSize()); } public void clear(int start, int length) { if (start + length > getSize()) { throw new ArrayIndexOutOfBoundsException("Attempt to clear outside of memory bounds"); } start = baseAddress | start; memory.clear(start, length); } public void copyContentsIntoArray(int offset, byte[] buffer, int off, int len) { offset = baseAddress | offset; memory.copyContentsIntoArray(offset, buffer, off, len); } public void copyArrayIntoContents(int offset, byte[] buffer, int off, int len) { offset = baseAddress | offset; memory.copyArrayIntoContents(offset, buffer, off, len); } public byte getByte(int offset) { offset = baseAddress | offset; return memory.getByte(offset); } public short getWord(int offset) { offset = baseAddress | offset; return memory.getWord(offset); } public int getDoubleWord(int offset) { offset = baseAddress | offset; return memory.getDoubleWord(offset); } public long getQuadWord(int offset) { offset = baseAddress | offset; return memory.getQuadWord(offset); } public long getLowerDoubleQuadWord(int offset) { offset = baseAddress | offset; return memory.getQuadWord(offset); } public long getUpperDoubleQuadWord(int offset) { offset += 8; offset = baseAddress | offset; return memory.getQuadWord(offset); } public void setByte(int offset, byte data) { offset = baseAddress | offset; memory.setByte(offset, data); } public void setWord(int offset, short data) { offset = baseAddress | offset; memory.setWord(offset, data); } public void setDoubleWord(int offset, int data) { offset = baseAddress | offset; memory.setDoubleWord(offset, data); } public void setQuadWord(int offset, long data) { offset = baseAddress | offset; memory.setQuadWord(offset, data); } public void setLowerDoubleQuadWord(int offset, long data) { offset = baseAddress | offset; memory.setQuadWord(offset, data); } public void setUpperDoubleQuadWord(int offset, long data) { offset += 8; offset = baseAddress | offset; memory.setQuadWord(offset, data); } public int executeReal(Processor cpu, int offset) { offset = baseAddress | offset; return memory.executeReal(cpu, offset); } public int executeProtected(Processor cpu, int offset) { throw new IllegalStateException("Cannot execute protected mode block in physical memory"); } public int executeVirtual8086(Processor cpu, int offset) { throw new IllegalStateException("Cannot execute protected mode block in physical memory"); } public String toString() { return "Mapped Memory"; } public void loadInitialContents(int address, byte[] buf, int off, int len) { throw new UnsupportedOperationException("Not supported yet."); } } public void clear() { for (Memory block : quickNonA20MaskedIndex) { block.clear(); } for (Memory[] subArray : nonA20MaskedIndex) { if (subArray == null) { continue; } for (Memory block : subArray) { try { block.clear(); } catch (NullPointerException e) { } } } } /** * Clears all mapping in the given address range. * <p> * The corresponding blocks are pointed to an unconnected memory block whose * data lines all float high. If the supplied range is not of <code>BLOCK_SIZE</code> * granularity then an <code>IllegalStateException</code> is thrown. * @param start inclusive lower bound * @param length number of addresses to clear * @throws java.lang.IllegalStateException if range is not of <code>BLOCK_SIZE</code> granularity */ public void unmap(int start, int length) { if ((start % BLOCK_SIZE) != 0) { throw new IllegalStateException("Cannot deallocate memory starting at " + Integer.toHexString(start) + "; this is not block aligned at " + BLOCK_SIZE + " boundaries"); } if ((length % BLOCK_SIZE) != 0) { throw new IllegalStateException("Cannot deallocate memory in partial blocks. " + length + " is not a multiple of " + BLOCK_SIZE); } for (int i = start; i < start + length; i += BLOCK_SIZE) { setMemoryBlockAt(i, UNCONNECTED); } } /** * Maps the given address range to the <code>underlying</code> object. * <p> * This will throw <code>IllegalStateException</code> if either the region is * not of <code>BLOCK_SIZE</code> granularity, or <code>underlying</code> is * not as long as the specified region. * @param underlying memory block to be mapped. * @param start inclusive start address. * @param length size of mapped region. * @throws java.lang.IllegalStateException if there is an error in the mapping. */ public void mapMemoryRegion(Memory underlying, int start, int length) { if (underlying.getSize() < length) { throw new IllegalStateException("Underlying memory (length=" + underlying.getSize() + ") is too short for mapping into region " + length + " bytes long"); } if ((start % BLOCK_SIZE) != 0) { throw new IllegalStateException("Cannot map memory starting at " + Integer.toHexString(start) + "; this is not aligned to " + BLOCK_SIZE + " blocks"); } if ((length % BLOCK_SIZE) != 0) { throw new IllegalStateException("Cannot map memory in partial blocks: " + length + " is not a multiple of " + BLOCK_SIZE); } unmap(start, length); if (Option.log_memory_maps.isSet()) if (((start & 0xffffffffL) > PC.SYS_RAM_SIZE) || !(underlying instanceof LazyCodeBlockMemory)) System.out.printf("Mapping %s into memory from %x to %x\n", underlying, start, start+length); long s = 0xFFFFFFFFl & start; for (long i = s; i < s + length; i += BLOCK_SIZE) { Memory w = new MapWrapper(underlying, (int) (i - s)); setMemoryBlockAt((int) i, w); } } /** * Maps the given block into the given address. * <p> * The supplied block must be <code>BLOCK_SIZE</code> long, and the start * address must be <code>BLOCK_SIZE</code> granularity otherwise an * <code>IllegalStateException</code> is thrown. * @param start address for beginning of <code>block</code>. * @param block object to be mapped. * @throws java.lang.IllegalStateException if there is an error in the mapping. */ public void mapMemory(int start, Memory block) { if ((start % BLOCK_SIZE) != 0) { throw new IllegalStateException("Cannot allocate memory starting at " + Integer.toHexString(start) + "; this is not aligned to " + BLOCK_SIZE + " bytes"); } if (block.getSize() != BLOCK_SIZE) { throw new IllegalStateException("Can only allocate memory in blocks of " + BLOCK_SIZE); } unmap(start, BLOCK_SIZE); if (Option.log_memory_maps.isSet()) if (block instanceof EPROMMemory) System.out.printf("Mapping %s into memory from %x to %x\n", block, start, start+BLOCK_SIZE); long s = 0xFFFFFFFFl & start; setMemoryBlockAt((int) s, block); } public static final class UnconnectedMemoryBlock implements Memory { public boolean isAllocated() { return false; } public void lock(int addr) {} public void unlock(int addr) {} public void addSpanningBlock(SpanningCodeBlock b, int lengthRemaining) {} public void clear() {} public void clear(int start, int length) {} public void copyContentsIntoArray(int address, byte[] buffer, int off, int len) {} public void copyArrayIntoContents(int address, byte[] buffer, int off, int len) { throw new IllegalStateException("Cannot load array into unconnected memory block"); } public long getSize() { return BLOCK_SIZE; } public byte getByte(int offset) { return (byte) -1; } public short getWord(int offset) { return (short) -1; } public int getDoubleWord(int offset) { return -1; } public long getQuadWord(int offset) { return -1l; } public long getLowerDoubleQuadWord(int offset) { return -1l; } public long getUpperDoubleQuadWord(int offset) { return -1l; } public void setByte(int offset, byte data) {} public void setWord(int offset, short data) {} public void setDoubleWord(int offset, int data) {} public void setQuadWord(int offset, long data) {} public void setLowerDoubleQuadWord(int offset, long data) {} public void setUpperDoubleQuadWord(int offset, long data) {} public int executeReal(Processor cpu, int offset) { throw new IllegalStateException("Trying to execute in Unconnected Block @ 0x" + Integer.toHexString(offset)); } public int executeProtected(Processor cpu, int offset) { throw new IllegalStateException("Trying to execute in Unconnected Block @ 0x" + Integer.toHexString(offset)); } public int executeVirtual8086(Processor cpu, int offset) { throw new IllegalStateException("Trying to execute in Unconnected Block @ 0x" + Integer.toHexString(offset)); } public String toString() { return "Unconnected Memory"; } public void loadInitialContents(int address, byte[] buf, int off, int len) { } } public void reset() { clear(); setGateA20State(false); linearAddr = null; } public boolean updated() { return true; } public void updateComponent(HardwareComponent component) { } public boolean initialised() { return (linearAddr != null); } public void acceptComponent(HardwareComponent component) { if (component instanceof LinearAddressSpace) { linearAddr = (LinearAddressSpace) component; } } public String toString() { return "Physical Pointer Bus"; } public Integer getPage(Integer addr, byte[] page) { Memory block = getMemoryBlockAt(addr); if (block instanceof MapWrapper) return 0; block.copyContentsIntoArray(0, page, 0, 4096); return 4096; } public Integer setPage(Integer addr, byte[] page) { Memory block = getMemoryBlockAt(addr); if (block instanceof MapWrapper) return 0; try { block.copyArrayIntoContents(0, page, 0, 4096); return 4096; } catch (IllegalStateException e) { System.out.printf("Tried to write to unconnected memory at %x.\n", addr); return 0; } } private Memory getMemoryBlockAt(int i) { try { return quickIndex[i >>> INDEX_SHIFT]; } catch (ArrayIndexOutOfBoundsException e) { try { return index[i >>> TOP_INDEX_SHIFT][(i >>> BOTTOM_INDEX_SHIFT) & BOTTOM_INDEX_MASK]; } catch (NullPointerException n) { return UNCONNECTED; } } } private void setMemoryBlockAt(int i, Memory b) { try { int idx = i >>> INDEX_SHIFT; quickNonA20MaskedIndex[idx] = b; if ((idx & (GATEA20_MASK >>> INDEX_SHIFT)) == idx) { quickA20MaskedIndex[idx] = b; quickA20MaskedIndex[idx | ((~GATEA20_MASK) >>> INDEX_SHIFT)] = b; } } catch (ArrayIndexOutOfBoundsException e) { try { nonA20MaskedIndex[i >>> TOP_INDEX_SHIFT][(i >>> BOTTOM_INDEX_SHIFT) & BOTTOM_INDEX_MASK] = b; } catch (NullPointerException n) { nonA20MaskedIndex[i >>> TOP_INDEX_SHIFT] = new Memory[BOTTOM_INDEX_SIZE]; nonA20MaskedIndex[i >>> TOP_INDEX_SHIFT][(i >>> BOTTOM_INDEX_SHIFT) & BOTTOM_INDEX_MASK] = b; } i &= GATEA20_MASK; if ((i & GATEA20_MASK) == i) { try { a20MaskedIndex[i >>> TOP_INDEX_SHIFT][(i >>> BOTTOM_INDEX_SHIFT) & BOTTOM_INDEX_MASK] = b; } catch (NullPointerException n) { a20MaskedIndex[i >>> TOP_INDEX_SHIFT] = new Memory[BOTTOM_INDEX_SIZE]; a20MaskedIndex[i >>> TOP_INDEX_SHIFT][(i >>> BOTTOM_INDEX_SHIFT) & BOTTOM_INDEX_MASK] = b; } int modi = i | ~GATEA20_MASK; try { a20MaskedIndex[modi >>> TOP_INDEX_SHIFT][(modi >>> BOTTOM_INDEX_SHIFT) & BOTTOM_INDEX_MASK] = b; } catch (NullPointerException n) { a20MaskedIndex[modi >>> TOP_INDEX_SHIFT] = new Memory[BOTTOM_INDEX_SIZE]; a20MaskedIndex[modi >>> TOP_INDEX_SHIFT][(modi >>> BOTTOM_INDEX_SHIFT) & BOTTOM_INDEX_MASK] = b; } } } } public void loadInitialContents(int address, byte[] buf, int off, int len) { throw new UnsupportedOperationException("Not supported yet."); } }