// 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; import java.awt.Window; import java.io.File; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.EnumMap; import java.util.List; import java.util.Locale; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.filechooser.FileNameExtensionFilter; import org.infinity.gui.NewChrSettings; import org.infinity.gui.NewProSettings; import org.infinity.gui.NewResSettings; import org.infinity.util.Misc; import org.infinity.util.ResourceStructure; import org.infinity.util.io.FileManager; import org.infinity.util.io.StreamUtils; // Create different pre-initialized IE game resources from scratch and writes them to disk. public final class StructureFactory { // Supported resource types public static enum ResType { RES_2DA, RES_ARE, RES_BAF, RES_BCS, RES_BIO, RES_CHR, RES_CRE, RES_EFF, RES_IDS, RES_INI, RES_ITM, RES_PRO, RES_RES, RES_SPL, RES_SRC, RES_STO, RES_VEF, RES_VVC, RES_WED, RES_WFX, RES_WMAP } private static final EnumMap<ResType, String> resExt = new EnumMap<ResType, String>(ResType.class); private static StructureFactory sfactory; static { resExt.put(ResType.RES_2DA, "2DA"); resExt.put(ResType.RES_ARE, "ARE"); resExt.put(ResType.RES_BAF, "BAF"); resExt.put(ResType.RES_BCS, "BCS"); resExt.put(ResType.RES_BIO, "BIO"); resExt.put(ResType.RES_CHR, "CHR"); resExt.put(ResType.RES_CRE, "CRE"); resExt.put(ResType.RES_EFF, "EFF"); resExt.put(ResType.RES_IDS, "IDS"); resExt.put(ResType.RES_INI, "INI"); resExt.put(ResType.RES_ITM, "ITM"); resExt.put(ResType.RES_PRO, "PRO"); resExt.put(ResType.RES_RES, "RES"); resExt.put(ResType.RES_SPL, "SPL"); resExt.put(ResType.RES_SRC, "SRC"); resExt.put(ResType.RES_STO, "STO"); resExt.put(ResType.RES_VEF, "VEF"); resExt.put(ResType.RES_VVC, "VVC"); resExt.put(ResType.RES_WED, "WED"); resExt.put(ResType.RES_WFX, "WFX"); resExt.put(ResType.RES_WMAP, "WMP"); }; public static StructureFactory getInstance() { if (sfactory == null) sfactory = new StructureFactory(); return sfactory; } // Write a new resource of specified type to disk public void newResource(ResType type, Window parent) { // use most appropriate initial folder for each file type Path savePath = null; switch (type) { case RES_BIO: case RES_CHR: case RES_RES: { List<Path> roots = new ArrayList<>(); if (Profile.isEnhancedEdition()) { roots.add(Profile.getHomeRoot()); roots.add(Profile.getGameRoot()); roots.add(Profile.getLanguageRoot()); } else { roots.add(Profile.getGameRoot()); } savePath = FileManager.query(roots, "Characters"); if (!Files.isDirectory(savePath)) { savePath = FileManager.query(Profile.getGameRoot(), Profile.getOverrideFolderName()); } break; } default: savePath = FileManager.query(Profile.getGameRoot(), Profile.getOverrideFolderName()); break; } if (savePath == null || !Files.isDirectory(savePath) ) { savePath = Profile.getGameRoot(); } JFileChooser fc = new JFileChooser(savePath.toFile()); String title = "Create new " + resExt.get(type) + " resource"; fc.setDialogTitle(title); fc.setFileFilter(new FileNameExtensionFilter(resExt.get(type) + " files", resExt.get(type).toLowerCase(Locale.ENGLISH))); fc.setFileSelectionMode(JFileChooser.FILES_ONLY); fc.setSelectedFile(new File(fc.getCurrentDirectory(), "UNTITLED." + resExt.get(type))); if (fc.showSaveDialog(parent) == JFileChooser.APPROVE_OPTION) { Path outFile = fc.getSelectedFile().toPath(); if (Files.exists(outFile)) { final String options[] = {"Overwrite", "Cancel"}; if (JOptionPane.showOptionDialog(parent, outFile + "exists. Overwrite?", title, JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[0]) != 0) return; } try { try { ResourceStructure struct = createStructure(type, outFile.getFileName().toString(), parent); if (struct != null) { try (OutputStream os = StreamUtils.getOutputStream(outFile, true)) { struct.write(os); } JOptionPane.showMessageDialog(parent, "File " + outFile + " created successfully.", title, JOptionPane.INFORMATION_MESSAGE); } else { unsupported(); } } catch (StructureException e) { switch (e.getReason()) { case UNSUPPORTED_TYPE: JOptionPane.showMessageDialog(parent, "Game doesn't support " + resExt.get(e.getType()) + " format!", "Error", JOptionPane.ERROR_MESSAGE); return; case CANCELLED_OPERATION: JOptionPane.showMessageDialog(parent, "Operation cancelled!", title, JOptionPane.INFORMATION_MESSAGE); return; default: throw e; } } } catch (Exception e) { JOptionPane.showMessageDialog(parent, "Error while creating " + outFile.getFileName(), title, JOptionPane.ERROR_MESSAGE); e.printStackTrace(); } } } // Create new structure of specified type public ResourceStructure createStructure(ResType type, String fileName, Window parent) throws StructureException { switch (type) { case RES_2DA: return create2DA(); case RES_ARE: return createARE(fileName); case RES_BAF: return createBAF(); case RES_BCS: return createBCS(); case RES_BIO: return createRES(parent); case RES_CHR: return createCHR(parent); case RES_CRE: return createCRE(); case RES_EFF: return createEFF(); case RES_IDS: return createIDS(); case RES_ITM: return createITM(); case RES_INI: return createINI(); case RES_PRO: return createPRO(parent); case RES_RES: return createRES(parent); case RES_SPL: return createSPL(); case RES_SRC: return createSRC(); case RES_STO: return createSTO(); case RES_VEF: return createVEF(); case RES_VVC: return createVVC(); case RES_WED: return createWED(); case RES_WFX: return createWFX(); case RES_WMAP: return createWMAP(); default: return createUnknown(); } } private ResourceStructure create2DA() throws StructureException { ResourceStructure s_2da = new ResourceStructure(); final String s = normalizeString("2DA V1.0\n0\n COLUMN1\nROW1 0\n"); s_2da.add(ResourceStructure.ID_STRING, s); return s_2da; } private ResourceStructure createARE(String fileName) throws StructureException { ResourceStructure s_are = new ResourceStructure(); String fileBase = extractFileBase(fileName); if (fileBase.length() > 8) fileBase = fileBase.substring(0, 8); boolean isV91 = Profile.getProperty(Profile.Key.IS_SUPPORTED_ARE_V91); s_are.add(ResourceStructure.ID_STRING, 4, "AREA"); // Signature s_are.add(ResourceStructure.ID_STRING, 4, isV91 ? "V9.1" : "V1.0"); // Version s_are.add(ResourceStructure.ID_RESREF, fileBase); // Area WED (replaced with actual WED filename) s_are.add(ResourceStructure.ID_BUFFER, isV91 ? 84 : 68); // block of zero int ofs = isV91 ? 0x12c : 0x11c; s_are.add(ResourceStructure.ID_DWORD, ofs); // Actors offset s_are.add(ResourceStructure.ID_DWORD); // 2x zero s_are.add(ResourceStructure.ID_DWORD, ofs); // Regions offset s_are.add(ResourceStructure.ID_DWORD, ofs); // Spawn points offset s_are.add(ResourceStructure.ID_DWORD); // zero s_are.add(ResourceStructure.ID_DWORD, ofs); // Entrances offset s_are.add(ResourceStructure.ID_DWORD); // zero s_are.add(ResourceStructure.ID_DWORD, ofs); // Containers offset s_are.add(ResourceStructure.ID_DWORD); // 2x zero s_are.add(ResourceStructure.ID_DWORD, ofs); // Items offset s_are.add(ResourceStructure.ID_DWORD, ofs); // Vertices offset s_are.add(ResourceStructure.ID_DWORD); // 2x zero s_are.add(ResourceStructure.ID_DWORD, ofs); // Ambients offset s_are.add(ResourceStructure.ID_DWORD, ofs); // Variables offset s_are.add(ResourceStructure.ID_BUFFER, 20); // block of zeros s_are.add(ResourceStructure.ID_DWORD, ofs); // Explored bitmask offset s_are.add(ResourceStructure.ID_DWORD); // zero s_are.add(ResourceStructure.ID_DWORD, ofs); // Doors offset s_are.add(ResourceStructure.ID_DWORD); // zero s_are.add(ResourceStructure.ID_DWORD, ofs); // Animations offset s_are.add(ResourceStructure.ID_DWORD); // zero s_are.add(ResourceStructure.ID_DWORD, ofs); // Tiled objects offset s_are.add(ResourceStructure.ID_DWORD, ofs, ofs); // Song entries offset s_are.add(ResourceStructure.ID_DWORD, ofs, ofs + 144); // Rest interruptions offset if (Profile.getEngine() == Profile.Engine.PST) { // Automap notes offset (except PST) s_are.add(ResourceStructure.ID_DWORD, -1); } else if (Profile.getEngine() == Profile.Engine.BG2) { // only BG2 actively uses automap notes in standalone ARE files s_are.add(ResourceStructure.ID_DWORD, ofs, ofs + 372); } else { s_are.add(ResourceStructure.ID_DWORD, ofs, 0); } s_are.add(ResourceStructure.ID_DWORD, 0); // PST: Automap notes offset s_are.add(ResourceStructure.ID_BUFFER, 80); // block of zeros // Song section s_are.add(ResourceStructure.ID_BUFFER, 144); // block of zeros // Rest interruptions section s_are.add(ResourceStructure.ID_BUFFER, 228); // block of zeros return s_are; } private ResourceStructure createBAF() throws StructureException { ResourceStructure s_baf = new ResourceStructure(); final String s = "// Empty BCS script" + Misc.LINE_SEPARATOR; s_baf.add(ResourceStructure.ID_STRING, s); return s_baf; } private ResourceStructure createBCS() throws StructureException { ResourceStructure s_bcs = new ResourceStructure(); final String s = normalizeString("SC\nSC\n"); s_bcs.add(ResourceStructure.ID_STRING, s); return s_bcs; } private ResourceStructure createCHR(Window parent) throws StructureException { NewChrSettings dlg = new NewChrSettings(parent); if (dlg.isAccepted()) { String name = dlg.getConfig().getName(); ResourceStructure s_chr = new ResourceStructure(); ResourceStructure s_cre = createCRE(); s_chr.add(ResourceStructure.ID_STRING, 4, "CHR "); // Signature if ((Boolean)Profile.getProperty(Profile.Key.IS_SUPPORTED_CHR_V22)) { s_chr.add(ResourceStructure.ID_STRING, 4, "V2.2"); // Version } else if ((Boolean)Profile.getProperty(Profile.Key.IS_SUPPORTED_CHR_V20)) { s_chr.add(ResourceStructure.ID_STRING, 4, "V2.0"); // Version } else { s_chr.add(ResourceStructure.ID_STRING, 4, "V1.0"); // Version } s_chr.add(ResourceStructure.ID_STRING, 32, name); // Name of Protagonist/Player if ((Boolean)Profile.getProperty(Profile.Key.IS_SUPPORTED_CHR_V22)) { s_chr.add(ResourceStructure.ID_DWORD, 0x0224); // Offset to CRE structure } else { s_chr.add(ResourceStructure.ID_DWORD, 0x0064); // Offset to CRE structure } s_chr.add(ResourceStructure.ID_DWORD, s_cre.size()); // Length of the CRE structure if ((Boolean)Profile.getProperty(Profile.Key.IS_SUPPORTED_CHR_V22)) { s_chr.add(ResourceStructure.ID_BUFFER, 500); // block of zeros } else { s_chr.add(ResourceStructure.ID_BUFFER, 52); // block of zeros } s_chr.add(ResourceStructure.ID_BUFFER, s_cre.size(), s_cre.getBuffer()); // CRE structure return s_chr; } else return cancelOperation(); } private ResourceStructure createCRE() throws StructureException { final String[] version = {"V1.0", "V1.2", "V2.2", "V9.0"}; final int[] ofs = {0x2d4, 0x378, 0, 0x33c}; final int[] count = {38, 46, 50, 38}; int idx; if ((Boolean)Profile.getProperty(Profile.Key.IS_SUPPORTED_CRE_V90)) { // IWD idx = 3; } else if ((Boolean)Profile.getProperty(Profile.Key.IS_SUPPORTED_CRE_V22)) { // IWD2 idx = 2; } else if ((Boolean)Profile.getProperty(Profile.Key.IS_SUPPORTED_CRE_V12)) { // PST idx = 1; } else { // BG1, BG2, EE idx = 0; } ResourceStructure s_cre = new ResourceStructure(); s_cre.add(ResourceStructure.ID_STRING, 4, "CRE "); // Signature s_cre.add(ResourceStructure.ID_STRING, 4, version[idx]); // Version s_cre.add(ResourceStructure.ID_STRREF, -1); // Long name strref s_cre.add(ResourceStructure.ID_STRREF, -1); // Short name strref s_cre.add(ResourceStructure.ID_BUFFER, 28); // block of zeros s_cre.add(ResourceStructure.ID_BUFFER, 8, new byte[]{30, 37, 57, 12, 23, 28, 0, 1}); // Color indices and EFF structure version s_cre.add(ResourceStructure.ID_BUFFER, 112); // block of zeros if (idx == 2) { s_cre.add(ResourceStructure.ID_BUFFER, 8); // block of zeros } for (int i = 0, size = (idx == 2) ? 64 : 100; i < size; i++) { s_cre.add(ResourceStructure.ID_DWORD, -1); // Char-related strrefs } if (idx == 2) { s_cre.add(ResourceStructure.ID_BUFFER, 182); // block of zeros } s_cre.add(ResourceStructure.ID_BUFFER, 4, new byte[]{0, 0, 0, 1}); // last byte: Gender if (idx == 3) { s_cre.add(ResourceStructure.ID_BUFFER, 172); // block of zeros } else if (idx == 2) { s_cre.add(ResourceStructure.ID_BUFFER, 298); // block of zeros } else if (idx == 1) { s_cre.add(ResourceStructure.ID_BUFFER, 92); // block of zeros } else { s_cre.add(ResourceStructure.ID_BUFFER, 68); // block of zeros } if (idx == 1) { s_cre.add(ResourceStructure.ID_DWORD, 0x3d8); // Overlays offset s_cre.add(ResourceStructure.ID_BUFFER, 136); // block of zeros } s_cre.add(ResourceStructure.ID_WORD, -1); // Global identifier s_cre.add(ResourceStructure.ID_WORD, -1); // Local identifier s_cre.add(ResourceStructure.ID_BUFFER, 32); // block of zeros if (idx == 2) { s_cre.add(ResourceStructure.ID_BUFFER, 6); // block of zeros } if (idx == 2) { for (int i = 0; i < 63; i++) { s_cre.add(ResourceStructure.ID_DWORD, 0x62e + i*8); // Spell levels offsets } s_cre.add(ResourceStructure.ID_BUFFER, 252); // blocks of zeros for (int i = 0; i < 9; i++) { s_cre.add(ResourceStructure.ID_DWORD, 0x826 + i*8); // Domain spells offsets } s_cre.add(ResourceStructure.ID_BUFFER, 36); // blocks of zeros for (int i = 0; i < 3; i++) { s_cre.add(ResourceStructure.ID_DWORD, 0x86e + i*8); // Spell levels offsets s_cre.add(ResourceStructure.ID_DWORD); // zero } s_cre.add(ResourceStructure.ID_DWORD, 0x886); // Item slots offset s_cre.add(ResourceStructure.ID_DWORD, 0x886); // Item offset s_cre.add(ResourceStructure.ID_DWORD); // zero s_cre.add(ResourceStructure.ID_DWORD, 0x886); // Effects offset s_cre.add(ResourceStructure.ID_BUFFER, 612); // block of zeros } else { s_cre.add(ResourceStructure.ID_DWORD, ofs[idx]); // Known spells offset s_cre.add(ResourceStructure.ID_DWORD); // zero s_cre.add(ResourceStructure.ID_DWORD, ofs[idx]); // Memorization info offset s_cre.add(ResourceStructure.ID_DWORD); // zero s_cre.add(ResourceStructure.ID_DWORD, ofs[idx]); // Memorized spells offset s_cre.add(ResourceStructure.ID_DWORD); // zero s_cre.add(ResourceStructure.ID_DWORD, ofs[idx]); // Item slots offset s_cre.add(ResourceStructure.ID_DWORD, ofs[idx]); // Item offset s_cre.add(ResourceStructure.ID_DWORD); // zero s_cre.add(ResourceStructure.ID_DWORD, ofs[idx]); // Effects offset s_cre.add(ResourceStructure.ID_BUFFER, 12); // block of zeros } for (int i = 0; i < count[idx]; i++) // item slots s_cre.add(ResourceStructure.ID_WORD, -1); s_cre.add(ResourceStructure.ID_WORD, 1000); // Weapon slot selected s_cre.add(ResourceStructure.ID_WORD); // zero return s_cre; } private ResourceStructure createEFF() throws StructureException { ResourceStructure s_eff = new ResourceStructure(); s_eff.add(ResourceStructure.ID_STRING, 4, "EFF "); // Signature s_eff.add(ResourceStructure.ID_STRING, 4, "V2.0"); // Version s_eff.add(ResourceStructure.ID_STRING, 4, "EFF "); // Signature 2 s_eff.add(ResourceStructure.ID_STRING, 4, "V2.0"); // Version 2 s_eff.add(ResourceStructure.ID_BUFFER, 256); // block of zeros return s_eff; } private ResourceStructure createIDS() throws StructureException { ResourceStructure s_ids = new ResourceStructure(); final String s = normalizeString("1\n0 Identifier1\n"); s_ids.add(ResourceStructure.ID_STRING, s); return s_ids; } private ResourceStructure createINI() throws StructureException { // TODO: distinguish between games ResourceStructure s_ini = new ResourceStructure(); final String s = normalizeString("[locals]\n\n[spawn_main]\n"); s_ini.add(ResourceStructure.ID_STRING, s); return s_ini; } private ResourceStructure createITM() throws StructureException { final String[] version = {"V1 ", "V1.1", "V2.0"}; final int[] ofs = {0x72, 0x9a, 0x82}; final int[] count = {4, 44, 20}; int idx; if ((Boolean)Profile.getProperty(Profile.Key.IS_SUPPORTED_ITM_V20)) { // IWD2 idx = 2; } else if ((Boolean)Profile.getProperty(Profile.Key.IS_SUPPORTED_ITM_V11)) { // PST idx = 1; } else { // BG1, BG2, IWD, EE idx = 0; } ResourceStructure s_itm = new ResourceStructure(); s_itm.add(ResourceStructure.ID_STRING, 4, "ITM "); // Signature s_itm.add(ResourceStructure.ID_STRING, 4, version[idx]); // Version s_itm.add(ResourceStructure.ID_STRREF, -1); // Unidentified name s_itm.add(ResourceStructure.ID_STRREF, -1); // Identified name s_itm.add(ResourceStructure.ID_BUFFER, 64); // block of zeros s_itm.add(ResourceStructure.ID_STRREF, -1); // Unidentified description s_itm.add(ResourceStructure.ID_STRREF, -1); // Identified description s_itm.add(ResourceStructure.ID_BUFFER, 12); // block of zeros s_itm.add(ResourceStructure.ID_DWORD, ofs[idx]); // Abilities offset s_itm.add(ResourceStructure.ID_WORD); // zero s_itm.add(ResourceStructure.ID_DWORD, ofs[idx]); // Effects offset if (idx == 1) { s_itm.add(ResourceStructure.ID_BUFFER, 12); // block of zeros s_itm.add(ResourceStructure.ID_STRREF, -1); // Conversable label s_itm.add(ResourceStructure.ID_BUFFER, count[idx] - 16); // block of zeros } else { s_itm.add(ResourceStructure.ID_BUFFER, count[idx]); // block of zeros } return s_itm; } private ResourceStructure createPRO(Window parent) throws StructureException { NewProSettings dlg = new NewProSettings(parent, 2); if (dlg.isAccepted()) { int type = dlg.getConfig().getProjectileType(); ResourceStructure s_pro = new ResourceStructure(); s_pro.add(ResourceStructure.ID_STRING, 4, "PRO "); // Signature s_pro.add(ResourceStructure.ID_STRING, 4, "V1.0"); // Version s_pro.add(ResourceStructure.ID_WORD, type); // Projectile type s_pro.add(ResourceStructure.ID_BUFFER, type*256 - 10); // block of zeros return s_pro; } else return cancelOperation(); } private ResourceStructure createRES(Window parent) throws StructureException { NewResSettings dlg = new NewResSettings(parent); if (dlg.isAccepted()) { String text = dlg.getConfig().getText(); ResourceStructure s_res = new ResourceStructure(); if (text.length() > 0) s_res.add(ResourceStructure.ID_STRING, text); return s_res; } else return cancelOperation(); } private ResourceStructure createSPL() throws StructureException { final String[] version = {"V1 ", "V2.0"}; final int[] ofs = {0x72, 0x82}; final int[] count = {4, 20}; int idx = (Boolean)Profile.getProperty(Profile.Key.IS_SUPPORTED_SPL_V2) ? 1 : 0; ResourceStructure s_spl = new ResourceStructure(); s_spl.add(ResourceStructure.ID_STRING, 4, "SPL "); // Signature s_spl.add(ResourceStructure.ID_STRING, 4, version[idx]); // Version s_spl.add(ResourceStructure.ID_STRREF, -1); // Unidentified spell name s_spl.add(ResourceStructure.ID_STRREF, -1); // Identified spell name s_spl.add(ResourceStructure.ID_BUFFER, 40); // block of zeros if (Profile.getEngine() == Profile.Engine.PST || Profile.getEngine() == Profile.Engine.IWD) { s_spl.add(ResourceStructure.ID_WORD, 1); // always set? } else { s_spl.add(ResourceStructure.ID_WORD, 0); // zero } s_spl.add(ResourceStructure.ID_BUFFER, 22); // block of zeros s_spl.add(ResourceStructure.ID_STRREF, -1); // Unidentified spell description s_spl.add(ResourceStructure.ID_STRREF, -1); // Identified spell description s_spl.add(ResourceStructure.ID_BUFFER, 12); // block of zeros s_spl.add(ResourceStructure.ID_DWORD, ofs[idx]); // Abilities offset s_spl.add(ResourceStructure.ID_WORD); // block of zeros s_spl.add(ResourceStructure.ID_DWORD, ofs[idx]); // Effects offset s_spl.add(ResourceStructure.ID_BUFFER, count[idx]); // block of zeros return s_spl; } private ResourceStructure createSRC() throws StructureException { if ((Boolean)Profile.getProperty(Profile.Key.IS_SUPPORTED_SRC_PST)) { ResourceStructure s_src = new ResourceStructure(); s_src.add(ResourceStructure.ID_DWORD, 1); // strref entry count s_src.add(ResourceStructure.ID_STRREF, -1); // strref s_src.add(ResourceStructure.ID_DWORD, 1); // always 1? return s_src; } else if ((Boolean)Profile.getProperty(Profile.Key.IS_SUPPORTED_SRC_IWD2)) { ResourceStructure s_src = new ResourceStructure(); final String s = "Placeholder text..."; s_src.add(ResourceStructure.ID_STRING, s); return s_src; } else return unsupportedFormat(ResType.RES_SRC); } private ResourceStructure createSTO() throws StructureException { final String[] version = {"V1.0", "V1.1", "V9.0"}; final int[] ofs = {0x9c, 0x9c, 0xf0}; final int[] count = {40, 40, 124}; int idx; if ((Boolean)Profile.getProperty(Profile.Key.IS_SUPPORTED_STO_V90)) { // IWD, IWD2 idx = 2; } else if ((Boolean)Profile.getProperty(Profile.Key.IS_SUPPORTED_STO_V11)) { // PST idx = 1; } else { // BG1, BG2, EE idx = 0; } ResourceStructure s_sto = new ResourceStructure(); s_sto.add(ResourceStructure.ID_STRING, 4, "STOR"); // Signature s_sto.add(ResourceStructure.ID_STRING, 4, version[idx]); // Version s_sto.add(ResourceStructure.ID_DWORD); // zero s_sto.add(ResourceStructure.ID_STRREF, -1); // name s_sto.add(ResourceStructure.ID_BUFFER, 28); // block of zeros s_sto.add(ResourceStructure.ID_DWORD, ofs[idx]); // Items purchased offset s_sto.add(ResourceStructure.ID_DWORD); // zero s_sto.add(ResourceStructure.ID_DWORD, ofs[idx]); // Items for sale offset s_sto.add(ResourceStructure.ID_BUFFER, 20); // block of zeros s_sto.add(ResourceStructure.ID_DWORD, ofs[idx]); // Drinks offset s_sto.add(ResourceStructure.ID_BUFFER, 32); // block of zeros s_sto.add(ResourceStructure.ID_DWORD, ofs[idx]); // Cures offset s_sto.add(ResourceStructure.ID_BUFFER, count[idx]); // block of zeros return s_sto; } private ResourceStructure createVEF() throws StructureException { ResourceStructure s_vef = new ResourceStructure(); s_vef.add(ResourceStructure.ID_STRING, 4, "VEF "); // Signature s_vef.add(ResourceStructure.ID_STRING, 4, "V1.0"); // Version s_vef.add(ResourceStructure.ID_DWORD, 0x18); // Component1 offset s_vef.add(ResourceStructure.ID_DWORD); // zero s_vef.add(ResourceStructure.ID_DWORD, 0x18); // Component2 offset s_vef.add(ResourceStructure.ID_DWORD); // zero return s_vef; } private ResourceStructure createVVC() throws StructureException { ResourceStructure s_vvc = new ResourceStructure(); s_vvc.add(ResourceStructure.ID_STRING, 4, "VVC "); // Signature s_vvc.add(ResourceStructure.ID_STRING, 4, "V1.0"); // Version s_vvc.add(ResourceStructure.ID_BUFFER, 484); // block of zeros return s_vvc; } private ResourceStructure createWED() throws StructureException { ResourceStructure s_wed = new ResourceStructure(); s_wed.add(ResourceStructure.ID_STRING, 4, "WED "); // Signature s_wed.add(ResourceStructure.ID_STRING, 4, "V1.3"); // Version s_wed.add(ResourceStructure.ID_DWORD, 5); // Overlays count s_wed.add(ResourceStructure.ID_DWORD); // Doors count s_wed.add(ResourceStructure.ID_DWORD, 0x20); // Overlays offset s_wed.add(ResourceStructure.ID_DWORD, 0x98); // Second header offset s_wed.add(ResourceStructure.ID_DWORD, 0xac); // Doors offset s_wed.add(ResourceStructure.ID_DWORD, 0xac); // Door tilemap loopup offset for (int i = 0; i < 5; i++) { // 5x Overlays s_wed.add(ResourceStructure.ID_BUFFER, 16); s_wed.add(ResourceStructure.ID_DWORD, 0xac); s_wed.add(ResourceStructure.ID_DWORD, 0xac); } s_wed.add(ResourceStructure.ID_DWORD, 0); // Wall poly count s_wed.add(ResourceStructure.ID_DWORD, 0xac); // Wall poly offset s_wed.add(ResourceStructure.ID_DWORD, 0xac); // Vertices offset s_wed.add(ResourceStructure.ID_DWORD, 0xac); // Wall groups offset s_wed.add(ResourceStructure.ID_DWORD, 0xac); // Wall poly lookup offset return s_wed; } private ResourceStructure createWFX() throws StructureException { ResourceStructure s_wfx = new ResourceStructure(); s_wfx.add(ResourceStructure.ID_STRING, 4, "WFX "); // Signature s_wfx.add(ResourceStructure.ID_STRING, 4, "V1.0"); // Version s_wfx.add(ResourceStructure.ID_BUFFER, 256); // block of zeros return s_wfx; } private ResourceStructure createWMAP() throws StructureException { ResourceStructure s_wmp = new ResourceStructure(); s_wmp.add(ResourceStructure.ID_STRING, 4, "WMAP"); // Signature s_wmp.add(ResourceStructure.ID_STRING, 4, "V1.0"); // Version s_wmp.add(ResourceStructure.ID_DWORD, 1); // Worldmap entries count s_wmp.add(ResourceStructure.ID_DWORD, 0x10); // Worldmap entries offset s_wmp.add(ResourceStructure.ID_RESREF); // Background MOS s_wmp.add(ResourceStructure.ID_BUFFER, 12); // block of zeros s_wmp.add(ResourceStructure.ID_STRREF, -1); // Area name s_wmp.add(ResourceStructure.ID_BUFFER, 12); // block of zeros s_wmp.add(ResourceStructure.ID_DWORD, 0x0c8); // Area entries offset s_wmp.add(ResourceStructure.ID_DWORD, 0x0c8); // Area link entries offset s_wmp.add(ResourceStructure.ID_DWORD); // zero s_wmp.add(ResourceStructure.ID_RESREF); // Map icons s_wmp.add(ResourceStructure.ID_BUFFER, 128); // block of zeros return s_wmp; } // create empty structure private ResourceStructure createUnknown() { return new ResourceStructure(); } private void unsupported() throws StructureException { throw new StructureException(); } private ResourceStructure unsupportedFormat(ResType type) throws StructureException { throw new StructureException(type); } private ResourceStructure cancelOperation() throws StructureException { throw new StructureException(StructureException.Reason.CANCELLED_OPERATION); } private String extractFileName(String fileName) { String[] s = fileName.split("[\\\\/]"); return (s.length > 0) ? s[s.length - 1] : fileName; } // returns filename without extension private String extractFileBase(String fileName) { String name = extractFileName(fileName); if (name.length() > 0) { int idx = name.lastIndexOf('.'); if (idx >= 0) return name.substring(0, idx); } return name; } private String normalizeString(String s) { if (s != null) { return s.replaceAll("\r?\n", Misc.LINE_SEPARATOR); } return ""; } //-------------------------- INNER CLASSES -------------------------- public static class StructureException extends Exception { public static enum Reason { UNSPECIFIED, CANCELLED_OPERATION, UNSUPPORTED_TYPE, UNSUPPORTED_GAME } private final ResType resType; private final Reason reason; public StructureException() { super(); this.reason = Reason.UNSPECIFIED; this.resType = ResType.RES_2DA; } public StructureException(Reason reason) { super(); this.reason = reason; this.resType = ResType.RES_2DA; } // Specialized ctor for 'unsupported resource type' public StructureException(ResType type) { super(); this.reason = Reason.UNSUPPORTED_TYPE; this.resType = type; } public Reason getReason() { return reason; } // valid only if reason == UNSUPPORTED_TYPE public ResType getType() { return resType; } } }