// Near Infinity - An Infinity Engine Browser and Editor // Copyright (C) 2001 - 2005 Jon Olav Hauglid // See LICENSE.txt for license information package org.infinity.util; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SeekableByteChannel; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.Arrays; import java.util.HashMap; import javax.swing.JOptionPane; import org.infinity.resource.Profile; import org.infinity.util.io.StreamUtils; public final class StringResource { private static final HashMap<Integer, StringEntry> cachedEntry = new HashMap<Integer, StringResource.StringEntry>(1000); private static Path dlgPath; private static ByteBuffer buffer; private static String version; private static int maxnr, startindex; private static Charset charset = Misc.CHARSET_DEFAULT; private static Charset usedCharset = charset; /** Returns the charset used to decode strings of the string resource. */ public static Charset getCharset() { return charset; } /** Specify the charset used to decode strings of the string resource. */ public static synchronized void setCharset(String cs) { charset = Charset.forName(cs); usedCharset = charset; } /** Explicitly closes the dialog.tlk file handle. */ public static synchronized void close() { if (buffer != null) { buffer = null; } cachedEntry.clear(); } /** Returns the {@link Path} instance of the dialog.tlk */ public static Path getPath() { return dlgPath; } /** Returns the available number of strref entries in the dialog.tlk */ public static int getMaxIndex() { return maxnr; } /** Returns whether the specified strref entry contains a sound resource. */ public static boolean hasWavResource(int index) { try { StringEntry entry = fetchStringEntry(index); return (entry.soundRes != null && !entry.soundRes.isEmpty()); } catch (IOException e) { e.printStackTrace(); } return false; } /** Returns the resource name of the sound file associated with the specified strref entry. */ public static String getWavResource(int index) { try { StringEntry entry = fetchStringEntry(index); if (entry.soundRes != null && !entry.soundRes.isEmpty()) { return entry.soundRes; } } catch (IOException e) { e.printStackTrace(); JOptionPane.showMessageDialog(null, "Error reading " + dlgPath.getFileName().toString(), "Error", JOptionPane.ERROR_MESSAGE); } return null; } /** Returns the string of the specified sttref entry. */ public static String getStringRef(int index) { return getStringRef(index, false); } /** Returns the string of the specified sttref entry, optionally with an appended strref value. */ public static String getStringRef(int index, boolean extended) { return getStringRef(index, extended, false); } /** * Returns the string of the specified strref entry. Optionally adds the specified * Strref entry to the returned string. * @param index The strref entry * @param extended If {@code true} adds the specified strref entry to the resulting string. * @param asPrefix Strref value is prepended (if {@code true}) or appended * (if {@code false}) to the string. Ignored if "extended" is {@code false}. * @return The string optionally including the strref entry. */ public static String getStringRef(int index, boolean extended, boolean asPrefix) { final String fmtResult; if (extended) { fmtResult = asPrefix ? "(Strref: %2$d) %1$s" : "%1$s (Strref: %2$d)"; } else { fmtResult = "%1$s"; } try { StringEntry entry = fetchStringEntry(index); if (entry != null) { return String.format(fmtResult, entry.text, entry.strref); } } catch (IOException e) { e.printStackTrace(); JOptionPane.showMessageDialog(null, "Error reading " + dlgPath.getFileName().toString(), "Error", JOptionPane.ERROR_MESSAGE); } return "Error"; } /** Returns message type of specified strref. */ public short getFlags(int index) { try { StringEntry entry = fetchStringEntry(index); if (entry != null) { return entry.type; } } catch (IOException e) { e.printStackTrace(); } return 0; } /** Returns volume variance of the specified strref. */ public int getVolume(int index) { try { StringEntry entry = fetchStringEntry(index); if (entry != null) { return entry.volume; } } catch (IOException e) { e.printStackTrace(); } return 0; } /** Returns pitch variance of the specified strref. */ public int getPitch(int index) { try { StringEntry entry = fetchStringEntry(index); if (entry != null) { return entry.pitch; } } catch (IOException e) { e.printStackTrace(); } return 0; } /** Specify a new dialog.tlk. */ public static void init(Path dlgPath) { close(); StringResource.dlgPath = dlgPath; } private static synchronized void open() throws IOException { if (buffer == null) { try (SeekableByteChannel ch = Files.newByteChannel(dlgPath, StandardOpenOption.READ)) { buffer = StreamUtils.getByteBuffer((int)ch.size()); if (ch.read(buffer) < ch.size()) { throw new IOException(); } buffer.position(0); String sig = StreamUtils.readString(buffer, 4); if (!sig.equals("TLK ")) { buffer = null; throw new IOException("Not valid TLK file"); } version = StreamUtils.readString(buffer, 4); if (version.equals("V1 ")) { buffer.position(0x0a); } else { buffer = null; throw new IOException("Invalid TLK version"); } maxnr = buffer.getInt(); startindex = buffer.getInt(); if (Profile.isEnhancedEdition()) { usedCharset = Misc.CHARSET_UTF8; } } } } private static synchronized StringEntry fetchStringEntry(int index) throws IOException { StringEntry entry = cachedEntry.get(Integer.valueOf(index)); if (entry == null) { entry = new StringEntry(index); cachedEntry.put(Integer.valueOf(index), entry); } return entry; } private StringResource(){} //-------------------------- INNER CLASSES -------------------------- private static class StringEntry { public final int strref; public final short type; public final String soundRes; public final int volume, pitch; public final String text; private StringEntry(int index) throws IOException { open(); if (index >= 0 && index < maxnr ) { strref = index; index *= 0x1a; buffer.position(0x12 + index); type = buffer.getShort(); byte[] buf = new byte[8]; buffer.get(buf); int len = buf.length; for (int i = 0; i < buf.length; i++) { if (buf[i] == 0) { len = i; break; } } soundRes = new String(Arrays.copyOf(buf, len)); volume = buffer.getInt(); pitch = buffer.getInt(); long offset = startindex + buffer.getInt(); int length = buffer.getInt(); buffer.position((int)offset); text = StreamUtils.readString(buffer, length, usedCharset); } else { strref = -1; type = 0; volume = pitch = 0; soundRes = null; text = "No such index"; } } } }