// Near Infinity - An Infinity Engine Browser and Editor // Copyright (C) 2001 - 2005 Jon Olav Hauglid // See LICENSE.txt for license information package org.infinity.resource.bcs; import java.nio.ByteBuffer; import java.util.HashSet; import java.util.Locale; import java.util.Set; import java.util.SortedMap; import java.util.StringTokenizer; import java.util.TreeMap; import org.infinity.gui.BrowserMenuBar; import org.infinity.resource.Profile; import org.infinity.resource.ResourceFactory; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IdsMap; import org.infinity.util.IdsMapCache; import org.infinity.util.IdsMapEntry; import org.infinity.util.StringResource; import org.infinity.util.io.StreamUtils; public final class Decompiler { /** Indicates how to decompile script code. */ public enum ScriptType { /** Treat code as full BCS resource. */ BCS, /** Treat code as script trigger only. */ TRIGGER, /** Treat code as script action only. */ ACTION, /** Do not decompile automatically. */ CUSTOM } private final Set<Integer> stringrefsUsed = new HashSet<Integer>(); private final Set<ResourceEntry> resourcesUsed = new HashSet<ResourceEntry>(); private final SortedMap<Integer, String> idsErrors = new TreeMap<Integer, String>(); private String code; // script byte code private String source; // decompiled script source private ScriptType scriptType; private String indent = "\t"; private boolean generateErrors; private int lineNr; public Decompiler(ResourceEntry bcsEntry, boolean generateErrors) throws Exception { this(bcsEntry, ScriptType.BCS, generateErrors); } public Decompiler(ResourceEntry bcsEntry, ScriptType type, boolean generateErrors) throws Exception { if (bcsEntry == null) { throw new NullPointerException(); } if (BrowserMenuBar.getInstance() != null) { if (BrowserMenuBar.getInstance().getBcsAutoIndentEnabled()) { indent = BrowserMenuBar.getInstance().getBcsIndent(); } else { indent = ""; } } this.scriptType = type; this.generateErrors = generateErrors; ByteBuffer buffer = bcsEntry.getResourceBuffer(); this.code = StreamUtils.readString(buffer, buffer.limit()); } public Decompiler(String code, boolean generateErrors) { this(code, ScriptType.BCS, generateErrors); } public Decompiler(String code, ScriptType type, boolean generateErrors) { if (BrowserMenuBar.getInstance() != null) { if (BrowserMenuBar.getInstance().getBcsAutoIndentEnabled()) { indent = BrowserMenuBar.getInstance().getBcsIndent(); } else { indent = ""; } } this.scriptType = type; this.generateErrors = generateErrors; this.code = (code != null) ? code : ""; } /** Returns unprocessed BCS byte code. */ public String getCode() { return code; } /** Set new BCS byte code to decompile. */ public void setCode(String code) { this.code = (code != null) ? code : ""; reset(); } /** Load new BCS byte code from the specified resource entry to decompile. */ public void setCode(ResourceEntry bcsEntry) throws Exception { if (bcsEntry == null) { throw new NullPointerException(); } ByteBuffer buffer = bcsEntry.getResourceBuffer(); this.code = StreamUtils.readString(buffer, buffer.limit()); reset(); } /** Returns the decompiled script source. Executes decompile process if needed. */ public String getSource() { if (source == null) { decompile(); } return source; } /** Returns currently used script type. */ public ScriptType getScriptType() { return scriptType; } /** * Specify new script type. * <b>Note:</b> Automatically invalidates previously decompiled script code. */ public void setScriptType(ScriptType type) { if (type != scriptType) { reset(); this.scriptType = type; } } /** Returns whether to generate decompile errors. */ public boolean isGenerateErrors() { return generateErrors; } /** * Specify whether to generate decompile errors. * <b>Note:</b> Automatically invalidates previously decompiled script code. */ public void setGenerateErrors(boolean flag) { if (flag != generateErrors) { reset(); generateErrors = flag; } } /** Returns currently used string for a single level of intendation. */ public String getIndent() { return indent; } /** Applies the indentation string defined in the currently selected item in the Options menu. */ public void setIndent() { if (BrowserMenuBar.getInstance() != null) { if (BrowserMenuBar.getInstance().getBcsAutoIndentEnabled()) { indent = BrowserMenuBar.getInstance().getBcsIndent(); } else { indent = ""; } } } /** Applies the specified string for a single level of indentation. */ public void setIndent(String newIndent) { if (newIndent != null && !indent.equals(newIndent)) { indent = newIndent; } } public SortedMap<Integer, String> getIdsErrors() { return idsErrors; } public Set<ResourceEntry> getResourcesUsed() { return resourcesUsed; } public Set<Integer> getStringRefsUsed() { return stringrefsUsed; } /** * Decompiles the currently loaded script code into human-readable source. * Uses {@link #getScriptType()} to determine the correct decompile action. * @return The decompiled script source. * @throws Exception Thrown if script type is {@code Custom}. */ public String decompile() { switch (scriptType) { case BCS: return decompileScript(); case TRIGGER: return decompileTrigger(); case ACTION: return decompileAction(); default: throw new IllegalArgumentException("Could not determine script type"); } } /** * Decompiles the current script code as if defined as {@code ScriptType.BCS}. * @return The decompiled script source. Also available via {@link #getSource()}. */ public String decompileScript() { reset(); StringBuilder sb = new StringBuilder(code.length() * 2); StringTokenizer st = new StringTokenizer(code); while (st.hasMoreTokens()) { if (st.nextToken().equalsIgnoreCase("CR")) decompileCR(sb, st); } source = sb.toString(); return source; } /** * Decompiles the current script code as if defined as {@code ScriptType.Trigger}. * @return The decompiled script source. Also available via {@link #getSource()}. */ public String decompileTrigger() { String curIndent = indent; indent = ""; // ignore indentation for dialog script actions try { reset(); StringBuilder sb = new StringBuilder(code.length() * 2); StringTokenizer st = new StringTokenizer(code); while (st.hasMoreTokens()) { if (st.nextToken().equalsIgnoreCase("TR")) sb.append(decompileTR(st)); } source = sb.toString(); return source; } finally { indent = curIndent; } } /** * Decompiles the current script code as if defined as {@code ScriptType.Action}. * @return The decompiled script source. Also available via {@link #getSource()}. */ public String decompileAction() { String curIndent = indent; indent = ""; // ignore indentation for dialog script actions try { reset(); StringBuilder sb = new StringBuilder(code.length() * 2); StringTokenizer st = new StringTokenizer(code); while (st.hasMoreTokens()) { if (st.nextToken().equalsIgnoreCase("AC")) decompileAC(sb, st); } source = sb.toString(); return source; } finally { indent = curIndent; } } public static String[] getResRefType(String function) { if (function.equalsIgnoreCase("DropItem") || function.equalsIgnoreCase("EquipItem") || function.equalsIgnoreCase("GetItem") || function.equalsIgnoreCase("GiveItem") || function.equalsIgnoreCase("UseItem") || function.equalsIgnoreCase("HasItem") || function.equalsIgnoreCase("Contains") || function.equalsIgnoreCase("NumItems") || function.equalsIgnoreCase("NumItemsGT") || function.equalsIgnoreCase("NumItemsLT") || function.equalsIgnoreCase("NumItemsParty") || function.equalsIgnoreCase("NumItemsPartyGT") || function.equalsIgnoreCase("NumItemsPartyLT") || function.equalsIgnoreCase("HasItemEquiped") || function.equalsIgnoreCase("PartyHasItem") || function.equalsIgnoreCase("PartyHasItemIdentified") || function.equalsIgnoreCase("HasItemEquipedReal") || function.equalsIgnoreCase("Acquired") || function.equalsIgnoreCase("Unusable") || function.equalsIgnoreCase("CreateItem") || function.equalsIgnoreCase("GiveItemCreate") || function.equalsIgnoreCase("DestroyItem") || function.equalsIgnoreCase("TakePartyItemNum") || function.equalsIgnoreCase("CreateItemNumGlobal") || function.equalsIgnoreCase("CreateItemGlobal") || function.equalsIgnoreCase("PickUpItem")) { return new String[] {".ITM"}; } else if (function.equalsIgnoreCase("ChangeAnimation") || function.equalsIgnoreCase("ChangeAnimationNoEffect") || function.equalsIgnoreCase("CreateCreature") || function.equalsIgnoreCase("CreateCreatureObject") || function.equalsIgnoreCase("CreateCreatureImpassable") || function.equalsIgnoreCase("CreateCreatureDoor") || function.equalsIgnoreCase("CreateCreatureObjectDoor") || function.equalsIgnoreCase("CreateCreatureObjectOffScreen") || function.equalsIgnoreCase("CreateCreatureOffScreen") || function.equalsIgnoreCase("CreateCreatureAtLocation") || function.equalsIgnoreCase("CreateCreatureObjectCopy") || function.equalsIgnoreCase("CreateCreatureObjectOffset") || function.equalsIgnoreCase("CreateCreatureCopyPoint") || function.equalsIgnoreCase("CreateCreatureImpassableAllowOverlap")) { return new String[] {".CRE"}; } else if (function.equalsIgnoreCase("AreaCheck") || function.equalsIgnoreCase("AreaCheckObject") || function.equalsIgnoreCase("RevealAreaOnMap") || function.equalsIgnoreCase("HideAreaOnMap") || function.equalsIgnoreCase("CopyGroundPilesTo") || function.equalsIgnoreCase("EscapeAreaObjectMove")) { return new String[] {".ARE"}; } else if (function.equalsIgnoreCase("G") || function.equalsIgnoreCase("GGT") || function.equalsIgnoreCase("GLT")) { return new String[] {}; } else if (function.equalsIgnoreCase("IncrementChapter") || function.equalsIgnoreCase("TakeItemListParty") || function.equalsIgnoreCase("TakeItemListPartyNum")) { return new String[] {".2DA"}; } else if (function.equalsIgnoreCase("StartMovie")) { if (Profile.isEnhancedEdition()) { return new String[] {".WBM", ".MVE"}; } else { return new String[] {".MVE"}; } } else if (function.equalsIgnoreCase("AddSpecialAbility")) { return new String[] {".SPL"}; } else if (function.equalsIgnoreCase("CreateVisualEffect")) { return new String[] {".VEF", ".VVC", ".BAM"}; } return new String[] {".CRE", ".ITM", ".ARE", ".2DA", ".BCS", ".MVE", ".SPL", ".DLG", ".VEF", ".VVC", ".BAM"}; } private void reset() { resourcesUsed.clear(); stringrefsUsed.clear(); idsErrors.clear(); lineNr = 1; source = null; } private void decompileAC(StringBuilder code, StringTokenizer st) { int numbers[] = new int[3]; String objects[] = new String[2]; String strings[] = null; String actionString = st.nextToken(); int actioncode; if (actionString.endsWith("OB")) actioncode = Integer.parseInt(actionString.substring(0, actionString.length() - 2)); else actioncode = Integer.parseInt(st.nextToken()); String overrideObject = decompileOB(st); st.nextToken(); // OB objects[0] = decompileOB(st); st.nextToken(); // OB objects[1] = decompileOB(st); numbers[0] = Integer.parseInt(st.nextToken()); int x = Integer.parseInt(st.nextToken()); int y = Integer.parseInt(st.nextToken()); numbers[1] = Integer.parseInt(st.nextToken()); String string1 = st.nextToken(); if (string1.endsWith("AC")) numbers[2] = Integer.parseInt(string1.substring(0, string1.length() - 2)); else { int i = string1.indexOf((int)'"'); if (i != -1) { numbers[2] = Integer.parseInt(string1.substring(0, i)); string1 = string1.substring(i); while (string1.charAt(0) == '"' && string1.charAt(string1.length() - 1) != '"') string1 += ' ' + st.nextToken(); } else { numbers[2] = Integer.parseInt(string1); string1 = st.nextToken(); while (string1.charAt(0) == '"' && string1.charAt(string1.length() - 1) != '"') string1 += ' ' + st.nextToken(); } String string2 = st.nextToken(); while (string2.charAt(0) == '"' && string2.charAt(string2.length() - 1) != '"') string2 += ' ' + st.nextToken(); strings = modifyStrings(string1, string2); st.nextToken(); // AC } IdsMapEntry action = IdsMapCache.get("ACTION.IDS").getValue((long)actioncode); if (action == null) { if (generateErrors) idsErrors.put(new Integer(lineNr), actioncode + " not found in ACTION.IDS"); code.append("Error - Could not find actionString ").append(actioncode).append('\n'); lineNr++; return; } StringTokenizer defParam = new StringTokenizer(action.getParameters(), ","); IdsMapEntry action2 = IdsMapCache.get("ACTION.IDS").getOverflowValue((long)actioncode); if (action2 != null) { if (useOverflowCommand(defParam, numbers, x, y, objects, strings)) action = action2; defParam = new StringTokenizer(action.getParameters(), ","); } if (strings != null) { int count_s = 0; while (defParam.hasMoreTokens()) { String p = defParam.nextToken(); if (p.substring(0, 2).equals("S:")) count_s++; } if (count_s > 0 && count_s < 4 && strings[count_s] != null && !strings[count_s].equals("\"\"")) strings[count_s - 1] = strings[count_s].substring(0, strings[count_s].length() - 1) + strings[count_s - 1].substring(1); defParam = new StringTokenizer(action.getParameters(), ","); } String comment = null; if (overrideObject.equals("[ANYONE]")) code.append(action.getString()); else code.append("ActionOverride(").append(overrideObject).append(',').append(action.getString()); int index_i = 0, index_o = 0, index_s = 0; boolean first = true; while (defParam.hasMoreTokens()) { if (!first) code.append(','); String p = defParam.nextToken(); if (p.substring(0, 2).equals("S:")) { String newp; if (strings == null) newp = objects[index_o++]; else newp = strings[index_s++]; code.append(newp); if (p.equalsIgnoreCase("S:Spells*")) { String spellNumbers = newp.substring(1, newp.length() - 1); int index = 4; IdsMap map = IdsMapCache.get("SPELL.IDS"); while (index <= spellNumbers.length()) { long spellNumber = Long.parseLong(spellNumbers.substring(index - 4, index)); IdsMapEntry entry = map.getValue(spellNumber); if (comment == null) comment = entry.toString(); else comment += ", " + entry.toString(); index += 4; } } else if (newp != null) { String function = action.getString().substring(0, action.getString().length() - 1); comment = getResourceName(function, p, newp.substring(1, newp.length() - 1)); } } else if (p.substring(0, 2).equals("O:")) { while (objects[index_o++].equals("[ANYONE]") && index_o < 2) ; code.append(objects[index_o - 1]); } else if (p.substring(0, 2).equals("I:")) { int nr = numbers[index_i++]; decompileInteger(code, (long)nr, p); if ((p.length() >= 8 && p.substring(0, 8).equalsIgnoreCase("I:StrRef")) || (p.length() >= 7 && p.substring(0, 7).equalsIgnoreCase("I:Entry"))) { comment = StringResource.getStringRef(nr); if (generateErrors) stringrefsUsed.add(new Integer(nr)); } else { StringBuilder sb = new StringBuilder(); decompileInteger(sb, (long)nr, p); String s = getResourceFileName(p, sb.toString()); if (s != null) { comment = s; } } } else if (p.substring(0, 2).equals("P:")) code.append('[').append(x).append('.').append(y).append(']').toString(); first = false; } if (!overrideObject.equals("[ANYONE]")) code.append("))"); else code.append(')'); if (comment != null) code.append(" // ").append(comment.replace('\n', ' ')); code.append('\n'); lineNr++; } private void decompileCO(StringBuilder code, StringTokenizer st) { code.append("IF\n"); lineNr++; String token = st.nextToken(); int orcount = 0; while (!token.equalsIgnoreCase("CO")) { if (token.equalsIgnoreCase("TR")) { String trigger = decompileTR(st); if (orcount > 0) { // NextTriggerObject doesn't count as separate trigger if (!trigger.startsWith("NextTriggerObject")) { orcount--; } code.append(indent); } else if (trigger.substring(0, 3).equalsIgnoreCase("OR(")) { orcount = Integer.parseInt(trigger.substring(3, trigger.indexOf(")"))); } code.append(indent).append(trigger); } token = st.nextToken(); } } private void decompileCR(StringBuilder code, StringTokenizer st) { String token = st.nextToken(); while (st.hasMoreTokens() && !token.equalsIgnoreCase("CR")) { if (token.equalsIgnoreCase("CO")) decompileCO(code, st); else if (token.equalsIgnoreCase("RS")) decompileRS(code, st); if (st.hasMoreTokens()) token = st.nextToken(); } code.append("END\n\n"); lineNr += 2; } private void decompileInteger(StringBuilder code, long nr, String p) { int pIndex = p.indexOf((int)'*'); if (pIndex != -1 && pIndex != p.length() - 1) { // if (nr < 0) // nr += 4294967296L; String idsFile = p.substring(pIndex + 1).toUpperCase(Locale.ENGLISH) + ".IDS"; IdsMap map = IdsMapCache.get(idsFile); if (map != null) { IdsMapEntry entry = map.getValue(nr); if (entry != null) { code.append(entry.getString()); } else if (nr != 0 && (map.toString().equalsIgnoreCase("AREATYPE.IDS") || map.toString().equalsIgnoreCase("BITS.IDS") || map.toString().equalsIgnoreCase("SPLCAST.IDS") || map.toString().equalsIgnoreCase("STATE.IDS"))) { if (nr < 0) { nr += 4294967296L; } StringBuilder temp = new StringBuilder(); for (int bit = 0; nr > 0 && bit < 32; bit++) { long bitnr = 1L << bit; if ((nr & bitnr) == bitnr) { entry = map.getValue(bitnr); if (entry != null) { if (temp.length() > 0) { temp.append(" | "); } temp.append(entry.getString()); nr ^= bitnr; } } } if (nr > 0) { code.append(nr); if (generateErrors) { idsErrors.put(new Integer(lineNr), nr + " not found in " + map.toString()); } } else { code.append(temp); } } else { code.append(nr); if (generateErrors) idsErrors.put(new Integer(lineNr), nr + " not found in " + map.toString()); } } else { code.append(nr); if (generateErrors) { idsErrors.put(new Integer(lineNr), "Could not find " + idsFile); } } } else { code.append(nr); } } private String decompileOB(StringTokenizer st) { int numbers[] = new int[15]; int numbersIndex = 0; String value = st.nextToken(); while (value.charAt(0) != '"' && value.charAt(0) != '[') { numbers[numbersIndex++] = Integer.parseInt(value); value = st.nextToken(); } while (value.charAt(0) == '"' && !(value.endsWith("\"") || value.endsWith("OB"))) value = value + ' ' + st.nextToken(); String coord = null; String name = value; if (name.charAt(0) == '[') { // Object Coordinate StringTokenizer coordst = new StringTokenizer(name.substring(1, name.length() - 1), "."); while (coordst.hasMoreTokens()) if (!coordst.nextToken().equals("-1")) { coord = name; break; } name = st.nextToken(); // ToDo: IWD can't handle spaces in objstring // this opens a fat can of stupid with Icewind2 (below); IWD2 still has // problems decompiling from "dirty" BCS source (in source view) } if (name.endsWith("OB")) name = name.substring(0, name.length() - 2); else { value = st.nextToken(); if (!value.equalsIgnoreCase("OB")) { // Icewind2 numbers[numbersIndex++] = Integer.parseInt(value); numbers[numbersIndex++] = Integer.parseInt(st.nextToken()); st.nextToken(); // OB } } String ids[] = new String[numbersIndex - 5]; int index = 0; ids[index] = lookup(IdsMapCache.get("EA.IDS"), numbers[index++]); if (numbersIndex == 14) { ids[index] = lookup(IdsMapCache.get("FACTION.IDS"), numbers[index++]); ids[index] = lookup(IdsMapCache.get("TEAM.IDS"), numbers[index++]); } ids[index] = lookup(IdsMapCache.get("GENERAL.IDS"), numbers[index++]); ids[index] = lookup(IdsMapCache.get("RACE.IDS"), numbers[index++]); ids[index] = lookup(IdsMapCache.get("CLASS.IDS"), numbers[index++]); ids[index] = lookup(IdsMapCache.get("SPECIFIC.IDS"), numbers[index++]); ids[index] = lookup(IdsMapCache.get("GENDER.IDS"), numbers[index++]); if (numbersIndex == 15) { ids[index] = lookup(IdsMapCache.get("ALIGNMNT.IDS"), numbers[index++]); ids[index] = lookup(IdsMapCache.get("SUBRACE.IDS"), numbers[index++]); } else { ids[index] = lookup(IdsMapCache.get("ALIGN.IDS"), numbers[index++]); } IdsMap objectMap = IdsMapCache.get("OBJECT.IDS"); String obj[] = { lookup(objectMap, numbers[index++]), lookup(objectMap, numbers[index++]), lookup(objectMap, numbers[index++]), lookup(objectMap, numbers[index++]), lookup(objectMap, numbers[index++])}; if (numbersIndex == 15) { ids[index - 5] = lookup(IdsMapCache.get("CLASS.IDS"), numbers[index++]); ids[index - 5] = lookup(IdsMapCache.get("CLASSMSK.IDS"), numbers[index++]); } StringBuilder code = new StringBuilder(); StringBuilder endcode = new StringBuilder(); for (int i = 4; i > 0; i--) { if (obj[i] != null) { code.append(obj[i]).append('('); endcode.append(')'); } } if (obj[0] != null) code.append(obj[0]); if (!name.trim().equals("\"\"")) { if (code.length() == 0) code.append(name); else { code.append('(').append(name); endcode.append(')'); } } else { int maxids = ids.length - 1; while (maxids >= 0 && ids[maxids] == null) maxids--; if (maxids == -1 && code.length() == 0) code.append("[ANYONE]"); else if (maxids >= 0) { if (code.length() > 0) { code.append('('); endcode.append(')'); } code.append('['); if (ids[0] == null) code.append('0'); else code.append(ids[0]); for (int i = 1; i <= maxids; i++) { if (ids[i] == null) code.append(".0"); else code.append('.').append(ids[i]); } code.append(']'); } if (coord != null) code.append(coord); } return code.append(endcode).toString(); } private void decompileRE(StringBuilder code, StringTokenizer st) { String token = st.nextToken(); int i = token.indexOf("AC"); if (i != -1) { code.append(indent).append("RESPONSE #").append(token.substring(0, i)).append('\n'); lineNr++; token = token.substring(i); } else if (token.indexOf("RE") != -1) { code.append(indent).append("RESPONSE #").append(token.substring(0, token.indexOf("RE"))).append('\n'); lineNr++; return; } else { code.append(indent).append("RESPONSE #").append(token).append('\n'); lineNr++; token = st.nextToken(); } while (token.equalsIgnoreCase("AC")) { code.append(indent).append(indent); decompileAC(code, st); token = st.nextToken(); } } private void decompileRS(StringBuilder code, StringTokenizer st) { code.append("THEN\n"); lineNr++; String token = st.nextToken(); while (st.hasMoreTokens() && !token.equalsIgnoreCase("RS")) { if (token.equalsIgnoreCase("RE")) decompileRE(code, st); token = st.nextToken(); } } private static ResourceEntry decompileStringCheck(String value, String[] fileTypes) { for (final String fileType : fileTypes) { if (ResourceFactory.resourceExists(value + fileType, true)) { return ResourceFactory.getResourceEntry(value + fileType, true); } } return null; } private String decompileTR(StringTokenizer st) { int triggercode = Integer.parseInt(st.nextToken()); IdsMapEntry trigger = IdsMapCache.get("TRIGGER.IDS").getValue((long)triggercode); if (trigger == null) { trigger = IdsMapCache.get("TRIGGER.IDS").getValue((long)(0x4000 + triggercode)); triggercode += 0x4000; } if (trigger == null) { while (!st.nextToken().equals("TR")) ; if (generateErrors) idsErrors.put(new Integer(lineNr), triggercode - 0x4000 + " not found in TRIGGER.IDS"); lineNr++; return "Error - Could not find trigger " + (triggercode - 0x4000) + '\n'; } String object, coord = null; int numbers[] = new int[3]; String strings[] = null; StringBuilder code = new StringBuilder(); String comment = null; String token = st.nextToken(); if (token.endsWith("OB")) { numbers[0] = Integer.parseInt(token.substring(0, token.length() - 2)); object = decompileOB(st); st.nextToken(); // TR } else { numbers[0] = Integer.parseInt(token); if ((Integer.parseInt(st.nextToken()) & 1) == 1) // Not flag code.append('!'); numbers[1] = Integer.parseInt(st.nextToken()); numbers[2] = Integer.parseInt(st.nextToken()); String string1 = st.nextToken(); if (string1.charAt(0) == '[') { coord = string1; string1 = st.nextToken(); } while (string1.charAt(0) == '"' && string1.charAt(string1.length() - 1) != '"') string1 += ' ' + st.nextToken(); String string2 = st.nextToken(); while (string2.charAt(0) == '"' && string2.charAt(string2.length() - 1) != '"') string2 += ' ' + st.nextToken(); st.nextToken(); // OB object = decompileOB(st); st.nextToken(); // TR strings = modifyStrings(string1, string2); } StringTokenizer defParam = new StringTokenizer(trigger.getParameters(), ","); IdsMapEntry trigger2 = IdsMapCache.get("TRIGGER.IDS").getOverflowValue((long)triggercode); if (trigger2 != null) { if (useOverflowCommand(defParam, numbers, 0, 0, new String[]{object}, strings)) trigger = trigger2; defParam = new StringTokenizer(trigger.getParameters(), ","); } code.append(trigger.getString()); int index_i = 0, index_s = 0; boolean first = true; while (defParam.hasMoreTokens()) { if (!first) code.append(','); String p = defParam.nextToken(); if (p.substring(0, 2).equals("S:")) { String newp = strings[index_s++]; String function = trigger.getString().substring(0, trigger.getString().length() - 1); comment = getResourceName(function, p, newp.substring(1, newp.length() - 1)); code.append(newp); } else if (p.substring(0, 2).equals("O:")) code.append(object); else if (p.substring(0, 2).equals("P:")) code.append(coord.replaceFirst(",", ".")); // for WeiDU compatability else if (p.substring(0, 2).equals("I:")) { int nr = numbers[index_i++]; decompileInteger(code, (long)nr, p); StringBuilder sb = new StringBuilder(); decompileInteger(sb, (long)nr, p); comment = getResourceFileName(p, sb.toString()); } first = false; } lineNr++; if (comment != null) { return code.append(") // ").append(comment.replace('\n', ' ')).append('\n').toString(); } else { return code.append(")\n").toString(); } } private String getResourceName(String function, String definition, String value) { if (definition.startsWith("S:") && value.length() > 8) return null; ResourceEntry entry = null; if (definition.equalsIgnoreCase("S:DialogFile*")) entry = decompileStringCheck(value, new String[]{".DLG", ".VEF", ".VVC", ".BAM"}); else if (definition.equalsIgnoreCase("S:CutScene*") || definition.equalsIgnoreCase("S:ScriptFile*") || definition.equalsIgnoreCase("S:Script*")) entry = decompileStringCheck(value, new String[]{".BCS"}); else if (definition.equalsIgnoreCase("S:Item*") || definition.equalsIgnoreCase("S:Take*") || definition.equalsIgnoreCase("S:Give*") || definition.equalsIgnoreCase("S:OldObject*")) entry = decompileStringCheck(value, new String[]{".ITM"}); else if (definition.equalsIgnoreCase("S:Sound*") || definition.equalsIgnoreCase("S:Voice*")) entry = decompileStringCheck(value, new String[]{".WAV"}); else if (definition.equalsIgnoreCase("S:TextList*")) entry = decompileStringCheck(value, new String[]{".2DA"}); else if (definition.equalsIgnoreCase("S:Effect*")) entry = decompileStringCheck(value, new String[]{".VEF", ".VVC", ".BAM"}); else if (definition.equalsIgnoreCase("S:Parchment*")) entry = decompileStringCheck(value, new String[]{".MOS"}); else if (definition.equalsIgnoreCase("S:Spell*") || definition.equalsIgnoreCase("S:Res*")) entry = decompileStringCheck(value, new String[]{".SPL"}); else if (definition.equalsIgnoreCase("S:Store*")) entry = decompileStringCheck(value, new String[]{".STO"}); else if (definition.equalsIgnoreCase("S:ToArea*") || definition.equalsIgnoreCase("S:Areaname*") || definition.equalsIgnoreCase("S:FromArea*") || definition.equalsIgnoreCase("S:Area*") || definition.equalsIgnoreCase("S:Area1*") || definition.equalsIgnoreCase("S:Area2*")) entry = decompileStringCheck(value, new String[]{".ARE"}); else if (definition.equalsIgnoreCase("S:BamResRef*")) entry = decompileStringCheck(value, new String[]{".BAM"}); else if (definition.equalsIgnoreCase("S:Pool*")) entry = decompileStringCheck(value, new String[]{".SRC"}); else if (definition.equalsIgnoreCase("S:Palette*")) entry = decompileStringCheck(value, new String[]{".BMP"}); else if (definition.equalsIgnoreCase("S:ResRef*")) { entry = decompileStringCheck(value, getResRefType(function)); } else if (definition.equalsIgnoreCase("S:Object*")) { entry = decompileStringCheck(value, getResRefType(function)); } else if (definition.equalsIgnoreCase("S:NewObject*")) { entry = decompileStringCheck(value, getResRefType(function)); } else if (definition.equalsIgnoreCase("I:Spell*Spell")) { String refValue = org.infinity.resource.spl.Viewer.getResourceName(value, false); if (refValue != null) { entry = decompileStringCheck(refValue, new String[]{".SPL"}); } } // else // System.out.println("Decompiler.getResourceName: " + definition + " - " + value); if (entry != null) { if (generateErrors) { resourcesUsed.add(entry); } return entry.getSearchString(); } return null; } private String getResourceFileName(String definition, String value) { if (!definition.startsWith("I:")) { return null; } ResourceEntry entry = null; if (definition.equalsIgnoreCase("I:Spell*Spell")) { String refName = org.infinity.resource.spl.Viewer.getResourceName(value, false); if (refName != null) { entry = decompileStringCheck(refName, new String[]{".SPL"}); } } String retVal = null; if (entry != null) { if (generateErrors) { resourcesUsed.add(entry); } retVal = String.format("%1$s (%2$s)", entry.getResourceName(), entry.getSearchString()); } return retVal; } private String lookup(IdsMap idsmap, int code) { if (idsmap == null || code == 0) return null; IdsMapEntry entry = idsmap.getValue((long)code); if (entry != null) return entry.getString(); else { if (generateErrors) idsErrors.put(new Integer(lineNr), code + " not found in " + idsmap.toString()); return String.valueOf(code); } } private static String[] modifyStrings(String string1, String string2) { String newStrings[] = new String[4]; int index = 0; if (string1.length() > 9 && (Compiler.isPossibleNamespace(string1.substring(0, 7) + '\"') // && (!string1.substring(0, 3).equalsIgnoreCase("\"AR") || ResourceFactory.resourceExists(string1.substring(1, 7) + ".ARE"))) { newStrings[index++] = '\"' + string1.substring(7); newStrings[index++] = string1.substring(0, 7) + '\"'; } else newStrings[index++] = string1; if (string2.length() > 9 && (Compiler.isPossibleNamespace(string2.substring(0, 7) + '\"') // && (!string2.substring(0, 3).equalsIgnoreCase("\"AR") || ResourceFactory.resourceExists(string2.substring(1, 7) + ".ARE"))) { newStrings[index++] = '\"' + string2.substring(7); newStrings[index++] = string2.substring(0, 7) + '\"'; } else { String[] splitted = splitString(string2); for (int i = 0; i < splitted.length; i++) { newStrings[index++] = splitted[i]; } } return newStrings; } private static String[] splitString(String string) { String[] values = string.split(":"); String[] retVal = new String[values.length]; for (int i = 0; i < values.length; i++) { StringBuilder sb = new StringBuilder(values[i].length() + 2); if (values[i].length() == 0 || values[i].charAt(0) != '"') { sb.append('"'); } sb.append(values[i]); if (values[i].length() < 2 || values[i].charAt(values[i].length() - 1) != '"') { sb.append('"'); } retVal[i] = sb.toString(); } return retVal; } private static boolean useOverflowCommand(StringTokenizer defParam, int numbers[], int x, int y, String objects[], String strings[]) { // Count definition parameters int i_c1 = 0, p_c1 = 0, o_c1 = 0, s_c1 = 0; while (defParam.hasMoreTokens()) { String param = defParam.nextToken().substring(0, 2); if (param.equals("I:")) i_c1++; else if (param.equals("P:")) p_c1++; else if (param.equals("O:")) o_c1++; else if (param.equals("S:")) s_c1++; } // Count supplied parameters int i_count = 0, p_count = 0, o_count = 0, s_count = 0; for (final int number : numbers) if (number != 0) i_count++; if (x != 0 || y != 0) p_count = 1; for (final String object : objects) if (!object.equals("[ANYONE]")) o_count++; for (final String string : strings) if (string != null && !string.equals("\"\"")) s_count++; // Decide... if (i_count > i_c1 || p_count > p_c1 || o_count > o_c1 || s_count > s_c1) return true; return false; // Don't use overflow action/trigger } }