/*
* jfhex.java
*
* Created on July 30, 2007, 10:13 AM
*/
/**
* Multi-tabbed hex/text editor.
*
* @author pquiring
*/
import javaforce.*;
import java.io.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
public class jfhex extends javax.swing.JFrame implements FindEvent, ReplaceEvent, DocumentListener {
/** Creates new form jfhex */
public jfhex() {
initComponents();
initApp();
}
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
tabs = new javax.swing.JTabbedPane();
jToolBar1 = new javax.swing.JToolBar();
status = new javax.swing.JLabel();
FormListener formListener = new FormListener();
setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE);
addWindowListener(formListener);
addComponentListener(formListener);
addWindowStateListener(formListener);
tabs.addChangeListener(formListener);
tabs.addKeyListener(formListener);
jToolBar1.setFloatable(false);
jToolBar1.setRollover(true);
status.setText("...");
jToolBar1.add(status);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(tabs, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE)
.addComponent(jToolBar1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(tabs, javax.swing.GroupLayout.DEFAULT_SIZE, 297, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jToolBar1, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE))
);
pack();
}
// Code for dispatching events from components to event handlers.
private class FormListener implements java.awt.event.ComponentListener, java.awt.event.KeyListener, java.awt.event.WindowListener, java.awt.event.WindowStateListener, javax.swing.event.ChangeListener {
FormListener() {}
public void componentHidden(java.awt.event.ComponentEvent evt) {
}
public void componentMoved(java.awt.event.ComponentEvent evt) {
if (evt.getSource() == jfhex.this) {
jfhex.this.formComponentMoved(evt);
}
}
public void componentResized(java.awt.event.ComponentEvent evt) {
if (evt.getSource() == jfhex.this) {
jfhex.this.formComponentResized(evt);
}
}
public void componentShown(java.awt.event.ComponentEvent evt) {
}
public void keyPressed(java.awt.event.KeyEvent evt) {
if (evt.getSource() == tabs) {
jfhex.this.tabsKeyPressed(evt);
}
}
public void keyReleased(java.awt.event.KeyEvent evt) {
}
public void keyTyped(java.awt.event.KeyEvent evt) {
}
public void windowActivated(java.awt.event.WindowEvent evt) {
}
public void windowClosed(java.awt.event.WindowEvent evt) {
}
public void windowClosing(java.awt.event.WindowEvent evt) {
if (evt.getSource() == jfhex.this) {
jfhex.this.formWindowClosing(evt);
}
}
public void windowDeactivated(java.awt.event.WindowEvent evt) {
}
public void windowDeiconified(java.awt.event.WindowEvent evt) {
}
public void windowIconified(java.awt.event.WindowEvent evt) {
}
public void windowOpened(java.awt.event.WindowEvent evt) {
}
public void windowStateChanged(java.awt.event.WindowEvent evt) {
if (evt.getSource() == jfhex.this) {
jfhex.this.formWindowStateChanged(evt);
}
}
public void stateChanged(javax.swing.event.ChangeEvent evt) {
if (evt.getSource() == tabs) {
jfhex.this.tabsStateChanged(evt);
}
}
}// </editor-fold>//GEN-END:initComponents
private void formComponentMoved(java.awt.event.ComponentEvent evt) {//GEN-FIRST:event_formComponentMoved
if (Settings.bWindowMax) return;
Point loc = getLocation();
Settings.WindowXPos = loc.x;
Settings.WindowYPos = loc.y;
}//GEN-LAST:event_formComponentMoved
private void formComponentResized(java.awt.event.ComponentEvent evt) {//GEN-FIRST:event_formComponentResized
// System.out.println("resized");
if (Settings.bWindowMax) return;
Dimension size = getSize();
Settings.WindowXSize = size.width;
Settings.WindowYSize = size.height;
}//GEN-LAST:event_formComponentResized
private void formWindowStateChanged(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowStateChanged
// System.out.println("stateChanged");
Settings.bWindowMax = evt.getNewState() == MAXIMIZED_BOTH;
}//GEN-LAST:event_formWindowStateChanged
private void tabsKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_tabsKeyPressed
//Key Pressed
updateStatus();
int f1 = evt.getKeyCode();
int f2 = evt.getModifiers();
int idx;
if ((f1 == KeyEvent.VK_F1) && (f2 == 0)) {
JOptionPane.showMessageDialog(this,
"jfhex/" + JF.getVersion() + "\n\n" +
"F1 = Help\n" +
"F2 = Edit Settings\n" +
"CTRL-O = Open\n" +
"CTRL-W = Close\n" +
"CTRL-S = Save\n" +
"CTRL-Q = Save As\n" +
"CTRL-F = Find (string)\n" +
"CTRL-G/F3 = Find Again\n" +
"CTRL-R = Replace (string)\n" +
"CTRL-B = Find (binary)\n" +
"CTRL-T = Replace (binary)\n" +
"CTRL-L = Goto Byte Offset\n" +
"CTRL-E = Execute Command\n" +
"ALT-# = Switch to document\n\n"
, "Help", JOptionPane.INFORMATION_MESSAGE);
return;
}
if ((f1 == KeyEvent.VK_F2) && (f2 == 0)) {
EditSettings.editSettings(this);
Settings.fnt = JF.getMonospacedFont(0, Settings.fontSize);
Hex.changeFont(Settings.fnt);
int cnt = pages.size();
for(int a=0;a<cnt;a++) {
pages.get(a).hex.repaint();
}
}
if ((f1 == KeyEvent.VK_N) && (f2 == KeyEvent.CTRL_MASK)) { addpage("untitled", ""); return; }
if ((f1 == KeyEvent.VK_S) && (f2 == KeyEvent.CTRL_MASK)) { savepage(); return; }
if ((f1 == KeyEvent.VK_Q) && (f2 == KeyEvent.CTRL_MASK)) { savepageas(); return; }
if ((f1 == KeyEvent.VK_W) && (f2 == KeyEvent.CTRL_MASK)) { closepage(); return; }
if ((f1 == KeyEvent.VK_O) && (f2 == KeyEvent.CTRL_MASK)) { openpage(); return; }
if ((f1 == KeyEvent.VK_F) && (f2 == KeyEvent.CTRL_MASK)) { find(); return; }
if ((f1 == KeyEvent.VK_G) && (f2 == KeyEvent.CTRL_MASK)) { findagain(false); return; }
if ((f1 == KeyEvent.VK_F3) && (f2 == 0)) { findagain(false); return; }
if ((f1 == KeyEvent.VK_R) && (f2 == KeyEvent.CTRL_MASK)) { replace(); return; }
if ((f1 == KeyEvent.VK_L) && (f2 == KeyEvent.CTRL_MASK)) { gotopos(); return; }
if ((f1 == KeyEvent.VK_E) && (f2 == KeyEvent.CTRL_MASK)) { execute(); return; }
if ((f2 == KeyEvent.ALT_MASK) && (f1 >= KeyEvent.VK_0) && (f1 <= KeyEvent.VK_9)) {
idx = f1 - KeyEvent.VK_0;
if (idx == 0) idx = 9; else idx--;
if (idx >= pages.size()) return;
tabs.setSelectedIndex(idx);
return;
}
if ((f1 == KeyEvent.VK_B) && (f2 == KeyEvent.CTRL_MASK)) { find_bin(); return; }
if ((f1 == KeyEvent.VK_T) && (f2 == KeyEvent.CTRL_MASK)) { replace_bin(); return; }
if ((f2 == KeyEvent.ALT_MASK) && (f1 == KeyEvent.VK_MINUS)) tabs.setSelectedIndex(10);
if ((f2 == KeyEvent.ALT_MASK) && (f1 == KeyEvent.VK_EQUALS)) tabs.setSelectedIndex(11);
}//GEN-LAST:event_tabsKeyPressed
private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
exit();
}//GEN-LAST:event_formWindowClosing
private void tabsStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_tabsStateChanged
updateStatus();
}//GEN-LAST:event_tabsStateChanged
/**
* args = command line arguments
*/
public static String args[];
public static void main(String args[]) {
jfhex.args = args;
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new jfhex().setVisible(true);
}
});
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JToolBar jToolBar1;
private javax.swing.JLabel status;
private javax.swing.JTabbedPane tabs;
// End of variables declaration//GEN-END:variables
private class page {
JPanel panel;
JScrollPane scroll;
Hex hex;
boolean dirty;
File filename;
};
private Vector<page> pages;
private boolean bLoading = false;
private void initApp() {
setTitle("jfhex");
pages = new Vector<page>();
loadcfg();
setSize(Settings.WindowXSize, Settings.WindowYSize);
setLocation(Settings.WindowXPos, Settings.WindowYPos);
if (Settings.bWindowMax) setExtendedState(MAXIMIZED_BOTH);
if (args != null) {
for(int a=0;a<args.length;a++) loadpages(args[a]);
}
if (pages.size() == 0) addpage("untitled", "");
tabs.setSelectedIndex(0);
pages.get(0).hex.grabFocus();
}
public void loadcfg() {
XML xml = new XML();
String filename = JF.getUserPath() + "/.jfhex.xml";
File file = new File(filename);
if (!file.exists()) return; //doesn't exist
if (!xml.read(filename)) return; //bad cfg
if (!xml.root.name.equals("jfhex")) return; //bad cfg
xml.writeClass(xml.root, new Settings());
Settings.fnt = JF.getMonospacedFont(0, Settings.fontSize);
}
public void savecfg() {
XML xml = new XML();
XML.XMLTag tag;
xml.root.name = "jfhex";
xml.readClass(xml.root, new Settings());
String filename = JF.getUserPath() + "/.jfhex.xml";
xml.write(filename);
}
//find data
private String findstr = "";
private String repstr = "";
private boolean findww;
private boolean findcw;
private boolean findbin = false;
//interface FindEvent
public void findEvent(FindDialog dialog) {
if (findbin) {
findstr = binary2String(dialog.getText());
} else {
findstr = dialog.getText();
}
findww = dialog.getWhole();
findcw = dialog.getCase();
findagain(false);
}
//interface ReplaceEvent
public boolean findEvent(ReplaceDialog dialog) {
if (findbin) {
findstr = binary2String(dialog.getFindText());
} else {
findstr = dialog.getFindText();
}
findww = dialog.getWhole();
findcw = dialog.getCase();
boolean ret = replace_find();
if (!ret) notfound();
return ret;
}
public void replaceEvent(ReplaceDialog dialog) {
if (findbin) {
findstr = binary2String(dialog.getFindText());
repstr = binary2String(dialog.getReplaceText());
} else {
findstr = dialog.getFindText();
repstr = dialog.getReplaceText();
}
findww = dialog.getWhole();
findcw = dialog.getCase();
replace_replace();
}
public void replaceAllEvent(ReplaceDialog dialog) {
if (findbin) {
findstr = binary2String(dialog.getFindText());
repstr = binary2String(dialog.getReplaceText());
} else {
findstr = dialog.getFindText();
repstr = dialog.getReplaceText();
}
findww = dialog.getWhole();
findcw = dialog.getCase();
replace_all();
}
private page addpage(String title, String txt) {
page pg = new page();
pg.panel = JF.createJPanel(new GridLayout(), null);
pg.hex = new Hex(this);
pg.hex.addKeyListener(new java.awt.event.KeyAdapter() {
public void keyPressed(java.awt.event.KeyEvent evt) {
tabsKeyPressed(evt);
}
});
pg.hex.setText(txt);
pg.hex.setCaretPosition(0);
pg.scroll = new JScrollPane(pg.hex);
pg.scroll.setViewportView(pg.hex);
pg.panel.add(pg.scroll);
pg.filename = new File(title);
pg.dirty = false;
pg.panel.setVisible(true);
tabs.addTab(title, pg.panel);
tabs.setSelectedComponent(pg.panel);
pages.add(pg);
return pg;
}
private int getidx() { return tabs.getSelectedIndex(); }
private boolean savepage() {
int idx = getidx();
if (pages.get(idx).dirty == false) return true;
if (pages.get(idx).filename.toString().equals("untitled")) {return savepageas();}
String tmp;
try {
tmp = pages.get(idx).hex.getText();
FileOutputStream fos = new FileOutputStream(pages.get(idx).filename);
byte data[] = new byte[tmp.length()];
tmp.getBytes(0, tmp.length(), data, 0); //this method will not encode the bytes (deprecated though)
fos.write(data);
fos.close();
tabs.setTitleAt(idx, pages.get(idx).filename.getName());
pages.get(idx).dirty = false;
return true;
} catch (Exception e) {
JOptionPane.showMessageDialog(this, "Failed to save '" + pages.get(idx).filename.toString() + "'", "Warning", JOptionPane.ERROR_MESSAGE);
return false;
}
}
private boolean savepageas() {
int idx = getidx();
JFileChooser chooser = new JFileChooser();
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
chooser.setMultiSelectionEnabled(false);
chooser.setCurrentDirectory(new File(JF.getCurrentPath()));
if (chooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
pages.get(idx).dirty = true;
pages.get(idx).filename = chooser.getSelectedFile();
tabs.setTitleAt(idx, pages.get(idx).filename.getName());
return savepage();
}
return false;
}
private boolean closepage(int idx) {
tabs.setSelectedIndex(idx);
return closepage();
}
private boolean closepage() {
int idx = getidx();
if (pages.get(idx).dirty) {
//confirm to Save : Yes/No/Cancel
switch (JOptionPane.showConfirmDialog(this, "Do you wish to save '" + pages.get(idx).filename.toString() + "' ?", "Confirm",
JOptionPane.YES_NO_CANCEL_OPTION)) {
case JOptionPane.YES_OPTION:
if (!savepage()) return false;
break;
case JOptionPane.NO_OPTION:
break;
default:
case JOptionPane.CANCEL_OPTION:
return false;
}
}
pages.remove(idx);
tabs.remove(idx);
if (pages.size() == 0) addpage("untitled", "");
return true;
}
private void openpage() {
int idx = getidx();
JFileChooser chooser = new JFileChooser();
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
chooser.setMultiSelectionEnabled(false);
chooser.setCurrentDirectory(new File(JF.getCurrentPath()));
if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
//check if current page is "untitled" and !dirty
if (pages.get(idx).filename.toString().equals("untitled") && pages.get(idx).dirty == false) {
//load on current page
pages.get(idx).filename = chooser.getSelectedFile();
} else {
addpage(chooser.getSelectedFile().getAbsolutePath(), "");
idx = tabs.getTabCount() - 1;
}
loadpage(idx);
pages.get(idx).hex.grabFocus();
}
}
private void loadpages(String filespec) {
File f = new File(filespec);
if (f.isDirectory()) {
/*
String files[] = f.list();
if (files == null || files.length == 0) return;
for(int a=0;a<files.length;a++) {
addpage(files[a], "");
loadpage(tabs.getTabCount() - 1);
}
*/
} else {
addpage(filespec, "");
loadpage(tabs.getTabCount() - 1);
}
}
private void loadpage(int idx) {
byte txt[];
if ((pages.get(idx).filename.toString().indexOf("*") != -1) || (pages.get(idx).filename.toString().indexOf("?") != -1)) {
closepage(idx);
return;
}
try {
File file = pages.get(idx).filename;
if (!file.exists()) return;
FileInputStream fis = new FileInputStream(file);
txt = new byte[fis.available()];
fis.read(txt);
fis.close();
bLoading = true;
setText(idx, new String(txt, 0)); //new String(byte[], int) will not encode the bytes (new String(byte[]) does)
pages.get(idx).hex.setCaretPosition(0);
bLoading = false;
tabs.setTitleAt(idx, pages.get(idx).filename.getName());
} catch (Exception e) {
JOptionPane.showMessageDialog(this, "Failed to open '" + pages.get(idx).filename.toString() + "'", "Warning", JOptionPane.ERROR_MESSAGE);
closepage(idx);
}
}
private void notfound() {
JOptionPane.showMessageDialog(this, "Unable to find match", "Notice", JOptionPane.ERROR_MESSAGE);
}
private void find() {
findbin = false;
FindDialog.showFindDialog(this, false, findstr, findww, findcw, this);
}
private void find_bin() {
findbin = true;
FindDialog.showFindDialog(this, false, string2Binary(findstr), findww, findcw, this);
}
private boolean isChar(char ch) {
//return true if ch is a char that would be a part of a whole word
if ((ch >= 'a') && (ch <= 'z')) return true;
if ((ch >= 'A') && (ch <= 'Z')) return true;
if ((ch >= '0') && (ch <= '9')) return true;
if (ch == '_') return true;
return false;
}
private boolean findagain(boolean quiet) {
Hex hex = pages.get(getidx()).hex;
if (findstr == null) return false;
int findstrlen = findstr.length();
char cafind[] = findstr.toCharArray();
String txt = hex.getText();
char ca[] = txt.toCharArray();
boolean ok;
int pos = hex.getCaretPosition();
if (hex.isSelect()) pos++;
for(;pos<ca.length - findstrlen+1;pos++) {
ok = true;
for(int a=0;a<findstrlen;a++) {
if (!findcw) {
if (Character.toUpperCase(ca[pos + a]) != Character.toUpperCase(cafind[a])) {ok = false; break;}
} else {
if (ca[pos + a] != cafind[a]) {ok = false; break;}
}
}
if (!ok) continue;
//found a match
if (findww) {
if ((pos > 0) && (isChar(ca[pos-1]))) continue; //not a whole word
if ((pos + findstrlen < ca.length) && (isChar(ca[pos + findstrlen]))) continue; //not a whole word
}
hex.select(pos, pos+findstrlen-1);
return true;
}
if (!quiet) notfound();
return false;
}
private void replace() {
findbin = false;
ReplaceDialog.showReplaceDialog(this, true, findstr, repstr, findww, findcw, this);
}
private void replace_bin() {
findbin = true;
ReplaceDialog.showReplaceDialog(this, true, string2Binary(findstr), string2Binary(repstr), findww, findcw, this);
}
private boolean replace_find() {
return findagain(true);
}
private void replace_replace() {
Hex hex = pages.get(getidx()).hex;
hex.delete();
hex.paste(repstr.toCharArray());
}
private void replace_all() {
int cnt = 0;
pages.get(getidx()).hex.setCaretPosition(0);
while (true) {
if (!replace_find()) break;
replace_replace();
cnt++;
}
JOptionPane.showMessageDialog(this,
"Replaced " + cnt + " occurances"
, "Info", JOptionPane.INFORMATION_MESSAGE);
}
private void gotopos() {
String str;
int line;
int idx = getidx();
try {
str = JOptionPane.showInputDialog(this, "Enter hex offset?",
"Goto", JOptionPane.QUESTION_MESSAGE);
if (str == null) return;
line = JF.atox(str);
pages.get(idx).hex.setCaretPosition(line);
} catch (Exception e1) {}
}
private boolean isActive(Process proc) {
try {
int exitValue = proc.exitValue();
return false;
} catch (Exception e) {
return true;
}
}
private void execute() {
try {
String str = JOptionPane.showInputDialog(this, "Enter OS command",
"Execute", JOptionPane.QUESTION_MESSAGE);
if (str == null) return;
String strs[] = str.split(" ");
ArrayList<String> list = new ArrayList<String>();
for(int a=0;a<strs.length;a++) list.add(strs[a]);
ProcessBuilder pb = new ProcessBuilder(list);
pb.redirectErrorStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Process proc = pb.start();
// proc.waitFor(); //dead locks often
InputStream is = proc.getInputStream();
while (isActive(proc)) {
int fs = is.available();
if (fs > 0) {
byte data[] = new byte[fs];
int read = is.read(data);
baos.write(data, 0, read);
}
}
int fs = is.available();
if (fs > 0) {
byte data[] = new byte[fs];
int read = is.read(data);
baos.write(data, 0, read);
}
if (baos.size() == 0) {
JOptionPane.showMessageDialog(this, "No output", "Execute",
JOptionPane.INFORMATION_MESSAGE);
return;
}
page pg = addpage("output-" + strs[0], baos.toString());
pg.hex.grabFocus();
pg.hex.setCaretPosition(0);
} catch (Exception e) {
}
}
private void exit() {
//close all windows
int cnt = pages.size();
for(int a=0;a<cnt;a++) {
if (!closepage()) return;
}
savecfg();
System.exit(0);
}
public void changedUpdate(DocumentEvent e) {
changed();
}
public void insertUpdate(DocumentEvent e) {
changed();
}
public void removeUpdate(DocumentEvent e) {
changed();
}
public void changed() {
if (bLoading) return;
int idx = getidx();
if (idx < 0 || idx >= pages.size()) return;
if (pages.get(idx).dirty == false) {
pages.get(idx).dirty = true;
tabs.setTitleAt(idx, pages.get(idx).filename.getName() + " *");
}
}
public void setText(int idx, String txt) {
pages.get(idx).hex.setText(txt);
}
public String binary2String(String in) {
//in = "XX..."
StringBuffer out = new StringBuffer();
char ch;
int len = in.length();
for(int a=0;a<len/2;a++) {
ch = (char)JF.atox(in.substring(a*2,a*2+2));
out.append(ch);
}
if (len % 2 == 1) {
ch = (char)JF.atox(in.substring(len-1));
out.append(ch);
}
return out.toString();
}
public String string2Binary(String in) {
//in = "abc..."
//out = "414243..."
StringBuffer out = new StringBuffer();
char ch;
int len = in.length();
for(int a=0;a<len;a++) {
ch = in.charAt(a);
if (ch < 10) out.append("0");
out.append(Integer.toString(in.charAt(a), 16));
}
return out.toString();
}
public void updateStatus() {
status.setText("...");
int idx = getidx();
if (idx == -1) return;
if (idx >= pages.size()) return;
page pg = pages.get(idx);
Hex hex = pg.hex;
long offset = hex.getOffset();
status.setText("Offset:0x" + Long.toString(offset, 16));
}
}