package com.bytezone.diskbrowser.wizardry; import java.awt.Color; import java.util.ArrayList; import java.util.List; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import com.bytezone.diskbrowser.applefile.AbstractFile; import com.bytezone.diskbrowser.applefile.AppleFileSource; import com.bytezone.diskbrowser.disk.DefaultAppleFileSource; import com.bytezone.diskbrowser.disk.DefaultDataSource; import com.bytezone.diskbrowser.disk.Disk; import com.bytezone.diskbrowser.disk.DiskAddress; import com.bytezone.diskbrowser.disk.SectorType; import com.bytezone.diskbrowser.gui.DataSource; import com.bytezone.diskbrowser.pascal.PascalDisk; import com.bytezone.diskbrowser.utilities.HexFormatter; import com.bytezone.diskbrowser.utilities.Utility; import com.bytezone.diskbrowser.wizardry.Header.ScenarioData; import com.bytezone.diskbrowser.wizardry.Spell.SpellType; // ---------------------------------------------------------------------------------// public class WizardryScenarioDisk extends PascalDisk // ---------------------------------------------------------------------------------// { public Header scenarioHeader; public List images; public List items; public List characters; public List spells; public List messages; public List monsters; public List levels; List experienceLevels; List rewards; private int monsterId; private int itemId; // leave these here until I decide whether to use them or not SectorType mazeSector = new SectorType ("Maze", Color.lightGray); SectorType monsterSector = new SectorType ("Monsters", Color.black); SectorType itemSector = new SectorType ("Items", Color.blue); SectorType characterSector = new SectorType ("Characters", Color.magenta); SectorType spellSector = new SectorType ("Spells", Color.orange); SectorType messageSector = new SectorType ("Messages", Color.cyan); SectorType imageSector = new SectorType ("Images", Color.red); SectorType experienceSector = new SectorType ("Experience", Color.darkGray); SectorType treasureSector = new SectorType ("Treasure", Color.pink); // ---------------------------------------------------------------------------------// public WizardryScenarioDisk (Disk disk) // ---------------------------------------------------------------------------------// { super (disk); if (false) { sectorTypesList.add (mazeSector); sectorTypesList.add (monsterSector); sectorTypesList.add (itemSector); sectorTypesList.add (characterSector); sectorTypesList.add (spellSector); sectorTypesList.add (messageSector); sectorTypesList.add (imageSector); sectorTypesList.add (experienceSector); sectorTypesList.add (treasureSector); } CodedMessage.codeOffset = 185; DefaultTreeModel model = (DefaultTreeModel) catalogTree.getModel (); DefaultMutableTreeNode currentRoot = (DefaultMutableTreeNode) model.getRoot (); DefaultMutableTreeNode dataNode = findNode (currentRoot, "SCENARIO.DATA"); DefaultMutableTreeNode msgNode = findNode (currentRoot, "SCENARIO.MESGS"); if (dataNode == null || msgNode == null) { System.out.println ("Wizardry data or msg node not found"); return; } dataNode.setAllowsChildren (true); msgNode.setAllowsChildren (true); scenarioHeader = new Header (dataNode, this); // Process SCENARIO.MESGS (requires scenario) AppleFileSource afs = (AppleFileSource) msgNode.getUserObject (); extractMessages (msgNode, afs.getSectors ()); // Process SCENARIO.DATA (requires scenario and messages) afs = (AppleFileSource) dataNode.getUserObject (); List sectors = afs.getSectors (); extractItems (linkNode ("Items", "Items string", dataNode), sectors); extractRewards (linkNode ("Rewards", "Treasure string", dataNode), sectors); extractMonsters (linkNode ("Monsters", "Monsters string", dataNode), sectors); extractCharacters (linkNode ("Characters", "Characters string", dataNode), sectors); extractImages (linkNode ("Images", "Images string", dataNode), sectors); extractExperienceLevels (linkNode ("Experience", "Experience string", dataNode), sectors); extractSpells (sectors); extractLevels (linkNode ("Maze", "Levels string", dataNode), sectors); // add information about each characters' baggage, spells known etc. for (CharacterV1 character : characters) character.link (items, spells, experienceLevels); for (ItemV1 item : items) item.link (items, spells); } // ---------------------------------------------------------------------------------// private DefaultMutableTreeNode linkNode (String name, String text, DefaultMutableTreeNode parent) // ---------------------------------------------------------------------------------// { DefaultAppleFileSource afs = new DefaultAppleFileSource (name, text, this); DefaultMutableTreeNode node = new DefaultMutableTreeNode (afs); parent.add (node); return node; } // ---------------------------------------------------------------------------------// public static boolean isWizardryFormat (Disk disk, boolean debug) // ---------------------------------------------------------------------------------// { byte[] buffer = disk.readBlock (2); int totalFiles = Utility.getShort (buffer, 16); if (totalFiles != 3) return false; for (int i = 1, ptr = 32; i <= totalFiles; i++, ptr += 26) { String text = HexFormatter.getPascalString (buffer, ptr); if (!text.equals ("SCENARIO.DATA") && !text.equals ("SCENARIO.MESGS") && !text.equals ("WIZARDRY.CODE")) return false; } return true; } // ---------------------------------------------------------------------------------// @Override public AppleFileSource getFile (String fileName) // ---------------------------------------------------------------------------------// { // System.out.println ("Wizardry disk looking for : " + fileName); return null; } // ---------------------------------------------------------------------------------// public String getCatalogText () // ---------------------------------------------------------------------------------// { return null; } // ---------------------------------------------------------------------------------// @Override public List getFileSectors (int fileNo) // ---------------------------------------------------------------------------------// { return null; } // ---------------------------------------------------------------------------------// @Override public DataSource getFile (int fileNo) // ---------------------------------------------------------------------------------// { return null; } // ---------------------------------------------------------------------------------// private void extractRewards (DefaultMutableTreeNode node, List sectors) // ---------------------------------------------------------------------------------// { List nodeSectors = new ArrayList<> (); ScenarioData sd = scenarioHeader.data.get (Header.TREASURE_TABLE_AREA); rewards = new ArrayList<> (sd.total); int max = sd.totalBlocks / 2; int seq = 0; for (int i = 0; i < max; i++) { List blocks = getTwoBlocks (sd, i, sectors); nodeSectors.addAll (blocks); byte[] buffer = disk.readBlocks (blocks); seq = addReward (buffer, blocks, node, seq); } StringBuilder text = new StringBuilder (); for (Reward t : rewards) text.append (t.getDump () + "\n"); DefaultAppleFileSource afs = (DefaultAppleFileSource) node.getUserObject (); afs.setSectors (nodeSectors); DefaultDataSource dds = (DefaultDataSource) afs.getDataSource (); dds.text = text.toString (); } // ---------------------------------------------------------------------------------// private int addReward (byte[] buffer, List blocks, DefaultMutableTreeNode node, int seq) // ---------------------------------------------------------------------------------// { int recLen = 168; for (int ptr = 0; ptr < 1008; ptr += recLen) { byte[] data2 = new byte[recLen]; System.arraycopy (buffer, ptr, data2, 0, recLen); Reward tt = new Reward ("Type " + seq, data2, seq++, items); rewards.add (tt); addToNode (tt, node, blocks, treasureSector); } return seq; } // ---------------------------------------------------------------------------------// private void extractCharacters (DefaultMutableTreeNode node, List sectors) // ---------------------------------------------------------------------------------// { List nodeSectors = new ArrayList<> (); ScenarioData sd = scenarioHeader.data.get (Header.CHARACTER_AREA); characters = new ArrayList<> (sd.total); int max = sd.totalBlocks / 2; // if (max < sd.total) // System.out.println ("Characters short in Wizardry disk"); for (int i = 0; i < max; i++) { List blocks = getTwoBlocks (sd, i, sectors); nodeSectors.addAll (blocks); byte[] buffer = disk.readBlocks (blocks); addCharacters (buffer, blocks, node); } StringBuilder text = new StringBuilder (); text.append ("Name Age Align Race Type " + "HP St In Pi Vi Ag Lu Status\n"); text.append ("------------- ---- -------- -------- ---------- " + "-- -- -- -- -- -- -- ------\n"); for (CharacterV1 ch : characters) { int[] att = ch.getAttributes (); text.append (String.format ("%-15s %2d %-8s %-8s %-8s %3d", ch, (ch.ageInWeeks / 52), ch.getAlignment (), ch.getRace (), ch.getType (), ch.hpMax)); text.append (String.format (" %2d %2d %2d %2d %2d %2d", att[0], att[1], att[2], att[3], att[4], att[5])); text.append (String.format (" %5s %s%n", ch.getStatus (), ch.isOut () ? "* OUT *" : "")); } DefaultAppleFileSource afs = (DefaultAppleFileSource) node.getUserObject (); afs.setSectors (nodeSectors); DefaultDataSource dds = (DefaultDataSource) afs.getDataSource (); dds.text = text.toString (); } // ---------------------------------------------------------------------------------// private void addCharacters (byte[] buffer, List blocks, DefaultMutableTreeNode node) // ---------------------------------------------------------------------------------// { int recLen = 208; for (int ptr = 0; ptr < 832; ptr += recLen) { int nameLength = buffer[ptr] & 0xFF; if (nameLength == 0xC3) return; String name = HexFormatter.getString (buffer, ptr + 1, nameLength); if (name.isEmpty () || ("UNSET".equals (name) && buffer[ptr + 40] == 0x07)) // 7 = LOST continue; byte[] data2 = new byte[recLen]; System.arraycopy (buffer, ptr, data2, 0, recLen); CharacterV1 c = new CharacterV1 (name, data2, scenarioHeader.scenarioID); characters.add (c); addToNode (c, node, blocks, characterSector); } } // ---------------------------------------------------------------------------------// private void extractMonsters (DefaultMutableTreeNode node, List sectors) // ---------------------------------------------------------------------------------// { List nodeSectors = new ArrayList<> (); ScenarioData sd = scenarioHeader.data.get (Header.MONSTER_AREA); monsters = new ArrayList<> (sd.total); int max = sd.totalBlocks / 2; for (int i = 0; i < max; i++) { List blocks = getTwoBlocks (sd, i, sectors); nodeSectors.addAll (blocks); byte[] buffer = disk.readBlocks (blocks); addMonsters (buffer, blocks, node); } StringBuilder text = new StringBuilder (); for (int block = 0; block < 4; block++) { text.append (" ID Name\n"); text.append ("--- ---------------"); for (int i = 0; i < 24; i++) text.append (" --"); text.append ("\n"); for (MonsterV1 m : monsters) text.append (m.getDump (block) + "\n"); text.append ("\n"); } DefaultAppleFileSource afs = (DefaultAppleFileSource) node.getUserObject (); afs.setSectors (nodeSectors); DefaultDataSource dds = (DefaultDataSource) afs.getDataSource (); dds.text = text.toString (); } // ---------------------------------------------------------------------------------// private void addMonsters (byte[] buffer, List blocks, DefaultMutableTreeNode node) // ---------------------------------------------------------------------------------// { int recLen = 158; for (int ptr = 0; ptr < 948; ptr += recLen) { int nameLength = buffer[ptr + 32] & 0xFF; if (nameLength == 0 || nameLength == 255) break; String itemName = HexFormatter.getString (buffer, ptr + 33, nameLength); byte[] data2 = new byte[recLen]; System.arraycopy (buffer, ptr, data2, 0, recLen); MonsterV1 m = new MonsterV1 (monsterId++, itemName, data2, rewards, monsters, scenarioHeader.scenarioID); monsters.add (m); addToNode (m, node, blocks, monsterSector); } } // ---------------------------------------------------------------------------------// private void extractItems (DefaultMutableTreeNode node, List sectors) // ---------------------------------------------------------------------------------// { List nodeSectors = new ArrayList<> (); ScenarioData sd = scenarioHeader.data.get (Header.ITEM_AREA); items = new ArrayList<> (sd.total); int max = sd.totalBlocks / 2; for (int i = 0; i < max; i++) { List blocks = getTwoBlocks (sd, i, sectors); nodeSectors.addAll (blocks); byte[] buffer = disk.readBlocks (blocks); addItems (buffer, blocks, node); } StringBuilder text = new StringBuilder (); for (int block = 0; block < 2; block++) { text.append (" ID Name "); for (int i = 0; i < 24; i++) text.append (String.format ("%2d ", i)); text.append ("\n--- ---------------"); for (int i = 0; i < 24; i++) text.append (" --"); text.append ("\n"); for (ItemV1 item : items) text.append (item.getDump (block) + "\n"); text.append ("\n"); } DefaultAppleFileSource afs = (DefaultAppleFileSource) node.getUserObject (); afs.setSectors (nodeSectors); DefaultDataSource dds = (DefaultDataSource) afs.getDataSource (); dds.text = text.toString (); } // ---------------------------------------------------------------------------------// private void addItems (byte[] buffer, List blocks, DefaultMutableTreeNode node) // ---------------------------------------------------------------------------------// { int recLen = 78; for (int ptr = 0; ptr < 1014; ptr += recLen) { if (buffer[ptr] == 0) break; String itemName = HexFormatter.getPascalString (buffer, ptr); byte[] data2 = new byte[recLen]; System.arraycopy (buffer, ptr, data2, 0, recLen); ItemV1 i = new ItemV1 (itemId++, itemName, data2); items.add (i); addToNode (i, node, blocks, itemSector); } } // ---------------------------------------------------------------------------------// private void extractSpells (List sectors) // ---------------------------------------------------------------------------------// { spells = new ArrayList<> (); List blocks = new ArrayList<> (2); int offset = scenarioHeader.scenarioID <= 2 ? 4 : 1; blocks.add (sectors.get (offset)); blocks.add (sectors.get (offset + 1)); SpellType spellType = SpellType.MAGE; for (DiskAddress da : blocks) { byte[] buffer = disk.readBlock (da); int level = 1; int ptr = -1; while (ptr < 255) { ptr++; int start = ptr; while (ptr < 256 && buffer[ptr] != 0x0D) ptr++; if (ptr == start) break; String spell = HexFormatter.getString (buffer, start, ptr - start); if (spell.startsWith ("*")) { spell = spell.substring (1); ++level; } Spell s = Spell.getSpell (spell, spellType, level, buffer); spells.add (s); } spellType = SpellType.PRIEST; } } // ---------------------------------------------------------------------------------// private void extractMessages (DefaultMutableTreeNode node, List sectors) // ---------------------------------------------------------------------------------// { MessageV1.resetMessageId (); messages = new ArrayList<> (); // Copy first 504 bytes from each sector to a single contiguous buffer int recordLength = 42; int max = recordLength * 12; byte[] buffer = new byte[sectors.size () * max]; int offset = 0; for (DiskAddress da : sectors) { byte[] tempBuffer = disk.readBlock (da); System.arraycopy (tempBuffer, 0, buffer, offset, max); offset += max; } // int id = 0; int totalLines = 0; for (int ptr = 0; ptr < buffer.length; ptr += recordLength) { int sequence = buffer[ptr + recordLength - 2]; ++totalLines; if (sequence == 1) // end of message { int totalBytes = totalLines * recordLength; byte[] newBuffer = new byte[totalBytes]; int messageEnd = ptr + recordLength; int messageStart = messageEnd - totalBytes; System.arraycopy (buffer, messageStart, newBuffer, 0, totalBytes); MessageV1 m; if (scenarioHeader.scenarioID == 1) m = new PlainMessage (newBuffer); else m = new CodedMessage (newBuffer); messages.add (m); List messageBlocks = new ArrayList<> (); int lastBlock = -1; for (int p2 = messageStart; p2 < messageEnd; p2 += recordLength) { int blockNo = p2 / max; offset = p2 % max; if (blockNo != lastBlock) { messageBlocks.add (sectors.get (blockNo)); lastBlock = blockNo; } } addToNode (m, node, messageBlocks, messageSector); // id += totalLines; totalLines = 0; } } } // ---------------------------------------------------------------------------------// private void extractLevels (DefaultMutableTreeNode node, List sectors) // ---------------------------------------------------------------------------------// { List nodeSectors = new ArrayList<> (); ScenarioData sd = scenarioHeader.data.get (Header.MAZE_AREA); levels = new ArrayList<> (sd.total); int max = sd.totalBlocks / 2; for (int i = 0; i < max; i++) { List blocks = getTwoBlocks (sd, i, sectors); nodeSectors.addAll (blocks); byte[] buffer = disk.readBlocks (blocks); byte[] data2 = new byte[896]; System.arraycopy (buffer, 0, data2, 0, data2.length); // System.out.println (HexFormatter.format (data2)); MazeLevel mazeLevel = new MazeLevel (data2, i + 1); mazeLevel.setMessages (messages); mazeLevel.setMonsters (monsters); mazeLevel.setItems (items); levels.add (mazeLevel); addToNode (mazeLevel, node, blocks, mazeSector); } StringBuilder text = new StringBuilder (); for (MazeLevel level : levels) text.append (level.getName () + "\n"); DefaultAppleFileSource afs = (DefaultAppleFileSource) node.getUserObject (); afs.setSectors (nodeSectors); DefaultDataSource dds = (DefaultDataSource) afs.getDataSource (); dds.text = text.toString (); } // ---------------------------------------------------------------------------------// private void extractImages (DefaultMutableTreeNode node, List sectors) // ---------------------------------------------------------------------------------// { List nodeSectors = new ArrayList<> (); ScenarioData sd = scenarioHeader.data.get (Header.IMAGE_AREA); int max = sd.totalBlocks; images = new ArrayList<> (); for (int i = 0; i < max; i++) { DiskAddress da = sectors.get (sd.dataOffset + i); nodeSectors.add (da); byte[] buffer = disk.readBlock (da); byte[] exactBuffer = new byte[480]; System.arraycopy (buffer, 0, exactBuffer, 0, exactBuffer.length); String name = "Unknown"; for (MonsterV1 m : monsters) if (m.imageID == i) { name = m.genericName; break; } AbstractImage mi = scenarioHeader.scenarioID < 3 ? new Image (name, buffer) : new ImageV2 (name, exactBuffer); images.add (mi); addToNode (mi, node, da, imageSector); } StringBuilder text = new StringBuilder (); for (AbstractImage image : images) text.append (image.getName () + "\n"); DefaultAppleFileSource afs = (DefaultAppleFileSource) node.getUserObject (); afs.setSectors (nodeSectors); DefaultDataSource dds = (DefaultDataSource) afs.getDataSource (); dds.text = text.toString (); } // ---------------------------------------------------------------------------------// private void extractExperienceLevels (DefaultMutableTreeNode node, List sectors) // ---------------------------------------------------------------------------------// { List nodeSectors = new ArrayList<> (); ScenarioData sd = scenarioHeader.data.get (Header.EXPERIENCE_AREA); experienceLevels = new ArrayList<> (sd.total); int max = sd.totalBlocks / 2; int count = 0; String[] classes = { "FIGHTER", "MAGE", "PRIEST", "THIEF", "BISHOP", "SAMURAI", "LORD", "NINJA" }; for (int i = 0; i < max; i++) { List blocks = getTwoBlocks (sd, i, sectors); nodeSectors.addAll (blocks); byte[] buffer = disk.readBlocks (blocks); for (int ptr = 0; ptr <= buffer.length; ptr += 78) { if (buffer[ptr] == 0) break; byte[] newBuffer = new byte[78]; System.arraycopy (buffer, ptr, newBuffer, 0, newBuffer.length); ExperienceLevel el = new ExperienceLevel (classes[count++], newBuffer); experienceLevels.add (el); addToNode (el, node, blocks, experienceSector); } } DefaultAppleFileSource afs = (DefaultAppleFileSource) node.getUserObject (); afs.setSectors (nodeSectors); } // ---------------------------------------------------------------------------------// private void addToNode (AbstractFile af, DefaultMutableTreeNode node, DiskAddress block, SectorType type) // ---------------------------------------------------------------------------------// { List blocks = new ArrayList<> (1); blocks.add (block); addToNode (af, node, blocks, type); } // ---------------------------------------------------------------------------------// private void addToNode (AbstractFile af, DefaultMutableTreeNode node, List blocks, SectorType type) // ---------------------------------------------------------------------------------// { DefaultAppleFileSource dafs = new DefaultAppleFileSource (af.getName (), af, this, blocks); DefaultMutableTreeNode childNode = new DefaultMutableTreeNode (dafs); node.add (childNode); childNode.setAllowsChildren (false); } // ---------------------------------------------------------------------------------// private List getTwoBlocks (ScenarioData sd, int i, List sectors) // ---------------------------------------------------------------------------------// { List blocks = new ArrayList<> (2); blocks.add (sectors.get (sd.dataOffset + i * 2)); blocks.add (sectors.get (sd.dataOffset + i * 2 + 1)); return blocks; } }