forked from mirrors/NBTExplorer
593 lines
19 KiB
C#
593 lines
19 KiB
C#
using System;
|
|
using System.Windows.Forms;
|
|
using Be.Windows.Forms;
|
|
using System.IO;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Text;
|
|
using System.Drawing;
|
|
|
|
namespace NBTExplorer.Windows
|
|
{
|
|
public partial class HexEditor : Form
|
|
{
|
|
private abstract class EditView
|
|
{
|
|
protected EditView (StatusStrip statusBar, int bytesPerElem)
|
|
{
|
|
BytesPerElem = bytesPerElem;
|
|
StatusBar = statusBar;
|
|
}
|
|
|
|
public event EventHandler Modified;
|
|
|
|
public int BytesPerElem { get; set; }
|
|
public StatusStrip StatusBar { get; set; }
|
|
|
|
public abstract TabPage TabPage { get; }
|
|
|
|
public abstract void Initialize ();
|
|
public abstract void Activate ();
|
|
public abstract byte[] GetRawData ();
|
|
public abstract void SetRawData (byte[] data);
|
|
|
|
protected virtual void OnModified ()
|
|
{
|
|
var ev = Modified;
|
|
if (ev != null)
|
|
ev(this, EventArgs.Empty);
|
|
}
|
|
}
|
|
|
|
private class TextView : EditView
|
|
{
|
|
private TabPage _tabPage;
|
|
private TextBox _textBox;
|
|
|
|
private ToolStripStatusLabel _elementLabel;
|
|
private ToolStripStatusLabel _spaceLabel;
|
|
|
|
private Dictionary<int, int> _elemIndex = new Dictionary<int, int>();
|
|
|
|
public TextView (StatusStrip statusBar, int bytesPerElem)
|
|
: base(statusBar, bytesPerElem)
|
|
{ }
|
|
|
|
public override void Initialize ()
|
|
{
|
|
_textBox = new TextBox() {
|
|
Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right,
|
|
Font = new Font("Courier New", 9F, FontStyle.Regular, GraphicsUnit.Point, 0),
|
|
Location = new Point(0, 0),
|
|
Margin = new Padding(0),
|
|
Multiline = true,
|
|
ScrollBars = ScrollBars.Vertical,
|
|
Size = new Size(500, 263),
|
|
TabIndex = 0,
|
|
MaxLength = 0,
|
|
};
|
|
|
|
_tabPage = new TabPage() {
|
|
Location = new Point(4, 22),
|
|
Padding = new Padding(3),
|
|
Size = new Size(500, 263),
|
|
TabIndex = 1,
|
|
Text = "Text View",
|
|
UseVisualStyleBackColor = true,
|
|
};
|
|
|
|
_tabPage.Controls.Add(_textBox);
|
|
|
|
_textBox.TextChanged += (s, e) => { OnModified(); RebuildElementIndex(); };
|
|
_textBox.PreviewKeyDown += (s, e) => { e.IsInputKey = true; };
|
|
_textBox.KeyUp += (s, e) => { UpdateElementLabel(); };
|
|
_textBox.MouseClick += (s, e) => { UpdateElementLabel(); };
|
|
|
|
InitializeStatusBar();
|
|
}
|
|
|
|
private void InitializeStatusBar ()
|
|
{
|
|
_elementLabel = new ToolStripStatusLabel() {
|
|
Size = new Size(100, 17),
|
|
Text = "Element 0",
|
|
TextAlign = ContentAlignment.MiddleLeft,
|
|
};
|
|
|
|
_spaceLabel = new ToolStripStatusLabel() {
|
|
Spring = true,
|
|
};
|
|
}
|
|
|
|
public override void Activate ()
|
|
{
|
|
StatusBar.Items.Clear();
|
|
StatusBar.Items.AddRange(new ToolStripItem[] {
|
|
_elementLabel, _spaceLabel,
|
|
});
|
|
}
|
|
|
|
public override TabPage TabPage
|
|
{
|
|
get { return _tabPage; }
|
|
}
|
|
|
|
public override byte[] GetRawData ()
|
|
{
|
|
return HexEditor.TextToRaw(_textBox.Text, BytesPerElem);
|
|
}
|
|
|
|
public override void SetRawData (byte[] data)
|
|
{
|
|
_textBox.Text = HexEditor.RawToText(data, BytesPerElem);
|
|
RebuildElementIndex();
|
|
}
|
|
|
|
private void RebuildElementIndex ()
|
|
{
|
|
_elemIndex.Clear();
|
|
|
|
int element = 0;
|
|
String text = _textBox.Text;
|
|
bool lcw = true;
|
|
|
|
for (int i = 0; i < text.Length; i++) {
|
|
bool w = IsWhiteSpace(text[i]);
|
|
if (lcw && !w)
|
|
_elemIndex[i] = element++;
|
|
lcw = w;
|
|
}
|
|
}
|
|
|
|
private bool IsWhiteSpace (char c)
|
|
{
|
|
return c == ' ' || c == '\n' || c == '\r' || c == '\t';
|
|
}
|
|
|
|
private void UpdateElementLabel ()
|
|
{
|
|
int index = _textBox.SelectionStart;
|
|
int element = 0;
|
|
|
|
while (index >= 0 && !_elemIndex.TryGetValue(index, out element))
|
|
index--;
|
|
|
|
_elementLabel.Text = "Element " + element;
|
|
}
|
|
}
|
|
|
|
private class HexView : EditView
|
|
{
|
|
private TabPage _tabPage;
|
|
private HexBox _hexBox;
|
|
|
|
private ToolStripStatusLabel _positionLabel;
|
|
private ToolStripStatusLabel _elementLabel;
|
|
private ToolStripStatusLabel _spaceLabel;
|
|
private ToolStripStatusLabel _insertLabel;
|
|
|
|
private DynamicByteProvider _byteProvider;
|
|
|
|
public HexView (StatusStrip statusBar, int bytesPerElem)
|
|
: base(statusBar, bytesPerElem)
|
|
{ }
|
|
|
|
public override void Initialize ()
|
|
{
|
|
_byteProvider = new DynamicByteProvider(new byte[0]);
|
|
_byteProvider.Changed += (o, e) => { OnModified(); };
|
|
|
|
_hexBox = new HexBox() {
|
|
Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right,
|
|
Font = new Font("Courier New", 9F, FontStyle.Regular, GraphicsUnit.Point, 0),
|
|
LineInfoForeColor = Color.Empty,
|
|
LineInfoVisible = true,
|
|
Location = new Point(0, 0),
|
|
ShadowSelectionColor = Color.FromArgb(100, 60, 188, 255),
|
|
Size = new Size(500, 263),
|
|
TabIndex = 0,
|
|
VScrollBarVisible = true,
|
|
ByteProvider = _byteProvider,
|
|
};
|
|
|
|
_tabPage = new TabPage() {
|
|
Location = new Point(4, 22),
|
|
Padding = new Padding(3),
|
|
Size = new Size(500, 263),
|
|
TabIndex = 0,
|
|
Text = "Hex View",
|
|
UseVisualStyleBackColor = true,
|
|
};
|
|
|
|
_tabPage.Controls.Add(_hexBox);
|
|
|
|
_hexBox.HorizontalByteCountChanged += (s, e) => { UpdatePosition(); };
|
|
_hexBox.CurrentLineChanged += (s, e) => { UpdatePosition(); };
|
|
_hexBox.CurrentPositionInLineChanged += (s, e) => { UpdatePosition(); };
|
|
|
|
_hexBox.InsertActiveChanged += (s, e) => { _insertLabel.Text = (_hexBox.InsertActive) ? "Insert" : "Overwrite"; };
|
|
|
|
InitializeStatusBar();
|
|
}
|
|
|
|
private void InitializeStatusBar ()
|
|
{
|
|
_positionLabel = new ToolStripStatusLabel() {
|
|
AutoSize = false,
|
|
Size = new Size(100, 17),
|
|
Text = "0000",
|
|
};
|
|
|
|
_elementLabel = new ToolStripStatusLabel() {
|
|
Size = new Size(59, 17),
|
|
Text = "Element 0",
|
|
TextAlign = ContentAlignment.MiddleLeft,
|
|
};
|
|
|
|
_spaceLabel = new ToolStripStatusLabel() {
|
|
Size = new Size(300, 17),
|
|
Spring = true,
|
|
};
|
|
|
|
_insertLabel = new ToolStripStatusLabel() {
|
|
Size = new Size(58, 17),
|
|
Text = (_hexBox.InsertActive) ? "Insert" : "Overwrite",
|
|
};
|
|
}
|
|
|
|
public override void Activate ()
|
|
{
|
|
StatusBar.Items.Clear();
|
|
StatusBar.Items.AddRange(new ToolStripItem[] {
|
|
_positionLabel, _elementLabel, _spaceLabel, _insertLabel,
|
|
});
|
|
|
|
UpdatePosition();
|
|
}
|
|
|
|
public override TabPage TabPage
|
|
{
|
|
get { return _tabPage; }
|
|
}
|
|
|
|
public override byte[] GetRawData ()
|
|
{
|
|
byte[] data = new byte[_byteProvider.Length];
|
|
for (int i = 0; i < data.Length; i++) {
|
|
data[i] = _byteProvider.Bytes[i];
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
public override void SetRawData (byte[] data)
|
|
{
|
|
_byteProvider = new DynamicByteProvider(data);
|
|
_byteProvider.Changed += (o, e2) => { OnModified(); };
|
|
|
|
_hexBox.ByteProvider = _byteProvider;
|
|
}
|
|
|
|
private void UpdatePosition ()
|
|
{
|
|
long pos = (_hexBox.CurrentLine - 1) * _hexBox.HorizontalByteCount + _hexBox.CurrentPositionInLine - 1;
|
|
|
|
_positionLabel.Text = "0x" + pos.ToString("X4");
|
|
_elementLabel.Text = "Element " + pos / BytesPerElem;
|
|
}
|
|
}
|
|
|
|
private class FixedByteProvider : DynamicByteProvider
|
|
{
|
|
public FixedByteProvider (byte[] data)
|
|
: base(data)
|
|
{ }
|
|
|
|
public override bool SupportsInsertBytes ()
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private TabPage _previousPage;
|
|
private int _bytesPerElem;
|
|
private byte[] _data;
|
|
private bool _modified;
|
|
|
|
private Dictionary<TabPage, EditView> _views = new Dictionary<TabPage, EditView>();
|
|
|
|
public HexEditor (string tagName, byte[] data, int bytesPerElem)
|
|
{
|
|
InitializeComponent();
|
|
|
|
EditView textView = new TextView(statusStrip1, bytesPerElem);
|
|
textView.Initialize();
|
|
textView.SetRawData(data);
|
|
textView.Modified += (s, e) => { _modified = true; };
|
|
|
|
_views.Add(textView.TabPage, textView);
|
|
viewTabs.TabPages.Add(textView.TabPage);
|
|
|
|
EditView hexView = null;
|
|
|
|
if (!IsMono()) {
|
|
hexView = new HexView(statusStrip1, bytesPerElem);
|
|
hexView.Initialize();
|
|
hexView.SetRawData(data);
|
|
hexView.Modified += (s, e) => { _modified = true; };
|
|
|
|
_views.Add(hexView.TabPage, hexView);
|
|
viewTabs.TabPages.Add(hexView.TabPage);
|
|
}
|
|
|
|
if (bytesPerElem > 1 || IsMono()) {
|
|
textView.Activate();
|
|
viewTabs.SelectedTab = textView.TabPage;
|
|
}
|
|
else {
|
|
hexView.Activate();
|
|
viewTabs.SelectedTab = hexView.TabPage;
|
|
}
|
|
|
|
viewTabs.Deselected += (o, e) => { _previousPage = e.TabPage; };
|
|
viewTabs.Selecting += HandleTabChanged;
|
|
|
|
this.Text = "Editing: " + tagName;
|
|
|
|
_bytesPerElem = bytesPerElem;
|
|
|
|
_data = new byte[data.Length];
|
|
Array.Copy(data, _data, data.Length);
|
|
}
|
|
|
|
private bool IsMono ()
|
|
{
|
|
return Type.GetType("Mono.Runtime") != null;
|
|
}
|
|
|
|
public byte[] Data
|
|
{
|
|
get { return _data; }
|
|
}
|
|
|
|
public bool Modified
|
|
{
|
|
get { return _modified; }
|
|
}
|
|
|
|
private void HandleTabChanged (object sender, TabControlCancelEventArgs e)
|
|
{
|
|
if (e.Action != TabControlAction.Selecting)
|
|
return;
|
|
|
|
if (e.TabPage == _previousPage)
|
|
return;
|
|
|
|
EditView oldView = _views[_previousPage];
|
|
EditView newView = _views[e.TabPage];
|
|
|
|
byte[] data = oldView.GetRawData();
|
|
newView.SetRawData(data);
|
|
|
|
newView.Activate();
|
|
}
|
|
|
|
private void Apply ()
|
|
{
|
|
EditView view = _views[viewTabs.SelectedTab];
|
|
_data = view.GetRawData();
|
|
|
|
DialogResult = DialogResult.OK;
|
|
Close();
|
|
}
|
|
|
|
private String RawToText (byte[] data)
|
|
{
|
|
return RawToText(data, _bytesPerElem);
|
|
}
|
|
|
|
private static String RawToText (byte[] data, int bytesPerElem)
|
|
{
|
|
switch (bytesPerElem) {
|
|
case 1: return RawToText(data, bytesPerElem, 16);
|
|
case 2: return RawToText(data, bytesPerElem, 8);
|
|
case 4: return RawToText(data, bytesPerElem, 4);
|
|
case 8: return RawToText(data, bytesPerElem, 2);
|
|
default: return RawToText(data, bytesPerElem, 1);
|
|
}
|
|
}
|
|
|
|
private static String RawToText (byte[] data, int bytesPerElem, int elementsPerLine)
|
|
{
|
|
StringBuilder builder = new StringBuilder();
|
|
|
|
for (int i = 0; i < data.Length; i += bytesPerElem) {
|
|
if (data.Length - i < bytesPerElem)
|
|
break;
|
|
|
|
switch (bytesPerElem) {
|
|
case 1:
|
|
builder.Append(((sbyte)data[i]).ToString());
|
|
break;
|
|
|
|
case 2:
|
|
builder.Append(BitConverter.ToInt16(data, i).ToString());
|
|
break;
|
|
|
|
case 4:
|
|
builder.Append(BitConverter.ToInt32(data, i).ToString());
|
|
break;
|
|
|
|
case 8:
|
|
builder.Append(BitConverter.ToInt64(data, i).ToString());
|
|
break;
|
|
}
|
|
|
|
if ((i / bytesPerElem) % elementsPerLine == elementsPerLine - 1)
|
|
builder.AppendLine();
|
|
else
|
|
builder.Append(" ");
|
|
}
|
|
|
|
return builder.ToString();
|
|
}
|
|
|
|
private byte[] TextToRaw (string text)
|
|
{
|
|
return TextToRaw(text, _bytesPerElem);
|
|
}
|
|
|
|
private static byte[] TextToRaw (string text, int bytesPerElem)
|
|
{
|
|
string[] items = text.Split(null as char[], StringSplitOptions.RemoveEmptyEntries);
|
|
byte[] data = new byte[bytesPerElem * items.Length];
|
|
|
|
for (int i = 0; i < items.Length; i++) {
|
|
int index = i * bytesPerElem;
|
|
|
|
switch (bytesPerElem) {
|
|
case 1:
|
|
sbyte val1;
|
|
if (sbyte.TryParse(items[i], out val1))
|
|
data[index] = (byte)val1;
|
|
break;
|
|
|
|
case 2:
|
|
short val2;
|
|
if (short.TryParse(items[i], out val2)) {
|
|
byte[] buffer = BitConverter.GetBytes(val2);
|
|
Array.Copy(buffer, 0, data, index, 2);
|
|
}
|
|
break;
|
|
|
|
case 4:
|
|
int val4;
|
|
if (int.TryParse(items[i], out val4)) {
|
|
byte[] buffer = BitConverter.GetBytes(val4);
|
|
Array.Copy(buffer, 0, data, index, 4);
|
|
}
|
|
break;
|
|
|
|
case 8:
|
|
long val8;
|
|
if (long.TryParse(items[i], out val8)) {
|
|
byte[] buffer = BitConverter.GetBytes(val8);
|
|
Array.Copy(buffer, 0, data, index, 8);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
private void ImportRaw (string path)
|
|
{
|
|
try {
|
|
using (FileStream fstr = File.OpenRead(path)) {
|
|
_data = new byte[fstr.Length];
|
|
fstr.Read(_data, 0, (int)fstr.Length);
|
|
|
|
EditView view = _views[viewTabs.SelectedTab];
|
|
view.SetRawData(_data);
|
|
|
|
_modified = true;
|
|
}
|
|
}
|
|
catch (Exception e) {
|
|
MessageBox.Show("Failed to import data from \"" + path + "\"\n\nException: " + e.Message);
|
|
}
|
|
}
|
|
|
|
private void ImportText (string path)
|
|
{
|
|
try {
|
|
using (FileStream fstr = File.OpenRead(path)) {
|
|
byte[] raw = new byte[fstr.Length];
|
|
fstr.Read(raw, 0, (int)fstr.Length);
|
|
|
|
string text = System.Text.Encoding.UTF8.GetString(raw, 0, raw.Length);
|
|
_data = TextToRaw(text);
|
|
|
|
EditView view = _views[viewTabs.SelectedTab];
|
|
view.SetRawData(_data);
|
|
|
|
_modified = true;
|
|
}
|
|
}
|
|
catch (Exception e) {
|
|
MessageBox.Show("Failed to import data from \"" + path + "\"\n\nException: " + e.Message);
|
|
}
|
|
}
|
|
|
|
private void ExportRaw (string path)
|
|
{
|
|
try {
|
|
using (FileStream fstr = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None)) {
|
|
EditView view = _views[viewTabs.SelectedTab];
|
|
byte[] data = view.GetRawData();
|
|
|
|
fstr.Write(data, 0, data.Length);
|
|
}
|
|
}
|
|
catch (Exception e) {
|
|
MessageBox.Show("Failed to export data to \"" + path + "\"\n\nException: " + e.Message);
|
|
}
|
|
}
|
|
|
|
private void ExportText (string path)
|
|
{
|
|
try {
|
|
using (FileStream fstr = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None)) {
|
|
EditView view = _views[viewTabs.SelectedTab];
|
|
string text = RawToText(view.GetRawData());
|
|
|
|
byte[] data = System.Text.Encoding.UTF8.GetBytes(text);
|
|
fstr.Write(data, 0, data.Length);
|
|
}
|
|
}
|
|
catch (Exception e) {
|
|
MessageBox.Show("Failed to export data to \"" + path + "\"\n\nException: " + e.Message);
|
|
}
|
|
}
|
|
|
|
private void _buttonOK_Click (object sender, EventArgs e)
|
|
{
|
|
Apply();
|
|
}
|
|
|
|
private void _buttonImport_Click (object sender, EventArgs e)
|
|
{
|
|
using (OpenFileDialog ofd = new OpenFileDialog()) {
|
|
ofd.RestoreDirectory = true;
|
|
ofd.Multiselect = false;
|
|
ofd.Filter = "Binary Data|*|Text Data (*.txt)|*.txt";
|
|
ofd.FilterIndex = 0;
|
|
|
|
if (ofd.ShowDialog() == DialogResult.OK) {
|
|
if (Path.GetExtension(ofd.FileName) == ".txt")
|
|
ImportText(ofd.FileName);
|
|
else
|
|
ImportRaw(ofd.FileName);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void _buttonExport_Click (object sender, EventArgs e)
|
|
{
|
|
using (SaveFileDialog sfd = new SaveFileDialog()) {
|
|
sfd.RestoreDirectory = true;
|
|
sfd.Filter = "Binary Data|*|Text Data (*.txt)|*.txt";
|
|
sfd.FilterIndex = 0;
|
|
sfd.OverwritePrompt = true;
|
|
|
|
if (sfd.ShowDialog() == DialogResult.OK) {
|
|
if (Path.GetExtension(sfd.FileName) == ".txt")
|
|
ExportText(sfd.FileName);
|
|
else
|
|
ExportRaw(sfd.FileName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|