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

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

//
// LUNA NVRAM.DAT エディタ
//

#include "header.h"
#include "mystring.h"
#include <cstdio>
#include <string>
#include <vector>
#include <err.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>

using updatefunc = bool (*)(const std::string&, const std::string&);

[[noreturn]] static void usage();
static void show();
static void edit(int ac, char *av[]);
static bool update_luna1(const std::string&, const std::string&);
static bool update_luna88k(const std::string&, const std::string&);
static std::string read_string(uint32 addr);
static void write_string(uint32 addr, const std::string&);
static uint32 find_string(const std::string&);
static uint8 calc_checksum();
static bool verify_checksum();

static int debug;
static bool opt_force;
static bool opt_showaddr;
static uint8 *nvram;

#define DPRINTF(fmt...)	do {	\
	if (debug) {	\
		printf("%s: ", __func__);	\
		printf(fmt);	\
	}	\
} while (0)

int
main(int ac, char *av[])
{
	const char *filename;
	struct stat st;
	int c;
	int fd;
	int mode;
	int prot;
	size_t maplen;

	while ((c = getopt(ac, av, "dfnh")) != -1) {
		switch (c) {
		 case 'd':
			debug++;
			break;
		 case 'f':
			opt_force = true;
			break;
		 case 'n':
			opt_showaddr = true;
			break;
		 case 'h':
		 default:
			usage();
			break;
		}
	}
	ac -= optind;
	av += optind;

	if (ac == 0) {
		usage();
	}

	filename = av[0];
	ac--;
	av++;

	if (ac == 0) {
		// show
		mode = O_RDONLY;
		prot = PROT_READ;
	} else {
		// edit
		mode = O_RDWR;
		prot = PROT_READ | PROT_WRITE;
	}

	fd = open(filename, mode);
	if (fd < 0) {
		err(1, "%s: open", filename);
	}

	if (fstat(fd, &st) < 0) {
		err(1, "%s: fstat", filename);
	}

	maplen = 2048;
	if (st.st_size == 2040) {
		warnx("%s: Old filesize (2040 bytes). Current size is %zu bytes.\n",
			filename, maplen);
	} else if (st.st_size < maplen) {
		errx(1, "%s: File too short.  Correct size is %zu bytes.\n",
			filename, maplen);
	} else if (st.st_size > maplen) {
		warnx("%s: File too large.  Correct size is %zu bytes. "
			"(Ignore the extra space)", filename, maplen);
	}

	int flag = MAP_FILE | MAP_SHARED;
	void *m = mmap(NULL, maplen, prot, flag, fd, 0);
	if (m == MAP_FAILED) {
		err(1, "%s: mmap", filename);
	}
	nvram = (uint8 *)m;

	if (ac == 0) {
		show();
	} else {
		edit(ac, av);
	}

	munmap(m, maplen);
	close(fd);
	return 0;
}

static void
usage()
{
	fprintf(stderr,
		"usage: %s [<options>] <NVRAM.DAT>             .. show all\n",
		getprogname());
	fprintf(stderr,
		"       %s [<options>] <NVRAM.DAT> <key=value> .. set\n",
		getprogname());
	fprintf(stderr,
		" -d : debug\n"
		" -f : force to show/set even if nvram is broken\n"
		" -n : show address\n"
	);
	exit(1);
}

// 表示する
static void
show()
{
	if (verify_checksum() == false && opt_force == false) {
		printf("Broken NVRAM.  Specify -f to force\n");
		return;
	}

	// LUNA-I の 0x560 には ENADDR があるけど、
	// チェックサムの範囲外だしどういう扱いなんだべ。
	uint32 end = 0x560;
	if (strcmp((const char *)&nvram[0x560], "ENADDR") == 0) {
		end = 0x580;
	}

	for (uint32 addr = 0x20; addr < end; addr += 0x20) {
		if (nvram[addr] == '\0') {
			continue;
		}

		std::string key = read_string(addr);
		std::string val = read_string(addr + 16);

		if (opt_showaddr) {
			printf("0x%04x: ", addr);
		}
		printf("%-16s = %s\n", key.c_str(), val.c_str());
	}
}

static void
edit(int ac, char *av[])
{
	if (verify_checksum() == false && opt_force == false) {
		printf("Broken NVRAM.  Specify -f to force\n");
		return;
	}

	// 機種によってたぶんフィールドの扱いが違う気がする。
	// LUNA-I か LUNA-88K か厳密には区別できない気がするけど、とりあえず
	// 自由フィールドっぽい 0x560 までより後ろに "ENADDR" があれば
	// LUNA-I ということにしてみる。LUNA-II と LUNA-88K2 は未調査。
	updatefunc updater;
	if (strcmp((const char *)&nvram[0x560], "ENADDR") == 0) {
		updater = update_luna1;
	} else {
		updater = update_luna88k;
	}

	int updated = 0;
	for (int i = 0; i < ac; i++) {
		const char *s;
		const char *e;

		s = av[i];
		e = strchr(s, '=');
		if (e == NULL) {
			warnx("%s: syntax error", av[i]);
			return;
		}

		std::string key(s, (e - s));
		std::string val(e + 1);

		if (key.empty()) {
			warnx("%s: key must be specified", av[i]);
			return;
		}
		if (val.empty()) {
			warnx("%s: value must be specified", av[i]);
			return;
		}

		if (updater(key, val)) {
			updated++;
		}
	}

	// 更新があればチェックサムを再計算
	if (updated > 0) {
		uint8 eor = calc_checksum();
		for (int i = 0; i < 3; i++) {
			nvram[0x1c + i] = eor;
		}
	}
}

// LUNA-I のパラメータ表
struct luna1param {
	uint32 addr;
	const char *key;
};
static std::vector<luna1param> luna1params = {
	{ 0x0020, "BOOT" },
	{ 0x0040, "HOST" },
	{ 0x0060, "SERVER" },
	{ 0x0080, "DKUNIT" },
	{ 0x00a0, "DKPART" },
	{ 0x00c0, "DKFILE" },
	{ 0x00e0, "FLUNIT" },
	{ 0x0100, "FLFILE" },
	{ 0x0120, "STUNIT" },
	{ 0x0140, "STFLNO" },
	{ 0x0160, "STFILE" },
	{ 0x0180, "ETBOOT" },
	{ 0x01a0, "ETFILE" },
	{ 0x0560, "ENADDR" },
};

// LUNA-I の場合、キーはたぶん固定じゃないかな。
// 更新したら true を返す。
static bool
update_luna1(const std::string& key, const std::string& val)
{
	uint32 addr = 0;

	// 探す
	for (const auto& p : luna1params) {
		if (key == p.key) {
			addr = p.addr;
			break;
		}
	}
	if (addr == 0) {
		warnx("%s: Unknown key name", key.c_str());
		return false;
	}

	// 値だけ更新でいいはず
	write_string(addr + 16, val);
	return true;
}

// LUNA-88K の場合、キーはフリーダムのように見える。
// 更新したら true を返す。
static bool
update_luna88k(const std::string& key, const std::string& val)
{
	uint32 addr = 0;

	addr = find_string(key);
	if (addr == 0) {
		// 見付からなければ新規追加
		addr = find_string("");
		if (addr == 0) {
			// 空きエントリがない
			warnx("No spaces available");
			return false;
		}
	}

	// 追加の場合もあるので常に更新してしまう。副作用はないはず。
	write_string(addr, key);
	write_string(addr + 16, val);
	return true;
}

// キーと値はいずれも 16文字未満なら '\0' 終端、
// 16文字の場合は '\0' で終端せず 16文字とするフォーマット。

// 文字列を読み込む
static std::string
read_string(uint32 addr)
{
	char buf[18];

	memset(buf, 0, sizeof(buf));
	memcpy(buf, &nvram[addr], 16);

	return std::string(buf);
}

// 文字列を書き込む
static void
write_string(uint32 addr, const std::string& str)
{
	memset(&nvram[addr], 0, 16);
	memcpy(&nvram[addr], str.data(), str.size());
}

// キーの位置を探す。主に LUNA-88K 用。
// str が "" でも探せる (新規追加時の空きエントリ探索用)
// 見付かればアドレスを返す。
// 見付からなければ 0 を返す (0 は変数のアドレスとしては使えない)
static uint32
find_string(const std::string& str)
{
	for (uint32 addr = 0x20; addr < 0x560; addr += 0x20) {
		std::string key = read_string(addr);
		if (str == key) {
			return addr;
		}
	}

	return 0;
}

// NVRAM のチェックサムを返す
static uint8
calc_checksum()
{
	uint8 eor = 0;

	for (uint32 addr = 0x20; addr < 0x560; addr++) {
		eor ^= nvram[addr];
	}
	DPRINTF("Calculated checksum is 0x%02x\n", eor);
	return eor;
}

// NVRAM のチェックサムを照合し、正しければ true を返す
static bool
verify_checksum()
{
	if (memcmp(&nvram[4], "<nv>", 4) != 0) {
		DPRINTF("Magic string mismatch\n");
		return false;
	}

	uint8 eor = calc_checksum();
	for (uint32 i = 0; i < 3; i++) {
		if (nvram[0x1c + i] != eor) {
			DPRINTF("Checksum[%d] mismatch\n", i);
			return false;
		}
	}
	DPRINTF("Checksum ok\n");
	return true;
}

unix.superglobalmegacorp.com

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