using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Globalization; using System.IO; using System.Linq; using System.Text; using System.Windows.Forms; using BizHawk.Common; using BizHawk.Common.NumberExtensions; using BizHawk.Common.StringExtensions; using BizHawk.Common.IOExtensions; using BizHawk.Emulation.Common; using BizHawk.Emulation.Common.IEmulatorExtensions; using BizHawk.Client.Common; using BizHawk.Client.EmuHawk.WinFormExtensions; using BizHawk.Client.EmuHawk.ToolExtensions; namespace BizHawk.Client.EmuHawk { // int to long TODO: 32 bit domains have more digits than the hex editor can account for and the address covers up the 0 column public partial class HexEditor : ToolFormBase, IToolFormAutoConfig { private class NullMemoryDomain : MemoryDomain { public override byte PeekByte(long addr) { return 0; } public override void PokeByte(long addr, byte val) { } public NullMemoryDomain() { EndianType = Endian.Unknown; Name = "Null"; Size = 1024; Writable = true; WordSize = 1; } } [RequiredService] private IMemoryDomains MemoryDomains { get; set; } [RequiredService] private IEmulator Emulator { get; set; } private readonly int _fontWidth; private readonly int _fontHeight; private readonly List _nibbles = new List(); private long? _highlightedAddress; private readonly List _secondaryHighlightedAddresses = new List(); private readonly Dictionary _textTable = new Dictionary(); private int _rowsVisible; private int _numDigits = 4; private string _numDigitsStr = "{0:X4}"; private string _digitFormatString = "{0:X2}"; private long _addressOver = -1; private long _maxRow; private MemoryDomain _domain = new NullMemoryDomain(); private long _row; private long _addr; private string _findStr = ""; private bool _mouseIsDown; private byte[] _rom; private MemoryDomain _romDomain; private HexFind _hexFind = new HexFind(); [ConfigPersist] private string LastDomain { get; set; } [ConfigPersist] private bool BigEndian { get; set; } [ConfigPersist] private int DataSize { get; set; } [ConfigPersist] private RecentFiles RecentTables { get; set; } public HexEditor() { RecentTables = new RecentFiles(8); DataSize = 1; var font = new Font("Courier New", 8); // Measure the font. There seems to be some extra horizontal padding on the first // character so we'll see how much the width increases on the second character. var fontSize1 = TextRenderer.MeasureText("0", font); var fontSize2 = TextRenderer.MeasureText("00", font); _fontWidth = fontSize2.Width - fontSize1.Width; _fontHeight = fontSize1.Height; InitializeComponent(); AddressesLabel.BackColor = Color.Transparent; LoadConfigSettings(); SetHeader(); Closing += (o, e) => CloseHexFind(); Header.Font = font; AddressesLabel.Font = font; AddressLabel.Font = font; } private WatchSize WatchSize => (WatchSize)DataSize; #region API public bool UpdateBefore => false; public bool AskSaveChanges() { return true; } public void NewUpdate(ToolFormUpdateType type) { } public void UpdateValues() { AddressesLabel.Text = GenerateMemoryViewString(true); } public void FullUpdate() { AddressesLabel.Text = GenerateMemoryViewString(true); AddressLabel.Text = GenerateAddressString(); } public void FastUpdate() { // Do nothing } private string _lastRom = ""; public void Restart() { _rom = GetRomBytes(); _romDomain = new MemoryDomainByteArray("File on Disk", MemoryDomain.Endian.Little, _rom, true, 1); if (_domain.Name == _romDomain.Name) { _domain = _romDomain; } else if (MemoryDomains.Any(x => x.Name == _domain.Name)) { _domain = MemoryDomains[_domain.Name]; } else { _domain = MemoryDomains.MainMemory; } BigEndian = _domain.EndianType == MemoryDomain.Endian.Big; _maxRow = _domain.Size / 2; // Don't reset scroll bar if restarting the same rom if (_lastRom != GlobalWin.MainForm.CurrentlyOpenRom) { _lastRom = GlobalWin.MainForm.CurrentlyOpenRom; ResetScrollBar(); } SetDataSize(DataSize); SetHeader(); FullUpdate(); } public void SetToAddresses(IEnumerable addresses, MemoryDomain domain, WatchSize size) { DataSize = (int)size; SetDataSize(DataSize); var addrList = addresses.ToList(); if (addrList.Any()) { SetDomain(domain); SetHighlighted(addrList[0]); _secondaryHighlightedAddresses.Clear(); _secondaryHighlightedAddresses.AddRange(addrList.Where(addr => addr != addrList[0])); ClearNibbles(); FullUpdate(); MemoryViewerBox.Refresh(); } } public byte[] ConvertTextToBytes(string str) { if (_textTable.Any()) { var byteArr = new List(); foreach (var chr in str) { byteArr.Add((byte)_textTable.FirstOrDefault(kvp => kvp.Value == chr).Key); } return byteArr.ToArray(); } return str.Select(Convert.ToByte).ToArray(); } public byte[] ConvertHexStringToByteArray(string str) { if (string.IsNullOrWhiteSpace(str)) { return new byte[0]; } // TODO: Better method of handling this? if (str.Length % 2 == 1) { str += "0"; } byte[] bytes = new byte[str.Length / 2]; for (int i = 0; i < str.Length; i += 2) { bytes[i / 2] = Convert.ToByte(str.Substring(i, 2), 16); } return bytes; } public void FindNext(string value, bool wrap) { long found = -1; var search = value.Replace(" ", "").ToUpper(); if (string.IsNullOrEmpty(search)) { return; } var numByte = search.Length / 2; long startByte; if (_highlightedAddress == null) { startByte = 0; } else if (_highlightedAddress >= (_domain.Size - 1 - numByte)) { startByte = 0; } else { startByte = _highlightedAddress.Value + DataSize; } byte[] searchBytes = ConvertHexStringToByteArray(search); for (var i = startByte; i < (_domain.Size - numByte); i++) { bool differenceFound = false; for (var j = 0; j < numByte; j++) { if (_domain.PeekByte(i + j) != searchBytes[j]) { differenceFound = true; break; } } if (!differenceFound) { found = i; break; } } if (found > -1) { HighlightSecondaries(search, found); GoToAddress(found); _findStr = search; } else if (wrap == false) { FindPrev(value, true); // Search the opposite direction if not found } _hexFind.Close(); } public void FindPrev(string value, bool wrap) { long found = -1; var search = value.Replace(" ", "").ToUpper(); if (string.IsNullOrEmpty(search)) { return; } var numByte = search.Length / 2; long startByte = _highlightedAddress - 1 ?? _domain.Size - DataSize - numByte; byte[] searchBytes = ConvertHexStringToByteArray(search); for (var i = startByte; i >= 0; i--) { bool differenceFound = false; for (var j = 0; j < numByte; j++) { if (_domain.PeekByte(i + j) != searchBytes[j]) { differenceFound = true; break; } } if (!differenceFound) { found = i; break; } } if (found > -1) { HighlightSecondaries(search, found); GoToAddress(found); _findStr = search; } else if (wrap == false) { FindPrev(value, true); // Search the opposite direction if not found } _hexFind.Close(); } #endregion private char Remap(byte val) { if (_textTable.Any()) { if (_textTable.ContainsKey(val)) { return _textTable[val]; } return '?'; } if (val < ' ' || val >= 0x7F) { return '.'; } return (char)val; } private static bool CurrentRomIsArchive() { var path = GlobalWin.MainForm.CurrentlyOpenRom; if (path == null) { return false; } using var file = new HawkFile(); file.Open(path); if (!file.Exists) { return false; } return file.IsArchive; } private static byte[] GetRomBytes() { var path = GlobalWin.MainForm.CurrentlyOpenRomArgs.OpenAdvanced.SimplePath; if (string.IsNullOrEmpty(path)) { return new byte[] { 0xFF }; } using var file = new HawkFile(); file.Open(path); if (!file.Exists) { return null; } if (file.IsArchive) { var stream = file.GetStream(); return stream.ReadAllBytes(); } return File.ReadAllBytes(path); } private static int GetNumDigits(long i) { if (i <= 0x10000) { return 4; } return i <= 0x1000000 ? 6 : 8; } private static bool IsHexKeyCode(char key) { if (key >= '0' && key <= '9') // 0-9 { return true; } if (key >= 'a' && key <= 'f') // A-F { return true; } if (key >= 'A' && key <= 'F') // A-F { return true; } return false; } private void HexEditor_Load(object sender, EventArgs e) { DataSize = _domain.WordSize; SetDataSize(DataSize); if (!string.IsNullOrWhiteSpace(LastDomain) && MemoryDomains.Any(m => m.Name == LastDomain)) { SetMemoryDomain(LastDomain); } if (RecentTables.AutoLoad) { LoadFileFromRecent(RecentTables[0]); } FullUpdate(); } private void LoadConfigSettings() { HexMenuStrip.BackColor = Global.Config.HexMenubarColor; MemoryViewerBox.BackColor = Global.Config.HexBackgrndColor; MemoryViewerBox.ForeColor = Global.Config.HexForegrndColor; Header.BackColor = Global.Config.HexBackgrndColor; Header.ForeColor = Global.Config.HexForegrndColor; } private void CloseHexFind() { if (_hexFind.IsHandleCreated || !_hexFind.IsDisposed) { _hexFind.Close(); } } private string GenerateAddressString() { var addrStr = new StringBuilder(); for (var i = 0; i < _rowsVisible; i++) { _row = i + HexScrollBar.Value; _addr = _row << 4; if (_addr >= _domain.Size) { break; } if (_numDigits == 4) { addrStr.Append(" "); // Hack to line things up better between 4 and 6 } else if (_numDigits == 6) { addrStr.Append(" "); } addrStr.AppendLine($"{_addr.ToHexString(_numDigits)} |"); } return addrStr.ToString(); } private string GenerateMemoryViewString(bool forWindow) { var rowStr = new StringBuilder(); for (var i = 0; i < _rowsVisible; i++) { _row = i + HexScrollBar.Value; _addr = _row << 4; if (_addr >= _domain.Size) { break; } for (var j = 0; j < 16; j += DataSize) { if (_addr + j + DataSize <= _domain.Size) { int tVal = 0; for (int k = 0; k < DataSize; k++) { int tNext = MakeValue(1, _addr + j + k); if (BigEndian) { tVal += (tNext << (k * 8)); } else { tVal += (tNext << ((DataSize - k - 1) * 8)); } } rowStr.AppendFormat(_digitFormatString, tVal); } else { for (var t = 0; t < DataSize; t++) { rowStr.Append(" "); } rowStr.Append(' '); } } rowStr.Append("| "); for (var k = 0; k < 16; k++) { if (_addr + k < _domain.Size) { byte b = MakeByte(_addr + k); char c = Remap(b); rowStr.Append(c); //winforms will be using these as escape codes for hotkeys if (forWindow) if (c == '&') rowStr.Append('&'); } } rowStr.AppendLine(); } return rowStr.ToString(); } private byte MakeByte(long address) { return Global.CheatList.IsActive(_domain, address) ? Global.CheatList.GetByteValue(_domain, address).Value : _domain.PeekByte(address); } private int MakeValue(int dataSize, long address) { switch (dataSize) { default: case 1: return _domain.PeekByte(address); case 2: return _domain.PeekUshort(address, BigEndian); case 4: return (int)_domain.PeekUint(address, BigEndian); } } private int MakeValue(long address) { return MakeValue(DataSize, address); } private void SetMemoryDomain(string name) { _domain = name == _romDomain.Name ? _romDomain : MemoryDomains[name]; BigEndian = _domain.EndianType == MemoryDomain.Endian.Big; _maxRow = _domain.Size / 2; SetUpScrollBar(); if (0 >= HexScrollBar.Minimum && 0 <= HexScrollBar.Maximum) { HexScrollBar.Value = 0; } AddressesLabel.ForeColor = _domain.CanPoke() ? SystemColors.ControlText : SystemColors.ControlDarkDark; if (_highlightedAddress >= _domain.Size || (_secondaryHighlightedAddresses.Any() && _secondaryHighlightedAddresses.Max() >= _domain.Size)) { _highlightedAddress = null; _secondaryHighlightedAddresses.Clear(); } UpdateGroupBoxTitle(); SetHeader(); FullUpdate(); LastDomain = _domain.Name; } private void SetDomain(MemoryDomain domain) { SetMemoryDomain(domain.Name); } private void UpdateGroupBoxTitle() { var addressesString = "0x" + $"{_domain.Size / DataSize:X8}".TrimStart('0'); var viewerText = $"{Emulator.SystemId} {_domain}{(_domain.CanPoke() ? string.Empty : " (READ-ONLY)")} - {addressesString} addresses"; if (_nibbles.Any()) { viewerText += $" Typing: ({MakeNibbles()})"; } MemoryViewerBox.Text = viewerText; } private void ClearNibbles() { _nibbles.Clear(); UpdateGroupBoxTitle(); } private void GoToAddress(long address) { if (address < 0) { address = 0; } if (address >= _domain.Size) { address = _domain.Size - DataSize; } SetHighlighted(address); ClearNibbles(); FullUpdate(); MemoryViewerBox.Refresh(); } private void SetHighlighted(long address) { if (address < 0) { address = 0; } if (address >= _domain.Size) { address = _domain.Size - DataSize; } if (!IsVisible(address)) { var value = (address / 16) - _rowsVisible + 1; if (value < 0) { value = 0; } HexScrollBar.Value = (int)value; // This will fail on a sufficiently large domain } _highlightedAddress = address; _addressOver = address; ClearNibbles(); UpdateFormText(); } private void UpdateFormText() { Text = "Hex Editor"; if (_highlightedAddress.HasValue) { Text += " - Editing Address 0x" + string.Format(_numDigitsStr, _highlightedAddress); if (_secondaryHighlightedAddresses.Any()) { Text += $" (Selected 0x{_secondaryHighlightedAddresses.Count + (_secondaryHighlightedAddresses.Contains(_highlightedAddress.Value) ? 0 : 1):X})"; } } } private bool IsVisible(long address) { var i = address >> 4; return i >= HexScrollBar.Value && i < _rowsVisible + HexScrollBar.Value; } private void SetHeader() { switch (DataSize) { case 1: Header.Text = " 0 1 2 3 4 5 6 7 8 9 A B C D E F"; break; case 2: Header.Text = " 0 2 4 6 8 A C E"; break; case 4: Header.Text = " 0 4 8 C"; break; } _numDigits = GetNumDigits(_domain.Size); _numDigitsStr = $"{{0:X{_numDigits}}} "; } private void SetDataSize(int size) { if (size == 1 || size == 2 || size == 4) { DataSize = size; _digitFormatString = $"{{0:X{DataSize * 2}}} "; SetHeader(); UpdateGroupBoxTitle(); FullUpdate(); _secondaryHighlightedAddresses.Clear(); } } private Watch MakeWatch(long address) { switch (DataSize) { default: case 1: return Watch.GenerateWatch(_domain, address, WatchSize.Byte, Common.DisplayType.Hex, BigEndian); case 2: return Watch.GenerateWatch(_domain, address, WatchSize.Word, Common.DisplayType.Hex, BigEndian); case 4: return Watch.GenerateWatch(_domain, address, WatchSize.DWord, Common.DisplayType.Hex, BigEndian); } } private bool IsFrozen(long address) { return Global.CheatList.IsActive(_domain, address); } private void UnFreezeAddress(long address) { if (address >= 0) { Global.CheatList.RemoveRange(Global.CheatList.Where(x => x.Contains(address))); } MemoryViewerBox.Refresh(); } private void FreezeAddress(long address) { if (address >= 0) { var watch = Watch.GenerateWatch( _domain, address, WatchSize, Common.DisplayType.Hex, BigEndian); Global.CheatList.Add(new Cheat( watch, watch.Value)); } } private void FreezeSecondaries() { var cheats = new List(); foreach (var address in _secondaryHighlightedAddresses) { var watch = Watch.GenerateWatch( _domain, address, WatchSize, Common.DisplayType.Hex, BigEndian); cheats.Add(new Cheat( watch, watch.Value)); } Global.CheatList.AddRange(cheats); } private void UnfreezeSecondaries() { Global.CheatList.RemoveRange( Global.CheatList.Where( cheat => !cheat.IsSeparator && cheat.Domain == _domain && _secondaryHighlightedAddresses.Contains(cheat.Address.Value))); } private void SaveFileBinary(string path) { var file = new FileInfo(path); using var binWriter = new BinaryWriter(File.Open(file.FullName, FileMode.Create)); for (var i = 0; i < _domain.Size; i++) { binWriter.Write(_domain.PeekByte(i)); } } private string GetSaveFileFilter() { if (_domain.Name == "File on Disk") { var extension = Path.GetExtension(RomName); return $"Binary (*{extension})|*{extension}|All Files|*.*"; } return "Binary (*.bin)|*.bin|All Files|*.*"; } private string RomDirectory { get { string path = Global.Config.RecentRoms.MostRecent; if (string.IsNullOrWhiteSpace(path)) { return ""; } if (path.Contains("|")) { path = path.Split('|').First(); } return Path.GetDirectoryName(path); } } private string RomName { get { string path = Global.Config.RecentRoms.MostRecent; if (string.IsNullOrWhiteSpace(path)) { return ""; } if (path.Contains("|")) { path = path.Split('|').Last(); } return Path.GetFileName(path); } } private string GetBinarySaveFileFromUser() { using var sfd = new SaveFileDialog { Filter = GetSaveFileFilter() , RestoreDirectory = true , InitialDirectory = RomDirectory , FileName = _domain.Name == "File on Disk" ? RomName : PathManager.FilesystemSafeName(Global.Game) }; var result = sfd.ShowHawkDialog(); return result == DialogResult.OK ? sfd.FileName : ""; } private string GetSaveFileFromUser() { using var sfd = new SaveFileDialog { Filter = "Text (*.txt)|*.txt|All Files|*.*" , RestoreDirectory = true , InitialDirectory = RomDirectory , FileName = _domain.Name == "File on Disk" ? $"{Path.GetFileNameWithoutExtension(RomName)}.txt" : PathManager.FilesystemSafeName(Global.Game) }; var result = sfd.ShowHawkDialog(); return result == DialogResult.OK ? sfd.FileName : ""; } private void ResetScrollBar() { HexScrollBar.Value = 0; SetUpScrollBar(); Refresh(); } private void SetUpScrollBar() { _rowsVisible = (MemoryViewerBox.Height - (_fontHeight * 2) - (_fontHeight / 2)) / _fontHeight; var totalRows = (int)((_domain.Size + 15) / 16); if (totalRows < _rowsVisible) { _rowsVisible = totalRows; } HexScrollBar.Maximum = totalRows - 1; HexScrollBar.LargeChange = _rowsVisible; HexScrollBar.Visible = totalRows > _rowsVisible; AddressLabel.Text = GenerateAddressString(); } private long GetPointedAddress(int x, int y) { long address; // Scroll value determines the first row long i = HexScrollBar.Value; var rowOffset = y / _fontHeight; i += rowOffset; int colWidth = DataSize * 2 + 1; var column = x / (_fontWidth * colWidth); var innerOffset = AddressesLabel.Location.X - AddressLabel.Location.X + AddressesLabel.Margin.Left; var start = GetTextOffset() - innerOffset; if (x > start) { column = (x - start) / (_fontWidth * DataSize); } if (i >= 0 && i <= _maxRow && column >= 0 && column < (16 / DataSize)) { address = (i * 16) + (column * DataSize); } else { address = -1; } return address; } private void DoShiftClick() { if (_addressOver >= 0 && _addressOver < _domain.Size) { _secondaryHighlightedAddresses.Clear(); if (_addressOver < _highlightedAddress) { for (var x = _addressOver; x < _highlightedAddress; x += DataSize) { _secondaryHighlightedAddresses.Add(x); } } else if (_addressOver > _highlightedAddress) { for (var x = _highlightedAddress.Value + DataSize; x <= _addressOver; x += DataSize) { _secondaryHighlightedAddresses.Add(x); } } if (!IsVisible(_addressOver)) { var value = (_addressOver / 16) + 1 - ((_addressOver / 16) < HexScrollBar.Value ? 1 : _rowsVisible); if (value < 0) { value = 0; } HexScrollBar.Value = (int)value; // This will fail on a sufficiently large domain } } } private void ClearHighlighted() { _highlightedAddress = null; UpdateFormText(); MemoryViewerBox.Refresh(); } private Point GetAddressCoordinates(long address) { var extra = (address % DataSize) * _fontWidth * 2; var xOffset = AddressesLabel.Location.X + _fontWidth / 2 - 2; var yOffset = AddressesLabel.Location.Y; return new Point( (int)((((address % 16) / DataSize) * (_fontWidth * (DataSize * 2 + 1))) + xOffset + extra), (int)((((address / 16) - HexScrollBar.Value) * _fontHeight) + yOffset)); } // TODO: rename this, but it is a hack work around for highlighting misaligned addresses that result from highlighting on in a smaller data size and switching size private bool NeedsExtra(long val) { return val % DataSize > 0; } private int GetTextOffset() { int start = (16 / DataSize) * _fontWidth * (DataSize * 2 + 1); start += AddressesLabel.Location.X + _fontWidth / 2; start += _fontWidth * 2; return start; } private long GetTextX(long address) { return GetTextOffset() + ((address % 16) * _fontWidth); } private string MakeNibbles() { var str = ""; foreach (var c in _nibbles) { str += c; } return str; } private void AddToSecondaryHighlights(long address) { if (address >= 0 && address < _domain.Size && !_secondaryHighlightedAddresses.Contains(address)) { _secondaryHighlightedAddresses.Add(address); } } private void IncrementAddress(long address) { if (Global.CheatList.IsActive(_domain, address)) { // TODO: Increment should be intelligent since IsActive is. If this address is part of a multi-byte cheat it should intelligently increment just that byte Global.CheatList.First(x => x.Domain == _domain && x.Address == address).Increment(); } else { switch (DataSize) { default: case 1: _domain.PokeByte( address, (byte)(_domain.PeekByte(address) + 1)); break; case 2: _domain.PokeUshort( address, (ushort)(_domain.PeekUshort(address, BigEndian) + 1), BigEndian); break; case 4: _domain.PokeUint( address, _domain.PeekUint(address, BigEndian) + 1, BigEndian); break; } } } private void DecrementAddress(long address) { if (Global.CheatList.IsActive(_domain, address)) { // TODO: Increment should be intelligent since IsActive is. If this address is part of a multi-byte cheat it should intelligently increment just that byte Global.CheatList.First(x => x.Domain == _domain && x.Address == address).Decrement(); } else { switch (DataSize) { default: case 1: _domain.PokeByte( address, (byte)(_domain.PeekByte(address) - 1)); break; case 2: _domain.PokeUshort( address, (ushort)(_domain.PeekUshort(address, BigEndian) - 1), BigEndian); break; case 4: _domain.PokeUint( address, _domain.PeekUint(address, BigEndian) - 1, BigEndian); break; } } } private string ValueString(long address) { if (address != -1) { return string.Format(_digitFormatString, MakeValue(address)).Trim(); } return ""; } private string GetFindValues() { if (_highlightedAddress.HasValue) { var values = ValueString(_highlightedAddress.Value); return _secondaryHighlightedAddresses.Aggregate(values, (current, x) => current + ValueString(x)); } return ""; } private void HighlightSecondaries(string value, long found) { // This function assumes that the primary highlighted value has been set and sets the remaining characters in this string _secondaryHighlightedAddresses.Clear(); var addrLength = DataSize * 2; if (value.Length <= addrLength) { return; } var numToHighlight = (value.Length / addrLength) - 1; for (var i = 0; i < numToHighlight; i += DataSize) { _secondaryHighlightedAddresses.Add(found + DataSize + i); } } private bool LoadTable(string path) { if (string.IsNullOrWhiteSpace(path)) { return false; } var file = new FileInfo(path); if (!file.Exists) { return false; } using (var sr = file.OpenText()) { string line; while ((line = sr.ReadLine()) != null) { var parts = line.Split('='); _textTable.Add( int.Parse(parts[0], NumberStyles.HexNumber), parts[1].First()); } } return true; } #region Events #region File Menu private void FileSubMenu_DropDownOpened(object sender, EventArgs e) { if (_domain.Name == "File on Disk") { SaveMenuItem.Visible = !CurrentRomIsArchive(); SaveAsBinaryMenuItem.Text = "Save as ROM..."; } else { SaveAsBinaryMenuItem.Text = "Save as binary..."; } CloseTableFileMenuItem.Enabled = _textTable.Any(); } private void SaveMenuItem_Click(object sender, EventArgs e) { if (!CurrentRomIsArchive()) { SaveFileBinary(GlobalWin.MainForm.CurrentlyOpenRom); } } private void SaveAsBinaryMenuItem_Click(object sender, EventArgs e) { var path = GetBinarySaveFileFromUser(); if (!string.IsNullOrEmpty(path)) { SaveFileBinary(path); } } private void importAsBinaryToolStripMenuItem_Click(object sender, EventArgs e) { if(!_domain.CanPoke()) { MessageBox.Show("This Memory Domain can't be Poked; so importing can't work"); return; } using var sfd = new OpenFileDialog { Filter = "Binary (*.bin)|*.bin|Save Files (*.sav)|*.sav|All Files|*.*", RestoreDirectory = true, }; var result = sfd.ShowHawkDialog(); if (result != DialogResult.OK) { return; } var path = sfd.FileName; using var inf = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read); long todo = Math.Min(inf.Length, _domain.Size); for (long i = 0; i < todo; i++) { _domain.PokeByte(i, (byte)inf.ReadByte()); } } private void SaveAsTextMenuItem_Click(object sender, EventArgs e) { var path = GetSaveFileFromUser(); if (!string.IsNullOrWhiteSpace(path)) { var file = new FileInfo(path); using var sw = new StreamWriter(file.FullName); var sb = new StringBuilder(); for (var i = 0; i < _domain.Size / 16; i++) { for (var j = 0; j < 16; j++) { sb.Append($"{_domain.PeekByte((i * 16) + j):X2} "); } sb.AppendLine(); } sw.WriteLine(sb); } } private void LoadTableFileMenuItem_Click(object sender, EventArgs e) { string initialDirectory = PathManager.MakeAbsolutePath(Global.Config.PathEntries.ToolsPathFragment, null); var romName = Global.Config.RecentRoms.MostRecent.Contains('|') ? Global.Config.RecentRoms.MostRecent.Split('|').Last() : Global.Config.RecentRoms.MostRecent; using var ofd = new OpenFileDialog { FileName = $"{Path.GetFileNameWithoutExtension(romName)}.tbl", InitialDirectory = initialDirectory, Filter = "Text Table files (*.tbl)|*.tbl|All Files|*.*", RestoreDirectory = false }; var result = ofd.ShowHawkDialog(); if (result == DialogResult.OK) { LoadTable(ofd.FileName); RecentTables.Add(ofd.FileName); FullUpdate(); } } private void CloseTableFileMenuItem_Click(object sender, EventArgs e) { _textTable.Clear(); } public void LoadFileFromRecent(string path) { var result = LoadTable(path); if (!result) { RecentTables.HandleLoadError(path); } else { RecentTables.Add(path); FullUpdate(); } } private void RecentTablesSubMenu_DropDownOpened(object sender, EventArgs e) { RecentTablesSubMenu.DropDownItems.Clear(); RecentTablesSubMenu.DropDownItems.AddRange( RecentTables.RecentMenu(LoadFileFromRecent, true)); } private void ExitMenuItem_Click(object sender, EventArgs e) { Close(); } #endregion #region Edit private void EditMenuItem_DropDownOpened(object sender, EventArgs e) { var data = Clipboard.GetDataObject(); PasteMenuItem.Enabled = _domain.CanPoke() && (_highlightedAddress.HasValue || _secondaryHighlightedAddresses.Any()) && data != null && data.GetDataPresent(DataFormats.Text); FindNextMenuItem.Enabled = !string.IsNullOrWhiteSpace(_findStr); } string MakeCopyExportString(bool export) { // make room for an array with _secondaryHighlightedAddresses and optionally HighlightedAddress long[] addresses = new long[_secondaryHighlightedAddresses.Count + (_highlightedAddress.HasValue ? 1 : 0)]; // if there was actually nothing to do, return if (addresses.Length == 0) { return null; } // fill the array with _secondaryHighlightedAddresses for (int i = 0; i < _secondaryHighlightedAddresses.Count; i++) { addresses[i] = _secondaryHighlightedAddresses[i]; } // and add HighlightedAddress if present if (_highlightedAddress.HasValue) { addresses[addresses.Length - 1] = _highlightedAddress.Value; } // these need to be sorted. it's not just for HighlightedAddress, _secondaryHighlightedAddresses can even be jumbled Array.Sort(addresses); // find the maximum length of the exported string int maximumLength = addresses.Length * (export ? 3 : 2) + 8; var sb = new StringBuilder(maximumLength); // generate it differently for export (as you see it) or copy (raw bytes) if (export) for (int i = 0; i < addresses.Length; i++) { sb.Append(ValueString(addresses[i])); if (i != addresses.Length - 1) { sb.Append(' '); } } else { foreach (var addr in addresses) { long start = addr; long end = addr + DataSize -1 ; for (long a = start; a <= end; a++) { sb.AppendFormat("{0:X2}", MakeValue(1, a)); } } } return sb.ToString(); } private void ExportMenuItem_Click(object sender, EventArgs e) { var value = MakeCopyExportString(true); if (!string.IsNullOrEmpty(value)) { Clipboard.SetDataObject(value); } } private void CopyMenuItem_Click(object sender, EventArgs e) { var value = MakeCopyExportString(false); if (!string.IsNullOrEmpty(value)) { Clipboard.SetDataObject(value); } } private void PasteMenuItem_Click(object sender, EventArgs e) { var data = Clipboard.GetDataObject(); if (data == null || !data.GetDataPresent(DataFormats.Text)) { return; } var clipboardRaw = (string)data.GetData(DataFormats.Text); var hex = clipboardRaw.OnlyHex(); var numBytes = hex.Length / 2; for (var i = 0; i < numBytes; i++) { var value = int.Parse(hex.Substring(i * 2, 2), NumberStyles.HexNumber); var address = (_highlightedAddress ?? 0) + i; if (address < _domain.Size) { _domain.PokeByte(address, (byte)value); } } FullUpdate(); } private bool _lastSearchWasText; private void SearchTypeChanged(bool isText) { _lastSearchWasText = isText; } private void FindMenuItem_Click(object sender, EventArgs e) { _findStr = GetFindValues(); if (!_hexFind.IsHandleCreated || _hexFind.IsDisposed) { _hexFind = new HexFind { InitialLocation = PointToScreen(AddressesLabel.Location), InitialValue = _findStr, SearchTypeChangedCallback = SearchTypeChanged, InitialText = _lastSearchWasText }; _hexFind.Show(); } else { _hexFind.InitialValue = _findStr; _hexFind.Focus(); } } private void FindNextMenuItem_Click(object sender, EventArgs e) { FindNext(_findStr, false); } private void FindPrevMenuItem_Click(object sender, EventArgs e) { FindPrev(_findStr, false); } #endregion #region Options private void OptionsSubMenu_DropDownOpened(object sender, EventArgs e) { BigEndianMenuItem.Checked = BigEndian; DataSizeByteMenuItem.Checked = DataSize == 1; DataSizeWordMenuItem.Checked = DataSize == 2; DataSizeDWordMenuItem.Checked = DataSize == 4; if (_highlightedAddress.HasValue && IsFrozen(_highlightedAddress.Value)) { FreezeAddressMenuItem.Image = Properties.Resources.Unfreeze; FreezeAddressMenuItem.Text = "Un&freeze Address"; } else { FreezeAddressMenuItem.Image = Properties.Resources.Freeze; FreezeAddressMenuItem.Text = "&Freeze Address"; } AddToRamWatchMenuItem.Enabled = _highlightedAddress.HasValue; PokeAddressMenuItem.Enabled = FreezeAddressMenuItem.Enabled = _highlightedAddress.HasValue && _domain.CanPoke(); } private void MemoryDomainsMenuItem_DropDownOpened(object sender, EventArgs e) { MemoryDomainsMenuItem.DropDownItems.Clear(); MemoryDomainsMenuItem.DropDownItems.AddRange( MemoryDomains.MenuItems(SetMemoryDomain, _domain.Name) .ToArray()); var romMenuItem = new ToolStripMenuItem { Text = _romDomain.Name, Checked = _domain.Name == _romDomain.Name }; MemoryDomainsMenuItem.DropDownItems.Add(new ToolStripSeparator()); MemoryDomainsMenuItem.DropDownItems.Add(romMenuItem); romMenuItem.Click += (o, ev) => SetMemoryDomain(_romDomain.Name); } private void DataSizeByteMenuItem_Click(object sender, EventArgs e) { SetDataSize(1); } private void DataSizeWordMenuItem_Click(object sender, EventArgs e) { SetDataSize(2); } private void DataSizeDWordMenuItem_Click(object sender, EventArgs e) { SetDataSize(4); } private void BigEndianMenuItem_Click(object sender, EventArgs e) { BigEndian ^= true; FullUpdate(); } private void GoToAddressMenuItem_Click(object sender, EventArgs e) { using var inputPrompt = new InputPrompt { Text = "Go to Address", StartLocation = this.ChildPointToScreen(MemoryViewerBox), Message = "Enter a hexadecimal value" }; var result = inputPrompt.ShowHawkDialog(); if (result == DialogResult.OK && inputPrompt.PromptText.IsHex()) { GoToAddress(long.Parse(inputPrompt.PromptText, NumberStyles.HexNumber)); } AddressLabel.Text = GenerateAddressString(); } private void AddToRamWatchMenuItem_Click(object sender, EventArgs e) { if (_highlightedAddress.HasValue || _secondaryHighlightedAddresses.Any()) { GlobalWin.Tools.LoadRamWatch(true); } if (_highlightedAddress.HasValue) { GlobalWin.Tools.RamWatch.AddWatch(MakeWatch(_highlightedAddress.Value)); } _secondaryHighlightedAddresses.ForEach(addr => GlobalWin.Tools.RamWatch.AddWatch(MakeWatch(addr))); } private void FreezeAddressMenuItem_Click(object sender, EventArgs e) { if (!_domain.CanPoke()) { return; } if (_highlightedAddress.HasValue) { var highlighted = _highlightedAddress.Value; if (IsFrozen(highlighted)) { UnFreezeAddress(highlighted); UnfreezeSecondaries(); } else { FreezeAddress(highlighted); FreezeSecondaries(); } } UpdateCheatRelatedTools(null, null); MemoryViewerBox.Refresh(); } private void UnfreezeAllMenuItem_Click(object sender, EventArgs e) { Global.CheatList.RemoveAll(); } private void PokeAddressMenuItem_Click(object sender, EventArgs e) { if (!_domain.CanPoke()) { return; } var addresses = new List(); if (_highlightedAddress.HasValue) { addresses.Add(_highlightedAddress.Value); } if (_secondaryHighlightedAddresses.Any()) { addresses.AddRange(_secondaryHighlightedAddresses); } if (addresses.Any()) { using var poke = new RamPoke { InitialLocation = this.ChildPointToScreen(AddressLabel), ParentTool = this }; var watches = addresses.Select( address => Watch.GenerateWatch( _domain, address, (WatchSize)DataSize, Common.DisplayType.Hex, BigEndian)); poke.SetWatch(watches); poke.ShowHawkDialog(); FullUpdate(); } } #endregion #region Settings Menu private void SetColorsMenuItem_Click(object sender, EventArgs e) { using var form = new HexColorsForm(); form.ShowHawkDialog(); } private void ResetColorsToDefaultMenuItem_Click(object sender, EventArgs e) { MemoryViewerBox.BackColor = Color.FromName("Control"); MemoryViewerBox.ForeColor = Color.FromName("ControlText"); this.HexMenuStrip.BackColor = Color.FromName("Control"); Header.BackColor = Color.FromName("Control"); Header.ForeColor = Color.FromName("ControlText"); Global.Config.HexMenubarColor = Color.FromName("Control"); Global.Config.HexForegrndColor = Color.FromName("ControlText"); Global.Config.HexBackgrndColor = Color.FromName("Control"); Global.Config.HexFreezeColor = Color.LightBlue; Global.Config.HexHighlightColor = Color.Pink; Global.Config.HexHighlightFreezeColor = Color.Violet; } #endregion #region Context Menu and Dialog Events private void HexEditor_Resize(object sender, EventArgs e) { SetUpScrollBar(); FullUpdate(); } private void HexEditor_ResizeEnd(object sender, EventArgs e) { SetUpScrollBar(); } private void HexEditor_KeyDown(object sender, KeyEventArgs e) { if (e.Control && e.KeyCode == Keys.G) { GoToAddressMenuItem_Click(sender, e); return; } if (e.Control && e.KeyCode == Keys.P) { PokeAddressMenuItem_Click(sender, e); return; } long currentAddress = _highlightedAddress ?? -1; long newHighlighted; switch (e.KeyCode) { case Keys.Up: newHighlighted = currentAddress - 16; if (e.Modifiers == Keys.Shift) { for (var i = newHighlighted + DataSize; i <= currentAddress; i += DataSize) { AddToSecondaryHighlights(i); } GoToAddress(newHighlighted); } else { _secondaryHighlightedAddresses.Clear(); GoToAddress(newHighlighted); } break; case Keys.Down: newHighlighted = currentAddress + 16; if (e.Modifiers == Keys.Shift) { for (var i = currentAddress; i < newHighlighted; i += DataSize) { AddToSecondaryHighlights(i); } GoToAddress(newHighlighted); } else { _secondaryHighlightedAddresses.Clear(); GoToAddress(newHighlighted); } break; case Keys.Left: newHighlighted = currentAddress - (1 * DataSize); if (e.Modifiers == Keys.Shift) { AddToSecondaryHighlights(currentAddress); GoToAddress(newHighlighted); } else { _secondaryHighlightedAddresses.Clear(); GoToAddress(newHighlighted); } break; case Keys.Right: newHighlighted = currentAddress + (1 * DataSize); if (e.Modifiers == Keys.Shift) { AddToSecondaryHighlights(currentAddress); GoToAddress(newHighlighted); } else { _secondaryHighlightedAddresses.Clear(); GoToAddress(newHighlighted); } break; case Keys.PageUp: newHighlighted = currentAddress - (_rowsVisible * 16); if (e.Modifiers == Keys.Shift) { for (var i = newHighlighted + 1; i <= currentAddress; i += DataSize) { AddToSecondaryHighlights(i); } GoToAddress(newHighlighted); } else { _secondaryHighlightedAddresses.Clear(); GoToAddress(newHighlighted); } break; case Keys.PageDown: newHighlighted = currentAddress + (_rowsVisible * 16); if (e.Modifiers == Keys.Shift) { for (var i = currentAddress + 1; i < newHighlighted; i += DataSize) { AddToSecondaryHighlights(i); } GoToAddress(newHighlighted); } else { _secondaryHighlightedAddresses.Clear(); GoToAddress(newHighlighted); } break; case Keys.Tab: _secondaryHighlightedAddresses.Clear(); if (e.Modifiers == Keys.Shift) { GoToAddress(currentAddress - 8); } else { GoToAddress(currentAddress + 8); } break; case Keys.Home: if (e.Modifiers == Keys.Shift) { for (var i = 1; i <= currentAddress; i += DataSize) { AddToSecondaryHighlights(i); } GoToAddress(0); } else { _secondaryHighlightedAddresses.Clear(); GoToAddress(0); } break; case Keys.End: newHighlighted = _domain.Size - DataSize; if (e.Modifiers == Keys.Shift) { for (var i = currentAddress; i < newHighlighted; i += DataSize) { AddToSecondaryHighlights(i); } GoToAddress(newHighlighted); } else { _secondaryHighlightedAddresses.Clear(); GoToAddress(newHighlighted); } break; case Keys.Add: IncrementContextItem_Click(sender, e); break; case Keys.Subtract: DecrementContextItem_Click(sender, e); break; case Keys.Space: FreezeAddressMenuItem_Click(sender, e); break; case Keys.Delete: if (e.Modifiers == Keys.Shift) { Global.CheatList.RemoveAll(); } else { if (_highlightedAddress.HasValue) { UnFreezeAddress(_highlightedAddress.Value); } } break; case Keys.W: if (e.Modifiers == Keys.Control) { AddToRamWatchMenuItem_Click(sender, e); } break; case Keys.Escape: _secondaryHighlightedAddresses.Clear(); ClearHighlighted(); break; } } private void HexEditor_KeyPress(object sender, KeyPressEventArgs e) { if (!IsHexKeyCode(e.KeyChar)) { e.Handled = true; return; } if ((ModifierKeys & (Keys.Control | Keys.Shift | Keys.Alt)) != 0) { return; } if (!_domain.CanPoke() || !_highlightedAddress.HasValue) { return; } var currentAddress = _highlightedAddress ?? 0; _nibbles.Add(e.KeyChar); if (_nibbles.Count == DataSize * 2) { var nibbleStr = MakeNibbles(); switch (DataSize) { default: case 1: var byteVal = byte.Parse(nibbleStr, NumberStyles.HexNumber); _domain.PokeByte(currentAddress, byteVal); break; case 2: var ushortVal = ushort.Parse(nibbleStr, NumberStyles.HexNumber); _domain.PokeUshort(currentAddress, ushortVal, !BigEndian); // TODO: is this method backwards? break; case 4: var uintVal = uint.Parse(nibbleStr, NumberStyles.HexNumber); _domain.PokeUint(currentAddress, uintVal, !BigEndian); // TODO: is this method backwards? break; } ClearNibbles(); SetHighlighted(currentAddress + DataSize); FullUpdate(); Refresh(); } UpdateGroupBoxTitle(); FullUpdate(); } private void ViewerContextMenuStrip_Opening(object sender, CancelEventArgs e) { var data = Clipboard.GetDataObject(); CopyContextItem.Visible = AddToRamWatchContextItem.Visible = _highlightedAddress.HasValue || _secondaryHighlightedAddresses.Any(); FreezeContextItem.Visible = PokeContextItem.Visible = IncrementContextItem.Visible = DecrementContextItem.Visible = ContextSeparator2.Visible = (_highlightedAddress.HasValue || _secondaryHighlightedAddresses.Any()) && _domain.CanPoke(); UnfreezeAllContextItem.Visible = Global.CheatList.ActiveCount > 0; PasteContextItem.Visible = _domain.CanPoke() && data != null && data.GetDataPresent(DataFormats.Text); ContextSeparator1.Visible = _highlightedAddress.HasValue || _secondaryHighlightedAddresses.Any() || (data != null && data.GetDataPresent(DataFormats.Text)); if (_highlightedAddress.HasValue && IsFrozen(_highlightedAddress.Value)) { FreezeContextItem.Text = "Un&freeze"; FreezeContextItem.Image = Properties.Resources.Unfreeze; } else { FreezeContextItem.Text = "&Freeze"; FreezeContextItem.Image = Properties.Resources.Freeze; } toolStripMenuItem1.Visible = viewN64MatrixToolStripMenuItem.Visible = DataSize == 4; } private void IncrementContextItem_Click(object sender, EventArgs e) { if (!_domain.CanPoke()) { return; } if (_highlightedAddress.HasValue) { IncrementAddress(_highlightedAddress.Value); } _secondaryHighlightedAddresses.ForEach(IncrementAddress); FullUpdate(); } private void DecrementContextItem_Click(object sender, EventArgs e) { if (!_domain.CanPoke()) { return; } if (_highlightedAddress.HasValue) { DecrementAddress(_highlightedAddress.Value); } _secondaryHighlightedAddresses.ForEach(DecrementAddress); FullUpdate(); } #endregion #region MemoryViewer Events private void HexEditor_MouseWheel(object sender, MouseEventArgs e) { var delta = 0; if (e.Delta > 0) { delta = -1; } else if (e.Delta < 0) { delta = 1; } var newValue = HexScrollBar.Value + delta; if (newValue < HexScrollBar.Minimum) { newValue = HexScrollBar.Minimum; } if (newValue > HexScrollBar.Maximum - HexScrollBar.LargeChange + 1) { newValue = HexScrollBar.Maximum - HexScrollBar.LargeChange + 1; } if (newValue != HexScrollBar.Value) { HexScrollBar.Value = newValue; MemoryViewerBox.Refresh(); } } private readonly Pen _blackPen = new Pen(Color.Black); private readonly SolidBrush _freezeBrush = new SolidBrush(Global.Config.HexFreezeColor); private readonly SolidBrush _freezeHighlightBrush = new SolidBrush(Global.Config.HexHighlightFreezeColor); private readonly SolidBrush _highlightBrush = new SolidBrush(Global.Config.HexHighlightColor); private readonly SolidBrush _secondaryHighlightBrush = new SolidBrush(Color.FromArgb(0x44, Global.Config.HexHighlightColor)); private void MemoryViewerBox_Paint(object sender, PaintEventArgs e) { var activeCheats = Global.CheatList.Where(x => x.Enabled); foreach (var cheat in activeCheats) { if (IsVisible(cheat.Address ?? 0)) { if (_domain.ToString() == cheat.Domain.Name) { var gaps = (int)cheat.Size - DataSize; if (cheat.Size == WatchSize.DWord && DataSize == 2) { gaps -= 1; } if (gaps < 0) { gaps = 0; } var width = (_fontWidth * 2 * (int)cheat.Size) + (gaps * _fontWidth); var rect = new Rectangle(GetAddressCoordinates(cheat.Address ?? 0), new Size(width, _fontHeight)); e.Graphics.DrawRectangle(_blackPen, rect); _freezeBrush.Color = Global.Config.HexFreezeColor; e.Graphics.FillRectangle(_freezeBrush, rect); } } } if (_highlightedAddress.HasValue && IsVisible(_highlightedAddress.Value)) { long addressHighlighted = _highlightedAddress ?? 0; // Create a slight offset to increase rectangle sizes var point = GetAddressCoordinates(addressHighlighted); var textX = (int)GetTextX(addressHighlighted); var textPoint = new Point(textX, point.Y); var rect = new Rectangle(point, new Size(_fontWidth * 2 * DataSize + (NeedsExtra(addressHighlighted) ? _fontWidth : 0) + 2, _fontHeight)); e.Graphics.DrawRectangle(_blackPen, rect); var textRect = new Rectangle(textPoint, new Size(_fontWidth * DataSize, _fontHeight)); if (Global.CheatList.IsActive(_domain, addressHighlighted)) { _freezeHighlightBrush.Color = Global.Config.HexHighlightFreezeColor; e.Graphics.FillRectangle(_freezeHighlightBrush, rect); e.Graphics.FillRectangle(_freezeHighlightBrush, textRect); } else { _highlightBrush.Color = Global.Config.HexHighlightColor; e.Graphics.FillRectangle(_highlightBrush, rect); e.Graphics.FillRectangle(_highlightBrush, textRect); } } foreach (var address in _secondaryHighlightedAddresses) { if (IsVisible(address)) { var point = GetAddressCoordinates(address); var textX = (int)GetTextX(address); var textPoint = new Point(textX, point.Y); var rect = new Rectangle(point, new Size(_fontWidth * 2 * DataSize + 2, _fontHeight)); e.Graphics.DrawRectangle(_blackPen, rect); var textRect = new Rectangle(textPoint, new Size(_fontWidth * DataSize, _fontHeight)); if (Global.CheatList.IsActive(_domain, address)) { _freezeHighlightBrush.Color = Global.Config.HexHighlightFreezeColor; e.Graphics.FillRectangle(_freezeHighlightBrush, rect); e.Graphics.FillRectangle(_freezeHighlightBrush, textRect); } else { _secondaryHighlightBrush.Color = Color.FromArgb(0x44, Global.Config.HexHighlightColor); e.Graphics.FillRectangle(_secondaryHighlightBrush, rect); e.Graphics.FillRectangle(_secondaryHighlightBrush, textRect); } } } } private void AddressesLabel_MouseUp(object sender, MouseEventArgs e) { _mouseIsDown = false; } private void AddressesLabel_MouseMove(object sender, MouseEventArgs e) { _addressOver = GetPointedAddress(e.X, e.Y); if (_mouseIsDown) { DoShiftClick(); UpdateFormText(); MemoryViewerBox.Refresh(); } } private void AddressesLabel_MouseLeave(object sender, EventArgs e) { _addressOver = -1; MemoryViewerBox.Refresh(); } private void AddressesLabel_MouseDown(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) { var pointedAddress = GetPointedAddress(e.X, e.Y); if (pointedAddress >= 0) { if ((ModifierKeys & Keys.Control) == Keys.Control) { if (pointedAddress == _highlightedAddress) { ClearHighlighted(); } else if (_secondaryHighlightedAddresses.Contains(pointedAddress)) { _secondaryHighlightedAddresses.Remove(pointedAddress); } else { _secondaryHighlightedAddresses.Add(pointedAddress); } } else if ((ModifierKeys & Keys.Shift) == Keys.Shift) { DoShiftClick(); } else { _secondaryHighlightedAddresses.Clear(); _findStr = ""; SetHighlighted(pointedAddress); } MemoryViewerBox.Refresh(); } _mouseIsDown = true; } } private bool _programmaticallyChangingValue; private void HexScrollBar_ValueChanged(object sender, EventArgs e) { if (!_programmaticallyChangingValue) { if (HexScrollBar.Value < 0) { _programmaticallyChangingValue = true; HexScrollBar.Value = 0; _programmaticallyChangingValue = false; } FullUpdate(); } } #endregion #endregion private void viewN64MatrixToolStripMenuItem_Click(object sender, EventArgs e) { if (!_highlightedAddress.HasValue) { return; } bool bigEndian = true; long addr = _highlightedAddress.Value; //ushort = _domain.PeekWord(addr, bigEndian); float[,] matVals = new float[4,4]; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { ushort hi = _domain.PeekUshort(((addr+(i<<3)+(j<<1) )^0x0), bigEndian); ushort lo = _domain.PeekUshort(((addr+(i<<3)+(j<<1) + 32)^0x0), bigEndian); matVals[i,j] = (int)(((hi << 16) | lo)) / 65536.0f; } } // if needed ////var mat = new SlimDX.Matrix(); ////mat.M11 = matVals[0, 0]; mat.M12 = matVals[0, 1]; mat.M13 = matVals[0, 2]; mat.M14 = matVals[0, 3]; ////mat.M21 = matVals[1, 0]; mat.M22 = matVals[1, 1]; mat.M23 = matVals[1, 2]; mat.M24 = matVals[1, 3]; ////mat.M31 = matVals[2, 0]; mat.M32 = matVals[2, 1]; mat.M33 = matVals[2, 2]; mat.M34 = matVals[2, 3]; ////mat.M41 = matVals[3, 0]; mat.M42 = matVals[3, 1]; mat.M43 = matVals[3, 2]; mat.M44 = matVals[3, 3]; ////MessageBox.Show(mat.ToString()); using var sw = new StringWriter(); for (int i = 0; i < 4; i++) { sw.WriteLine("{0,18:0.00000} {1,18:0.00000} {2,18:0.00000} {3,18:0.00000}", matVals[i, 0], matVals[i, 1], matVals[i, 2], matVals[i, 3]); } var str = sw.ToString(); MessageBox.Show(str); } } }