File:  [Isaki's NoNo m68k/m88k emulator] / nono / wx / wxdumpmonitor.cpp
Revision 1.1.1.18 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:58 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
nono 1.7.0

//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//

//
// メモリダンプウィンドウ
//

#include "wxdumpmonitor.h"
#include "mainapp.h"
#include "mainbus.h"
#include "monitor.h"
#include "mpu680x0.h"

enum {
	ID_TEXT = IDGROUP_MEMDUMP,
	ID_PREVLINE,
	ID_PREVPAGE,
	ID_NEXTLINE,
	ID_NEXTPAGE,
	ID_FORMAT,

	ID_EDIT,

	ID_local_end,	// 最後に置く (チェック用)
};
static_assert(ID_local_end - 1 <= (int)IDGROUP_MEMDUMP_END, "ID exceeded");

// イベントテーブル
wxBEGIN_EVENT_TABLE(WXMemdumpWindow, inherited)
	EVT_TEXT_ENTER(ID_TEXT, WXMemdumpWindow::OnTextEnter)
	EVT_COMMAND(ID_PREVLINE, NONO_EVT_BUTTON, WXMemdumpWindow::OnPrevLine)
	EVT_COMMAND(ID_PREVPAGE, NONO_EVT_BUTTON, WXMemdumpWindow::OnPrevPage)
	EVT_COMMAND(ID_NEXTLINE, NONO_EVT_BUTTON, WXMemdumpWindow::OnNextLine)
	EVT_COMMAND(ID_NEXTPAGE, NONO_EVT_BUTTON, WXMemdumpWindow::OnNextPage)
	EVT_CHOICE(ID_FORMAT, WXMemdumpWindow::OnFormat)
wxEND_EVENT_TABLE()

// コンストラクタ
// (タイトルは UpdateAddr() でセットする)
WXMemdumpWindow::WXMemdumpWindow(wxWindow *parent, int monid_)
	: inherited(parent, wxID_ANY, wxEmptyString,
		DEFAULT_STYLE | wxRESIZE_BORDER)
{
	monid = monid_;
	if (IS_MONITOR_XPMEMDUMP(monid)) {
		myidx = monid - ID_MONITOR_XPMEMDUMP0;
	} else {
		myidx = monid - ID_MONITOR_MEMDUMP0;
	}

	// ↓ topsizer
	//   +--------------------------------------------+
	//   |上枠 →                                     |
	//   |+-----+------+-----+----------+-----+------+|
	//   ||余白 | "<<" | "<" | アドレス | ">" | ">>" ||
	//   |+-----+------+-----+----------+-----+------+|
	//   +--------------------------------------------+
	//   | TextScreen                                 |
	//   +--------------------------------------------+

	auto *topsizer = new wxBoxSizer(wxVERTICAL);

	// 上枠用の下敷きパネル
	auto *ctrlpanel = new wxPanel(this);
	ctrlpanel->SetName("WXMemdumpWindow.CtrlPanel");
	topsizer->Add(ctrlpanel, 0, wxEXPAND);

	// 上枠用の横 sizer
	ctrlbox = new wxBoxSizer(wxHORIZONTAL);

	// アドレスコントロール
	addrctrl = new wxTextCtrl(ctrlpanel, ID_TEXT,
		wxEmptyString, wxDefaultPosition, wxDefaultSize,
		wxTE_PROCESS_ENTER);
	buttons[0] = new WXButton(ctrlpanel, ID_PREVPAGE, wxDefaultSize, "<<");
	buttons[1] = new WXButton(ctrlpanel, ID_PREVLINE, wxDefaultSize, "<");
	buttons[2] = new WXButton(ctrlpanel, ID_NEXTLINE, wxDefaultSize, ">");
	buttons[3] = new WXButton(ctrlpanel, ID_NEXTPAGE, wxDefaultSize, ">>");

	ctrlbox->AddSpacer(3);
	ctrlbox->Add(buttons[0], 0, wxALIGN_CENTER);
	ctrlbox->Add(buttons[1], 0, wxALIGN_CENTER);
	ctrlbox->AddSpacer(3);
	ctrlbox->Add(addrctrl, 0, wxALIGN_CENTER);
	ctrlbox->AddSpacer(3);
	ctrlbox->Add(buttons[2], 0, wxALIGN_CENTER);
	ctrlbox->Add(buttons[3], 0, wxALIGN_CENTER);
	ctrlbox->AddSpacer(3);

	// 表示形式 (機種によって選択肢と名前が異なる)
	if (gMainApp.Has(VMCap::M68K)) {
		auto mpu680x0 = GetMPU680x0Device(GetMPUDevice());
		fmts.emplace_back(Memdump::Byte, _("Byte"));
		fmts.emplace_back(Memdump::Word, _("Word"));
		fmts.emplace_back(Memdump::Long, _("Long Word"));
		switch (mpu680x0->GetMPUType()) {
		 case m680x0MPUType::M68030:
			fmts.emplace_back(Memdump::M68030PageShort,
				_("MMU Descriptor (Short Format)"));
			break;
		 case m680x0MPUType::M68040:
			fmts.emplace_back(Memdump::M68040TableDesc,
				_("MMU Table Descriptor"));
			fmts.emplace_back(Memdump::M68040PageDesc,
				_("MMU Page Descriptor"));
			break;
		 default:
			break;
		}
		fmts.emplace_back(Memdump::M680x0Disasm, _("680x0 Disassemble"));
	} else {
		fmts.emplace_back(Memdump::Byte, _("Byte"));
		fmts.emplace_back(Memdump::Word, _("Half Word"));
		fmts.emplace_back(Memdump::Long, _("Word"));
		fmts.emplace_back(Memdump::M88200Page, _("88200 Descriptor"));
		fmts.emplace_back(Memdump::M88100Disasm, _("88100 Disassemble"));
	}
	if (gMainApp.Has(VMCap::LUNA)) {
		fmts.emplace_back(Memdump::HD64180Disasm, _("HD64180 Disassemble"));
	}

	// fmts から名前だけの配列を作る
	std::vector<wxString> fmt_choice;
	for (auto p : fmts) {
		fmt_choice.push_back(p.second);
	}
	fmtctrl = new wxChoice(ctrlpanel, ID_FORMAT,
		wxDefaultPosition, wxDefaultSize,
		fmt_choice.size(), &fmt_choice[0]);
	ctrlbox->Add(fmtctrl, 0, wxALIGN_CENTER);

	// 上枠の sizer と下敷きを紐付ける
	ctrlpanel->SetSizer(ctrlbox);
	ctrlbox->SetSizeHints(ctrlpanel);

	screen = new WXMonitorPanel(this, gMonitorManager->Get(monid));
	topsizer->Add(screen, 1, wxEXPAND);

	SetSizer(topsizer);

	// 自前レイアウト。
	SetMyLayout();

	// ウィンドウサイズを確定させる
	FontChanged();

	// パネルに来るマウスイベントをこっちに回す
	screen->Bind(wxEVT_MOUSEWHEEL, &WXMemdumpWindow::OnMouseWheel, this);
	screen->Bind(wxEVT_LEFT_DCLICK, &WXMemdumpWindow::OnDClick, this);

	// メモリダンプオブジェクトを取得
	auto mon = screen->GetMonitor();
	memdump = dynamic_cast<MemdumpMonitor *>(mon->obj);
	assert(memdump);

	// 初期値をコントロールに設定
	auto fmt = memdump->GetFormat();
	fmtctrl->SetSelection(Format2Selection(fmt));
	DoFormat(fmt);
	UpdateAddr();
}

// デストラクタ
WXMemdumpWindow::~WXMemdumpWindow()
{
}

void
WXMemdumpWindow::FontChanged()
{
	for (auto *btn : buttons) {
		// ボタンサイズを先に一旦リセット。
		btn->SetMinSize(wxSize(1, 1));
		btn->SetSize(wxSize(1, 1));

		// 各ボタンコントロールに伝搬。
		btn->FontChanged();

		// ボタンを正方形に固定する (テキストは1文字か2文字と知っている)。
		// 押しやすくするため、最小 24x24 にしてみる。
		wxSize size = btn->GetSize();
		if (size.y < 24) {
			size.y = 24;
		}
		size.x = size.y;

		// 勝手にベストサイズに戻らないように最小サイズごと固定する。
		btn->SetSize(size);
		btn->SetMinSize(size);
	}

	screen->FontChanged();

	Fit();
}

bool
WXMemdumpWindow::GetMySizeHints(wxSize *newp, wxSize *minp, wxSize *maxp,
	wxSize *incp)
{
	// この後の Fit() でリサイズに使われる大きさを取得。
	wxSize fitsize = GetSizer()->ComputeFittingClientSize(this);
	wxSize ctrlsize = ctrlbox->ComputeFittingClientSize(this);

	// 高さの最小は (コントロールパネル分と) TextScreen 1行分にしておく。
	int min_y = ctrlsize.y + screen->GetScreenHeight(1);
	int new_y = ctrlsize.y + screen->GetSize().y;
	wxSize newsize(fitsize.x, new_y);
	wxSize minsize(fitsize.x, min_y);
	wxSize maxsize(fitsize.x, GetMaxClientSize().y);
	wxSize incsize(0, screen->GetFontHeight());

	if (newp) *newp = newsize;
	if (minp) *minp = minsize;
	if (maxp) *maxp = maxsize;
	if (incp) *incp = incsize;
	return true;
}

bool
WXMemdumpWindow::Layout()
{
	if (MyLayout() == false) {
		// Sizer 使ってるのでその処理は(さらに親に)任せる。
		BaseLayout();
	}
	return true;
}

// テキスト入力イベント
void
WXMemdumpWindow::OnTextEnter(wxCommandEvent& event)
{
	// 入力文字列(16進文字列)を数値に変換
	const wxString& wstr = event.GetString();
	uint32 val = strtoul((const char *)wstr.mb_str(), NULL, 16);

	// アドレスを更新
	memdump->SetAddr(val);
	UpdateAddr();
}

// "<"(前行) ボタン押下イベント
void
WXMemdumpWindow::OnPrevLine(wxCommandEvent& event)
{
	// 1行戻る
	memdump->Offset(memdump->GetLineOffset(-1));

	UpdateAddr();
}

// "<<"(前ページ) ボタン押下イベント
void
WXMemdumpWindow::OnPrevPage(wxCommandEvent& event)
{
	// 1ページ戻る
	memdump->Offset(memdump->GetPageOffset(-1));

	UpdateAddr();
}

// ">"(次行) ボタン押下イベント
void
WXMemdumpWindow::OnNextLine(wxCommandEvent& event)
{
	// 1行進む
	memdump->Offset(memdump->GetLineOffset(1));

	UpdateAddr();
}

// ">>"(次ページ) ボタン押下イベント
void
WXMemdumpWindow::OnNextPage(wxCommandEvent& event)
{
	// 1ページ進む
	memdump->Offset(memdump->GetPageOffset(1));

	UpdateAddr();
}

// アドレス欄の表示を更新する
void
WXMemdumpWindow::UpdateAddr()
{
	int width;
	std::string title;
	if (IS_MONITOR_XPMEMDUMP(monid)) {
		width = 5;
		title = _("XP Memory Dump");
	} else {
		width = 8;
		title = _("Main Memory Dump");
	}

	uint32 addr = memdump->GetAddr().Addr();
	std::string addrstr = strhex(addr, width);

	// EVT_TEXT を起こさず変更する
	addrctrl->ChangeValue(addrstr);

	// カーソルを末尾に
	addrctrl->SetInsertionPointEnd();

	// タイトルを変更
	title += string_format(" %d: $", myidx);
	title += addrstr;
	SetTitle(title);

	// 即更新
	Refresh();
}

// 表示フォーマット変更イベント
void
WXMemdumpWindow::OnFormat(wxCommandEvent& event)
{
	int idx = event.GetSelection();
	DoFormat(Selection2Format(idx));
}

// 表示フォーマット変更処理
void
WXMemdumpWindow::DoFormat(Memdump::Format fmt)
{
	memdump->SetFormat(fmt);

	// SetFormat() によってアドレスが切り捨てられる場合があるので
	// アドレス欄の表示ごと更新。
	UpdateAddr();
}

// マウスホイールイベント
void
WXMemdumpWindow::OnMouseWheel(wxMouseEvent& event)
{
	int delta = event.GetWheelRotation();

	// 1回の変化で +/-120 が来る。
	// たいていのアプリケーションで3行ずつの移動に換算することが多いようだが
	// ここはメモリダンプなので 4行ずつにする。

	if (delta != 0) {
		memdump->Offset(memdump->GetLineOffset(-delta / 30));
		UpdateAddr();
	}
}

// ダブルクリックイベント
void
WXMemdumpWindow::OnDClick(wxMouseEvent& event)
{
	event.Skip();

	auto fmt = memdump->GetFormat();

	// テキストの桁数、行数にする
	wxPoint tpos = screen->GetTextPosition(event.GetPosition());
	int x = tpos.x;
	int y = tpos.y;

	// ウィンドウサイズを変えた時に出来る下の空き地にはヒットさせない
	if (y >= screen->GetRow()) {
		return;
	}

	// アドレス部分は常に除外でいいか
	if (x < 19) {
		return;
	}

	// 現在のフォーマットでのクリック位置のアドレスを求める
	int len = 0;
	uint64 addr = -1;
	switch (fmt) {
	 case Memdump::Byte:
		len = 1;
		addr = GetAddrAtHexdump(x, y, len);
		break;
	 case Memdump::Word:
		len = 2;
		addr = GetAddrAtHexdump(x, y, len);
		break;
	 case Memdump::Long:
		len = 4;
		addr = GetAddrAtHexdump(x, y, len);
		break;
	 case Memdump::M68030PageShort:
	 case Memdump::M88200Page:
		len = 4;
		addr = GetAddrAtPageShort(x, y);
		break;

	 case Memdump::M68030PageLong:
		PANIC("not supported");
		break;

	 case Memdump::M680x0Disasm:
		len = 2;
		addr = GetAddrAtDisasm(x, y, len);
		break;

	 case Memdump::M88100Disasm:
		len = 4;
		addr = GetAddrAtDisasm(x, y, len);
		break;

	 case Memdump::HD64180Disasm:
		len = 1;
		addr = GetAddrAtDisasm(x, y, len);
		break;

	 default:
		break;
	}
	if ((int64)addr < 0) {
		return;
	}
	addr &= ~(len - 1);

	// このアドレスが編集可能 (≒メモリ) か?
	if (memdump->CanPoke(addr) == false) {
		::wxMessageBox(_("Can not edit here"), _("Memory Edit"),
			wxICON_EXCLAMATION | wxOK | wxOK_DEFAULT, this);
		return;
	}

	// 出来ればここをハイライトとかしたい

	// 現在値を(再)取得。
	uint32 val = memdump->Peek(addr, len);

	// sizestr は表示形式ではなく単なるサイズ。(DescShort なら Long)
	std::string sizestr;
	switch (len) {
	 case 1:
		sizestr = Format2String(Memdump::Byte);
		break;
	 case 2:
		sizestr = Format2String(Memdump::Word);
		break;
	 case 4:
		sizestr = Format2String(Memdump::Long);
		break;
	 default:
		PANIC("corrupted len=%d", len);
	}

	// 編集
	auto dlg = new MemEditDialog(this, addr, val, len, sizestr);
	if (dlg->ShowModal() == wxID_OK) {
		uint32 data = dlg->GetData();

		// 更新
		if (memdump->Poke(addr, data, len) == false) {
			::wxMessageBox(_("Could not edit"), _("Memory Edit"),
				wxICON_EXCLAMATION | wxOK | wxOK_DEFAULT, this);
		}
	}
}

// 16進ダンプ画面のテキスト座標 (x, y) に対応するアドレスを返す。
// 対応するアドレスがない場合は (uint64)-1 を返す。
uint64
WXMemdumpWindow::GetAddrAtHexdump(int x, int y, int len)
{
	if (x < 68) {
		// 16進ダンプ部分

		// 空白部分なら除外
		x -= 19;
		if (x % (len * 2 + 1) == len * 2) {
			return (uint64)-1;
		}
		x = (x / (len * 2 + 1)) * len;
	} else {
		// 文字ダンプ部分
		x -= 68;
	}

	if (x > 15) {
		return (uint64)-1;
	}

	return memdump->GetAddr().Addr() + (y * memdump->GetStride()) + x;
}

// 32bit ページテーブル画面のテキスト座標 (x, y) に対応するアドレスを返す。
// 対応するアドレスがない場合は (uint64)-1 を返す。
uint64
WXMemdumpWindow::GetAddrAtPageShort(int x, int y)
{
	// 32bit ページテーブルなら1行で1ロングワード。
	// 16進の場合との親和性のため、ダンプ値のところだけ反応。
	if (x >= 19 + 8) {
		return (uint64)-1;
	}
	return memdump->GetAddr().Addr() + y * 4;
}

// 逆アセンブル画面のテキスト座標 (x, y) に対応するアドレスを返す。
// minlen は命令の最小バイト数。
// 対応するアドレスがない場合は (uint64)-1 を返す。
uint64
WXMemdumpWindow::GetAddrAtDisasm(int x, int y, int minlen)
{
	// 先頭から y 行目のアドレス
	uint32 yaddr = memdump->GetLineOffset(y);

	// y 行目の命令のバイト数
	uint32 y1addr = memdump->GetLineOffset(y + 1);
	uint32 bytes = y1addr - yaddr;

	// 空白部分なら除外
	x -= 19;

	if (x % (minlen * 2 + 1) == minlen * 2) {
		return (uint64)-1;
	}
	x = (x / (minlen * 2 + 1)) * minlen;

	if (x >= bytes) {
		return (uint64)-1;
	}

	return memdump->GetAddr().Addr() + yaddr + x;
}

// セレクション番号からフォーマット番号への変換
Memdump::Format
WXMemdumpWindow::Selection2Format(int idx)
{
	assert(idx < fmts.size());
	return fmts[idx].first;
}

// フォーマット番号からセレクション番号への変換
int
WXMemdumpWindow::Format2Selection(Memdump::Format fmt_)
{
	for (int idx = 0, end = fmts.size(); idx < end; idx++) {
		auto p = fmts[idx];
		if (fmt_ == p.first) {
			return idx;
		}
	}
	// ?
	return 0;
}

// フォーマット番号から文字列への変換
wxString
WXMemdumpWindow::Format2String(Memdump::Format fmt_)
{
	for (int idx = 0, end = fmts.size(); idx < end; idx++) {
		auto p = fmts[idx];
		if (fmt_ == p.first) {
			return p.second;
		}
	}
	// ?
	return wxEmptyString;
}


//
// 編集ダイアログ
//

wxBEGIN_EVENT_TABLE(MemEditDialog, inherited)
	EVT_TEXT_ENTER(ID_EDIT, MemEditDialog::OnTextEnter)
wxEND_EVENT_TABLE()

// コンストラクタ
MemEditDialog::MemEditDialog(wxWindow *parent, uint32 addr, uint32 oldval,
	int len, const std::string& sizestr)
	: inherited(parent, wxID_ANY, _("Edit memory"))
{
	auto topsizer = new wxBoxSizer(wxVERTICAL);

	auto gridsizer = new wxFlexGridSizer(2);
	topsizer->Add(gridsizer, 0, wxEXPAND | wxALL, 3);

	gridsizer->Add(new wxStaticText(this, wxID_ANY, _("Address:")),
		0, wxEXPAND);

	std::string addrstr = "$";
	addrstr += strhex(addr);
	auto addrctrl = new wxStaticText(this, wxID_ANY, addrstr,
		wxDefaultPosition, wxDefaultSize, wxBORDER_SUNKEN);
	gridsizer->Add(addrctrl, 0, wxEXPAND);

	gridsizer->Add(new wxStaticText(this, wxID_ANY, _("Size:")),
		0, wxEXPAND);
	gridsizer->Add(new wxStaticText(this, wxID_ANY, sizestr),
		0, wxEXPAND);

	gridsizer->Add(new wxStaticText(this, wxID_ANY, _("Data:")),
		0, wxEXPAND);

	std::string oldstr = strhex(oldval, len * 2);
	datactrl = new wxTextCtrl(this, ID_EDIT, oldstr,
		wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER);
	gridsizer->Add(datactrl, 0, wxEXPAND);

	topsizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL | wxNO_DEFAULT),
		0, wxEXPAND | wxALL, 3);

	SetSizer(topsizer);
	wxSize sz = topsizer->Fit(this);
	SetClientSize(sz);
	SetMinClientSize(sz);
}

// デストラクタ
MemEditDialog::~MemEditDialog()
{
}

// テキスト入力
void
MemEditDialog::OnTextEnter(wxCommandEvent& event)
{
	EndModal(wxID_OK);
}

// テキストコントロールの値を返す。
// 変換できなければ (uint64)-1 を返す。
uint64
MemEditDialog::GetData() const
{
	unsigned long int ul;
	char *end;

	// wxString だと地味にめんどい
	wxString wtext = datactrl->GetValue();
	std::string text((const char *)wtext.mb_str());

	errno = 0;
	ul = strtoul(&text[0], &end, 16);
	if (end == &text[0] || *end != '\0' || errno != 0) {
		return (uint64)-1;
	}
	return (uint32)ul;
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.