// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ // ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ // Magicbane Emulator Project © 2013 - 2026 // https://www.magicbane.com package engine.parsers.wpak; import engine.gameManager.ConfigManager; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; // Reads the client's wpak cfg files (an mbEditorPro export of Config.wpak) into // tokenized lines and XxxBEGIN/XxxEND blocks. Each parser maps the tokens itself. public class WpakFileReader { public static final String wpakPath = ConfigManager.DEFAULT_DATA_DIR + "wpak/"; // Lines starting with '#' or '*' are comments (PowerActions_LoadCfg 0x578EC0). // A '#' outside quotes ends the line, as in PowActionCostInfo.cfg. public static ArrayList readLines(String fileName) throws IOException { ArrayList lines = new ArrayList<>(); List fileLines = Files.readAllLines(Paths.get(wpakPath + fileName), StandardCharsets.ISO_8859_1); String text; String trimmed; ArrayList tokens; for (int i = 0; i < fileLines.size(); i++) { text = fileLines.get(i); trimmed = text.trim(); if (trimmed.isEmpty() || trimmed.charAt(0) == '#' || trimmed.charAt(0) == '*') continue; tokens = tokenize(fileName, i + 1, text); if (!tokens.isEmpty()) lines.add(new WpakLine(fileName, i + 1, text, tokens)); } return lines; } // A one-token line XxxBEGIN opens block "Xxx"; XxxEND closes the innermost one. public static WpakBlock readBlocks(String fileName) throws IOException { WpakBlock root = new WpakBlock(fileName, null); ArrayDeque openBlocks = new ArrayDeque<>(); WpakBlock block; String token; openBlocks.push(root); for (WpakLine line : readLines(fileName)) { token = line.token(0); if (line.size() == 1 && token.length() > 5 && token.endsWith("BEGIN")) { block = new WpakBlock(token.substring(0, token.length() - 5), line); openBlocks.peek().children.add(block); openBlocks.push(block); continue; } if (line.size() == 1 && token.equals(openBlocks.peek().name + "END")) { openBlocks.pop(); continue; } if (line.size() == 1 && closesOuterBlock(openBlocks, token)) throw line.error(token + " before " + openBlocks.peek().name + "END (opened at " + openBlocks.peek().location() + ")"); openBlocks.peek().children.add(line); } if (openBlocks.size() > 1) throw new WpakFormatException(openBlocks.peek().location() + ": " + openBlocks.peek().name + "BEGIN is never closed"); return root; } private static boolean closesOuterBlock(ArrayDeque openBlocks, String token) { for (WpakBlock block : openBlocks) if (block.beginLine != null && token.equals(block.name + "END")) return true; return false; } // Whitespace separates tokens; double quotes group one token (quotes removed, no escapes). private static ArrayList tokenize(String fileName, int lineNumber, String text) { ArrayList tokens = new ArrayList<>(); StringBuilder token = new StringBuilder(); boolean inToken = false; boolean inQuotes = false; char c; for (int i = 0; i < text.length(); i++) { c = text.charAt(i); if (inQuotes) { if (c == '"') { inQuotes = false; tokens.add(token.toString()); token.setLength(0); } else token.append(c); continue; } if (c == '"' || c == '#' || Character.isWhitespace(c)) { if (inToken) { tokens.add(token.toString()); token.setLength(0); inToken = false; } if (c == '#') break; if (c == '"') inQuotes = true; continue; } token.append(c); inToken = true; } if (inQuotes) throw new WpakFormatException(fileName + ":" + lineNumber + ": unclosed quote"); if (inToken) tokens.add(token.toString()); return tokens; } }