/** * This file is a part of JaC64 - a Java C64 Emulator * Main Developer: Joakim Eriksson (Dreamfabric.com) * Contact: joakime@sics.se * Web: http://www.dreamfabric.com/c64 * --------------------------------------------------- */ package com.dreamfabric.jac64; /** * @(#)Reader.java Created date: 99-7-06 * */ import java.io.FileInputStream; import java.io.InputStream; import java.io.DataInputStream; import java.net.URL; import java.util.*; import java.io.OutputStream; import java.io.FileOutputStream; import java.io.BufferedOutputStream; import java.io.IOException; /** * * * @author Joakim Eriksson (joakime@sics.se) * @version $Revision: 1.5 $, $Date: 2006/05/01 14:57:57 $ */ public class C64Reader { public final static int NONE = 0; public final static int TAPE = 1; public final static int DISK = 2; private int[] memory; private String label = ""; private ArrayList dirNames = new ArrayList(); private Hashtable dirEntries = new Hashtable(); private DiskListener listener; private int type = 0; private int diskSize = 0; // Size in sectors // This is typically a DISK but can also hold a tape! private byte sectors[][] = new byte[800][256]; // For tape - counts the number of bytes stored in the // sector array!! private int noBytes; public void setCPU(CPU cpu) { /* CPU argument is ignored for now */ this.memory = cpu.getMemory(); } public void setDiskListener(DiskListener l) { listener = l; } public int getLoadedType() { return type; } public ArrayList getDirNames() { return dirNames; } public DirEntry getDirEntry(String name) { return (DirEntry) dirEntries.get(name); } public boolean readDisk(InputStream stream) { int sector = 0; int sectrak = 0; int trak = 1; int numRead = 0; DataInputStream reader = new DataInputStream(stream); dirNames.clear(); dirEntries.clear(); type = DISK; try { while((numRead = reader.read(sectors[sector])) > 0) { if (numRead < 256) { reader.readFully(sectors[sector], numRead, 256 - numRead); } if (trak == 18) { readDir(sectors[sector], trak, sectrak); } sector++; sectrak++; if ((trak < 18 && sectrak == 21) || (trak >= 18 && trak <25 && sectrak == 19) || (trak >= 25 && trak <31 && sectrak == 18) || (trak >= 31 && trak <41 && sectrak == 17)) { sectrak = 0; trak++; } } System.out.println("Read " + sector + " sectors"); diskSize = sector; } catch (Exception e) { System.out.println("Error reading sectors"); System.out.println("Track: " + trak + " sec: " + sector); e.printStackTrace(); return false; } finally { try { reader.close(); } catch (Exception x) {} } if (listener != null) { listener.diskChanged(); } return true; } public byte[] getSector(int sector) { return sectors[sector]; } // Returns the number of sector for a specific track // Note that the track count starts with 1 (not zero) public static int getSectorCount(int track) { if (track > 30) return 17; if (track > 24) return 18; if (track > 17) return 19; return 21; } public byte[] getSector(int trak, int sectrak) { int sector = getSecTrack(trak); sector = sector + sectrak; // System.out.println("Trak: " + trak + " sector: " + sectrak + " -> " + // sector); return sectors[sector]; } private int getSecTrack(int trak) { int sector = 0; if (trak < 18) sector = (trak-1) * 21; else if ((trak >= 18) && (trak <25) ) sector = (trak-1)*19 + 17*2; else if ((trak >= 25) && (trak <31) ) sector = (trak-1)*18 + 17*3 + 7; else sector = (trak-1)*17 + 17*4 + 7*2 + 6; return sector; } public void writeDisk(OutputStream stream) throws IOException { BufferedOutputStream out = new BufferedOutputStream(stream); for (int i = 0, n = diskSize; i < n; i++) { out.write(sectors[i], 0, 256); } out.close(); } public void setSector(int track, int sectrack, byte[] newSector) { int sector = getSecTrack(track); sector = sector + sectrack; // Write the new sector! for (int i = 0, n = 255; i < n; i++) { sectors[sector][i] = newSector[i]; } } private boolean lastEntry; private int nextSector; private void readDir(byte[] data, int trak, int sec) { if (sec == 0) { label = ""; for (int i = 0; i < 16 ; i++) if (data[0x90 + i] != (byte) 0xa0) { label += (char) data[0x90 + i]; } System.out.println("Directory listing of '" + label + "'"); lastEntry = false; nextSector = trak; nextSector = 1; } else { if (!lastEntry) { if ((nextSector == sec) && data[0] == 0) { lastEntry = true; } else { nextSector = data[1]; } int start = 0; while((start < 0xff) && (data[start + 2] != 0) ) { int tp = data[start + 2] & 0xff; String name = ""; int size; for (int i = 0; i < 16 ; i++) if (data[start + 5 + i] != (byte) 0xa0) name = name + (char) data[start + 5 + i]; else name = name + " "; size = data[start + 0x1e] & 0xff + data[start + 0x1f] * 256; DirEntry entry = new DirEntry(name, data[start+3], data[start+4], size, tp); dirNames.add(entry); dirEntries.put(name, entry); start = start + 0x20; } if (lastEntry) System.out.println("No more files."); } } } /* private void dumpSector(byte[] data) { String tmp = ""; String t; for (int i = 0; i <256 ; i++) { if (data[i] > 32 && data[i] < 99) System.out.print((char)data[i]); else System.out.print("?"); t = Integer.toString(data[i] & 0xff, 16); if (t.length() < 2) t = "0"+t; tmp = tmp + t; if (i % 16 == 15) { System.out.println(" " + tmp); tmp = ""; } } } */ public String readFile(String str) { return readFile(str, -1); } public String readFile(String str, int adr) { return readFile(str, adr, null); } private void printDirListing(OutputStream out) { // 4 kbyts - should be set-up before! byte[] dir = new byte[4096]; int p = 0; int adr = 0x801; // Load address dir[p++] = (byte) (adr & 0xff); dir[p++] = (byte) (adr >> 8); adr += label.length() + 5; dir[p++] = (byte) (adr & 0xff); dir[p++] = (byte) (adr >> 8); dir[p++] = 0; dir[p++] = 0; dir[p++] = 0x12; dir[p++] = '"'; // Disk label! for (int i = 0, n = label.length(); i < n; i++) { dir[p++] = (byte) label.charAt(i); } dir[p++] = '"'; dir[p++] = 0; for (int i = 0, n = dirNames.size(); i < n; i++) { DirEntry dire = (DirEntry) dirNames.get(i); adr += 26; int fill = 1; if (dire.size < 10) fill = 3; else if (dire.size < 100) fill = 2; adr += fill; // Address dir[p++] = (byte) (adr & 0xff); dir[p++] = (byte) (adr >> 8); dir[p++] = (byte) (dire.size & 0xff); dir[p++] = (byte) (dire.size >> 8); for (int j = 0, m = fill; j < m; j++) { dir[p++] = ' '; } for (int j = 0, m = dire.name.length(); j < m; j++) { dir[p++] = (byte) dire.name.charAt(j); } // Fill up name... for (int j = 0, m = 18 - dire.name.length(); j < m; j++) { dir[p++] = ' '; } String type = dire.getTypeString(); for (int j = 0, m = type.length(); j < m; j++) { dir[p++] = (byte) type.charAt(j); } dir[p++] = 0; } dir[p++] = 0; dir[p++] = 0; // Not inserted into memory yet... try { out.write(dir, 0, p); } catch (Exception e) { e.printStackTrace(); } } // Returns filename of actual read file... or null if failed public String readFile(String filename, int adr, OutputStream out) { int sindex = 0; DirEntry dire = null; System.out.println("Loading: '" + filename + "' at " + adr); if ((sindex = filename.indexOf('*')) >= 0) { String match = filename.substring(0, sindex); System.out.println("Matcher: " + match); Enumeration names = dirEntries.keys(); while (names.hasMoreElements()) { String name = (String) names.nextElement(); if (name.startsWith(match)) { System.out.println("Found: " + name); dire = (DirEntry) dirEntries.get(name); } } } else if (filename.equals("$")) { printDirListing(out); return filename; } else { dire = (DirEntry) dirEntries.get(filename); if (dire == null) { for (int i = filename.length(), n = 16; i < n; i++) { filename+=' '; } dire = (DirEntry) dirEntries.get(filename); } } if (dire == null) return null; if (type == TAPE) { return readTapeFile(dire); } // Otherwise read the disk file! return readDiskFile(dire, filename, adr, out); } public String readDiskFile(DirEntry dire, String str, int adr, OutputStream out) { byte[] sec = getSector(dire.trk, dire.sec); // dumpSector(sec); int address = (sec[2] & 0xff) + (sec[3] & 0xff) * 256; System.out.println("*** Reading DISK file at " + Integer.toString(address, 16)); if (out != null) { // Send over the address if out exists - this is a part of the file! try { out.write(sec[2] & 0xff); out.write(sec[3] & 0xff); } catch (Exception e) { e.printStackTrace(); } } if (adr != -1) { System.out.println("Address override: " + address + " -> " + adr); address = adr; } int nextSector = sec[1] & 0xff; int nextTrak = sec[0] & 0xff; try { for (int i = 0; i < 252; i++) { if (out != null) out.write(sec[i + 4] & 0xff); else memory[i + address] = sec[i + 4] & 0xff; } } catch (Exception e) { System.out.println("Could not write to output stream"); e.printStackTrace(); } address = address + 252; boolean reading = nextTrak != 0; while (reading) { // System.out.println("Storing at: " + address); sec = getSector(nextTrak, nextSector); // dumpSector(sec); nextSector = sec[1] & 0xff; nextTrak = sec[0] & 0xff; reading = nextTrak != 0; try { for (int i = 0; i < 254; i++) { if (out != null) out.write(sec[i + 2] & 0xff); else memory[i + address] = sec[i + 2] & 0xff; } } catch (Exception e) { System.out.println("Could not write to output stream"); e.printStackTrace(); } address = address + 254; } //System.out.println("NEXT SECTOR -> Length of last read: " + // nextSector); // to get the correct "last address" address = address - 255 + nextSector; if (out == null) setAddress(address); System.out.println("*** File loaded - end at: " + Integer.toString(address, 16)); return dire.name; } // Will return a string of hexadecimally encoded .prg file! // Just de-hex and make a .prg file is such a file is desired... public String saveFile() { int startAdr = memory[43] + (memory[44] << 8); int lastAdr = memory[45] + (memory[46] << 8); System.out.println("Dumping mem from: " + startAdr + " to " + lastAdr); // All in hex... StringBuffer sb = new StringBuffer(); if ((startAdr & 0xff) < 16) sb.append('0'); sb.append(Integer.toString(startAdr & 0xff)); if ((startAdr >> 8) < 16) sb.append('0'); sb.append(Integer.toString(startAdr >> 8)); for (int i = startAdr; i < lastAdr; i++) { int m = memory[i]; if (m < 16) sb.append('0'); sb.append(Integer.toString(memory[i], 16)); } return sb.toString(); } private void setAddress(int address) { // Set the right upper memory !!! if (address > 0x9f00) address = 0x9f00; memory[45] = address & 0xff; memory[46] = (address & 0xff00) >> 8; memory[47] = address & 0xff; memory[48] = (address & 0xff00) >> 8; memory[49] = address & 0xff; memory[50] = (address & 0xff00) >> 8; } private String readTapeFile(DirEntry dire) { // The sector stores the start address?! int address = dire.sec; int offset = dire.trk; // Skip the position in the prg file System.out.println("Reading from " + offset); System.out.println("Storing at: " + address); System.out.println("Size: " + dire.size); for (int i=0; i < dire.size; i++) { memory[address++] = sectors[(i + offset) >> 8][(i + offset) & 0xff] & 0xff; } setAddress(address); return dire.name; } private boolean readPGM(InputStream stream, int address) { // read program files .pgm byte[] start = new byte[2]; try { DataInputStream reader = new DataInputStream(stream); reader.readFully(start); int sector = 0; int numRead = 0; int noBytes = 0; while((numRead = readSector(reader, sector)) == 256) sector++; noBytes = sector * 256 + numRead; System.out.println("Read " + noBytes + " program data"); if (address == -1) address = (start[0] + (start[1]*256)); System.out.println("Storing at: " + address); for (int i = 0; i < noBytes; i++) memory[address++] = sectors[(i) >> 8][i & 0xff] & 0xff; // Fixa set address and then it is finished??? setAddress(address); return true; } catch (Exception e) { System.out.println("Error while reading pgm file " + e); } return false; } private boolean readTape(InputStream stream) { byte[] start = new byte[32]; dirNames.clear(); dirEntries.clear(); type = TAPE; try { DataInputStream reader = new DataInputStream(stream); reader.readFully(start); String st = new String(start); if (st.startsWith("C64")) { System.out.println("Tape Archive found:"); reader.readFully(start); int max = start[3]*256 + start[2]; int used = start[5]*256 + start[4]; if (used == 0) used = 1; String name = ""; System.out.println("Type: " + start[0]*256 + start[1]); System.out.println("Max Entries: " + max); System.out.println("Used Entries: " + used); for (int i = 8; i < 32; i++) name = name + (char) start[i]; System.out.println("Name: " + name); int startAdr, endAdr, offset; for (int i = 0; i < used; i++) { reader.readFully(start); startAdr = (start[2]&0xff) + (start[3]&0xff) * 256; endAdr = (start[4]&0xff) + (start[5]&0xff) * 256; offset = (start[8]&0xff) + (start[9]&0xff) * 256; System.out.println("---------------------"); System.out.println("Entry: " + i); System.out.println("File Type: " + start[0]); System.out.println("1541 Type: " + start[1]); System.out.println("Start Adr: " + startAdr); System.out.println("End Adr: " + endAdr + " -> size = " + (endAdr - startAdr)); System.out.println("Offset: " + offset); name = ""; for (int j = 16; j < 32; j++) name = name + (char) start[j]; System.out.println("File Name: " + name); DirEntry entry = new DirEntry(name, offset - 32 * (max + 2), startAdr, endAdr - startAdr, start[1]); dirNames.add(entry); dirEntries.put(name, entry); } // Skip all empty entries!!! for (int i = used; i < max; i++) { reader.readFully(start); } int sector = 0; int numRead; while((numRead = readSector(reader, sector)) == 256) sector++; noBytes = sector * 256 + numRead; System.out.println("Read " + noBytes + " program data"); // dumpSector(sectors[0]); } return true; } catch (Exception e) { System.out.println("Error while reading tape"); } return false; } // Should return a SID object ... private boolean readSID(InputStream stream) { byte[] start = new byte[0x16]; try { DataInputStream reader = new DataInputStream(stream); reader.readFully(start); String st = new String(start); if (st.startsWith("PSID")) { int version = start[4]*256 + start[5]; // int offset = start[6]*256 + start[7]; int addr = start[8]*256 + start[9]; int iaddr = start[10]*256 + start[11]; int paddr = start[12]*256 + start[13]; int songs = start[14]*256 + start[15]; int startsong = start[16]*256 + start[17]; long speed = (start[18] << 24) + (start[19]<<16) + (start[20] << 8) + start[21]; System.out.println("FOUND SID TUNE!"); System.out.println("Version: " + version); System.out.println("LAddr: " + addr); System.out.println("IAddr: " + iaddr); System.out.println("PAddr: " + paddr); System.out.println("Songs: " + songs); System.out.println("StartSong: " + startsong); System.out.println("Speed: " + speed); byte[] str = new byte[0x20]; for (int i = 0 ; i < songs; i++) { reader.readFully(str); System.out.println("Song " + (i + 1)); System.out.println("Name :" + new String(str)); reader.readFully(str); System.out.println("Author :" + new String(str)); reader.readFully(str); System.out.println("Copyright :" + new String(str)); } if (version == 2) { byte[] garbage = new byte[6]; reader.readFully(garbage); } byte[] adr = new byte[2]; reader.readFully(adr); // int startAddress = adr[1]*256 + adr[0]; } } catch(Exception e) { System.out.println("Error while reading SID"); } return false; } private int readSector(DataInputStream reader, int sector) { // Read this sector... int no, numRead = 0; try { no = numRead = reader.read(sectors[sector]); if (no == -1) return numRead; while (numRead < 256) { no = reader.read(sectors[sector], numRead, 256 - numRead); if (no == -1) return numRead; numRead += no; } } catch(Exception e) { System.out.println("Exception while reading file... " + e); e.printStackTrace(); } return numRead; } public boolean readDiskFromFile(String name) { try { System.out.println("Loading " + name); FileInputStream reader = new FileInputStream(name); return readDisk(reader); } catch (Exception e) { System.out.println("readDiskFromFile(filename): Error while opening file " + name + ":"); e.printStackTrace(); } return false; } public boolean readPGM(URL url, int address) { try { InputStream s = url.openStream(); return readPGM(s, address); } catch (Exception e) { System.out.println("readPGM(url): Error when opening url " + url + ":"); e.printStackTrace(); } return false; } public boolean readPGM(String file, int address) { try { return readPGM(new FileInputStream(file), address); } catch (Exception e) { System.out.println("readPGM(filename): Error when opening file " + file + ":"); e.printStackTrace(); } return false; } public boolean readDiskFromURL(URL url) { try { System.out.println("Loading from " + url); InputStream reader = url.openConnection().getInputStream(); return readDisk(reader); } catch (Exception e) { System.out.println("readDiskFromURL(url): Error when opening url " + url + ":"); e.printStackTrace(); } return false; } public boolean readTapeFromFile(String name) { try { System.out.println("Loading " + name); FileInputStream reader = new FileInputStream(name); return readTape(reader); } catch (Exception e) { System.out.println("readTapeFromFile(filename): Error while opening file " + name + ":"); e.printStackTrace(); } return false; } public boolean readTapeFromURL(URL url) { try { System.out.println("Loading from " + url); InputStream reader = url.openConnection().getInputStream(); return readTape(reader); } catch (Exception e) { System.out.println("readTapeFromURL(url): Error when opening url " + url + ":"); e.printStackTrace(); } return false; } public boolean readSIDFromFile(String name) { try { System.out.println("Loading SID " + name); FileInputStream reader = new FileInputStream(name); return readSID(reader); } catch (Exception e) { System.out.println("readSIDFromFile(filename): Error while opening file " + name + ":"); e.printStackTrace(); } return false; } public static void main(String[] args) { C64Reader cr = new C64Reader(); //cr.readTapeFromFile("games/defender.t64"); //cr.readSIDFromFile("sids/City.sid"); cr.readDiskFromFile(args[0]); if (args.length > 1) { try { FileOutputStream fio = new FileOutputStream(args[2]); cr.readFile(args[1], 0, fio); } catch (Exception e) { e.printStackTrace(); } } else { cr.printDirListing(System.out); } } }