mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-02-16 15:40:14 +00:00
Merge remote-tracking branch 'origin/GP-4516_ryanmkurtz_PR-6337_agatti_midi-resource'
This commit is contained in:
commit
f2a94605eb
@ -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<Address> 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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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"
|
||||
|
@ -65,7 +65,7 @@ public class AudioPlayer implements Playable, LineListener {
|
||||
clip.start();
|
||||
}
|
||||
catch (UnsupportedAudioFileException | IOException | LineUnavailableException e) {
|
||||
Msg.debug(this, "Unable to play audio", e);
|
||||
Msg.error(this, "Unable to play audio", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,145 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.program.model.data;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.EOFException;
|
||||
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.program.model.mem.MemBuffer;
|
||||
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);
|
||||
}
|
||||
|
||||
public MIDIDataType(DataTypeManager dtm) {
|
||||
super(null, "MIDI-Score", dtm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength(MemBuffer buf, int maxLength) {
|
||||
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') {
|
||||
return -1;
|
||||
}
|
||||
long chunkLength = Integer.toUnsignedLong(stream.readInt());
|
||||
if (chunkLength != 6) {
|
||||
throw new InvalidDataTypeException("Unexpected header length.");
|
||||
}
|
||||
stream.skip(2);
|
||||
int tracks = Short.toUnsignedInt(stream.readShort());
|
||||
stream.skip(2);
|
||||
int computedLength = 14;
|
||||
while (tracks > 0) {
|
||||
if (stream.read(chunkType) < chunkType.length) {
|
||||
throw new EOFException();
|
||||
}
|
||||
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') {
|
||||
continue;
|
||||
}
|
||||
tracks--;
|
||||
}
|
||||
return computedLength;
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.debug(this, "Invalid MIDI data at " + buf.getAddress());
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSpecifyLength() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataType clone(DataTypeManager dtm) {
|
||||
if (dtm == getDataTypeManager()) {
|
||||
return this;
|
||||
}
|
||||
return new MIDIDataType(dtm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "MIDI score stored within program";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMnemonic(Settings settings) {
|
||||
return "MIDI";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRepresentation(MemBuffer buf, Settings settings, int length) {
|
||||
return "<MIDI-Resource>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(MemBuffer buf, Settings settings, int length) {
|
||||
byte[] data = new byte[length];
|
||||
if (buf.getBytes(data, 0) != length) {
|
||||
Msg.error(this, "MIDI-Score error: Not enough bytes!");
|
||||
return null;
|
||||
}
|
||||
return new ScorePlayer(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getValueClass(Settings settings) {
|
||||
return ScorePlayer.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultLabelPrefix(MemBuffer buf, Settings settings, int len,
|
||||
DataTypeDisplayOptions options) {
|
||||
return "MIDI";
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataType getReplacementBaseType() {
|
||||
return ByteDataType.dataType;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.program.model.data;
|
||||
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
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;
|
||||
|
||||
// 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 Sequencer currentSequencer;
|
||||
|
||||
private byte[] bytes;
|
||||
|
||||
public ScorePlayer(byte[] bytes) {
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getImageIcon() {
|
||||
return MIDI_ICON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clicked(MouseEvent event) {
|
||||
try {
|
||||
// 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);
|
||||
sequencer.setSequence(MidiSystem.getSequence(new ByteArrayInputStream(bytes)));
|
||||
sequencer.open();
|
||||
currentSequencer = sequencer;
|
||||
currentSequencer.start();
|
||||
}
|
||||
catch (MidiUnavailableException | InvalidMidiDataException | IOException e) {
|
||||
Msg.error(this, "Unable to play score", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void meta(MetaMessage message) {
|
||||
if (message.getType() == END_OF_TRACK_MESSAGE) {
|
||||
Swing.runNow(() -> stop());
|
||||
}
|
||||
}
|
||||
|
||||
private void stop() {
|
||||
if (currentSequencer == null) {
|
||||
return;
|
||||
}
|
||||
currentSequencer.removeMetaEventListener(this);
|
||||
currentSequencer.stop();
|
||||
currentSequencer.close();
|
||||
currentSequencer = null;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user