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

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

//
// Windrv (相当)
//

// Windrv インスタンスは、設定 (Windrv の有無) によらず常に生成するが、
// メモリにマップするかどうかが変わる。
// Windrv 設定がない時にインスタンスごと作らない方式だと、設定を無効にすると
// 同時にログ識別子も無効になってコマンドラインの -L windrv オプションも
// エラーになってしまう。これは操作性がよくないので WindrvDevice の
// インスタンスは常に存在してほしい。一方、常にメモリマップ上に載せておくと
// Windrv を無効にしているにも関わらずメモリマップモニタで表示されるのが
// 気になることになるため。

#if !defined(TEST_WINDRV)

#include "windrv.h"
#include "config.h"
#include "hostwindrv.h"
#include "mainbus.h"
#include "mpu680x0.h"
#include "mytime.h"
#include <fcntl.h>
#include <time.h>
#include <algorithm>

// コンストラクタ
WindrvDevice::WindrvDevice()
	: inherited(OBJ_WINDRV)
{
	for (int i = 0; i < hosts.size(); i++) {
		hosts[i].reset();
	}
}

// デストラクタ
WindrvDevice::~WindrvDevice()
{
	for (auto& host : hosts) {
		if ((bool)host) {
			host.reset();
		}
	}
}

// 設定ファイルを読んで Windrv を組み込む場合は true を返す。
/*static*/ bool
WindrvDevice::IsWindrv()
{
	const ConfigItem& item = gConfig->Find("windrv-path");
	return (item.AsString().empty() == false);
}

bool
WindrvDevice::Create()
{
	if (IsWindrv()) {
		// 対応するホストドライバを作成。
		// 今はまだ1つのみ。
		try {
			hosts[0].reset(new HostWindrv(this));
		} catch (...) { }
		if ((bool)hosts[0] == false) {
			warnx("Failed to initialize HostWindrv at %s", __method__);
			return false;
		}
	}

	return true;
}

void
WindrvDevice::SetLogLevel(int loglevel_)
{
	inherited::SetLogLevel(loglevel_);

	for (auto& host : hosts) {
		if ((bool)host) {
			host->SetLogLevel(loglevel_);
		}
	}
}

// 初期化
bool
WindrvDevice::Init()
{
	mainbus = GetMainbusDevice();
	mpu680x0 = GetMPU680x0Device(mpu);

	// 今は1つしかない。
	HostWindrv *host = hosts[0].get();
	if (host != NULL) {
		const ConfigItem& item = gConfig->Find("windrv-path");
		// パスが空ならここには来ないはず。
		assert(item.AsString().empty() == false);
		std::string path = gMainApp.NormalizePath(item.AsString());
		if (host->InitRootPath(path) == false) {
			item.Err("Invalid root path");
			return false;
		}
	}

	return true;
}

void
WindrvDevice::ResetHard(bool poweron)
{
	// FILES 管理バッファをクリア。
	for (auto& it : filesmap) {
		delete it.second;
	}
	filesmap.clear();

	// FCB バッファをクリア。
	for (auto& it : fcbmap) {
		delete it.second;
	}
	fcbmap.clear();
}

busdata
WindrvDevice::ReadPort(uint32 offset)
{
	busdata data;

	switch (offset) {
	 case 0x1000:	// 識別ポート
		data = GetIdent();
		putlog(2, "Ident -> $%02x", data.Data());
		break;
	 default:		// 他はバスエラー
		data.SetBusErr();
		break;
	}

	data |= BusData::Size1;
	return data;
}

busdata
WindrvDevice::WritePort(uint32 offset, uint32 data)
{
	busdata r;

	switch (offset) {
	 case 0x1000:	// 動作ポート
		// 実行
		Execute();
		break;
	 default:		// 他はバスエラー
		r.SetBusErr();
		break;
	}

	r |= BusData::Size1;
	return r;
}

busdata
WindrvDevice::PeekPort(uint32 offset)
{
	switch (offset) {
	 case 0x1000:	// 識別ポート
		return GetIdent();
	 default:		// 他はバスエラー
		return BusData::BusErr;
	}
}

bool
WindrvDevice::PokePort(uint32 offset, uint32 data)
{
	return false;
}

// モード識別ポートの読み出し
uint32
WindrvDevice::GetIdent() const
{
	return 'W';	// WINDRV 互換
}

// コマンド実行
void
WindrvDevice::Execute()
{
	uint32 error;

	// A5 レジスタ経由で受け取るらしい。
	a5 = mpu680x0->reg.A[5];

	// エラー情報をクリア。
	WriteMem16(a5 + 3, 0);

	// コマンド番号取得。
	command = ReadMem8(a5 + 2);

	cmdname = "Cmd";
	if (__predict_false(loglevel >= 2)) {
		cmdname += string_format(" $%02x", command);
	}

	if (command == 0x40) {
		// 初期化コマンドだけいろいろ挙動が違う。
		CmdInitialize();
		error = 0;
	} else {
		// コマンド実行。
		uint32 result = DispatchCommand();

		// 結果を整理。
		SetResult(result);

		// 戻り値は result ではなくエラー情報のようだ。
		error = ReadMem8(a5 + 3) | (ReadMem8(a5 + 4) << 8);
	}

	mpu680x0->reg.D[0] = error;
}

// コマンドに分岐。
uint32
WindrvDevice::DispatchCommand()
{
	uint32 result;

	// ユニット番号取得。
	int unit = ReadMem8(a5 + 1);

	// 初期化コマンド以外ならユニット番号をチェック。
	if (unit >= hosts.size()) {
		return -Human68k::ERR_INVALID_UNIT_IA;
	}
	HostWindrv *host = hosts[unit].get();
	if (host == NULL) {
		return -Human68k::ERR_INVALID_UNIT_IA;
	}

	// 最上位ビットはベリファイフラグのようだ。(無視する)
	switch (command & 0x7f) {
	 case 0x41:	result = CmdCheckDir(host);		break;	// ディレクトリチェック
	 case 0x42:	result = CmdMakeDir(host);		break;	// ディレクトリ作成
	 case 0x43:	result = CmdRemoveDir(host);	break;	// ディレクトリ削除
	 case 0x44:	result = CmdRename(host);		break;	// ファイル名変更
	 case 0x45:	result = CmdDelete(host);		break;	// ファイル削除
	 case 0x46:	result = CmdAttribute(host);	break;	// ファイル属性取得設定
	 case 0x47:	result = CmdFiles(host);		break;	// ファイル検索(First)
	 case 0x48:	result = CmdNFiles(host);		break;	// ファイル検索(Next)
	 case 0x49:	result = CmdCreate(host);		break;	// ファイル作成
	 case 0x4a:	result = CmdOpen(host);			break;	// ファイルオープン
	 case 0x4b:	result = CmdClose(host);		break;	// ファイルクローズ
	 case 0x4c:	result = CmdRead(host);			break;	// ファイル読み込み
	 case 0x4d:	result = CmdWrite(host);		break;	// ファイル書き込み
	 case 0x4e:	result = CmdSeek(host);			break;	// ファイルシーク
	 case 0x4f:	result = CmdFileTime(host);		break;	// ファイル更新時刻
	 case 0x50:	result = CmdGetCapacity(host);	break;	// 容量取得
	 case 0x51:	result = CmdCtrlDrive(host);	break;	// ドライブ制御/状態検査
	 case 0x52:	result = CmdGetDPB(host);		break;	// DPB取得
	 case 0x53:	result = CmdDiskRead(host);		break;	// セクタ読み込み
	 case 0x54:	result = CmdDiskWrite(host);	break;	// セクタ書き込み
	 case 0x55:	result = CmdIoControl(host);	break;	// IOCTRL
	 case 0x56:	result = CmdFlush(host);		break;	// フラッシュ
	 case 0x57:	result = CmdCheckMedia(host);	break;	// メディア交換チェック
	 case 0x58:	result = CmdLock(host);			break;	// 排他制御
	 default:
		// 無効なコマンド
		putlog(1, "Cmd $%02x (INVALID)", command);
		result = -Human68k::ERR_INVALID_COMMAND_IA;
		break;
	}
	return result;
}

// $40 初期化
//
// IN
//   +0, 1.B: 定数 ($16)
//	 +2, 1.B: コマンド ($40)
//	+18, 1.L: 引数が書いてあるアドレス
//	+22, 1.B: (先頭の) ドライブ番号 (A:=0、…)
// OUT
//	 +3, 1.W: エラーコード(下位、上位の順)
//	+13, 1.B: ユニット数
//	+14, 1.L: デバイスドライバの終了アドレス + 1
uint32
WindrvDevice::CmdInitialize()
{
	if (__predict_false(loglevel >= 1)) {
		cmdname += " Initialize";
		putlog(2, "%s", cmdname.c_str());
	}

	// ベースドライブ名を取得。
	uint32 base = ReadMem8(a5 + 22);
	if (base >= 26) {
		uint32 result = Human68k::RES_INVALID_DRIVE;
		putlog(2, "%s => %s", cmdname.c_str(), ResultStr(result).c_str());
		return result;
	}

	uint drives = 0;
	for (auto& host : hosts) {
		if ((bool)host) {
			uint32 result = host->Initialize();
			if ((int32)result < 0) {
				return result;
			}
			drives++;
		}
	}

	if (loglevel == 1) {
		putlogn("%s => %c:", cmdname.c_str(), 'A' + base);
	} else {
		putlog(2, "%s Base=%c: Drives=%u", cmdname.c_str(), 'A' + base, drives);
	}

	WriteMem8(a5 + 13, drives);
	return 0;
}

// $41 ディレクトリチェック
//
// IN
//   +0, 1.B: 定数 ($1a)
//   +1, 1.B: ユニット番号
//   +2, 1.B: コマンド ($41/$c1)
//	+14, 1.L: NAMESTS 構造体のアドレス
// OUT
//   +3, 1.W: エラーコード (下位、上位の順)
//  +18, 1.L: ステータスコード
//
// NAMESTS の drive:path に存在を確認したいディレクトリのパス。
uint32
WindrvDevice::CmdCheckDir(HostWindrv *host)
{
	NAMESTS ns(this, ReadMemAddr(a5 + 14));

	if (__predict_false(loglevel >= 1)) {
		cmdname += string_format(" CheckDir %s", ns.PrintDrivePath().c_str());
		putlog(2, "%s", cmdname.c_str());
	}

	AutoVDir vdir(host, host->OpenVDir(ns.GetPath()));
	if ((bool)vdir == false) {
		return Human68k::RES_DIR_NOT_FOUND;
	}
	return 0;
}

// $42 ディレクトリ作成
//
// IN
//   +0, 1.B: 定数 ($1a)
//   +1, 1.B: ユニット番号
//   +2, 1.B: コマンド ($42/$c2)
//	+14, 1.L: NAMESTS 構造体のアドレス
// OUT
//   +3, 1.W: エラーコード (下位、上位の順)
//  +18, 1.L: ステータスコード
//
// NAMESTS の drive:path 配下の name に作成したいディレクトリ名。
// 同名のディレクトリがある場合はエラー。
uint32
WindrvDevice::CmdMakeDir(HostWindrv *host)
{
	NAMESTS ns(this, ReadMemAddr(a5 + 14));

	if (__predict_false(loglevel >= 1)) {
		cmdname += string_format(" MakeDir %s '%s'",
			ns.PrintDrivePath().c_str(), ns.GetName().c_str());
		putlog(2, "%s", cmdname.c_str());
	}

	AutoVDir vdir(host, host->OpenVDir(ns.GetPath()));
	if ((bool)vdir == false) {
		return Human68k::RES_DIR_NOT_FOUND;
	}

	const VDirent *ent = vdir->MatchName(ns.GetName(), false);
	if (ent) {
		return Human68k::RES_FILE_EXISTS;
	}
	uint32 result = host->MakeDir(vdir->GetPath() + ns.GetName());
	return result;
}

// $43 ディレクトリ削除
//
// IN
//   +0, 1.B: 定数 ($1a)
//   +1, 1.B: ユニット番号
//   +2, 1.B: コマンド ($43/$c3)
//	+14, 1.L: NAMESTS 構造体アドレス
// OUT
//   +3, 1.W: エラーコード (下位、上位の順)
//  +18, 1.L: ステータスコード
//
// NAMESTS の drive:path 配下の name に削除したいディレクトリ名。
// ディレクトリが空でない場合はエラー。
uint32
WindrvDevice::CmdRemoveDir(HostWindrv *host)
{
	NAMESTS ns(this, ReadMemAddr(a5 + 14));

	if (__predict_false(loglevel >= 1)) {
		cmdname += string_format(" RemoveDir %s '%s'",
			ns.PrintDrivePath().c_str(), ns.GetName().c_str());
		putlog(2, "%s", cmdname.c_str());
	}

	AutoVDir vdir(host, host->OpenVDir(ns.GetPath()));
	if ((bool)vdir == false) {
		return Human68k::RES_DIR_NOT_FOUND;
	}

	const VDirent *ent = vdir->MatchName(ns.GetName(), true);
	if (ent == NULL) {
		return Human68k::RES_DIR_NOT_FOUND;
	}
	// ディレクトリが空かどうかは RemoveDir() にお任せしてある。
	uint32 result = host->RemoveDir(vdir->GetPath() + ent->name);
	return result;
}

// $44 ファイル名変更
//
// IN
//   +0, 1.B: 定数 ($1a)
//   +1, 1.B: ユニット番号
//   +2, 1.B: コマンド ($44/$c4)
//	+14, 1.L: NAMESTS 構造体アドレス (旧ファイル名)
//	+18, 1.L: NAMESTS 構造体アドレス (新ファイル名)
// OUT
//   +3, 1.W: エラーコード (下位、上位の順)
//  +18, 1.L: ステータスコード
//
// o パスが違う場合はディレクトリを移動する (ドライブはどちらも同じはず)。
// o 新ファイル名がすでに存在する場合はエラー。
// o システムファイル、書き込み禁止ファイルはファイル名の変更はできない。
//   (システムファイルのディレクトリの移動を伴うファイル名の変更は可らしい)
// o ディレクトリ、ボリュームラベル、隠しファイル、書き込み禁止ファイルは
//   ディレクトリ間の移動はできない。
uint32
WindrvDevice::CmdRename(HostWindrv *host)
{
	NAMESTS oldns(this, ReadMemAddr(a5 + 14));
	NAMESTS newns(this, ReadMemAddr(a5 + 18));

	if (__predict_false(loglevel >= 1)) {
		cmdname += string_format(" Rename %s '%s' to %s '%s'",
			oldns.PrintDrivePath().c_str(), oldns.GetName().c_str(),
			newns.PrintDrivePath().c_str(), newns.GetName().c_str());
		putlog(2, "%s", cmdname.c_str());
	}

	// 移動元
	AutoVDir oldvdir(host, host->OpenVDir(oldns.GetPath()));
	if ((bool)oldvdir == false) {
		return Human68k::RES_DIR_NOT_FOUND;
	}
	const VDirent *oldent = oldvdir->MatchName(oldns.GetName(), true);
	if (oldent == NULL) {
		return Human68k::RES_FILE_NOT_FOUND;
	}

	// 移動先
	AutoVDir newvdir(host, host->OpenVDir(newns.GetPath()));
	if ((bool)newvdir == false) {
		return Human68k::RES_DIR_NOT_FOUND;
	}
	const VDirent *newent = newvdir->MatchName(newns.GetName(), false);
	if (newent) {
		return Human68k::RES_FILE_EXISTS;
	}

	// 移動元ファイルの属性。
	uint32 attr = host->GetAttribute(oldvdir->GetPath() + oldent->name);
	if ((int32)attr < 0) {
		return attr;
	}
	// 書き込み禁止ファイルなら何も出来ない。
	if ((attr & Human68k::ATTR_RDONLY)) {
		return Human68k::RES_NOT_A_FILE;
	}

	// ディレクトリなら、ディレクトリ間の移動は出来ない。
	if ((attr & Human68k::ATTR_DIR)) {
		if (oldvdir->GetPath() != newvdir->GetPath()) {
			return Human68k::RES_NOT_A_FILE;
		}
	}

	// ボリューム、システムファイル、隠しファイルはまだない。

	uint32 result = host->Rename(
		oldvdir->GetPath() + oldent->name,
		newvdir->GetPath() + newns.GetName());
	return result;
}

// $45 ファイル削除
//
// IN
//   +0, 1.B: 定数 ($1a)
//   +1, 1.B: ユニット番号
//   +2, 1.B: コマンド ($45/$c5)
//	+14, 1.L: NAMESTS 構造体アドレス
// OUT
//   +3, 1.W: エラーコード (下位、上位の順)
//  +18, 1.L: ステータスコード
//
// ディレクトリ、ボリュームラベル、システムファイル、書き込み禁止ファイル
// ならエラー。
uint32
WindrvDevice::CmdDelete(HostWindrv *host)
{
	NAMESTS ns(this, ReadMemAddr(a5 + 14));

	if (__predict_false(loglevel >= 1)) {
		cmdname += string_format(" Delete %s '%s'",
			ns.PrintDrivePath().c_str(), ns.GetName().c_str());
		putlog(2, "%s", cmdname.c_str());
	}

	AutoVDir vdir(host, host->OpenVDir(ns.GetPath()));
	if ((bool)vdir == false) {
		return Human68k::RES_DIR_NOT_FOUND;
	}

	const VDirent *ent = vdir->MatchName(ns.GetName(), true);
	if (ent == NULL) {
		return Human68k::RES_FILE_NOT_FOUND;
	}
	uint32 result = host->Delete(vdir->GetPath() + ent->name);
	return result;
}

// $46 ファイル属性取得/設定
//
// IN
//   +0, 1.B: 定数 ($1a)
//   +1, 1.B: ユニット番号
//   +2, 1.B: コマンド ($46/$c6)
//	+13, 1.B: 設定する属性、0xff なら取得
//	+14, 1.L: NAMESTS 構造体アドレス
// OUT
//   +3, 1.W: エラーコード (下位、上位の順)
//  +18, 1.L: ステータスコード
uint32
WindrvDevice::CmdAttribute(HostWindrv *host)
{
	uint32 attr = ReadMem8(a5 + 13);
	NAMESTS ns(this, ReadMemAddr(a5 + 14));
	if (__predict_false(loglevel >= 1)) {
		if (attr == 0xff) {
			cmdname += string_format(" Attribute(Get) %s '%s'",
				ns.PrintDrivePath().c_str(), ns.GetName().c_str());
		} else {
			cmdname += string_format(" Attribute(Set) %s '%s' %s",
				ns.PrintDrivePath().c_str(), ns.GetName().c_str(),
				Human68k::AttrStr(attr).c_str());
		}
		putlog(2, "%s", cmdname.c_str());
	}

	AutoVDir vdir(host, host->OpenVDir(ns.GetPath()));
	if ((bool)vdir == false) {
		return Human68k::RES_DIR_NOT_FOUND;
	}

	const VDirent *ent = vdir->MatchName(ns.GetName(), true);
	if (ent == NULL) {
		return Human68k::RES_FILE_NOT_FOUND;
	}

	std::string fullname = vdir->GetPath() + ent->name;
	uint32 result;
	if (attr == 0xff) {
		// 取得
		result = host->GetAttribute(fullname);
		if ((int32)result >= 0) {
			if (__predict_false(loglevel >= 2)) {
				putlogn("result=$%02x(%s)", result,
					Human68k::AttrStr(result).c_str());
			} else if (__predict_false(loglevel == 1)) {
				putlogn("%s => %s", cmdname.c_str(),
					Human68k::AttrStr(result).c_str());
			}
			cmdname.clear();
		}
	} else {
		// 設定
		result = host->SetAttribute(fullname, attr);
	}
	return result;
}

// $47 ファイル検索(First)
//
// IN
//   +0, 1.B: 定数 ($1a)
//   +1, 1.B: ユニット番号
//   +2, 1.B: コマンド ($47/$c7)
//	+13, 1.B: 設定する属性、0xff なら取得
//	+14, 1.L: NAMESTS 構造体アドレス (検索対象)
//	+18, 1.L: FILES 構造体アドレス (検索バッファ)
// OUT
//	 +3, 1.W: エラーコード (下位、上位の順)
//	+18, 1.L: ステータスコード
//
// NAMESTS の drive:path 配下から name (ワイルドカードあり) にマッチして
// 検索属性(+13.B) で立ててあるビットがどれか立っている(でいいのかな?)
// エントリを探して先頭の1つを FILES に返す。
// 見付からなければ RES_FILE_NOT_FOUND を返す。
uint32
WindrvDevice::CmdFiles(HostWindrv *host)
{
	uint8 attr = ReadMem8(a5 + 13);
	NAMESTS ns(this, ReadMemAddr(a5 + 14));
	uint32 filesaddr = ReadMemAddr(a5 + 18);
	uint32 result;

	if (__predict_false(loglevel >= 1)) {
		cmdname += string_format(" Files %s '%s' (%s)",
			ns.PrintDrivePath().c_str(), ns.GetName().c_str(),
			Human68k::AttrStr(attr).c_str());
		putlog(2, "%s", cmdname.c_str());
	}

	// 同じ FILES バッファが見付かればクリア。ボリューム検索より先に行う。
	FilesBuf *fbuf = SearchFilesBuf(filesaddr);
	if (fbuf) {
		putlog(2, "Reuse FilesBuf at $%08x", filesaddr);
		fbuf->Init(filesaddr, host);
	}

	// ボリュームの検索はたびたび来るけどサポートしてないので先に弾く。
	// これは検索が成功した(マッチしたのが0だっただけ)扱いのログを出す。
	if (attr == Human68k::ATTR_VOLUME) {
		result = Human68k::RES_LAST_FILE;
		if (__predict_false(loglevel == 1)) {
			putlogn("%s", cmdname.c_str());
			putlogn("Cmd Files  => %s", ResultStr(result).c_str());
			cmdname.clear();
		}
		return result;
	}

	if (fbuf == NULL) {
		fbuf = AllocFilesBuf(filesaddr, host);
		if (fbuf == NULL) {
			return Human68k::RES_NO_MEMORY;
		}
	}

	AutoVDir vdir(host, host->OpenVDir(ns.GetPath()));
	if ((bool)vdir == false) {
		return Human68k::RES_DIR_NOT_FOUND;
	}

	// FilesBuf にマッチしたエントリ一覧を作成。
	fbuf->path = vdir->GetPath();
	for (const VDirent& ent : vdir->list) {
		if (__predict_false(ent.IsValid() == false)) {
			continue;
		}

		// 属性が一致するか。
		// attr に立っているビットがエントリ側にも立っているか、かな。
		// ent のほうはディレクトリかどうかしか区別がない。
		if (ent.IsDir()) {
			if ((attr & Human68k::ATTR_DIR) == 0) {
				continue;
			}
		} else {
			if ((attr & Human68k::ATTR_ARCHIVE) == 0) {
				continue;
			}
		}

		// ルートディレクトリなら "." と ".." を除外。
		if (vdir->GetPath() == "/") {
			if (ent.name == "." || ent.name == "..") {
				continue;
			}
		}

		// ファイル名が一致するか (ワイルドカードを含む)。
		if (MatchFilename(ent.name, ns) == false) {
			continue;
		}

		fbuf->list.emplace_back(ent.name);
	}

	result = FilesCommon(fbuf, filesaddr);
	if (result == 0) {
		if (loglevel == 1) {
			putlogn("%s", cmdname.c_str());
			putlogn("Cmd Files  '%s'", fbuf->fname.c_str());
			cmdname.clear();
		}
	}
	return result;
}

// $48 ファイル検索(NFiles)
//
// IN
//   +0, 1.B: 定数 ($1a)
//   +1, 1.B: ユニット番号
//   +2, 1.B: コマンド ($48/$c8)
//	+18, 1.L: FILES 構造体アドレス (検索バッファ)
// OUT
//	 +3, 1.W: エラーコード (下位、上位の順)
//	+18, 1.L: ステータスコード
uint32
WindrvDevice::CmdNFiles(HostWindrv *host)
{
	if (__predict_false(loglevel >= 1)) {
		cmdname += " NFiles";
		putlog(2, "%s", cmdname.c_str());
	}

	uint32 filesaddr = ReadMemAddr(a5 + 18);
	FilesBuf *fbuf = SearchFilesBuf(filesaddr);
	if (fbuf == NULL) {
		return Human68k::RES_FILE_NOT_FOUND;
	}

	uint32 result = FilesCommon(fbuf, filesaddr);
	if (result == 0) {
		if (loglevel == 1) {
			putlogn("%s '%s'", cmdname.c_str(), fbuf->fname.c_str());
			cmdname.clear();
		}
	}
	return result;
}

// Files/NFiles の共通部分。
// FilesBuf から一つ取り出して VM メモリ filesaddr に書き出す。
uint32
WindrvDevice::FilesCommon(FilesBuf *fbuf, uint32 filesaddr)
{
	HostWindrv *host = fbuf->host;
	assert(host);

	// 取り出せても取り出せなくても参照時刻は更新。
	fbuf->UpdateATime();

	// 見付かった先頭の一つを返す。
	while (fbuf->list.empty() == false) {
		fbuf->fname = fbuf->list.front();
		fbuf->list.pop_front();

		Human68k::FILES files;
		if (host->GetFileStat(&files, fbuf->path + fbuf->fname) == false) {
			continue;
		}

		putlog(2, "name: '%s'", fbuf->fname.c_str());
		files.name = fbuf->fname;
		files.sector = fbuf->key;
		files.offset = 0;
		files.WriteToMem(this, filesaddr);
		return 0;
	}

	return Human68k::RES_LAST_FILE;
}

// $49 ファイル作成
//
// IN
//   +0, 1.B: 定数 ($1a)
//   +1, 1.B: ユニット番号
//   +2, 1.B: コマンド ($49/$c9)
//  +13, 1.B: ファイル属性
//	+14, 1.L: NAMESTS 構造体アドレス
//  +18, 1.L: モード
//  +22, 1.L: FCB 構造体アドレス
// OUT
//   +3, 1.W: エラーコード (下位、上位の順)
//  +18, 1.L: ステータスコード
//
// 同名ファイルがすでに存在していた場合、モードが 0 ならエラーを返す。
// モードが 1 なら既存ファイルを削除してからファイルを作成する。
// ボリュームラベルには未対応。
uint32
WindrvDevice::CmdCreate(HostWindrv *host)
{
	uint32 result;

	uint8 attr = ReadMem8(a5 + 13);
	NAMESTS ns(this, ReadMemAddr(a5 + 14));
	uint32 mode = ReadMem32(a5 + 18);
	uint32 fcbaddr = ReadMemAddr(a5 + 22);
	FCB fcb(this, fcbaddr, host);

	if (__predict_false(loglevel >= 1)) {
		cmdname += string_format(" Create $%06x %s '%s' (%s) mode=%u",
			fcbaddr, ns.PrintDrivePath().c_str(), ns.GetName().c_str(),
			Human68k::AttrStr(attr).c_str(), mode);
		putlog(2, "%s", cmdname.c_str());
	}

	// 同じ FCB が見付かれば先に閉じてしまうか。
	FCB *oldfcb = SearchFCB(fcbaddr);
	if (oldfcb) {
		FreeFCB(oldfcb);
	}

	// ボリュームラベルには未対応。
	if (attr == Human68k::ATTR_VOLUME) {
		return Human68k::RES_NOT_A_FILE;
	}

	AutoVDir vdir(host, host->OpenVDir(ns.GetPath()));
	if ((bool)vdir == false) {
		return Human68k::RES_DIR_NOT_FOUND;
	}

	// 同名のファイルが(1つ以上)存在していた場合。
	if (vdir->MatchName(ns.GetName(), false) != NULL) {
		if (mode == 0) {
			// モード 0 ならエラーにする。
			return Human68k::RES_FILE_EXISTS;
		} else {
			// モード 1 なら削除して作成なので既存ファイルが1つでないと困る。
			const VDirent *ent = vdir->MatchName(ns.GetName(), true);
			if (ent == NULL) {
				// 複数候補があると消そうにも消せない…。
				return Human68k::RES_FILE_EXISTS;
			} else {
				// 既存ファイルは1つなので削除する。
				result = host->Delete(vdir->GetPath() + ent->name);
				if ((int32)result < 0) {
					return result;
				}
			}
		}
	}

	const std::string fullname = vdir->GetPath() + ns.GetName();
	result = host->CreateFile(&fcb, fullname, attr);
	if ((int32)result < 0) {
		return result;
	}

	// ここで内部に登録。
	// ディスクリプタもコピーされるがただの整数なので問題ない。ほんとだろうか。
	return AddFCB(fcb);
}

// $4a ファイルオープン
//
// IN
//   +0, 1.B: 定数 ($1a)
//   +1, 1.B: ユニット番号
//   +2, 1.B: コマンド ($4a/$ca)
//	+14, 1.L: NAMESTS 構造体アドレス
//  +22, 1.L: FCB 構造体アドレス
// OUT
//   +3, 1.W: エラーコード (下位、上位の順)
//  +18, 1.L: ステータスコード
//
// o 成功すれば、FCB のファイル名、更新日時、ファイルサイズを更新する。
// o ディレクトリ、ボリュームラベルはオープンできない。
// o システムファイル、書き込み禁止ファイルは読み込みモード以外ではオープン
//   できない。
uint32
WindrvDevice::CmdOpen(HostWindrv *host)
{
	NAMESTS ns(this, ReadMemAddr(a5 + 14));
	uint32 fcbaddr = ReadMemAddr(a5 + 22);
	FCB fcb(this, fcbaddr, host);
	uint8 mode = fcb.GetMode();

	if (__predict_false(loglevel >= 1)) {
		cmdname += string_format(" Open $%06x %s '%s' %s",
			fcbaddr, ns.PrintDrivePath().c_str(), ns.GetName().c_str(),
			Human68k::OpenModeStr(mode).c_str());
		putlog(2, "%s", cmdname.c_str());
	}

	// 同じ FCB が見付かれば先に閉じてしまうか。
	FCB *oldfcb = SearchFCB(fcbaddr);
	if (oldfcb) {
		FreeFCB(oldfcb);
	}

	AutoVDir vdir(host, host->OpenVDir(ns.GetPath()));
	if ((bool)vdir == false) {
		return Human68k::RES_DIR_NOT_FOUND;
	}

	const VDirent *ent = vdir->MatchName(ns.GetName(), true);
	if (ent == NULL) {
		return Human68k::RES_FILE_NOT_FOUND;
	}

	// エントリがディレクトリかどうかはこの時点で分かる。
	// ボリュームラベルは現状存在しない。
	if (ent->IsDir()) {
		return Human68k::RES_NOT_A_FILE;
	}

	const std::string fullname = vdir->GetPath() + ent->name;
	if (mode != Human68k::OPEN_RDONLY) {
		// 書き込みを伴うなら、ファイルの属性を確認。
		uint32 attr = host->GetAttribute(fullname);
		if ((int32)attr < 0) {
			return Human68k::RES_NOT_A_FILE;
		}

		if ((attr & Human68k::ATTR_RDONLY)) {
			return Human68k::RES_CANNOT_WRITE;
		}
	}

	uint32 result = host->Open(&fcb, fullname);
	if ((int32)result < 0) {
		return result;
	}

	// タイムスタンプとサイズは Open() がセットしている。
	// 名前はこっちでセットする。
	fcb.SetName(ns.GetName1(), ns.GetName2(), ns.GetExt());

	// ここで内部に登録。
	// ディスクリプタもコピーされるがただの整数なので問題ない。ほんとだろうか。
	return AddFCB(fcb);
}

// $4b ファイルクローズ
//
// IN
//   +0, 1.B: 定数 ($1a)
//   +1, 1.B: ユニット番号
//   +2, 1.B: コマンド ($4b/$cb)
//  +22, 1.L: FCB 構造体アドレス
// OUT
//   +3, 1.W: エラーコード (下位、上位の順)
//  +18, 1.L: ステータスコード
uint32
WindrvDevice::CmdClose(HostWindrv *host)
{
	uint32 fcbaddr = ReadMemAddr(a5 + 22);

	if (__predict_false(loglevel >= 1)) {
		cmdname += string_format(" Close $%06x", fcbaddr);
		putlog(2, "%s", cmdname.c_str());
	}

	FCB *fcb = SearchFCB(fcbaddr);
	if (fcb == NULL) {
		return Human68k::RES_INVALID_MEM; // ?
	}

	FreeFCB(fcb);
	return 0;
}

// $4c ファイル読み込み
//
// IN
//   +0, 1.B: 定数 ($1a)
//   +1, 1.B: ユニット番号
//   +2, 1.B: コマンド ($4c/$cc)
//	+14, 1.L: 読み込み先アドレス
//  +18, 1.L: 読み込みバイト数
//  +22, 1.L: FCB 構造体アドレス
// OUT
//   +3, 1.W: エラーコード (下位、上位の順)
//  +18, 1.L: ステータスコード
//
// 読み込みバイト数が負数ならファイルサイズを指定したものとする。
// ステータスコードに読み込んだバイト数。
uint32
WindrvDevice::CmdRead(HostWindrv *host)
{
	uint32 dstaddr = ReadMemAddr(a5 + 14);
	uint32 reqlen = ReadMem32(a5 + 18);
	uint32 fcbaddr = ReadMemAddr(a5 + 22);

	if (__predict_false(loglevel >= 1)) {
		cmdname += string_format(" Read $%06x len=$%x", fcbaddr, reqlen);
		putlog(2, "%s", cmdname.c_str());
	}

	FCB *fcb = SearchFCB(fcbaddr);
	if (fcb == NULL) {
		return Human68k::RES_INVALID_MEM; // ?
	}

	// 負数ならファイルサイズとするらしい。
	if ((int32)reqlen < 0) {
		reqlen = fcb->GetSize();
	}

	std::array<uint8, 65536> buf;
	uint32 totallen = 0;
	for (;;) {
		uint32 remain = reqlen - totallen;
		if (remain == 0) {
			break;
		}
		int len = std::min(remain, (uint32)buf.size());
		int n = host->Read(fcb, buf.data(), len);
		if (n < 0) {
			return Human68k::RES_INVALID_MEM; // ?
		}
		if (n == 0) {
			break;
		}
		WriteMem(dstaddr, buf.data(), n);
		dstaddr +=n;
		totallen += n;
	}

	fcb->AddPos(totallen);

	return totallen;
}

// $4d ファイル書き込み
//
// IN
//   +0, 1.B: 定数 ($1a)
//   +1, 1.B: ユニット番号
//   +2, 1.B: コマンド ($4d/$cd)
//	+14, 1.L: 書き込み元アドレス
//  +18, 1.L: 書き込みバイト数
//  +22, 1.L: FCB 構造体アドレス
// OUT
//   +3, 1.W: エラーコード (下位、上位の順)
//  +18, 1.L: ステータスコード
//
// 書き込みバイト数が負数ならファイルサイズを指定したものとする。
// ステータスコードに読み込んだバイト数。
uint32
WindrvDevice::CmdWrite(HostWindrv *host)
{
	uint32 srcaddr = ReadMemAddr(a5 + 14);
	uint32 reqlen = ReadMem32(a5 + 18);
	uint32 fcbaddr = ReadMemAddr(a5 + 22);

	if (__predict_false(loglevel >= 1)) {
		cmdname += string_format(" Write $%06x len=$%x", fcbaddr, reqlen);
		putlog(2, "%s", cmdname.c_str());
	}

	FCB *fcb = SearchFCB(fcbaddr);
	if (fcb == NULL) {
		return Human68k::RES_INVALID_MEM; // ?
	}

	// 負数ならファイルサイズとするらしい。
	if ((int32)reqlen < 0) {
		reqlen = fcb->GetSize();
	}

	std::array<uint8, 65536> buf;
	uint32 totallen = 0;
	for (;;) {
		uint32 remain = reqlen - totallen;
		if (remain == 0) {
			break;
		}
		int len = std::min(remain, (uint32)buf.size());
		ReadMem(buf.data(), srcaddr, len);
		int n = host->Write(fcb, buf.data(), len);
		if (n < 0) {
			return Human68k::RES_INVALID_MEM; // ?
		}
		if (n == 0) {
			break;
		}
		srcaddr += n;
		totallen += n;
	}

	fcb->AddPos(totallen);

	return totallen;
}

// $4e ファイルシーク
//
// IN
//   +0, 1.B: 定数 ($1a)
//   +1, 1.B: ユニット番号
//   +2, 1.B: コマンド ($4e/$ce)
//  +13, 1.B: whence (0:先頭から、1:現在位置から、2:末尾から)
//  +18, 1.L: オフセット
//  +22, 1.L: FCB 構造体アドレス
// OUT
//   +3, 1.W: エラーコード (下位、上位の順)
//  +18, 1.L: ステータスコード
uint32
WindrvDevice::CmdSeek(HostWindrv *host)
{
	uint8 mode = ReadMem8(a5 + 13);
	int32 offset = ReadMem32(a5 + 18);
	uint32 fcbaddr = ReadMemAddr(a5 + 22);

	if (__predict_false(loglevel >= 1)) {
		cmdname += string_format(" Seek $%06x ($%x, %u)",
			fcbaddr, offset, mode);
		putlog(2, "%s", cmdname.c_str());
	}

	FCB *fcb = SearchFCB(fcbaddr);
	if (fcb == NULL) {
		return Human68k::RES_INVALID_MEM; // ?
	}

	// 全世界的に同じ値だと思うけど…。
	int whence;
	switch (mode) {
	 case 0:	whence = SEEK_SET;	break;
	 case 1:	whence = SEEK_CUR;	break;
	 case 2:	whence = SEEK_END;	break;
	 default:
		return Human68k::RES_CANNOT_SEEK;
	}

	uint32 result = host->Seek(fcb, offset, whence);
	if ((int32)result < 0) {
		return result;
	}

	fcb->SetPos(result);
	return result;
}

// $4f ファイル更新時刻の取得/設定
//
// IN
//   +0, 1.B: 定数 ($1a)
//   +1, 1.B: ユニット番号
//   +2, 1.B: コマンド ($4f/$cf)
//  +18, 1.L: 日付時刻 (0 なら取得)
//  +22, 1.L: FCB 構造体アドレス
// OUT
//   +3, 1.W: エラーコード (下位、上位の順)
//  +18, 1.L: ステータスコード
//
// 取得が成功すればステータスコードに日付時刻。
uint32
WindrvDevice::CmdFileTime(HostWindrv *host)
{
	uint32 datetime = ReadMem32(a5 + 18);
	uint32 fcbaddr = ReadMemAddr(a5 + 22);

	if (__predict_false(loglevel >= 1)) {
		if (datetime == 0) {
			cmdname += string_format(" FileTime(Get) $%06x", fcbaddr);
		} else {
			cmdname += string_format(" FileTime(Set) $%06x ($%08x)", fcbaddr,
				datetime);
		}
		putlog(2, "%s", cmdname.c_str());
	}

	FCB *fcb = SearchFCB(fcbaddr);
	if (fcb == NULL) {
		return Human68k::RES_INVALID_MEM; // ?
	}

	uint32 result;
	if (datetime == 0) {
		result = host->GetFileTime(fcb);
	} else {
		result = host->SetFileTime(fcb, datetime);
	}
	return result;
}

// $50 GetCapacity
//
// IN
//   +0, 1.B: 定数 ($1a)
//   +1, 1.B: ユニット番号
//   +2, 1.B: コマンド ($50/$d0)
//  +14, 1.L: capacity 構造体アドレス
// OUT
//   +3, 1.W: エラーコード (下位、上位の順)
//  +18, 1.L: ステータスコード
uint32
WindrvDevice::CmdGetCapacity(HostWindrv *host)
{
	if (__predict_false(loglevel >= 1)) {
		cmdname += " GetCapacity";
		putlog(2, "%s", cmdname.c_str());
	}

	uint32 capaddr = ReadMemAddr(a5 + 14);

	Human68k::capacity cap;
	uint32 result = host->GetCapacity(&cap);
	if ((int32)result < 0) {
		return result;
	}
	uint32 availbytes = cap.avail_clusters *
		cap.sectors_per_cluster * cap.bytes_per_sector;

	cap.WriteToMem(this, capaddr);

	return availbytes;
}

// $51 ドライブ制御/状態検査
//
// IN
//   +0, 1.B: 定数 ($1a)
//   +1, 1.B: ユニット番号
//   +2, 1.B: コマンド ($51/$d1)
//  +13, 1.B: 内部コマンド
//  +14, 1.L: 引数列のアドレス
// OUT
//   +3, 1.W: エラーコード (下位、上位の順)
//  +13, 1.B: ドライブの状態
//  +18, 1.L: ステータスコード
//
// 内部コマンドは以下らしい。IOCS B_DRVCHK 参照。
//  0: 状態検査1
//  1: 排出
//  2: 排出禁止
//  3: 排出許可
//  4: メディア未挿入時に LED 点滅
//  5: メディア未挿入時に LED 消灯
//  6: 排出禁止2 (OS 用)
//  7: 排出許可2 (OS 用)
//  8: -
//  9: 状態検査2
//
// 応答のドライブの状態は以下のビットマップらしい。
//  0x80: LED 点灯
//  0x40: 排出禁止
//  0x20: 排出禁止2
//  0x10: 排出禁止1
//  0x08: 書き込み禁止
//  0x04: ドライブが準備出来ていない
//  0x02: メディア挿入
//  0x01: メディア誤挿入
uint32
WindrvDevice::CmdCtrlDrive(HostWindrv *host)
{
	uint8 ctrl = ReadMem8(a5 + 13);

	uint8 res;
	switch (ctrl) {
	 case 0:	// 状態検査1
		if (__predict_false(loglevel >= 1)) {
			cmdname += string_format(" CtrlDrive(%u)", ctrl);
			putlog(2, "%s", cmdname.c_str());
		}
		res = 0x02;	// メディア挿入
		break;

	 default:
		if (__predict_false(loglevel >= 1)) {
			cmdname += string_format(" CtrlDrive(%u) (NOT IMPLEMENTED)", ctrl);
			putlog(2, "%s", cmdname.c_str());
		}
		return -Human68k::ERR_INVALID_COMMAND_IA;
	}
	WriteMem8(a5 + 13, res);
	return 0;
}

// $52 DPB 取得
//
// IN
//   +0, 1.B: 定数 ($1a)
//   +1, 1.B: ユニット番号
//   +2, 1.B: コマンド ($52/$d2)
//  +14, 1.L: DPB 構造体アドレス (実際には+2 を指している)
// OUT
//   +3, 1.W: エラーコード (下位、上位の順)
//  +18, 1.L: ステータスコード
uint32
WindrvDevice::CmdGetDPB(HostWindrv *host)
{
	if (__predict_false(loglevel >= 1)) {
		cmdname += " GetDPB";
		putlog(2, "%s", cmdname.c_str());
	}

	uint32 dpbaddr = ReadMemAddr(a5 + 14);

	Human68k::capacity cap;
	uint32 result = host->GetCapacity(&cap);
	if ((int32)result < 0) {
		return result;
	}

	uint shift = 31 - __builtin_clz(cap.sectors_per_cluster);

	// クラスタ 0: -
	// クラスタ 1: FAT
	// クラスタ 2: ルートディレクトリ
	// クラスタ 3: データ領域
	uint32 fat_sect  = cap.sectors_per_cluster * 1;
	uint32 root_sect = cap.sectors_per_cluster * 2;
	uint32 data_sect = cap.sectors_per_cluster * 3;

	Human68k::DPB dpb;
	memset(&dpb, 0, sizeof(dpb));
	dpb.sector_size = cap.bytes_per_sector;
	dpb.cluster_size = cap.sectors_per_cluster - 1;
	dpb.shift = shift;
	dpb.fat_sector = fat_sect;
	dpb.fat_max = 1;
	dpb.fat_size = cap.sectors_per_cluster;
	dpb.file_max = (cap.sectors_per_cluster * cap.bytes_per_sector / 32);
	dpb.cluster_max = cap.total_clusters;
	dpb.root_sector = root_sect;
	dpb.data_sector = data_sect;
	dpb.media = 0xf3;
	putmsg(2, "sector_size=$%x, cluster_size=$%x",
		dpb.sector_size, dpb.cluster_size);
	putmsg(2, "shift=%u, fat_sector=$%x fat_size=$%x",
		dpb.shift, dpb.fat_sector, dpb.fat_size);
	putmsg(2, "file_max=$%x cluster_max=$%x",
		dpb.file_max, dpb.cluster_max);
	putmsg(2, "root_sector=%u data_sector=%u",
		dpb.root_sector, dpb.data_sector);

	// DPB を書き戻す。
	// ここに渡されるアドレスは DPB+2 を指している。
	dpb.WriteToMem(this, dpbaddr - 2);

	return result;
}

uint32
WindrvDevice::CmdDiskRead(HostWindrv *host)
{
	cmdname += " DiskRead (NOT IMPLEMENTED)";
	putlog(0, "%s", cmdname.c_str());
	return -Human68k::ERR_INVALID_COMMAND_IA;
}

uint32
WindrvDevice::CmdDiskWrite(HostWindrv *host)
{
	cmdname += " DiskWrite (NOT IMPLEMENTED)";
	putlog(0, "%s", cmdname.c_str());
	return -Human68k::ERR_INVALID_COMMAND_IA;
}

uint32
WindrvDevice::CmdIoControl(HostWindrv *host)
{
	cmdname += " IoControl (NOT IMPLEMENTED)";
	putlog(0, "%s", cmdname.c_str());
	return -Human68k::ERR_INVALID_COMMAND_IA;
}

// $56 フラッシュ
//
// IN
//   +0, 1.B: 定数 ($1a)
//   +1, 1.B: ユニット番号
//   +2, 1.B: コマンド ($56/$d6)
// OUT
//   +3, 1.W: エラーコード (下位、上位の順)
//  +18, 1.L: ステータスコード
uint32
WindrvDevice::CmdFlush(HostWindrv *host)
{
	if (__predict_false(loglevel >= 1)) {
		cmdname += " Flush";
		putlog(2, "%s", cmdname.c_str());
	}

	uint32 result = host->Flush();
	return result;
}

// $57 メディア交換チェック
//
// IN
//   +0, 1.B: 定数 ($1a)
//   +1, 1.B: ユニット番号
//   +2, 1.B: コマンド ($57/$d7)
// OUT
//   +3, 1.W: エラーコード (下位、上位の順)
//  +18, 1.L: ステータスコード
uint32
WindrvDevice::CmdCheckMedia(HostWindrv *host)
{
	// 頻繁に呼ばれるのでレベル上げておく。
	if (__predict_false(loglevel >= 1)) {
		cmdname += " CheckMedia";
		putlog(3, "%s", cmdname.c_str());
		cmdname.clear();
	}

	// メディアが有効なら 1 を返すようだ。
	// 現状リムーバブルメディアには対応していないので常に 1 を返す。
	return 1;
}

uint32
WindrvDevice::CmdLock(HostWindrv *host)
{
	cmdname += " Lock (NOT IMPLEMENTED)";
	putlog(0, "%s", cmdname.c_str());
	return -Human68k::ERR_INVALID_COMMAND_IA;
}

// 結果コードを処理して VM メモリに書き出す。
//
// デバイスドライバの各コマンドからの応答フォーマットには
// +3.W (エラーコード) と +18.L (ステータスコード) があり、
// 以下のように応答させるようだ。
//
// o 白帯の出るエラーの場合、
//   +3.W のほうにエラーコード(ERR_*)、+18.L のほうは -1(?)。
// o (白帯でない)エラーの場合、
//   +3.W のほうは 0、+18.L のほうにステータスコード。
// o 正常系の場合、
//   +3.W のほうは 0、+18.L に 0 か正の値?
// XXX FileTime (や Seek) のように 32bit 全域が有効な戻り値の場合
// どういう扱いになってるかまでは調べてない。
//
// コマンドからの戻り値を
// o 白帯なら -ERR_*。これには IGNORE/RETRY/ABORT のいずれかが立っているはず
//   なので、必ず -0x1000 より絶対値が大きい値になる。
// o 白帯でないエラーなら RES_*。
// o 正常なら 0 以上。
// とすることで SetResult() が +3.W と +18.L によしなに書き出す。
void
WindrvDevice::SetResult(uint32 result)
{
	// ログ表示。
	if (__predict_false(loglevel >= 1)) {
		// cmdname が空なら出力済み。
		if (cmdname.empty() == false) {
			if (loglevel == 1) {
				// ここでまとめて表示 (長い、1行で)。
				putlogn("%s => %s", cmdname.c_str(), ResultStr(result).c_str());
			} else {
				// level 2 ならエラー時のみ表示
				// level 3 なら正常時も表示
				int thresh = result != 0 ? 2 : 3;
				putlog(thresh, "result=%s", ResultStr(result).c_str());
			}
		}
	}

	// 結果を格納。
	if ((int32)result <= -0x1000) {
		WriteMem8(a5 + 3, result & 0xff);
		WriteMem8(a5 + 4, (result >> 8) & 0xff);
		// ?
		if ((result & Human68k::ERR_RETRY) == 0) {
			WriteMem32(a5 + 18, result);
		}
	} else {
		WriteMem32(a5 + 18, result);
	}
}

// result を表示用に整形して返す。
/*static*/ std::string
WindrvDevice::ResultStr(uint32 result)
{
	if (__predict_true((int32)result >= 0)) {
		// 正常
		return string_format("$%x", result);
	}

	const char *msg = Human68k::GetResultMsg(result);
	if (__predict_true((int32)result > -0x1000)) {
		// エラー応答
		if (__predict_true(msg)) {
			return string_format("%d(%s)", (int32)result, msg);
		} else {
			return string_format("%d", (int32)result);
		}
	} else {
		// 白帯
		if (__predict_true(msg)) {
			return string_format("-$%04x(%s)", -(int32)result, msg);
		} else {
			return string_format("-$%04x", -(int32)result);
		}
	}
}

// VM メモリから1バイト読み込む。
uint32
WindrvDevice::ReadMem8(uint32 addr) const
{
	return mainbus->HVRead1(addr);
}

// VM メモリに1バイト書き出す。
void
WindrvDevice::WriteMem8(uint32 addr, uint32 data)
{
	mainbus->HVWrite1(addr, data & 0xff);
}

// VM メモリから1ワード読み込む。
uint32
WindrvDevice::ReadMem16(uint32 addr) const
{
	return mainbus->HVRead2(addr);
}

// VM メモリに1ワード書き出す。
void
WindrvDevice::WriteMem16(uint32 addr, uint32 data)
{
	mainbus->HVWrite2(addr, data);
}

// VM メモリから1ロングワード読み込む。
uint32
WindrvDevice::ReadMem32(uint32 addr) const
{
	return mainbus->HVRead4(addr);
}

// VM メモリに1ロングワード書き出す。
void
WindrvDevice::WriteMem32(uint32 addr, uint32 data)
{
	mainbus->HVWrite4(addr, data);
}

// VM メモリからアドレスを読み込む。(下位24bitのみにする)
uint32
WindrvDevice::ReadMemAddr(uint32 addr) const
{
	uint32 data = ReadMem32(addr);
	return data & 0x00ffffff;
}

// VM メモリにアドレスを書き出す。(下位24bitのみにする)
void
WindrvDevice::WriteMemAddr(uint32 addr, uint32 data)
{
	data &= 0x00ffffff;
	WriteMem32(addr, data);
}

// VM メモリから指定の領域を読み込む。
// dst はホストの領域、srcaddr はゲスト内のアドレス。
void
WindrvDevice::ReadMem(void *dst, uint32 srcaddr, int len) const
{
	uint8 *d = (uint8 *)dst;
	for (int i = 0; i < len; i++) {
		*d++ = mainbus->HVRead1(srcaddr++);
	}
}

// VM メモリに指定の領域を書き出す。
// dstaddr はゲスト内のアドレス、src はホストの領域。
void
WindrvDevice::WriteMem(uint32 dstaddr, const void *src, int len)
{
	const uint8 *s = (const uint8 *)src;
	for (int i = 0; i < len; i++) {
		mainbus->HVWrite1(dstaddr++, *s++);
	}
}

// fname が ns の検索パターンに一致すれば true を返す。
bool
WindrvDevice::MatchFilename(const std::string& fname_, const NAMESTS& ns) const
{
	if (ns.wildcard == 21) {
		// "*.*" なら調べずに全部一致。
		return true;
	}

	std::string fname;
	std::string pname;
	if (option_case_sensitive) {
		fname = fname_;
		pname = ns.GetName();
	} else {
		fname = string_tolower(fname_);
		pname = string_tolower(ns.GetName());
	}

	if (ns.wildcard == 0) {
		// ワイルドカードなしなら単純比較。
		if (strcmp(fname.c_str(), pname.c_str()) == 0) {
			return true;
		}
		return false;
	} else {
		return MatchWildcard(fname, pname);
	}
}

#endif // !TEST_WINDRV

// fname がパターン pname と一致すれば true を返す。
// pname にはワイルドカード '?' が使われている('*' は展開済み)。
// fname, pname の大文字小文字は必要に応じて処理済み。
// TODO: 漢字対応
/*static*/ bool
WindrvDevice::MatchWildcard(const std::string& fname, const std::string& pname)
{
	std::string pstr;
	std::string fstr;

	// TODO: 現状マルチピリオドには対応していないのでこれでいい。

	// pname、fname をそれぞれ 21文字固定形式にする。
	auto p = pname.find('.');
	if (p == std::string::npos) {
		pstr = string_format("%-18s%-3s", pname.c_str(), "");
	} else {
		std::string p1 = pname.substr(0, p);
		pstr = string_format("%-18s%-3s", p1.c_str(), &pname[p + 1]);
	}

	p = fname.find('.');
	if (p == std::string::npos) {
		fstr = string_format("%-18s%-3s", fname.c_str(), "");
	} else {
		std::string f1 = fname.substr(0, p);
		fstr = string_format("%-18s%-3s", f1.c_str(), &fname[p + 1]);
	}

	// 比較。
	for (int i = 0, iend = pstr.size(); i < iend; i++) {
		if (pstr[i] != '?') {
			if (fstr[i] != pstr[i]) {
				return false;
			}
		}
	}
	return true;
}

#if !defined(TEST_WINDRV)

// addr に対応する FILES バッファを探して返す。
// 見付からなければ NULL を返す。
FilesBuf *
WindrvDevice::SearchFilesBuf(uint32 addr) const
{
	auto it = filesmap.find(addr);
	if (it == filesmap.end()) {
		return NULL;
	}
	return it->second;
}

// addr に対応する FILES バッファを作成して返す。
FilesBuf *
WindrvDevice::AllocFilesBuf(uint32 addr, HostWindrv *host)
{
	if (filesmap.size() >= MaxFilesBuf) {
		// 仕方ないので最も古いのを消すか…。
		auto it = std::min_element(filesmap.begin(), filesmap.end(),
			[](const auto& a, const auto& b) {
				return a.second->GetATime() < b.second->GetATime();
			}
		);
		delete it->second;
		filesmap.erase(it);
	}
	putlog(2, "%s for $%08x", __func__, addr);
	filesmap.emplace(addr, new FilesBuf(addr, host));
	return filesmap[addr];
}

// addr に対応する FCB を探して返す。
// 見付からなければ NULL を返す。
Windrv::FCB *
WindrvDevice::SearchFCB(uint32 addr) const
{
	auto it = fcbmap.find(addr);
	if (it == fcbmap.end()) {
		return NULL;
	}
	return it->second;
}

// fcb のコピーを FCB を登録する。
uint32
WindrvDevice::AddFCB(const Windrv::FCB& fcb)
{
	assert(fcbmap.find(fcb.GetAddr()) == fcbmap.end());

	FCB *newfcb;
	try {
		newfcb = new FCB(fcb);
	} catch (...) {
		newfcb = NULL;
	}
	if (newfcb == NULL) {
		return Human68k::RES_NO_MEMORY;
	}

	fcbmap.emplace(newfcb->GetAddr(), newfcb);
	return 0;
}

// fcb を解放する。
// ホスト側のディスクリプタがオープンされていればクローズする。
void
WindrvDevice::FreeFCB(FCB *fcb)
{
	assert(fcb);

	fcb->Close();
	auto it = fcbmap.find(fcb->GetAddr());
	if (it != fcbmap.end()) {
		fcbmap.erase(it);
	}
}


//
// FilesBuf
//

// コンストラクタ
FilesBuf::FilesBuf(uint32 key_, HostWindrv *host_)
{
	Init(key_, host_);
}

// 初期化
void
FilesBuf::Init(uint32 key_, HostWindrv *host_)
{
	key = key_;
	host = host_;
	path.clear();
	list.clear();
	UpdateATime();
}

// 参照時刻を更新。
void
FilesBuf::UpdateATime()
{
	atime = get_hosttime_usec();
}


//
// Human68k
//

// 属性 attr をデバッグ表示用文字列にして返す。
/*static*/ std::string
Human68k::AttrStr(uint8 attr)
{
	static const char attrchr[6] = {
		'A',	// Archive
		'D',	// Directory
		'V',	// Volume
		'S',	// System
		'H',	// Hidden
		'R',	// ReadOnly
	};
	char buf[8 + 1];
	char *d = buf;

	// bit 7,6 はサポートしていないので立ってる時だけ表示。
	if (__predict_false((attr & 0xc0) != 0)) {
		*d++ = (attr & 0x80) ? '7' : '-';
		*d++ = (attr & 0x40) ? '6' : '-';
	}
	uint8 mask = 0x20;
	for (int i = 0; i < 6; i++) {
		*d++ = (attr & mask) ? attrchr[i] : '-';
		mask >>= 1;
	}
	*d = '\0';
	return std::string(buf);
}

// オープンモードを表示用文字列にする。
/*static*/ std::string
Human68k::OpenModeStr(uint8 openmode)
{
	switch (openmode) {
	 case OPEN_RDONLY:	return "O_RDONLY";
	 case OPEN_WRONLY:	return "O_WRONLY";
	 case OPEN_RDWR:	return "O_RDWR";
	 default:
		return string_format("$%02x", openmode);
	}
}

/*static*/ const char * const
Human68k::result_msg[] =
{
	"File exists",							// -80 RES_FILE_EXIST

	"Invalid Function",						//  -1 RES_INVALID_FUNC
	"File not found",						//  -2 RES_FILE_NOT_FOUND
	"Directory not found",					//  -3 RES_DIR_NOT_FOUND
	"Too many opened handles",				//  -4 RES_TOO_MANY_HANDLE
	"Directory or volume",					//  -5 RES_NOT_A_FILE
	"Handle not opened",					//  -6 RES_NOT_OPENED
	"Broken memory",						//  -7 RES_INVALID_MEM
	"Out of memory",						//  -8 RES_NO_MEMORY
	"Invalid pointer",						//  -9 RES_INVALID_PTR
	"Invalid environment",					// -10 RES_INVALID_ENV
	"Illegal executable format",			// -11 RES_ILLEGAL_FORMAT
	"Invalid open mode",					// -12 RES_INVALID_MODE
	"Invalid path",							// -13 RES_INVALID_PATH
	"Invalid parameter",					// -14 RES_INVALID_PARAM
	"Invalid drive",						// -15 RES_INVALID_DRIVE
	"Current directory cannot be removed",	// -16 RES_CURRENT_DIR
	"No ioctrl for this device",			// -17 RES_NO_IOCTRL
	"No more files found",					// -18 RES_LAST_FILE
	"Cannot Write a file",					// -19 RES_CANNOT_WRITE
	"Directory exists",						// -20 RES_DIR_EXISTS
	"Cannot delete because file exists",	// -21 RES_CANNOT_DELETE
	"Cannot rename because file exists",	// -22 RES_CANNOT_RENAME
	"Disk full",							// -23 RES_DISK_FULL
	"Directory entry full",					// -24 RES_DIR_FULL
	"Cannot Seek",							// -25 RES_CANNOT_SEEK
	"Supervisor called again",				// -26 RES_SUPERVISOR
	"Thread name exists",					// -27 RES_THREAD_EXISTS
	"Inter-process buffer write-protected",	// -28 RES_PROCESS_BUF
	"Cannot execute background process",	// -29 RES_BACKGROUND
	NULL,	// -30
	NULL,	// -31
	"Lock buffer full",						// -32 RES_NO_LOCKBUF
	"Locked",								// -33 RES_LOCKED
	"Drive handle opened",					// -34 RES_DRIVE_OPENED
	"Too many links",						// -35 RES_TOO_MANY_LINKS
};

/*static*/ const char * const
Human68k::err_msg[] =
{
	NULL,
	"Invalid unit number",		// $0001 ERR_INVALID_UNIT
	NULL,						// $0002
	"Invalid command",			// $0003 ERR_INVALID_COMMAND
	NULL,						// $0004
	NULL,						// $0005
	NULL,						// $0006
	NULL,						// $0007
	NULL,						// $0008
	NULL,						// $0009
	"Write error",				// $000a ERR_WRITE
	"Read error",				// $000b ERR_READ
	"Unknown error",			// $000c ERR_MISC
	NULL,						// $000d
	"Write protected",			// $000e ERR_WRITE_PROTECT
};

// ステータスコードまたはエラーコード result に対応するメッセージを返す。
// なければ NULL を返す。
// result は負数を渡すこと。
// result がエラーコードの場合 Ignore/Retry/Abort ビットの差異は無視する。
/*static*/ const char *
Human68k::GetResultMsg(uint32 result)
{
	assert((int32)result < 0);

	result = -(int32)result;
	if (result < 0x1000) {
		// これ一つだけ遠いのでリマップする
		if (__predict_false(result == 80)) {
			result = 0;
		}
		if (__predict_true(result < countof(result_msg))) {
			return result_msg[result];
		}
	} else {
		result &= 0xfff;

		if (__predict_true(result < countof(err_msg))) {
			return err_msg[result];
		}
	}
	return NULL;
}

#endif // !TEST_WINDRV

// Unix 時刻を MS-DOS 形式の日付時刻に変換する。
/*static*/ uint32
Human68k::Unixtime2DateTime(time_t t)
{
	struct tm *tm = localtime(&t);
	int year = tm->tm_year + 1900;
	int mon  = tm->tm_mon + 1;
	int day  = tm->tm_mday;
	int hour = tm->tm_hour;
	int min  = tm->tm_min;
	int sec  = tm->tm_sec;

	if (year < 1980) {
		year = 1980;
		mon  = 1;
		day  = 1;
		hour = 0;
		min  = 0;
		sec  = 0;
	}

	uint32 datetime =
		  ((year - 1980) << 25)
		| (mon << 21)
		| (day << 16)
		| (hour << 11)
		| (min << 5)
		| (sec / 2);
	return datetime;
}

// MS-DOS 形式の日付時刻を Unix 時刻に変換する。
/*static*/ time_t
Human68k::DateTime2Unixtime(uint32 datetime)
{
	int year = (datetime >> 25) + 1980;
	int mon  = (datetime >> 21) & 0xf;
	int mday = (datetime >> 16) & 0x1f;
	int hour = (datetime >> 11) & 0x1f;
	int min  = (datetime >>  5) & 0x3f;
	int sec  = (datetime & 0x1f) << 1;

	struct tm tm {};
	tm.tm_year = year - 1900;
	tm.tm_mon  = mon - 1;
	tm.tm_mday = mday;
	tm.tm_hour = hour;
	tm.tm_min  = min;
	tm.tm_sec  = sec;
	return mktime(&tm);
}

#if !defined(TEST_WINDRV)

// この FILES を (parent を通じて) VM メモリに書き出す。
// Files/NFiles で書き戻す必要のあるフィールドのみ。
void
Human68k::FILES::WriteToMem(WindrvDevice *parent, uint32 addr) const
{
	//parent->WriteMem8 (addr + 0x00, sattr);
	//parent->WriteMem8 (addr + 0x01, drive);
	parent->WriteMem32(addr + 0x02, sector);
	parent->WriteMem16(addr + 0x08, offset);
	parent->WriteMem8 (addr + 0x15, attr);
	parent->WriteMem16(addr + 0x16, time);
	parent->WriteMem16(addr + 0x18, date);
	parent->WriteMem32(addr + 0x1a, size);

	std::array<char, 23> namebuf {};
	strlcpy(namebuf.data(), name.c_str(), namebuf.size());
	parent->WriteMem(addr + 0x1e, namebuf.data(), namebuf.size());
}

// この capacity を (parent を通じて) VM メモリに書き出す。
void
Human68k::capacity::WriteToMem(WindrvDevice *parent, uint32 addr) const
{
	parent->WriteMem16(addr + 0, avail_clusters);
	parent->WriteMem16(addr + 2, total_clusters);
	parent->WriteMem16(addr + 4, sectors_per_cluster);
	parent->WriteMem16(addr + 6, bytes_per_sector);
}

// この DPB を (parent を通じて) VM メモリに書き出す。
void
Human68k::DPB::WriteToMem(WindrvDevice *parent, uint32 addr) const
{
	parent->WriteMem16(addr +  2, sector_size);
	parent->WriteMem8 (addr +  4, cluster_size);
	parent->WriteMem8 (addr +  5, shift);
	parent->WriteMem16(addr +  6, fat_sector);
	parent->WriteMem8 (addr +  8, fat_max);
	parent->WriteMem8 (addr +  9, fat_size);
	parent->WriteMem16(addr + 10, file_max);
	parent->WriteMem16(addr + 12, data_sector);
	parent->WriteMem16(addr + 14, cluster_max);
	parent->WriteMem16(addr + 16, root_sector);
	parent->WriteMem8 (addr + 20, media);
}


//
// FCB
//

// コンストラクタ (Human68k のほう)
Human68k::FCB::FCB(WindrvDevice *parent_, uint32 addr_)
{
	parent = parent_;
	addr = addr_;
}

uint32
Human68k::FCB::GetPos() const
{
	return parent->ReadMem32(addr + 0x06);
}

void
Human68k::FCB::SetPos(uint32 pos)
{
	parent->WriteMem32(addr + 0x06, pos);
}

void
Human68k::FCB::AddPos(uint32 diff)
{
	uint32 pos = GetPos();
	SetPos(pos + diff);
}

uint8
Human68k::FCB::GetMode() const
{
	return parent->ReadMem8(addr + 0x0e);
}

void
Human68k::FCB::SetDateTime(uint32 datetime)
{
	uint32 date = datetime >> 16;
	uint32 time = datetime & 0xffff;
	parent->WriteMem16(addr + 0x3a, time);
	parent->WriteMem16(addr + 0x3c, date);
}

uint32
Human68k::FCB::GetSize() const
{
	return parent->ReadMem32(addr + 0x40);
}

void
Human68k::FCB::SetSize(uint32 size)
{
	parent->WriteMem32(addr + 0x40, size);
}

void
Human68k::FCB::SetName(const std::string& name1, const std::string& name2,
	const std::string& ext)
{
	std::array<char, 8 + 10> namebuf;
	std::array<char, 3>  extbuf;

	// name1
	int i = 0;
	for (auto c : name1) {
		namebuf[i++] = c;
	}
	while (i < 8) {
		namebuf[i++] = ' ';
	}

	// name2
	for (auto c : name2) {
		namebuf[i++] = c;
	}
	while (i < 8 + 10) {
		namebuf[i++] = '\0';
	}

	// ext
	i = 0;
	for (auto c : ext) {
		extbuf[i++] = c;
	}
	while (i < 3) {
		extbuf[i++] = ' ';
	}

	parent->WriteMem(addr + 0x24, namebuf.data(), 8);
	parent->WriteMem(addr + 0x30, namebuf.data() + 8, 10);
	parent->WriteMem(addr + 0x2c, extbuf.data(), extbuf.size());
}

// コンストラクタ (Windrv のほう)
Windrv::FCB::FCB(WindrvDevice *parent, uint32 addr, HostWindrv *host_)
	: inherited(parent, addr)
{
	host = host_;
}

// FCB をクローズする。
// CmdOpen() の構造上デストラクタではクローズしないので明示的に呼ぶこと。
void
Windrv::FCB::Close()
{
	if (host) {
		host->Close(this);
	}
}


//
// NAMESTS
//

// コンストラクタ
WindrvDevice::NAMESTS::NAMESTS(WindrvDevice *windrv, uint32 addr)
{
	std::array<uint8, Size> data;
	int i;

	windrv->ReadMem(data.data(), addr, data.size());

	// メンバをセット。
	wildcard = data[0];
	drive = data[1];

	// パスは '\\' らしいけど '\t' が来るようだ。何故?
	// どちらにしてもここでは '/' にする。
	for (i = 0; i < 65; i++) {
		uint8 c = data[2 + i];
		if (c == '\0') {
			break;
		}
		if (c == '\\' || c == '\t') {
			c = '/';
		}
		path += c;
	}

	// name1, ext は ' ' でパディングされているので取り除く。
	for (i = 0; i < 8; i++) {
		uint8 c = data[0x43 + i];
		if (c == ' ') {
			break;
		}
		name1 += c;
	}

	for (i = 0; i < 3; i++) {
		uint8 c = data[0x4b + i];
		if (c == ' ') {
			break;
		}
		ext += c;
	}

	// name2 は '\0' でパディングされている。
	for (i = 0; i < 10; i++) {
		uint8 c = data[0x4e + i];
		if (c == '\0') {
			break;
		}
		name2 += c;
	}
}

// ドライブ:パスを返す。
std::string
WindrvDevice::NAMESTS::GetDrivePath() const
{
	std::string buf;

	buf = 'A' + drive;
	buf += ':';
	buf += GetPath();
	return buf;
}

// ファイル名文字列を返す。
std::string
WindrvDevice::NAMESTS::GetName() const
{
	std::string buf;

	buf = name1 + name2;
	if (ext.empty() == false) {
		buf += '.';
		buf += ext;
	}
	return buf;
}

// ドライブ:パス+ファイル名を返す。
std::string
WindrvDevice::NAMESTS::GetFull() const
{
	return GetDrivePath() + GetName();
}

// ドライブとパス名のログ表示用文字列を返す。
// "D:'/path/'"
std::string
WindrvDevice::NAMESTS::PrintDrivePath() const
{
	std::string buf;

	buf = 'A' + drive;
	buf += ":'";
	buf += GetPath();
	buf += '\'';
	return buf;
}


//
// VDir
//

// コンストラクタ
VDir::VDir()
{
}

// コンストラクタ
VDir::VDir(HostWindrv *host_, const std::string& path_)
{
	host = host_;
	path = path_;
}

// このディレクトリを使用開始する。
void
VDir::Acquire()
{
	refcount++;
}

// このディレクトリを使用終了する。
void
VDir::Release()
{
	if (__predict_true(refcount > 0)) {
		refcount--;
	} else {
		host->putlogf(0, lstr("%s %s: refcount=%d",
			__func__, path.c_str(), refcount));
	}
}

// name にマッチするエントリを返す。
// want_exist == true なら(オープンや削除のために)存在する1つを見付けたい方。
// want_exist == false なら(作成する前とかに)存在しないことを確認したい方。
//
// 大文字小文字を区別しないモードで、大文字と小文字の同じファイル名が
// 複数あった場合、true なら見付かったが1つに定まらないので NULL を返す。
// false ならいずれにしろ存在するのでどれか1つを返す。
const VDirent *
VDir::MatchName(const std::string& name, bool want_exist) const
{
	if (Windrv::option_case_sensitive) {
		// 大文字小文字を区別する場合。
		for (const auto& e : list) {
			if (e.name == name) {
				return &e;
			}
		}
		return NULL;
	} else {
		// 大文字小文字を区別しない場合。
		const VDirent *found = NULL;
		int n = 0;
		for (const auto& e : list) {
			if (strcasecmp(e.name.c_str(), name.c_str()) == 0) {
				if (n++ == 0) {
					found = &e;
				}
			}
		}
		// 1つ以下なら確定。
		if (n <= 1) {
			return found;
		}
		// 2つ以上なら、
		if (want_exist) {
			return NULL;
		} else {
			return found;
		}
	}
}

// 参照時刻を更新する。
void
VDir::UpdateATime()
{
	atime = get_hosttime_usec();
}


//
// AutoVDir
//

// デストラクタ
AutoVDir::~AutoVDir()
{
	if (host && vdir) {
		host->CloseVDir(vdir);
	}
}


//
// VDirent
//

// コンストラクタ
VDirent::VDirent(const std::string& name_, bool isdir_)
	: name(name_), isdir(isdir_), isvalid(true)
{
}

#endif // !TEST_WINDRV

unix.superglobalmegacorp.com

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