diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTrace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTrace.java index a620aa6575..4912377b78 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTrace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTrace.java @@ -525,22 +525,28 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace @Override public DBTraceProgramView getFixedProgramView(long snap) { - synchronized (fixedProgramViews) { - DBTraceProgramView view = fixedProgramViews.computeIfAbsent(snap, t -> { - Msg.debug(this, "Creating fixed view at snap=" + snap); - return new DBTraceProgramView(this, snap, baseCompilerSpec); - }); - return view; + // NOTE: The new viewport will need to read from the time manager during init + try (LockHold hold = lockRead()) { + synchronized (fixedProgramViews) { + DBTraceProgramView view = fixedProgramViews.computeIfAbsent(snap, t -> { + Msg.debug(this, "Creating fixed view at snap=" + snap); + return new DBTraceProgramView(this, snap, baseCompilerSpec); + }); + return view; + } } } @Override public DBTraceVariableSnapProgramView createProgramView(long snap) { - synchronized (programViews) { - DBTraceVariableSnapProgramView view = - new DBTraceVariableSnapProgramView(this, snap, baseCompilerSpec); - programViews.put(view, null); - return view; + // NOTE: The new viewport will need to read from the time manager during init + try (LockHold hold = lockRead()) { + synchronized (programViews) { + DBTraceVariableSnapProgramView view = + new DBTraceVariableSnapProgramView(this, snap, baseCompilerSpec); + programViews.put(view, null); + return view; + } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java index 4d8c299056..506da19a6b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java @@ -612,7 +612,9 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { void runScript(ResourceFile scriptFile, TaskListener listener) { lastRunScript = scriptFile; GhidraScript script = doGetScriptInstance(scriptFile); - doRunScript(script, listener); + if (script != null) { + doRunScript(script, listener); + } } private GhidraScript doGetScriptInstance(ResourceFile scriptFile) { diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4ExtentIdx.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4ExtentIdx.java index 8fb5d924fe..105465492c 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4ExtentIdx.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4ExtentIdx.java @@ -55,6 +55,15 @@ public class Ext4ExtentIdx implements StructConverter { return ei_leaf_hi; } + /** + * Return the calculated ei_leaf value by combining ei_leaf_lo and ei_leaf_hi + * + * @return the calculated ei_leaf value by combining ei_leaf_lo and ei_leaf_hi + */ + public long getEi_leaf() { + return (Short.toUnsignedLong(ei_leaf_hi) << 32) | Integer.toUnsignedLong(ei_leaf_lo); + } + public short getEi_unused() { return ei_unused; } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4FileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4FileSystem.java index 5d6f9f0a0b..118a6318e3 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4FileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4FileSystem.java @@ -40,6 +40,7 @@ public class Ext4FileSystem implements GFileSystem { private ByteProvider provider; private String volumeName; private String uuid; + private Ext4SuperBlock superBlock; public Ext4FileSystem(FSRLRoot fsrl, ByteProvider provider) { this.fsrl = fsrl; @@ -51,17 +52,18 @@ public class Ext4FileSystem implements GFileSystem { BinaryReader reader = new BinaryReader(provider, true); reader.setPointerIndex(0x400); - Ext4SuperBlock superBlock = new Ext4SuperBlock(reader); - + this.superBlock = new Ext4SuperBlock(reader); this.volumeName = superBlock.getVolumeName(); - this.uuid = NumericUtilities.convertBytesToString(superBlock.getS_uuid()); long blockCount = superBlock.getS_blocks_count(); - int s_log_block_size = superBlock.getS_log_block_size(); - blockSize = (int) Math.pow(2, (10 + s_log_block_size)); + this.blockSize = (int) Math.pow(2, (10 + s_log_block_size)); + int groupSize = blockSize * superBlock.getS_blocks_per_group(); + if (groupSize <= 0) { + throw new IOException("Invalid groupSize: " + groupSize); + } int numGroups = (int) (blockCount / superBlock.getS_blocks_per_group()); if (blockCount % superBlock.getS_blocks_per_group() != 0) { numGroups++; @@ -79,7 +81,7 @@ public class Ext4FileSystem implements GFileSystem { monitor.incrementProgress(1); } - Ext4Inode[] inodes = getInodes(reader, superBlock, groupDescriptors, monitor); + Ext4Inode[] inodes = getInodes(reader, groupDescriptors, monitor); int s_inodes_count = superBlock.getS_inodes_count(); for (int i = 0; i < s_inodes_count; i++) { @@ -88,17 +90,16 @@ public class Ext4FileSystem implements GFileSystem { continue; } if ((inode.getI_mode() & Ext4Constants.I_MODE_MASK) == Ext4Constants.S_IFDIR) { - processDirectory(reader, superBlock, inodes, i, null, null, monitor); + processDirectory(reader, inodes, i, null, null, monitor); } else if ((inode.getI_mode() & Ext4Constants.I_MODE_MASK) == Ext4Constants.S_IFREG) { - processFile(reader, superBlock, inode, monitor); + // TODO: handle files if needed here } } } - private void processDirectory(BinaryReader reader, Ext4SuperBlock superBlock, - Ext4Inode[] inodes, int index, String name, GFile parent, TaskMonitor monitor) - throws IOException, CancelledException { + private void processDirectory(BinaryReader reader, Ext4Inode[] inodes, int index, String name, + GFile parent, TaskMonitor monitor) throws IOException, CancelledException { if (name != null && (name.equals(".") || name.equals(".."))) { return; @@ -119,20 +120,23 @@ public class Ext4FileSystem implements GFileSystem { } boolean isDirEntry2 = (superBlock.getS_feature_incompat() & Ext4Constants.INCOMPAT_FILETYPE) != 0; - // if uses extents - if ((inode.getI_flags() & Ext4Constants.EXT4_EXTENTS_FL) != 0) { - Ext4IBlock i_block = inode.getI_block(); - processIBlock(reader, superBlock, inodes, parent, isDirEntry2, i_block, monitor); - } - else { - throw new IOException("File system fails to use extents."); - } + Ext4IBlock i_block = inode.getI_block(); + processDirectoryIBlock(reader, inodes, parent, isDirEntry2, i_block, monitor); + + // null out directories that have already been processed inodes[index] = null; } - private void processIBlock(BinaryReader reader, Ext4SuperBlock superBlock, Ext4Inode[] inodes, - GFile parent, boolean isDirEntry2, Ext4IBlock i_block, TaskMonitor monitor) - throws CancelledException, IOException { + interface Checked2Consumer { + void accept(T t) throws E1, E2; + } + + interface ExtentConsumer extends Checked2Consumer { + // no additional def + } + + private void forEachExtentEntry(Ext4IBlock i_block, ExtentConsumer extentConsumer, + TaskMonitor monitor) throws CancelledException, IOException { Ext4ExtentHeader header = i_block.getHeader(); if (header.getEh_depth() == 0) { short numEntries = header.getEh_entries(); @@ -140,42 +144,41 @@ public class Ext4FileSystem implements GFileSystem { for (int i = 0; i < numEntries; i++) { monitor.checkCanceled(); Ext4Extent extent = entries.get(i); - long offset = extent.getExtentStartBlockNumber() * blockSize; - reader.setPointerIndex(offset); - if (isDirEntry2) { - processDirEntry2(reader, superBlock, inodes, parent, monitor, extent, offset); - } - else { - processDirEntry(reader, superBlock, inodes, parent, monitor, extent, offset); - } + extentConsumer.accept(extent); } } else { - //throw new IOException( "Unhandled extent tree depth > 0 for inode " + index ); short numEntries = header.getEh_entries(); List entries = i_block.getIndexEntries(); for (int i = 0; i < numEntries; i++) { monitor.checkCanceled(); Ext4ExtentIdx extentIndex = entries.get(i); - long lo = extentIndex.getEi_leaf_lo(); - long hi = extentIndex.getEi_leaf_hi(); - long physicalBlockOfNextLevel = (hi << 16) | lo; - long offset = physicalBlockOfNextLevel * blockSize; + long offset = extentIndex.getEi_leaf() * blockSize; -// System.out.println( ""+physicalBlockOfNextLevel ); -// System.out.println( "" ); - - reader.setPointerIndex(offset); - Ext4IBlock intermediateBlock = new Ext4IBlock(reader, true); - processIBlock(reader, superBlock, inodes, parent, isDirEntry2, intermediateBlock, - monitor); + forEachExtentEntry(Ext4IBlock.readIBlockWithExtents(provider, offset), + extentConsumer, monitor); } } } - private void processDirEntry(BinaryReader reader, Ext4SuperBlock superBlock, Ext4Inode[] inodes, - GFile parent, TaskMonitor monitor, Ext4Extent extent, long offset) + private void processDirectoryIBlock(BinaryReader reader, Ext4Inode[] inodes, GFile parent, + boolean isDirEntry2, Ext4IBlock i_block, TaskMonitor monitor) + throws CancelledException, IOException { + forEachExtentEntry(i_block, extent -> { + long offset = extent.getExtentStartBlockNumber() * blockSize; + reader.setPointerIndex(offset); + if (isDirEntry2) { + processDirEntry2(reader, inodes, parent, monitor, extent, offset); + } + else { + processDirEntry(reader, inodes, parent, monitor, extent, offset); + } + }, monitor); + } + + private void processDirEntry(BinaryReader reader, Ext4Inode[] inodes, GFile parent, + TaskMonitor monitor, Ext4Extent extent, long offset) throws CancelledException, IOException { while ((reader.getPointerIndex() - offset) < ((long) extent.getEe_len() * blockSize)) { @@ -189,8 +192,7 @@ public class Ext4FileSystem implements GFileSystem { if ((child.getI_mode() & Ext4Constants.I_MODE_MASK) == Ext4Constants.S_IFDIR) { String childName = dirEnt.getName(); long readerOffset = reader.getPointerIndex(); - processDirectory(reader, superBlock, inodes, childIndex, childName, parent, - monitor); + processDirectory(reader, inodes, childIndex, childName, parent, monitor); reader.setPointerIndex(readerOffset); } else if ((child.getI_mode() & Ext4Constants.I_MODE_MASK) == Ext4Constants.S_IFREG || @@ -204,8 +206,8 @@ public class Ext4FileSystem implements GFileSystem { } } - private void processDirEntry2(BinaryReader reader, Ext4SuperBlock superBlock, - Ext4Inode[] inodes, GFile parent, TaskMonitor monitor, Ext4Extent extent, long offset) + private void processDirEntry2(BinaryReader reader, Ext4Inode[] inodes, GFile parent, + TaskMonitor monitor, Ext4Extent extent, long offset) throws CancelledException, IOException { while ((reader.getPointerIndex() - offset) < ((long) extent.getEe_len() * blockSize)) { @@ -218,8 +220,7 @@ public class Ext4FileSystem implements GFileSystem { int childInode = dirEnt2.getInode(); String childName = dirEnt2.getName(); long readerOffset = reader.getPointerIndex(); - processDirectory(reader, superBlock, inodes, childInode, childName, parent, - monitor); + processDirectory(reader, inodes, childInode, childName, parent, monitor); reader.setPointerIndex(readerOffset); } else if (dirEnt2.getFile_type() == Ext4Constants.FILE_TYPE_REGULAR_FILE || @@ -254,11 +255,6 @@ public class Ext4FileSystem implements GFileSystem { inodes[fileInodeNum] = null; } - private void processFile(BinaryReader reader, Ext4SuperBlock superBlock, Ext4Inode inode, - TaskMonitor monitor) { - - } - @Override public int getFileCount() { return fsih.getFileCount(); @@ -368,38 +364,38 @@ public class Ext4FileSystem implements GFileSystem { throw new IOException("Unsupported file storage: not EXT4_EXTENTS: " + file.getPath()); } - Ext4IBlock i_block = inode.getI_block(); - Ext4ExtentHeader header = i_block.getHeader(); - if (header.getEh_depth() != 0) { - throw new IOException("Unsupported file storage: eh_depth: " + file.getPath()); - } + try { + long fileSize = inode.getSize(); + ExtentsByteProvider result = new ExtentsByteProvider(provider, file.getFSRL()); - long fileSize = inode.getSize(); - ExtentsByteProvider ebp = new ExtentsByteProvider(provider, file.getFSRL()); - for (Ext4Extent extent : i_block.getExtentEntries()) { - long startPos = extent.getStreamBlockNumber() * blockSize; - long providerOfs = extent.getExtentStartBlockNumber() * blockSize; - long extentLen = extent.getExtentBlockCount() * blockSize; - if (ebp.length() < startPos) { - ebp.addSparseExtent(startPos - ebp.length()); - } - if (ebp.length() + extentLen > fileSize) { - // the last extent may have a trailing partial block - extentLen = fileSize - ebp.length(); - } + Ext4IBlock i_block = inode.getI_block(); + forEachExtentEntry(i_block, extent -> { + long startPos = extent.getStreamBlockNumber() * blockSize; + long providerOfs = extent.getExtentStartBlockNumber() * blockSize; + long extentLen = extent.getExtentBlockCount() * blockSize; + if (result.length() < startPos) { + result.addSparseExtent(startPos - result.length()); + } + if (result.length() + extentLen > fileSize) { + // the last extent may have a trailing partial block + extentLen = fileSize - result.length(); + } - ebp.addExtent(providerOfs, extentLen); + result.addExtent(providerOfs, extentLen); + }, monitor); + if (result.length() < fileSize) { + // trailing sparse. not sure if possible. + result.addSparseExtent(fileSize - result.length()); + } + return result; } - if (ebp.length() < fileSize) { - // trailing sparse. not sure if possible. - ebp.addSparseExtent(fileSize - ebp.length()); + catch (CancelledException e) { + throw new IOException(e); } - return ebp; } - private Ext4Inode[] getInodes(BinaryReader reader, Ext4SuperBlock superBlock, - Ext4GroupDescriptor[] groupDescriptors, TaskMonitor monitor) - throws IOException, CancelledException { + private Ext4Inode[] getInodes(BinaryReader reader, Ext4GroupDescriptor[] groupDescriptors, + TaskMonitor monitor) throws IOException, CancelledException { int inodeCount = superBlock.getS_inodes_count(); int inodesPerGroup = superBlock.getS_inodes_per_group(); diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4IBlock.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4IBlock.java index 7947bf9696..9c33f95947 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4IBlock.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/Ext4IBlock.java @@ -36,6 +36,21 @@ public class Ext4IBlock implements StructConverter { private boolean isExtentTree; + /** + * Reads an IBlock that is expected to contain extents + * + * @param provider {@link ByteProvider} to read from + * @param offset offset of the extent header + * @return new Ext4IBlock + * @throws IOException if error + */ + public static Ext4IBlock readIBlockWithExtents(ByteProvider provider, long offset) + throws IOException { + BinaryReader reader = new BinaryReader(provider, true); + reader.setPointerIndex(offset); + return new Ext4IBlock(reader, true); + } + public Ext4IBlock(ByteProvider provider, boolean isExtentTree) throws IOException { this( new BinaryReader( provider, true ), isExtentTree ); } @@ -48,14 +63,14 @@ public class Ext4IBlock implements StructConverter { count++; short numEntries = header.getEh_entries(); if( header.getEh_depth() > 0 ) { - indexEntries = new ArrayList(); + indexEntries = new ArrayList<>(); for( int i = 0; i < numEntries; i++ ) { indexEntries.add( new Ext4ExtentIdx(reader) ); count++; } } else { - extentEntries = new ArrayList(); + extentEntries = new ArrayList<>(); for( int i = 0; i < numEntries; i++ ) { extentEntries.add( new Ext4Extent(reader) ); count++; diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/ObjectCache.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/ObjectCache.java index 52c1762cc1..bf6883701b 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/ObjectCache.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/ObjectCache.java @@ -21,8 +21,8 @@ import java.util.*; /** * ObjectClass provides a fixed-size long-key-based object cache. - * Both a hard and weak cache are maintained, where the weak cache is only - * limited by available memory. This cache mechanism is useful in ensuring that + * Both a hard and weak cache are maintained, where the weak cache is only + * limited by available memory. This cache mechanism is useful in ensuring that * only a single object instance for a given key exists. *

* The weak cache is keyed, while the hard cache simply maintains the presence of @@ -42,8 +42,8 @@ public class ObjectCache { public ObjectCache(int hardCacheSize) { this.hardCacheSize = hardCacheSize; hashTable = new HashMap<>(); - refQueue = new ReferenceQueue(); - hardCache = new LinkedList(); + refQueue = new ReferenceQueue<>(); + hardCache = new LinkedList<>(); } /** @@ -100,7 +100,7 @@ public class ObjectCache { */ public synchronized void put(long key, Object obj) { processQueue(); - KeyedSoftReference ref = new KeyedSoftReference(key, obj, refQueue); + KeyedSoftReference ref = new KeyedSoftReference<>(key, obj, refQueue); hashTable.put(key, ref); addToHardCache(obj); } @@ -143,7 +143,7 @@ public class ObjectCache { /** * Provides a weak wrapper for a keyed-object */ - private class KeyedSoftReference extends WeakReference { + private static class KeyedSoftReference extends WeakReference { private long key; /** diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/DBObjectCache.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/DBObjectCache.java index e2e766c868..d05e427c40 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/DBObjectCache.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/DBObjectCache.java @@ -35,7 +35,7 @@ import ghidra.program.model.address.KeyRange; */ public class DBObjectCache { - private Map map; + private Map> map; private ReferenceQueue refQueue; private LinkedList hardCache; private int hardCacheSize; @@ -49,9 +49,9 @@ public class DBObjectCache { */ public DBObjectCache(int hardCacheSize) { this.hardCacheSize = hardCacheSize; - map = new HashMap(); - refQueue = new ReferenceQueue(); - hardCache = new LinkedList(); + map = new HashMap<>(); + refQueue = new ReferenceQueue<>(); + hardCache = new LinkedList<>(); } /** @@ -60,7 +60,7 @@ public class DBObjectCache { * @return the cached object or null if the object with that key is not currently cached. */ public synchronized T get(long key) { - KeyedSoftReference ref = map.get(key); + KeyedSoftReference ref = map.get(key); if (ref != null) { T obj = ref.get(); if (obj == null) { @@ -89,7 +89,7 @@ public class DBObjectCache { */ public synchronized T get(DBRecord objectRecord) { long key = objectRecord.getKey(); - KeyedSoftReference ref = map.get(key); + KeyedSoftReference ref = map.get(key); if (ref != null) { T obj = ref.get(); if (obj == null) { @@ -133,7 +133,7 @@ public class DBObjectCache { processQueue(); long key = data.getKey(); addToHardCache(data); - KeyedSoftReference ref = new KeyedSoftReference(key, data, refQueue); + KeyedSoftReference ref = new KeyedSoftReference<>(key, data, refQueue); map.put(key, ref); } @@ -142,9 +142,9 @@ public class DBObjectCache { * @return an List of all the cached objects. */ public synchronized List getCachedObjects() { - ArrayList list = new ArrayList(); + ArrayList list = new ArrayList<>(); processQueue(); - for (KeyedSoftReference ref : map.values()) { + for (KeyedSoftReference ref : map.values()) { T obj = ref.get(); if (obj != null) { list.add(obj); @@ -180,7 +180,7 @@ public class DBObjectCache { private void deleteSmallKeyRanges(List keyRanges) { for (KeyRange range : keyRanges) { for (long key = range.minKey; key <= range.maxKey; key++) { - KeyedSoftReference ref = map.remove(key); + KeyedSoftReference ref = map.remove(key); if (ref != null) { DatabaseObject obj = ref.get(); if (obj != null) { @@ -202,7 +202,7 @@ public class DBObjectCache { map.values().removeIf(ref -> checkRef(ref, keyRanges)); } - private boolean checkRef(KeyedSoftReference ref, List keyRanges) { + private boolean checkRef(KeyedSoftReference ref, List keyRanges) { long key = ref.getKey(); if (keyRangesContain(keyRanges, key)) { DatabaseObject obj = ref.get(); @@ -252,7 +252,7 @@ public class DBObjectCache { processQueue(); if (++invalidateCount <= 0) { invalidateCount = 1; - for (KeyedSoftReference ref : map.values()) { + for (KeyedSoftReference ref : map.values()) { DatabaseObject obj = ref.get(); if (obj != null) { obj.setInvalid(); @@ -276,7 +276,7 @@ public class DBObjectCache { */ public synchronized void delete(long key) { processQueue(); - KeyedSoftReference ref = map.get(key); + KeyedSoftReference ref = map.get(key); if (ref != null) { T obj = ref.get(); if (obj != null) { @@ -297,10 +297,10 @@ public class DBObjectCache { // we know the cast is safe--we put them in there @SuppressWarnings("unchecked") private void processQueue() { - KeyedSoftReference ref; - while ((ref = (KeyedSoftReference) refQueue.poll()) != null) { + KeyedSoftReference ref; + while ((ref = (KeyedSoftReference) refQueue.poll()) != null) { long key = ref.getKey(); - KeyedSoftReference oldValue = map.remove(key); + KeyedSoftReference oldValue = map.remove(key); if (oldValue != null && oldValue != ref) { // we have put another item in the cache with the same key. Further, we @@ -312,7 +312,7 @@ public class DBObjectCache { } } - private class KeyedSoftReference extends WeakReference { + private static class KeyedSoftReference extends WeakReference { private long key; KeyedSoftReference(long key, T obj, ReferenceQueue queue) { @@ -328,7 +328,7 @@ public class DBObjectCache { public synchronized void keyChanged(long oldKey, long newKey) { processQueue(); - KeyedSoftReference ref = map.remove(oldKey); + KeyedSoftReference ref = map.remove(oldKey); if (ref != null) { map.put(newKey, ref); T t = ref.get(); diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraJarApplicationLayout.java b/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraJarApplicationLayout.java index 16fb854b87..21b6922301 100644 --- a/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraJarApplicationLayout.java +++ b/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraJarApplicationLayout.java @@ -15,11 +15,9 @@ */ package ghidra; -import java.io.FileNotFoundException; -import java.io.IOException; +import java.io.*; import java.net.URL; import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; import java.util.*; import generic.jar.ResourceFile; @@ -93,6 +91,17 @@ public class GhidraJarApplicationLayout extends GhidraApplicationLayout { * @return A {@link ResourceFile} from the given {@link URL} */ private ResourceFile fromUrl(URL url) { - return new ResourceFile(URLDecoder.decode(url.toExternalForm(), StandardCharsets.UTF_8)); + String urlString = url.toExternalForm(); + try { + // Decode the URL to replace things like %20 with real spaces. + // Note: can't use URLDecoder.decode(String, Charset) because Utility must be + // Java 1.8 compatible. + urlString = URLDecoder.decode(urlString, "UTF-8"); + } + catch (UnsupportedEncodingException e) { + // Shouldn't happen, but failed to find UTF-8 encoding. + // Proceed without decoding, and hope for the best. + } + return new ResourceFile(urlString); } }