From 30600863cf12d8a4db42007f44ae8fd2e3d7928b Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Thu, 2 May 2024 12:41:22 -0400 Subject: [PATCH] GP-4516: Improvements --- .../FindAudioInProgramScript.java | 19 ++-- .../core/analysis/EmbeddedMediaAnalyzer.java | 3 + .../bin/format/pe/ResourceDataDirectory.java | 13 +++ .../program/model/data/MIDIDataType.java | 86 +++++++------------ .../program/model/data/ScorePlayer.java | 50 +++++------ 5 files changed, 75 insertions(+), 96 deletions(-) diff --git a/Ghidra/Features/Base/ghidra_scripts/FindAudioInProgramScript.java b/Ghidra/Features/Base/ghidra_scripts/FindAudioInProgramScript.java index bc49371a36..3b04838e24 100644 --- a/Ghidra/Features/Base/ghidra_scripts/FindAudioInProgramScript.java +++ b/Ghidra/Features/Base/ghidra_scripts/FindAudioInProgramScript.java @@ -15,17 +15,16 @@ */ //Finds programs containing various audio resources such as WAV's //@category Resources +import java.util.ArrayList; +import java.util.List; + import ghidra.app.script.GhidraScript; import ghidra.program.model.address.Address; -import ghidra.program.model.data.DataType; -import ghidra.program.model.data.WAVEDataType; +import ghidra.program.model.data.*; import ghidra.program.model.listing.Data; import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.MemoryBlock; -import java.util.ArrayList; -import java.util.List; - public class FindAudioInProgramScript extends GhidraScript { @Override @@ -36,8 +35,10 @@ public class FindAudioInProgramScript extends GhidraScript { //look for WAV data types WAVEDataType wdt = new WAVEDataType(); + MIDIDataType mdt = new MIDIDataType(); totalFound += findAudioData("WAV", wdt, WAVEDataType.MAGIC, WAVEDataType.MAGIC_MASK); + totalFound += findAudioData("MIDI", mdt, MIDIDataType.MAGIC, MIDIDataType.MAGIC_MASK); if (totalFound == 0) { println("No Audio data found in " + currentProgram.getName()); @@ -54,12 +55,12 @@ public class FindAudioInProgramScript extends GhidraScript { int numDataFound = 0; List
foundList = scanForAudioData(pattern, mask); - //Loop over all potential found WAVs + //Loop over all potential found audio for (int i = 0; i < foundList.size(); i++) { boolean foundData = false; - //See if already applied WAV + //See if already applied data type Data data = getDataAt(foundList.get(i)); - //If not already applied, try to apply WAV data type + //If not already applied, try to apply audio data type if (data == null) { println("Trying to apply " + dataName + " datatype at " + foundList.get(i).toString()); @@ -67,7 +68,7 @@ public class FindAudioInProgramScript extends GhidraScript { try { Data newData = createData(foundList.get(i), dt); if (newData != null) { - println("Applied WAV at " + newData.getAddressString(false, true)); + printf("Applied %s at %s", dataName, newData.getAddressString(false, true)); foundData = true; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/EmbeddedMediaAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/EmbeddedMediaAnalyzer.java index 141404a300..334acc3a1f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/EmbeddedMediaAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/EmbeddedMediaAnalyzer.java @@ -81,6 +81,9 @@ public class EmbeddedMediaAnalyzer extends AbstractAnalyzer { addByteSearchPattern(searcher, program, foundMedia, new WAVEDataType(), "WAVE", WAVEDataType.MAGIC, WAVEDataType.MAGIC_MASK); + addByteSearchPattern(searcher, program, foundMedia, new MIDIDataType(), "MIDI", + MIDIDataType.MAGIC, MIDIDataType.MAGIC_MASK); + addByteSearchPattern(searcher, program, foundMedia, new AUDataType(), "AU", AUDataType.MAGIC, AUDataType.MAGIC_MASK); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/ResourceDataDirectory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/ResourceDataDirectory.java index b6aa8db00b..688211a313 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/ResourceDataDirectory.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/ResourceDataDirectory.java @@ -269,6 +269,19 @@ public class ResourceDataDirectory extends DataDirectory { } PeUtils.createData(program, addr, dataType, log); } + else if (info.getName().startsWith("Rsrc_MIDI")) { + DataType dataType = null; + // Check for MIDI magic number + try { + if (program.getMemory().getInt(addr) == 0x6468544d) { + dataType = new MIDIDataType(); + } + } + catch (MemoryAccessException e) { + // ignore - let createData produce error + } + PeUtils.createData(program, addr, dataType, log); + } else if (info.getName().startsWith("Rsrc_WEVT")) { DataType dataType = null; // Check for WEVT magic number "CRIM" diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/MIDIDataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/MIDIDataType.java index 4a93231918..4b2ab9eb6a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/MIDIDataType.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/MIDIDataType.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,16 +15,25 @@ */ package ghidra.program.model.data; +import java.io.DataInputStream; import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; import ghidra.docking.settings.Settings; import ghidra.program.model.mem.MemBuffer; -import ghidra.program.model.mem.Memory; import ghidra.util.Msg; public class MIDIDataType extends BuiltIn implements Dynamic { + + public static byte[] MAGIC = + new byte[] { (byte) 'M', (byte) 'T', (byte) 'h', (byte) 'd', (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 'M', (byte) 'T', (byte) 'r', (byte) 'k' }; + + public static byte[] MAGIC_MASK = + new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff }; + public MIDIDataType() { this(null); } @@ -40,80 +49,43 @@ public class MIDIDataType extends BuiltIn implements Dynamic { @Override public int getLength(MemBuffer buf, int maxLength) { - try { - return computeLength(buf, maxLength); - } - catch (Exception e) { - Msg.debug(this, "Invalid MIDI data at " + buf.getAddress()); - } - return -1; - } - - private long readUnsignedInteger(InputStream stream) throws IOException { - long value = 0; - for (int index = 0; index < 4; index++) { - int currentByte = stream.read(); - if (currentByte == -1) { - throw new EOFException(); - } - value = (value << 8) | currentByte; - } - return value; - } - - private int readUnsignedShort(InputStream stream) throws IOException { - int value = 0; - for (int index = 0; index < 2; index++) { - int currentByte = stream.read(); - if (currentByte == -1) { - throw new EOFException(); - } - value = (value << 8) | currentByte; - } - return value; - } - - private int computeLength(MemBuffer buf, int maxLength) throws IOException, InvalidDataTypeException { - int computedLength = -1; - - try (InputStream stream = buf.getInputStream(0, maxLength > 0 ? maxLength : Integer.MAX_VALUE)) { + try (DataInputStream stream = new DataInputStream( + buf.getInputStream(0, maxLength > 0 ? maxLength : Integer.MAX_VALUE))) { byte[] chunkType = new byte[4]; if (stream.read(chunkType) < chunkType.length) { throw new EOFException(); } - if (chunkType[0] != (byte)'M' || - chunkType[1] != (byte)'T' || - chunkType[2] != (byte)'h' || - chunkType[3] != (byte)'d') { + if (chunkType[0] != (byte) 'M' || chunkType[1] != (byte) 'T' || + chunkType[2] != (byte) 'h' || chunkType[3] != (byte) 'd') { return -1; } - long chunkLength = readUnsignedInteger(stream); + long chunkLength = Integer.toUnsignedLong(stream.readInt()); if (chunkLength != 6) { throw new InvalidDataTypeException("Unexpected header length."); } stream.skip(2); - int tracks = readUnsignedShort(stream); + int tracks = Short.toUnsignedInt(stream.readShort()); stream.skip(2); - computedLength = 14; + int computedLength = 14; while (tracks > 0) { if (stream.read(chunkType) < chunkType.length) { throw new EOFException(); } - chunkLength = readUnsignedInteger(stream); + chunkLength = Integer.toUnsignedLong(stream.readInt()); stream.skip(chunkLength); computedLength += 8 + chunkLength; - if (chunkType[0] != (byte)'M' || - chunkType[1] != (byte)'T' || - chunkType[2] != (byte)'r' || - chunkType[3] != (byte)'k') { + if (chunkType[0] != (byte) 'M' || chunkType[1] != (byte) 'T' || + chunkType[2] != (byte) 'r' || chunkType[3] != (byte) 'k') { continue; } tracks--; } - } finally { + return computedLength; + } + catch (Exception e) { + Msg.debug(this, "Invalid MIDI data at " + buf.getAddress()); + return -1; } - - return computedLength; } @Override diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ScorePlayer.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ScorePlayer.java index d8c7dd33c4..48251df628 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ScorePlayer.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ScorePlayer.java @@ -19,29 +19,24 @@ import java.awt.event.MouseEvent; import java.io.ByteArrayInputStream; import java.io.IOException; -import javax.sound.midi.InvalidMidiDataException; -import javax.sound.midi.MetaEventListener; -import javax.sound.midi.MetaMessage; -import javax.sound.midi.MidiSystem; -import javax.sound.midi.MidiUnavailableException; -import javax.sound.midi.Sequence; -import javax.sound.midi.Sequencer; +import javax.sound.midi.*; import javax.swing.Icon; import generic.theme.GIcon; import ghidra.util.Msg; import ghidra.util.Swing; +/** + * Plays a MIDI score + */ public class ScorePlayer implements Playable, MetaEventListener { + private static final Icon MIDI_ICON = new GIcon("icon.data.type.audio.player"); private static final int END_OF_TRACK_MESSAGE = 47; - private static final Icon AUDIO_ICON = new GIcon("icon.data.type.audio.player"); - // This currently only allows one sequence to be played for the entire application, // which seems good enough. The MIDI instance variables are currently synchronized // by the Swing thread. - private static volatile Sequence currentSequence; private static volatile Sequencer currentSequencer; private byte[] bytes; @@ -52,30 +47,23 @@ public class ScorePlayer implements Playable, MetaEventListener { @Override public Icon getImageIcon() { - return AUDIO_ICON; + return MIDI_ICON; } @Override public void clicked(MouseEvent event) { try { - // any new request should stop any previous sequence being played - if (currentSequence != null && currentSequencer != null) { - assert currentSequencer.isOpen(); - currentSequencer.stop(); - currentSequence = null; // this field is also updated when the sound thread calls back - currentSequencer = null; // same as above + // Any new request should stop any previous sequence being played + if (currentSequencer != null) { + stop(); return; } Sequencer sequencer = MidiSystem.getSequencer(true); sequencer.addMetaEventListener(this); sequencer.setLoopCount(0); - Sequence sequence = MidiSystem.getSequence(new ByteArrayInputStream(bytes)); - if (!sequencer.isOpen()) { - sequencer.open(); - } - sequencer.setSequence(sequence); - currentSequence = sequence; + sequencer.setSequence(MidiSystem.getSequence(new ByteArrayInputStream(bytes))); + sequencer.open(); currentSequencer = sequencer; currentSequencer.start(); } @@ -86,16 +74,18 @@ public class ScorePlayer implements Playable, MetaEventListener { @Override public void meta(MetaMessage message) { - if (message.getType() != END_OF_TRACK_MESSAGE) { + if (message.getType() == END_OF_TRACK_MESSAGE) { + Swing.runNow(() -> stop()); + } + } + + private void stop() { + if (currentSequencer == null) { return; } - - assert currentSequencer != null && currentSequence != null; currentSequencer.removeMetaEventListener(this); currentSequencer.stop(); - Swing.runNow(() -> { - currentSequence = null; - currentSequencer = null; - }); + currentSequencer.close(); + currentSequencer = null; } }