diff scn2k/scn2k_impl.cc @ 0:223b71206888

Initial import
author thib
date Fri, 01 Aug 2008 16:32:45 +0000
parents
children 2c890434e30f
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/scn2k/scn2k_impl.cc
@@ -0,0 +1,1909 @@
+/*
+ * Copyright (c) 2004-2006  Kazunori "jagarl" Ueno
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdexcept>
+#include"scn2k_impl.h"
+#include"system/file.h"
+#include"system/system_config.h"
+#include"window/picture.h"
+#include"window/system.h"
+
+// #define DEBUG 1
+
+using namespace std;
+
+/**********************************************
+** Scn2k
+*/
+
+void kconv(const unsigned char* src, unsigned char* dest);
+void kconv_rev(const unsigned char* src, unsigned char* dest);
+string kconv(const string& s);
+string kconv_rev(const string& s);
+
+Scn2k::Scn2k(Event::Container& _event, PicContainer& _parent, class MuSys& mu, AyuSysConfig& _config) :
+	Event::Time(_event),
+	event(_event),
+	parent(_parent),
+	config(_config),
+	text_exec(_event, _parent, config),
+	grp_exec(_event, _parent, flag, flag.cgm_data, mu, config)
+{
+	system_version = 0;
+	skip_mode = SKIP_NO;
+
+	script_start = 0;
+	script = 0;
+	script_end = 0;
+
+	backlog_script_scn = -1;
+	backlog_script_start = 0;
+	backlog_script_end = 0;
+
+	save_scn = 0;
+	save_point = 0;
+	scn_number = 0;
+	scn_point = 0;
+	cmd_stack_str = cmd_stack_str_orig;
+
+	dialog = 0;
+	dialog_type = CMD_NOP;
+	menu = 0;
+	menu_mouseshown = false;
+
+	/* マウスカーソルを作成 */
+	mouse_type = 0;
+	mouse_surface = 0;
+	mouse_pressed = 0;
+	ShowCursor();
+
+	LoadSys();
+	text_exec.InitWindow();
+	grp_exec.InitSel(config);
+}
+
+Scn2k::~Scn2k() {
+	HideCursor();
+	SaveSys();
+}
+
+char* Scn2k::OpenScript(int new_scn_number, char*& end, int* call_vec, int& system_version) {
+	char fname[1024];
+	const char* data;
+	char* ret_data;
+	int offset = 0;
+	int scenario_magic;
+
+	sprintf(fname, "SEEN%04d.TXT", new_scn_number);
+	ARCINFO* info = file_searcher.Find(FILESEARCH::SCN, fname, "");
+	if (info == 0) goto err;
+	data = info->Read();
+
+	/* version 確認 */
+	scenario_magic = read_little_endian_int(data + 4);
+	if (scenario_magic != 0x2712 && scenario_magic != 0x1adb2) {
+		fprintf(stderr,"Invalid scenario header : scenario number %d\n",new_scn_number);
+		goto err;
+	}
+	if (read_little_endian_int(data) == 0x1cc) {
+		system_version = 0;
+		offset = 0x1cc + read_little_endian_int(data+0x20) + 4;
+	} else if (read_little_endian_int(data) == 0x1d0) {
+		system_version = 1;
+		offset = read_little_endian_int(data + 0x20);
+	} else {
+		fprintf(stderr,"Invalid scenario header : scenario number %d\n",new_scn_number);
+		goto err;
+	}
+	/* header から subroutine number とりだし */
+	if (call_vec) {
+		int i;
+		for (i=0; i<100; i++) {
+			call_vec[i] = read_little_endian_int(data + 0x34 + i * 4);
+		}
+	}
+	ret_data = new char[info->Size() - offset + 1024];
+	memcpy(ret_data, data+offset, info->Size()-offset);
+	memset(ret_data+info->Size()-offset, 0, 1024);
+	end = ret_data + info->Size() - offset;
+	delete info;
+	return ret_data;
+
+err:
+	delete info;
+	fprintf(stderr,"Cannot open scenario number %d\n",new_scn_number);
+	throw std::invalid_argument("Scn2k::OpenScript");
+
+	return false;
+}
+bool Scn2k::ChangeScript(int new_scn_number, int call_no) {
+	int old_scn_number = scn_number;
+	int old_scn_pt = script - script_start;
+	int scn_pt = 0;
+
+	if (script_start) delete[] script_start;
+	script_start = 0;
+	script = 0;
+	script_end = 0;
+
+	int call_vec[100];
+
+	try {
+		script_start = OpenScript(new_scn_number, script_end, call_vec, system_version);
+	} catch(...) {
+		fprintf(stderr,"\tFrom script %d pt %d\n",old_scn_number, old_scn_pt);
+		throw;
+	}
+	if (call_no > 0 && call_no < 100) {
+		scn_pt = call_vec[call_no];
+		if (scn_pt == 0) {
+			fprintf(stderr,"Invalid subroutine number: scn %d sub %d\n",new_scn_number, call_no);
+			scn_pt = 0;
+		}
+	} else if (call_no < 0) {
+		scn_pt = -call_no; // デバッグ用
+	}
+
+	scn_number = new_scn_number;
+	scn_point = scn_pt;
+	script = script_start + scn_pt;
+	if (script < script_start || script >= script_end) 
+		fprintf(stderr,"scn %d pt %d: Cannot jump to %d:%d; fall back to the top\n",old_scn_number, old_scn_pt, scn_number, scn_pt);
+	return true;
+}
+bool Scn2k::ReadCmdAt(Cmd& cmd, int scn, int pt) {
+	const char* d;
+	if (scn ==scn_number) {
+		d = script_start + pt;
+		if (d < script_start || d >= script_end) {
+			fprintf(stderr,"Cannot read script at current scn %d pt %d\n", scn, pt);
+			return false;
+		}
+	} else {
+		if (backlog_script_scn != scn) {
+			if (backlog_script_start) delete[] backlog_script_start;
+			backlog_script_start = OpenScript(scn, backlog_script_end, 0, system_version);
+		}
+		d = backlog_script_start + pt;
+		if (d < backlog_script_start || d >= backlog_script_end) {
+			fprintf(stderr,"Cannot read script at scn %d pt %d\n", scn, pt);
+			return false;
+		}
+	}
+	
+	cmd.GetCmd(flag, d);
+	return true;
+}
+
+extern bool save_req, load_req; // キーボードからセーブ・ロードできるように
+extern bool pressAreq;
+
+void Scn2k::Elapsed(unsigned int current_time) {
+	SetWakeup(current_time + 10); // 10msに一回シナリオスクリプト解釈
+	if (script == 0) return;
+//VarInfo info; info.type = 6; info.number = 0; // PB の「一回ゲームを開始したことがある」フラグ
+//flag.Set(info,1);
+//info.type = 0; info.number = 604; // Princess Bride: クリア対象設定フラグ (聖)
+//flag.Set(info, 1); 
+
+
+	Cmd cmd(flag, system_version);
+	int cnt1;
+	int cnt2 = 1000; // flag / jump / flag 系コマンドの最大実行回数
+
+	/* XXX */
+	if (save_req) {
+		save_req = false;
+		load_req = false;
+		cmd.cmd_type = CMD_SAVEREQ;
+	} else if (load_req) {
+		load_req = false;
+		save_req = false;
+		cmd.cmd_type = CMD_LOADREQ;
+	}
+	if (pressAreq) {
+		pressAreq = false;
+		LoadRollback(cmd);
+		return;
+	}
+
+	/* キー入力などに対応 */
+	// メニュー内以外で shift キーが押されたらスキップ開始
+	if ( (skip_mode&SKIP_IN_MENU) == 0) {
+		if (event.pressed(KEY_SHIFT)) {
+			if (skip_mode & SKIP_TEXT) {
+				; // スキップ中ならなにもしない
+			} else {
+				SetSkipMode(SkipMode(SKIP_TEXT | SKIP_GRP_NOEFFEC | SKIPEND_KEY));
+			}
+		} else {
+			if ( skip_mode & SKIPEND_KEY) {
+				if ( (skip_mode & SKIPEND_TEXT) && (skip_mode & SKIP_TEXT)) {
+					SkipMode new_skip_mode = SkipMode(skip_mode & (~SKIPEND_KEY));
+					if ( (new_skip_mode & SKIP_GRP_FAST) || (new_skip_mode & SKIP_GRP_NODRAW)) {
+						new_skip_mode = SkipMode(skip_mode & (~SKIP_GRP_NOEFFEC));
+					}
+					SetSkipMode(new_skip_mode);
+				} else {
+					SetSkipMode(SKIP_NO);
+				}
+			}
+		}
+	}
+
+	for (cnt1=0; cnt1<20; cnt1++) { // 一回につき 20 個のコマンド実行
+		// 他のコマンド実行中なら終了
+		if ( (cmd.cmd_type == CMD_NOP && SysWait(cmd)) ||
+		     // (cmd.cmd_type == CMD_NOP && text_exec.Wait(current_time, cmd)) ||
+		     // (cmd.cmd_type == CMD_NOP && grp_exec.Wait(current_time, cmd))) {
+		     (cmd.cmd_type == CMD_NOP && grp_exec.Wait(current_time, cmd)) ||
+		     (cmd.cmd_type == CMD_NOP && text_exec.Wait(current_time, cmd))) {
+			break;
+		}
+		// コマンド読み込み
+		for (; cnt2 > 0; cnt2--) {
+			scn_point = script - script_start;
+			eprintf("%d / %d :", script - script_start, script_end-script_start);
+// fprintf(stderr,"%d: %d / %d :",scn_number,  script - script_start, script_end-script_start);
+			cmd.GetCmd(flag, script);
+//		if (cmd.cmd_type != CMD_NOP) {
+if (0) {
+			fprintf(stderr,"%d / %d : 0x23 - cmd %02x-%02x:%04x:%02x[%2d] \n",
+				scn_point, script_end-script_start,
+				cmd.cmd1,cmd.cmd2,cmd.cmd3,cmd.cmd4,cmd.argc);
+			int i; for (i=0; i<cmd.args.size(); i++) {
+				if (i == 0) fprintf(stderr,"\t");
+				VarInfo info = cmd.args[i];
+				if (info.type == TYPE_STR || info.type == TYPE_VARSTR)
+					fprintf(stderr,"\"%s\",", cmd.Str(info));
+				else
+					fprintf(stderr,"%d,",info.value);
+			}
+			fprintf(stderr,"\n");
+		}
+			cmd.scn = scn_number;
+			cmd.pos = scn_point;
+			if (cmd.IsError()) break;
+			if (cmd.cmd_type == CMD_NOP) continue;
+			if (cmd.cmd_type == CMD_JMP) {
+				// local jump
+				if (cmd.cmd1 == 0 && cmd.cmd2 == 1 && cmd.cmd3 == 16) {
+					int i;
+					for (i=0; i<cmd.args.size()-1; i++) {
+						VarInfo var;
+						var.type = 11;
+						var.number = i;
+						flag.Set(var, cmd.args[i].value);
+					}
+					cmd.args[0].value = cmd.args[i].value;
+				}
+				if ( cmd.cmd1 == 0 && cmd.cmd2 == 1 && (cmd.cmd3 == 5 || cmd.cmd3 == 8 || cmd.cmd3 == 16) ) { // local call / simple switch
+					int scn_pt = script - script_start;
+// fprintf(stderr,"\nlocal call %d:%d from %d\n",scn_number,cmd.args[0].value,scn_pt);
+					stack.push_back(StackItem(-1, scn_pt));
+				}
+if (cmd.cmd1 == 0 && cmd.cmd2 == 1 && cmd.cmd3 == 1) {
+	fprintf(stderr,"***  unsupported: cond 1\n");
+}
+				script = script_start + cmd.args[0].value;
+				if (script < script_start || script >= script_end) {
+					fprintf(stderr,"scn %d pt %d: Cannot jump to %d; fall back to the top\n",scn_number, cmd.args[0].value);
+					script = script_start;
+				}
+				cmd.clear();
+				continue;
+			}
+			if (flag.Exec(cmd)) continue;
+			break;
+		}
+		if (cmd.IsError()) {
+fprintf(stderr,"cmd error occured: scn %d pt %d / cur %d",scn_number,scn_point,script-script_start);
+			while(script < script_end) {
+				if (*script == 0x29 && script[1] == 0x0a) {script++;break;}
+				if (*script == 0 && script[1] == 0x0a) {script++;break;}
+				if (*script == 0 && script[1] == 0x23) {script++;break;}
+				script++;
+fprintf(stderr," -> fall back to %d\n",script-script_start);
+			}
+			const char* dprev = script - 0x60;
+			if (dprev < script_start) dprev = script_start;
+			int ilen = (script-dprev+65)/16;
+			int i; for (i=0; i<ilen; i++) {
+				fprintf(stderr, "%6d: ",dprev-script_start);
+				int j; for (j=0; j<16; j++) {
+					if (dprev >= script_end) break;
+					fprintf(stderr, "%02x ",*(unsigned char*)(dprev));
+					dprev++;
+				}
+				fprintf(stderr, "\n");
+			}
+			break;
+		}
+		if (cmd.cmd_type == CMD_NOP) continue;
+
+		if (cmd.cmd_type == CMD_TEXT && cmd.pos != -1) {
+			set<int>& readflag = text_readflag[scn_number];
+			if (readflag.find(cmd.pos) == readflag.end()) { // 未読テキスト発見
+				readflag.insert(cmd.pos);
+				if (skip_mode & SKIPEND_TEXT) {
+					if (!(skip_mode & SKIPEND_KEY)) SetSkipMode(SKIP_NO);
+				}
+			}
+		}
+		text_exec.Exec(cmd);
+		grp_exec.Exec(cmd);
+		SysExec(cmd);
+		if (cmd.cmd_type == CMD_WAITFRAMEUPDATE) {
+			SetWakeup(Event::Time::FRAME_UPDATE);
+			break;
+		} else if (cmd.cmd_type != CMD_NOP) {
+#if DEBUG
+			fprintf(stderr,"%d-%d / %d : unsupported command; 0x23 - cmd %02x-%02x:%04x:%02x[%2d] \n",
+				cmd.scn, script - script_start, script_end-script_start,
+				cmd.cmd1,cmd.cmd2,cmd.cmd3,cmd.cmd4,cmd.argc);
+			int i; for (i=0; i<cmd.args.size(); i++) {
+				if (i == 0) fprintf(stderr,"\t");
+				VarInfo info = cmd.args[i];
+				if (info.type == TYPE_STR || info.type == TYPE_VARSTR)
+					fprintf(stderr,"\"%s\",", cmd.Str(info));
+				else
+					fprintf(stderr,"%d,",info.value);
+			}
+			fprintf(stderr,"\n");
+#endif
+			cmd.clear();
+		}
+	}
+	return;
+}
+
+void Scn2k::ShowCursor(void) {
+	HideCursor();
+	char key[1024];
+	sprintf(key, "#MOUSE_CURSOR.%03d.NAME",mouse_type);
+	const char* name = config.GetParaStr(key);
+	if (name == 0 || name[0] == 0) mouse_surface = DEFAULT_MOUSECURSOR;
+	else {
+		mouse_surface = parent.Root().NewSurface(name, COLOR_MASK);
+	}
+	if (mouse_surface == 0) mouse_surface = DEFAULT_MOUSECURSOR;
+	System::Main::SetCursor(mouse_surface, Rect(8, 8, 8+32, 8+32));
+}
+
+void Scn2k::HideCursor(void) {
+	if (mouse_surface) {
+		System::Main::SetCursor(0, Rect(0,0));
+		if (mouse_surface != DEFAULT_MOUSECURSOR)
+			parent.Root().DeleteSurface(mouse_surface);
+		mouse_surface = 0;
+	}
+	return;
+}
+
+bool Scn2k::SysWait(Cmd& cmd) {
+
+	if (menu) {
+		menu->Exec(cmd);
+		if (menu->status & Scn2kMenu::MENU_DELETE || menu->pimpl == 0) {
+			delete menu;
+			menu = 0;
+			if (! menu_mouseshown) HideCursor();
+			else ShowCursor();
+			SetSkipMode(SkipMode(skip_mode & (~SKIP_IN_MENU) ));
+		}
+		if (cmd.cmd_type == CMD_NOP) return true;
+		else return false; /* exec command */
+	}
+	return false;
+}
+
+void DllCall_LB(Cmd& cmd, Flags& flags);
+void Scn2k::SysExec(Cmd& cmd) {
+	if (cmd.cmd_type == CMD_SYSVAR) {
+		int i;
+		for (i=0; i<cmd.args.size(); i++) {
+			if (cmd.args[i].type == TYPE_SYS) {
+				if (cmd.args[i].number == TYPE_SYS_SYS) {
+					flag.SetSys(cmd.args[i].value);
+				} else if (cmd.args[i].number == TYPE_SYS_SKIPMODE) {
+					SetSkipMode(SkipMode(cmd.args[i].value));
+				}
+			} else if (cmd.args[i].type == TYPE_VARSTR) {
+				flag.SetStr(cmd.args[i].number, cmd.Str(cmd.args[i]));
+			} else {
+				flag.Set(cmd.args[i], cmd.args[i].value);
+			}
+		}
+		cmd.clear();
+	}
+	if (cmd.cmd_type == CMD_SAVEPOINT || cmd.cmd_type == CMD_ROLLBACKPOINT) {
+		if (text_exec.backlog_item.scn != -1) {
+			text_exec.backlog.push_back(text_exec.backlog_item);
+			text_exec.backlog_item.Clear();
+		}
+		save_scn = scn_number;
+		save_point = scn_point;
+		if (!new_rollback_save.empty()) {
+			rollback_save.push_back(new_rollback_save);
+			new_rollback_save = "";
+		}
+		if (cmd.cmd_type == CMD_ROLLBACKPOINT) SaveRollback();
+		cmd.clear();
+	}
+	if (cmd.cmd_type == CMD_SAVEREQ || cmd.cmd_type == CMD_SAVE) {
+		Save(cmd);
+		return;
+	}
+	if (cmd.cmd_type == CMD_LOADREQ || cmd.cmd_type == CMD_LOAD) {
+		Load(cmd);
+		return;
+	}
+	if (cmd.cmd_type == CMD_BACKLOGREQ || cmd.cmd_type == CMD_BACKLOGREQ_FWD) {
+		if (menu) {
+			fprintf(stderr,"BACKLOG_REQ requested!!!\n");
+			return;
+		}
+		if (cmd.cmd_type == CMD_BACKLOGREQ_FWD) {
+			cmd.clear(); // backlog mode 以外で fwd を押されてもなにもしない
+			return;
+		}
+		SetSkipMode(SKIP_IN_MENU); // テキストスキップ等はここで中断
+		menu = new Scn2kMenu(Scn2kMenu::MENU_BACKLOG, *this, flag, text_exec, system_version);
+		menu->InitPanel(event, parent);
+		menu->InitTitle(Scn2kSaveTitle(*this));
+		if (mouse_surface) menu_mouseshown = true;
+		else menu_mouseshown = false;
+		ShowCursor();
+		return;
+	}
+	if (cmd.cmd_type == CMD_MENUREQ) {
+		int scn=0, pt=0;
+		config.GetParam("#CANCELCALL", 2, &scn, &pt);
+		if (scn) {
+			// 右クリックされたら global call を行う
+			cmd.cmd_type = CMD_OTHER;
+			cmd.cmd1 = 0;
+			cmd.cmd2 = 1;
+			cmd.cmd3 = 0x0c;
+			cmd.cmd4 = 1;
+			cmd.args.clear();
+			cmd.args.push_back(VarInfo(SCN_INFO_MENU));
+			cmd.args.push_back(0);
+			SetSkipMode(SKIP_IN_MENU); // テキストスキップ等はここで中断
+		}
+	}
+	if (cmd.cmd_type == CMD_SAVECMDGRP || cmd.cmd_type == CMD_SAVECMDGRP_START || cmd.cmd_type == CMD_SAVECMDGRP_ONCE || cmd.cmd_type == CMD_SAVECMD_ONCE) {
+		// 画像コマンド等はスタックに保存し、セーブ時に保存できるようにする
+		if (cmd.cmd_type == CMD_SAVECMDGRP_START) {
+			vector<CmdSimplified>::iterator it, cur;
+			cur = cmd_stack.begin();
+			cmd_stack_str = cmd_stack_str_orig;
+			/* 画像関連コマンド以外を別にする */
+			for (it=cmd_stack.begin(); it != cmd_stack.end(); it++) {
+				if (it->type != CMD_SAVECMDGRP && it->type != CMD_SAVECMDGRP_START && it->type != CMD_SAVECMDGRP_ONCE) {
+					cur->copy(*it, cmd_stack_str);
+					cur++;
+				}
+			}
+			cmd_stack.erase(cur, cmd_stack.end());
+		}
+		if (cmd.cmd_type == CMD_SAVECMD_ONCE || cmd.cmd_type == CMD_SAVECMDGRP_ONCE) { // 同じコマンドがあれば削除する
+			vector<CmdSimplified>::iterator it;
+			for (it = cmd_stack.end(); it != cmd_stack.begin(); ) {
+				--it;
+				if (it->cmd1 == cmd.cmd1 && it->cmd2 == cmd.cmd2 && it->cmd3 == cmd.cmd3 && it->cmd4 == cmd.cmd4) {
+					cmd_stack.erase(it);
+					break;
+				}
+			}
+		}
+		CmdSimplified cmd_item;
+		cmd.write(cmd_item, cmd_stack_str);
+		cmd_stack.push_back(cmd_item);
+		cmd.clear();
+		if (cmd_stack_str > cmd_stack_str_orig + 30000) { // char cmd_stack_str_orig[32768]
+			fprintf(stderr,"Error in Scn2k::SysExec: too long cmdstack (%d): stack string overflow\n",cmd_stack.size());
+			cmd_stack_str = cmd_stack_str_orig;
+			cmd_stack.clear();
+		}
+	}
+	if (cmd.cmd_type != CMD_OTHER) return;
+	if (cmd.cmd1 == 0 && cmd.cmd2 == 1) {
+		if (cmd.cmd3 == 0x0b) { // global jump
+			eprintf("global jump to %d\n",cmd.args[0].value);
+			if (! ChangeScript(cmd.args[0].value, 0)) return; // 読み込めない; abort.
+			cmd.clear();
+		} else if (cmd.cmd3 == 0x0c || cmd.cmd3 == 0x12) { // call (0x12 の方は微妙)
+			int new_scn = cmd.args[0].value;
+			int new_pt = 0;
+			if (cmd.args.size() >= 1) { // subroutine number が付く
+					// 引数が付くのもあるらしい
+				new_pt = cmd.args[1].value;
+			}
+			if (new_scn == SCN_INFO_MENU) { // menu call
+				config.GetParam("#CANCELCALL", 2, &new_scn, &new_pt);
+				stack.push_back(StackItem(SCN_INFO, SCN_INFO_MENU)); // menu call を示す特殊な記号
+			} else {
+				int i;
+				VarInfo var;
+				// ローカル変数を伴う subroutine call
+				var.type = 11;
+				var.number = 0;
+				int saved_vars = 0;
+				for (i=0; i<40; i++) {
+					int val = flag.Get(var.type, i);
+					if (val != 0) {
+						stack.push_back(StackItem(SCN_INFO_LOCALS + i, val));
+						saved_vars++;
+					}
+				}
+				var.type = TYPE_VARLOCSTR;
+				for (i=0; i<3; i++) {
+					string s = flag.Str(var.type, i);
+					if (s.size()) {
+						int sp = stack_strbuffer.size();
+						stack.push_back(StackItem(SCN_INFO_LOCALSTR+i, sp));
+						stack_strbuffer.push_back(s);
+						saved_vars++;
+					}
+				}
+				stack.push_back(StackItem(SCN_INFO, SCN_INFO_LOCALS + saved_vars));
+					
+				var.type = 11;
+				var.number = 0;
+				// 特殊な subroutine call なので、余計な情報を引数に渡す
+				for (i=2; i<cmd.args.size(); i++) {
+					flag.Set(var, cmd.args[i].value);
+// fprintf(stderr,"<%d:%d>=%d;",var.type,var.number,cmd.args[i].value);
+					var.number++;
+				}
+// fprintf(stderr,"%d; ",stack.size());
+			}
+			int scn_pt = script - script_start;
+			stack.push_back(StackItem(scn_number, scn_pt));
+// fprintf(stderr,"\nglobal call %d:%d from %d:%d\n",new_scn,new_pt,scn_number,scn_pt);
+			eprintf("global call to %d, %d\n",new_scn, new_pt);
+			if (! ChangeScript(new_scn, new_pt)) return; // 読み込めない; abort.
+			cmd.clear();
+		} else if (cmd.cmd3 == 0x65) { // 文字列の返り値をセットする
+			int arg1 = cmd.args[0].value;
+			string s = cmd.Str(cmd.args[1]);
+			int sp = stack_strbuffer.size();
+			stack.push_back(StackItem(SCN_INFO_RETSTR+arg1, sp));
+			stack_strbuffer.push_back(s);
+			cmd.clear();
+		} else if (cmd.cmd3 == 0x0d || cmd.cmd3 == 0x0a || cmd.cmd3 == 0x11 || cmd.cmd3 == 0x13) { // return (0a: local return) (0x13はよくわからない)
+// fprintf(stderr,"global return : stack size %d\n",stack.size());
+			if (stack.empty()) {
+				cmd.clear();
+				return; // スタックがおかしい:abort
+			}
+			map<int, string> retstr;
+			while( (!stack.empty()) && stack.back().scn_number >= SCN_INFO_RETSTR) {
+				int ret_num = stack.back().scn_number - SCN_INFO_RETSTR;
+// fprintf(stderr,"\nRetStr;");
+				string str = stack_strbuffer.back();
+				stack_strbuffer.pop_back();
+				retstr[ret_num] = str;
+				stack.pop_back();
+			}
+			if (stack.empty()) {
+				cmd.clear();
+				return; // スタックがおかしい:abort
+			}
+			StackItem s = stack.back();
+			stack.pop_back();
+			bool localvar_init = false;
+			while( (!stack.empty()) && stack.back().scn_number == SCN_INFO) {
+				int mode = stack.back().scn_pt;
+				stack.pop_back();
+				if (mode == SCN_INFO_MENU) {
+// fprintf(stderr,"\nInfo Menu;");
+					// menu モード終了
+					SetSkipMode(SkipMode(skip_mode & (~SKIP_IN_MENU) ));
+				} else if (mode >= SCN_INFO_LOCALS && mode <= SCN_INFO_LOCALS+50) {
+// fprintf(stderr,"\nInfo Local;");
+					int i;
+					// ローカル変数を元に戻す
+					VarInfo var;
+					var.type = 11;
+					var.number = 0;
+					for (i=0; i<40; i++) {
+						var.number = i;
+						flag.Set(var, 0);
+					}
+					var.type = TYPE_VARLOCSTR;
+					for (i=0; i<3; i++) {
+						var.number = i;
+						flag.SetStr(var, "");
+					}
+					int args = mode - SCN_INFO_LOCALS;
+// fprintf(stderr," args = %d; ",args);
+					for (i=0; i<args; i++) {
+						if (stack.empty() || stack.back().scn_number < SCN_INFO) {
+							fprintf(stderr,"Fatal : Invalid stack found in preserved local variables!\n");
+							break;
+						}
+						var.number = stack.back().scn_number;
+// fprintf(stderr,"%d:%d; ",stack.back().scn_number,stack.back().scn_pt);
+						if (var.number >= SCN_INFO_LOCALS && var.number < SCN_INFO_LOCALSTR) {
+							var.type = 11;
+							var.number -=  SCN_INFO_LOCALS;
+							flag.Set(var, stack.back().scn_pt);
+						} else if (var.number >= SCN_INFO_LOCALSTR && var.number < SCN_INFO_RETSTR) {
+							var.type = TYPE_VARLOCSTR;
+							var.number -= SCN_INFO_LOCALSTR;
+							flag.SetStr(var, stack_strbuffer.back());
+							stack_strbuffer.pop_back();
+						}
+						stack.pop_back();
+					}
+				}
+// fprintf(stderr,"stack size %d string size %d\n",stack.size(),stack_strbuffer.size());
+			}
+			if (cmd.cmd3 == 0x11 || cmd.cmd3 == 0x13) {
+// fprintf(stderr,"\nSet RetLocal;");
+				// 返り値をセットする
+				map<int,string>::iterator it;
+				VarInfo var;
+				var.type = TYPE_VARLOCSTR;
+				for (it=retstr.begin(); it!=retstr.end(); it++) {
+					var.number = it->first;
+					flag.SetStr(var, it->second);
+				}
+				var.type = 11;
+// fprintf(stderr,"return : cmd.cmd3 == 0x11; size %d\n",cmd.args.size());
+				if (cmd.args.size() == 1) {
+// fprintf(stderr,"return value %d\n",cmd.args[0].value);
+					flag.SetSys(cmd.args[0].value);
+				} else {
+					int i;for (i=0; i<cmd.args.size(); i++) {
+						var.number = i;
+						flag.Set(var, cmd.args[i].value);
+					}
+				}
+			}
+// fprintf(stderr,"global return : return to %d:%d\n",s.scn_number,s.scn_pt);
+// fprintf(stderr,"\nglobal return %d:%d from %d:%d\n",s.scn_number,s.scn_pt,scn_number, script - script_start);
+			if (s.scn_number != -1) {
+				if (! ChangeScript(s.scn_number, 0)) return; // 読み込めない; abort.
+			}
+			script = script_start + s.scn_pt;
+			cmd.clear();
+		}
+	} else if (cmd.cmd1 == 2 && cmd.cmd2 == 1 && cmd.cmd3 == 12) { // DLL Call
+		const char* regname = config.GetParaStr("#REGNAME");
+		const char key_lb[] = "KEY\\LittleBusters";
+		if (strcmp(regname, key_lb) == 0) {
+			DllCall_LB(cmd, flag);
+			cmd.clear();
+		}
+	} else if (cmd.cmd1 == 0 && cmd.cmd2 == 0x04) { // メニューモード
+		if (cmd.cmd3 == 300 || cmd.cmd3 == 301 || cmd.cmd3 == 302) {
+			// メニューからのreturn
+			cmd.cmd2 = 1;
+			cmd.cmd3 = 0x0d;
+			SysExec(cmd);
+		}
+	} else if (cmd.cmd1 == 1 && cmd.cmd2 == 0x04) {
+		if (cmd.cmd3 == 0 && cmd.cmd4 == 0) { // タイトル名設定
+			const char* name = cmd.Str(cmd.args[0]);
+			if (name == 0) name = "";
+			window_title = name;
+			const char* config_name = config.GetParaStr("#CAPTION");
+			if (config_name == 0) config_name = "";
+			string setname = kconv(string(config_name) + "  " + window_title);
+			parent.Root().SetWindowCaption(setname.c_str());
+			cmd.clear();
+		} else if (cmd.cmd3 == 0x82 && cmd.cmd4 == 0) {
+			/* cmd.cmd3 == 0x82 : マウスの press 状態クリアかも */
+			event.presscount(MOUSE_LEFT);
+			event.presscount(MOUSE_RIGHT);
+			cmd.clear();
+		} else if (cmd.cmd3 == 0x85 && cmd.cmd4 == 0) {
+			int x,y,left,right;
+			event.MousePos(x,y);
+			if (event.presscount(MOUSE_LEFT)) left = 2;
+			else if (event.pressed(MOUSE_LEFT)) left = 1;
+			else left = 0;
+			
+			if (event.presscount(MOUSE_RIGHT)) right = 2;
+			else if (event.pressed(MOUSE_RIGHT)) right = 1;
+			else right = 0;
+			
+			// eprintf("mouse pos\n");
+			flag.Set(cmd.args[0], x);
+			flag.Set(cmd.args[1], y);
+			flag.Set(cmd.args[2], left);
+			flag.Set(cmd.args[3], right);
+			cmd.clear();
+		} else if (cmd.cmd3 == 0x15e || cmd.cmd3 == 0x161 || cmd.cmd3 == 0x162 || cmd.cmd3 == 0x14c || cmd.cmd3 == 0x7d1) {
+/* 15e, 161, 162, 14c, 7d1 : なんらかのシステム情報を返す(skip modeなど?) */
+/* 7d1: == 1 || 14c: == 1 || (15e==1&&161==1&&162==0) || (press_val == 2) : スキップ中? タイトル画面のアニメーション終了 */
+			flag.SetSys(0);
+			cmd.clear();
+		} else if (cmd.cmd3 == 0x4b0) { // 終了
+			System::Main::Quit();
+			script = 0; script_start = 0; script_end = 0;
+			cmd.clear();
+			cmd.cmd_type = CMD_WAITFRAMEUPDATE;
+		} else if (cmd.cmd3 == 0x4b4 || cmd.cmd3 == 0x4b5) { // 選択肢巻き戻し
+			LoadRollback(cmd);
+		} else if (cmd.cmd3 == 0x58d) {
+        		// 前にロード|セーブされた番号を返す。
+			flag.SetSys(-1);
+		} else if (cmd.cmd3 == 0x585) {
+        		// 第一引数の記録された日付、タイトルなどが返される
+        		// データがないなら sys に 0が、あるなら 1 が返る
+			int y,m,d,wd,h,min,s,ms;
+			string title;
+fprintf(stderr,"StatSave %d:",cmd.args[0].value+1);
+			if (StatSaveFile(cmd.args[0].value+1,y,m,d,wd,h,min,s,ms,title) == true) {
+				flag.Set(cmd.args[1], y);
+				flag.Set(cmd.args[2], m);
+				flag.Set(cmd.args[3], d);
+				flag.Set(cmd.args[4], wd);
+				flag.Set(cmd.args[5], h);
+				flag.Set(cmd.args[6], min);
+				flag.Set(cmd.args[7], s);
+				flag.Set(cmd.args[8], ms);
+				if (cmd.args[9].type == TYPE_VARSTR) {
+					flag.SetStr(cmd.args[9].number, kconv_rev(title));
+				}
+				flag.SetSys(1);
+			} else {
+				flag.SetSys(0);
+			}
+			cmd.clear();
+		} else if (cmd.cmd3 == 0xc23) { // save
+			Save(cmd);
+		} else if (cmd.cmd3 == 0xc25) { // load
+			Load(cmd);
+		} else if (cmd.cmd3 == 0x4b1 || cmd.cmd3 == 0x4b3) { // menu へ戻る (4b3: バッドエンド)
+			int scn_start;
+			if (config.GetParam("#SEEN_MENU", 1, &scn_start) == 0) {
+				ChangeScript(scn_start, 0);
+				save_scn = 0;
+				save_point = 0;
+				window_title = "";
+				const char* window_title_config = config.GetParaStr("#CAPTION");
+				if (window_title_config) window_title = window_title_config;
+				parent.Root().SetWindowCaption(kconv(window_title).c_str());
+				stack.clear();
+				cmd_stack.clear();
+				cmd_stack_str = cmd_stack_str_orig;
+				flag.Load("");
+				text_exec.Load("");
+				grp_exec.Load("");
+				SetSkipMode(SKIP_NO);
+			}
+		} else if (cmd.cmd3 == 0xcc) {
+			eprintf("show mouse cursor\n");
+			ShowCursor();
+			cmd.clear();
+		} else if (cmd.cmd3 == 0xcd) {
+			eprintf("hide mouse cursor\n");
+			HideCursor();
+			cmd.clear();
+		} else if (cmd.cmd3 == 0xcf) {
+			mouse_type = cmd.args[0].value;
+			eprintf("change mouse cursor : %d\n", mouse_type);
+			if (mouse_surface) ShowCursor();
+			cmd.clear();
+		}
+	}
+
+}
+
+#include<sys/types.h>
+#include<sys/stat.h>
+#include<errno.h>
+#include<unistd.h>
+
+// セーブファイルの名前をつくる
+string Scn2k::MakeSaveFile(void) const {
+	struct stat sstatus;
+	string dir = "~/.xkanon";
+
+	if (dir.c_str()[0] == '~' && dir.c_str()[1] == '/') {
+		char* home = getenv("HOME");
+		if (home != 0) {
+			string new_dir = string(home) + (dir.c_str()+1);
+			dir = new_dir;
+		}
+	}
+	// savepathにファイル名が入っていれば、それをセーブファイルとして使う
+	if (stat(dir.c_str(), &sstatus) == -1) {
+		if (errno != ENOENT) {
+			fprintf(stderr,"Cannot open save file; dir %s is not directory\n",dir.c_str());
+			return "";
+		}
+		if (mkdir(dir.c_str(), S_IRWXU) != 0 && errno != EEXIST) {
+			fprintf(stderr, "Cannot create directory %s ; Please create manually!!\n",dir.c_str());
+		}
+	} else {
+		if ( (sstatus.st_mode & S_IFMT) == S_IFREG) {
+			return dir;
+		}
+	}
+	// ファイル名を作る
+	const char* regname = config.GetParaStr("#REGNAME");
+
+	char* fname = new char[strlen(regname)+1];
+	/* レジストリ名をファイル名として有効なものにする */
+	int i; for (i=0; regname[i]!=0; i++) {
+		char c = regname[i];
+		if (c == '\\' || c == '/' || c == ':' || c <= 0x20) c = '_';
+		fname[i] = tolower(c);
+	}
+	fname[i] = 0;
+	dir += "/save.";
+	dir += fname;
+	return dir;
+}
+// セーブファイルの名前をつくる
+string Scn2kSaveTitle::operator() (int number) const {
+	int y,m,d,wd,h,min,sec,msec;
+	string title;
+	if (! impl.StatSaveFile(number, y,m,d,wd,h,min,sec,msec,title)) {
+		return "";
+	} else {
+		char buf[1024];
+		sprintf(buf, "%2d/%2d %2d:%2d ",m,d,h,min);
+		return string(buf) + title;
+	}
+};
+
+void Scn2k::SaveSys(void) {
+	char buf[1024];
+	string save;
+	string path = MakeSaveFile();
+	
+	sprintf(buf, "KEY=%s\n", config.GetParaStr("#REGNAME")); save += buf;
+	string save_config;
+	config.DiffOriginal(save_config);
+	save += "CONFIG=";
+	save += save_config;
+	save += "\n";
+	string save_flag; flag.SaveSys(save_flag);
+	save += save_flag;
+	string save_grp; grp_exec.SaveSys(save_grp);
+	save += save_grp;
+	map<int,set<int> >::iterator it;
+	save += "[TextRead]\n";
+	for (it=text_readflag.begin(); it != text_readflag.end(); it++) {
+		set<int>& read_flag = it->second;
+		set<int>::iterator jt;
+		char buf[1024];
+		sprintf(buf,"T<%05d>=",it->first);
+		string save_readflag = buf;
+		for (jt=read_flag.begin(); jt != read_flag.end(); jt++) {
+			sprintf(buf, "%d,", *jt);
+			save_readflag += buf;
+		}
+		save_readflag += "\n";
+		save += save_readflag;
+	}
+
+	path += ".0";
+	FILE* f = fopen(path.c_str(), "w");
+	if (f == 0) {
+		fprintf(stderr,"Cannot open save file %s\n",path.c_str());
+		return;
+	}
+	fwrite(save.c_str(), save.length(), 1, f);
+	fclose(f);
+	return;
+}
+
+void Scn2k::LoadSys(void) {
+	char buf[1024];
+	string path = MakeSaveFile();
+	path += ".0";
+	FILE* f = fopen(path.c_str(), "r");
+	if (f == 0) {
+		fprintf(stderr, "Cannot open save file %s\n",path.c_str());
+	} else {
+		fseek(f,0,2);
+		int sz = ftell(f);
+		fseek(f,0,0);
+		char* savedata = new char[sz+1];
+		fread(savedata, sz, 1, f);
+		savedata[sz] = 0;
+		fclose(f);
+
+		sprintf(buf, "KEY=%s\n", config.GetParaStr("#REGNAME"));
+		if (strncmp(savedata, buf, strlen(buf)) != 0) {
+			fprintf(stderr,"Invalid header in save file %s: it must be started with \"%s\"\n",buf);
+		} else  {
+			char* config_str = strstr(savedata, "\nCONFIG=");
+			if (config_str) {
+				config_str += strlen("\nCONFIG=");
+				char* strend = strchr(config_str, '\n');
+				if (strend) {
+					int l = strend - config_str;
+					char* config_copy = new char[l+1];
+					strncpy(config_copy, config_str, l);
+					config_copy[l] = 0;
+					config.PatchOriginal(config_copy);
+					delete[] config_copy;
+				}
+			}
+			flag.LoadSys(savedata);
+			grp_exec.LoadSys(savedata);
+			char* save = strstr(savedata, "\n[TextRead]\n");
+			if (save) {
+				save += strlen("\n[TextRead]\n");
+				do {
+					if (save[0] == '[') break; // next section
+					char* next_save = strchr(save, '\n');
+					if (next_save) {
+						*next_save++ = 0;
+					}
+					// T<XXXXX>=YYY,YYY,YYY,...
+					if (strncmp(save,"T<",2) == 0) {
+						int scn_num = atoi(save+2);
+						set<int>& read_flag = text_readflag[scn_num];
+						save += strlen("T<XXXXX>=");
+						while(save && *save) {
+							if (save[0] >= '0' && save[0] <= '9') {
+								int num = atoi(save);
+								read_flag.insert(num);
+							}
+							save = strchr(save, ',');
+							if (save) save++;
+						}
+					}
+					save = next_save;		
+				} while(save);
+			}
+
+		}
+		delete[] savedata;
+	}
+
+	/* 初期化 */
+	int scn_start; config.GetParam("#SEEN_START", 1, &scn_start);
+	ChangeScript(scn_start, 0);
+	save_scn = 0;
+	save_point = 0;
+	window_title = "";
+	const char* window_title_config = config.GetParaStr("#CAPTION");
+	if (window_title_config) window_title = window_title_config;
+	parent.Root().SetWindowCaption(kconv(window_title).c_str());
+	stack.clear();
+	cmd_stack.clear();
+	cmd_stack_str = cmd_stack_str_orig;
+
+	return;
+}
+
+bool Scn2k::StatSaveFile(int num, int& year, int& month, int& day, int& wday, int& hour,int& min, int& sec, int& msec, string& title) const {
+	char buf[1024];
+	string path = MakeSaveFile();
+	if (num <= 0 || num > 99) return false;
+	sprintf(buf,".%d",num);
+	path += buf;
+
+	struct stat sb;
+	if (stat(path.c_str(), &sb) == -1) return false;
+	struct tm* t = localtime(&sb.st_mtime);
+	month = t->tm_mon + 1;
+	day = t->tm_mday;
+	hour = t->tm_hour;
+	min = t->tm_min;
+	/* タイトルの取得 */
+	FILE* savefile = fopen(path.c_str(), "rb");
+	if (savefile == 0) return false;
+	char regname[1024];
+	sprintf(regname, "KEY=%s\n", config.GetParaStr("#REGNAME"));
+	fgets(buf,1000,savefile);
+	if (strncmp(regname, buf, strlen(regname)) != 0) {
+		fprintf(stderr,"invalid save file %s (registory name is not %s)\n",path.c_str(),regname);
+		fclose(savefile);
+		return false;
+	}
+	title="none";
+	while(!feof(savefile)) {
+		fgets(buf,1000,savefile);
+		if (strncmp(buf,"Title=",6) == 0) {
+			if (buf[strlen(buf)-2] == 0x0a) buf[strlen(buf)-2] = 0;
+			if (strlen(buf) > 20) buf[20] = 0, buf[21] = 0;
+			title = kconv(buf+6);
+			break;
+		}
+	}
+	fclose(savefile);
+	return true;
+}
+
+void Scn2k::SaveRollback(void) {
+fprintf(stderr,"Save rollback\n");
+	new_rollback_save = "";
+	string save_sys; SaveImpl(save_sys);
+	string save_flag; flag.Save(save_flag);
+	string save_text; text_exec.Save(save_text, true);
+	string save_grp; grp_exec.Save(save_grp);
+	new_rollback_save += save_sys;
+	new_rollback_save += save_flag;
+	new_rollback_save += save_text;
+	new_rollback_save += save_grp;
+}
+
+void Scn2k::LoadRollback(Cmd& cmd) {
+	if (rollback_save.empty()) return;
+	new_rollback_save = "";
+	string savedata = rollback_save.back();
+	rollback_save.pop_back();
+	LoadImpl(savedata.c_str());
+	flag.Load(savedata.c_str());
+	text_exec.Load(savedata.c_str());
+	grp_exec.Load(savedata.c_str());
+
+	/* 画面の回復など */
+	SetSkipMode(SKIP_NO);
+	vector<CmdSimplified>::iterator it;
+	cmd.clear();
+	for (it = cmd_stack.begin(); it != cmd_stack.end(); it++) {
+		cmd.read(*it);
+		cmd.cmd_type = CMD_OTHER;
+		flag.Exec(cmd);
+		text_exec.Exec(cmd);
+		grp_exec.Exec(cmd);
+	}
+	cmd.clear();
+	return;
+}
+
+void Scn2k::Save(Cmd& cmd) {
+	if (cmd.cmd_type == CMD_SAVEREQ) {
+		if (menu == 0) {
+			SetSkipMode(SKIP_IN_MENU); // テキストスキップ等はここで中断
+			menu = new Scn2kMenu(Scn2kMenu::MENU_SAVE, *this, flag, text_exec, system_version);
+			menu->InitPanel(event, parent);
+			menu->InitTitle(Scn2kSaveTitle(*this));
+			if (mouse_surface) menu_mouseshown = true;
+			else menu_mouseshown = false;
+			ShowCursor();
+			return;
+		}
+	}
+	char buf[1024];
+	string save;
+	FILE* f = 0;
+	if (save_scn == 0) {
+		fprintf(stderr,"Cannot decide save point\n");
+		return; // セーブ位置が保存されてない
+	}
+	string path = MakeSaveFile();
+	int file_number = 1;
+	if (cmd.args.size() == 1)
+		file_number = cmd.args[0].value + 1;
+	if (file_number <= 0) {
+		fprintf(stderr, "Cannot open save file %s\n",path.c_str());
+		return;
+	}
+	sprintf(buf, ".%d",file_number);
+	path += buf;
+
+	/* セーブファイル確認 */
+	
+	sprintf(buf, "KEY=%s\n", config.GetParaStr("#REGNAME")); save += buf;
+	string save_sys; SaveImpl(save_sys);
+	string save_flag; flag.Save(save_flag);
+	string save_text; text_exec.Save(save_text, false);
+	string save_grp; grp_exec.Save(save_grp);
+	save += save_sys;
+	save += save_flag;
+	save += save_text;
+	save += save_grp;
+	vector<string>::iterator it;
+	for (it=rollback_save.begin(); it != rollback_save.end(); it++) {
+		save += "[Rollback Data]\n";
+		save += *it;
+		save += "[Rollback End]\n";
+	}
+
+	f = fopen(path.c_str(), "w");
+	if (f == 0) {
+		fprintf(stderr,"Cannot open save file %s\n",path.c_str());
+		return;
+	}
+	fwrite(save.c_str(), save.length(), 1, f);
+	fclose(f);
+	cmd.clear();
+	return;
+}
+
+void Scn2k::Load(Cmd& cmd) {
+	if (cmd.cmd_type == CMD_LOADREQ) {
+		if (menu == 0) {
+			menu = new Scn2kMenu(Scn2kMenu::MENU_LOAD, *this, flag, text_exec, system_version);
+			menu->InitPanel(event, parent);
+			menu->InitTitle(Scn2kSaveTitle(*this));
+			SetSkipMode(SKIP_IN_MENU); // テキストスキップ等はここで中断
+			if (mouse_surface) menu_mouseshown = true;
+			else menu_mouseshown = false;
+			ShowCursor();
+			return;
+		}
+	}
+	char buf[1024];
+	string path = MakeSaveFile();
+	int file_number = 1;
+	if (cmd.args.size() == 1)
+		file_number = cmd.args[0].value + 1;
+	sprintf(buf, ".%d",file_number);
+	path += buf;
+	FILE* f = 0;
+	if (file_number > 0) f = fopen(path.c_str(), "r");
+	if (f == 0) {
+		fprintf(stderr, "Cannot open save file %s\n",path.c_str());
+		return;
+	}
+	
+	fseek(f,0,2);
+	int sz = ftell(f);
+	fseek(f,0,0);
+	char* savedata = new char[sz+1];
+	fread(savedata, sz, 1, f);
+	savedata[sz] = 0;
+	fclose(f);
+
+	sprintf(buf, "KEY=%s\n", config.GetParaStr("#REGNAME"));
+	if (strncmp(savedata, buf, strlen(buf)) != 0) {
+		fprintf(stderr,"Invalid header in save file %s: it must be started with \"%s\"\n",buf);
+		delete[] savedata;
+		return;
+	}
+	LoadImpl(savedata);
+	flag.Load(savedata);
+	text_exec.Load(savedata);
+	grp_exec.Load(savedata);
+	rollback_save.clear();
+	new_rollback_save = "";
+	char* rollback_data = savedata;
+	while( (rollback_data = strstr(rollback_data,"[Rollback Data]\n")) != 0) {
+		rollback_data += strlen("[Rollback Data]\n");
+		char* rollback_end = strstr(rollback_data, "[Rollback End]\n");
+		if (rollback_end == 0) rollback_end = rollback_data + strlen(rollback_data);
+		string s(rollback_data, rollback_end);
+		rollback_save.push_back(s);
+		rollback_data = rollback_end;
+	}
+
+	/* 画面の回復など */
+	SetSkipMode(SKIP_NO);
+	vector<CmdSimplified>::iterator it;
+	for (it = cmd_stack.begin(); it != cmd_stack.end(); it++) {
+		cmd.read(*it);
+		cmd.cmd_type = CMD_OTHER;
+		flag.Exec(cmd);
+		text_exec.Exec(cmd);
+		grp_exec.Exec(cmd);
+	}
+	cmd.clear();
+	return;
+}
+
+void Scn2k::SaveImpl(string& save) {
+	char buf[1024];
+
+	/* save point */
+	sprintf(buf, "\n[SCENARIO]\nScn=%d\nPoint=%d\n",save_scn, save_point); save += buf;
+	sprintf(buf, "Title=%s\nMouseType=%d\nMouseShown=1\n",window_title.c_str(), mouse_type); save += buf;
+	vector<StackItem>::iterator sit;
+	for (sit=stack.begin(); sit!=stack.end(); sit++) {
+		if (sit->scn_number == SCN_INFO && sit->scn_pt == SCN_INFO_MENU) break; // メニューに入る直前までのスタックを保存
+		sprintf(buf, "Stack=%d,%d\n",sit->scn_number,sit->scn_pt);
+		save += buf;
+	}
+	vector<string>::reverse_iterator ssit;
+	for (ssit=stack_strbuffer.rbegin(); ssit != stack_strbuffer.rend(); ssit++) {
+		sprintf(buf, "StackStr=%s\n",ssit->c_str());
+		save += buf;
+	}
+	vector<CmdSimplified>::iterator cit;
+	for (cit=cmd_stack.begin(); cit != cmd_stack.end(); cit++) {
+		if (cit->type == CMD_SAVECMDGRP || cit->type == CMD_SAVECMDGRP_ONCE || cit->type == CMD_SAVECMDGRP_START) {
+			save += "CmdG=";
+		} else {
+			save += "Cmd=";
+		}
+		string s; cit->Save(s);
+		save += s;
+		save += "\n";
+	}
+}
+
+void Scn2k::LoadImpl(const char* save) {
+	char buf[1024];
+	save_scn = 0;
+	save_point = 0;
+	window_title = "";
+	stack.clear();
+	cmd_stack.clear();
+	cmd_stack_str = cmd_stack_str_orig;
+
+	save = strstr(save, "\n[SCENARIO]\n");
+	if (save == 0) return;
+	save += strlen("\n[SCENARIO]\n");
+	while(save[0] != 0 && save[0] != '[') { // while next section start
+		if (strncmp(save, "Scn=", 4) == 0) {
+			sscanf(save, "Scn=%d", &save_scn);
+		} else if (strncmp(save, "Point=", 6) == 0) {
+			sscanf(save, "Point=%d", &save_point);
+		} else if (strncmp(save, "Title=", 6) == 0) {
+			save += 6;
+			char* s = strchr(save, '\n');
+			if (s == 0) window_title = save;
+			else window_title.assign(save, s-save);
+			const char* config_name = config.GetParaStr("#CAPTION");
+			if (config_name == 0) config_name = "";
+			string setname = kconv(string(config_name)+"  "+window_title);
+			parent.Root().SetWindowCaption(setname.c_str());
+		} else if (strncmp(save, "MouseType=", 10) == 0) {
+			sscanf(save, "MouseType=%d", &mouse_type);
+		} else if (strncmp(save, "MouseShown=", 11) == 0) {
+			int v;
+			sscanf(save, "MouseShown=%d", &v);
+			if (v) ShowCursor();
+			else HideCursor();
+		} else if (strncmp(save, "Stack=", 6) == 0) {
+			int scn, pt;
+			sscanf(save, "Stack=%d,%d", &scn, &pt);
+			stack.push_back( StackItem(scn, pt));
+		} else if (strncmp(save, "StackStr=", 9) == 0) {
+			save += 9;
+			char* s = strchr(save, '\n');
+			if (s == 0) stack_strbuffer.push_back("");
+			else stack_strbuffer.push_back(string(save, s-save));
+		} else if (strncmp(save, "Cmd=", 4) == 0) {
+			CmdSimplified cmd;
+			cmd.Load(save+4, cmd_stack_str);
+			cmd_stack.push_back(cmd);
+		} else if (strncmp(save, "CmdG=", 5) == 0) {
+			CmdSimplified cmd;
+			cmd.Load(save+5, cmd_stack_str);
+			cmd.type = CMD_SAVECMDGRP;
+			cmd_stack.push_back(cmd);
+		}
+		save = strchr(save, '\n');
+		if (save != 0) save++;
+	}
+	ChangeScript(save_scn, 0);
+	script = script_start + save_point;
+	return;
+}
+void Scn2k::SetSkipMode(SkipMode mode) {
+	if (skip_mode != mode) {
+		skip_mode = mode;
+		text_exec.SetSkipMode(mode);
+		grp_exec.SetSkipMode(mode);
+	}
+}
+
+/***********************************************************
+**
+**	DLL Call Implementation
+**
+**/
+static double* lb_ef_param = 0;
+void DLLCall_LB_EF00_0(Cmd& cmd, Flags& flags) { // エフェクトの設定
+	if (lb_ef_param == 0) {
+		lb_ef_param = new double[sizeof(double) * 0x60 * 8];
+	}
+	int i,j;
+	int param_top, param_size;
+	if (cmd.args[2].value == 1) {
+		param_top = 0;
+		param_size = 0x20;
+	} else {
+		param_top = cmd.args[3].value;
+		param_size = cmd.args[4].value;
+		if (param_top < 0) param_top = 0;
+		if (param_top > 0x20) param_top = 0x20;
+		if (param_size+param_top > 0x20) param_size = 0x20 - param_top;
+	}
+	for (i=0; i<8; i++) {
+		double* param = lb_ef_param + i*0x60 + param_top*3;
+		for (j=0; j<param_size; j++) {	
+			*param++ = random() % 800 - 400;
+			*param++ = random() % 600 - 300;
+			*param++ = random() % 700 - 350;
+		}
+	}
+	if (cmd.args[5].value != 1) return;
+static int random_dirtable[] = {
+		0, 2, 1, 3, 0, 2, 1, 3,
+		1, 3, 2, 0, 1, 3, 2, 0,
+		0, 0, 0, 0, 3, 1, 2, 0,
+		3, 1, 3, 1, 0, 2, 3, 1
+	};
+	int* dir = &random_dirtable[(random()&3) * 8];
+	for (i=0; i<8; i++) {
+		double* param = lb_ef_param + i*0x60;
+		double x = random()%600 - 300;
+		double y = random()%480-240;
+		if (x < 0) x -= 80;
+		else x += 80;
+		if (y < 0) y -= 80;
+		else y += 80;
+		switch(*dir++) {
+		case 0:
+			if (x < 0) x = -x;
+			if (y < 0) y = -y;
+			break;
+		case 1:
+			if (x > 0) x = -x;
+			if (y < 0) y = -y;
+			break;
+		case 2:
+			if (x < 0) x = -x;
+			if (y > 0) y = -y;
+			break;
+		case 4:
+			if (x > 0) x = -x;
+			if (y > 0) y = -y;
+			break;
+		}
+		param[9] = x*1.2;
+		param[10] = y*1.2;
+		param[11] *= 1.2;
+		param[12] *= -0.08;
+		param[13] *= -0.08;
+		param[14] *= -0.08;
+		param[15] = -param[9];
+		param[16] = -param[10];
+		param[17] = -param[11];
+	}
+	return;
+}
+void DLLCall_LB_EF00_1(Cmd& cmd, Flags& flags) { // 計算を行う
+	if (lb_ef_param == 0) {
+		fprintf(stderr,"Warning : DLLCall_LB_EF00_1 : Script error : effect calculation was called before setting\n");
+		return;
+	}
+	int index = cmd.args[2].value;
+	int v5_1154 = flags.Get(5, 1154+index);
+	int j = ((v5_1154) & 0x1f) + index * 0x20;
+	int k = ((v5_1154+1) & 0x1f) + index * 0x20;
+	int l = ((v5_1154+2) & 0x1f) + index * 0x20;
+	int m = ((v5_1154+3) & 0x1f) + index * 0x20;
+	j *= 3;
+	k *= 3;
+	l *= 3;
+	m *= 3;
+
+	// 0 < x < 1
+	// va - vd は 0-1 の範囲で対称性を持つ3次関数
+	double x = double(flags.Get(5, 1162 + index)) * 0.001;
+	double va = (x * x * x)/6;
+	double vb = (-x*x*x + 3*x*x - 3*x + 1) / 6;
+	double vc = (3*x*x*x - 6*x*x + 4) / 6;
+	double vd = (-3*x*x*x+3*x*x+3*x+1) / 6;
+
+	double r1 = va * lb_ef_param[m+3] + vd * lb_ef_param[l+3] + vc * lb_ef_param[k+3] + vb * lb_ef_param[j+3];
+	double r2 = va * lb_ef_param[m+2] + vd * lb_ef_param[l+2] + vc * lb_ef_param[k+2] + vb * lb_ef_param[j+2];
+	double r3 = va * lb_ef_param[m+1] + vd * lb_ef_param[l+1] + vc * lb_ef_param[k+1] + vb * lb_ef_param[j+1];
+	if (r1 != 400) {
+		r2 = r2 * 800 / (400-r1);
+		r3 = r3 * 700 / (400-r1);
+	}
+	VarInfo var;
+	var.type = 5;
+	var.number = 1151;
+	flags.Set(var, int(r2));
+	var.number = 1152;
+	flags.Set(var, int(r3));
+	var.number = 1153;
+	flags.Set(var, int(r1));
+	return;
+}
+
+
+void DllCall_LB(Cmd& cmd, Flags& flags) {	// リトルバスターズ!の EF00.dll をエミュレート
+	if (cmd.args[0].value == 1) {
+		// "EF00.dll"
+		if (cmd.args[1].value == 0) { // エフェクトの設定
+			DLLCall_LB_EF00_0(cmd, flags);
+		} else if (cmd.args[1].value == 1) { // 計算を行う
+			DLLCall_LB_EF00_1(cmd, flags);
+		}
+	} else {
+		fprintf(stderr,"Unsupported DLL call for DLL<%d>\n",cmd.args[0].value);
+	}
+	return;
+}
+
+/**********************************************************
+**
+**	MenuImpl
+**
+*/
+
+#include"window/widget.h"
+#include"window/menuitem.h"
+
+void DSurfaceFill(Surface* src, const Rect& rect, int r, int g, int b, int a = 0xff);
+
+struct Scn2kMenuImpl {
+	Scn2kMenu& interface;
+	MenuItem* menu;
+	Event::Container* pevent;
+	PicContainer* pparent;
+
+	virtual void InitPanel(Event::Container& event, PicContainer& parent) = 0;
+	virtual void InitTitle(const SaveTitle&) = 0;
+	virtual void Cancel(void) = 0;
+	virtual void Exec(Cmd& cmd) = 0;
+	Scn2kMenuImpl(Scn2kMenu& _interface) : interface(_interface) {
+		menu = 0;
+		pevent = 0;
+		pparent = 0;
+	}
+	virtual ~Scn2kMenuImpl() {
+		if (menu) delete menu;
+		menu = 0;
+	}
+};
+
+struct LoadMenu : Scn2kMenuImpl {
+	vector<string> title;
+	vector<int> title_valid;
+	RadioButton* btn_local;
+	RadioButton* btn_page;
+	RadioButton* btn_set;
+	Scale* btn_scale;
+	Dialog* awk_dialog;
+	int btn_page_val, btn_set_val, btn_local_val, select_page, select_value;
+	LoadMenu(Scn2kMenu& _interface);
+	~LoadMenu();
+	void InitPanel(Event::Container& event, PicContainer& parent);
+	void InitTitle(const SaveTitle&);
+	void Cancel(void);
+	void Exec(Cmd& cmd);
+	static void ChangeBtnPage(void* pointer, MenuItem* widget);
+	static void ChangeBtnLocal(void* pointer, MenuItem* widget);
+	static void ChangeBtnScale(void* pointer, Scale* widget);
+	static void ChangeBtnSet(void* pointer, MenuItem* widget);
+	static void ChangeDialog(void* pointer, Dialog* widget);
+	bool in_setpage;
+	void SetPage(int new_page);
+	void SetValue(int new_value);
+	void PressOk(void);
+};
+LoadMenu::LoadMenu(Scn2kMenu& _interface) : Scn2kMenuImpl(_interface) {
+	btn_local = 0;
+	btn_scale = 0;
+	btn_set = 0;
+	btn_page_val = 0;
+	btn_set_val = -1;
+	btn_local_val = -1;
+	awk_dialog = 0;
+	in_setpage = false;
+	select_page = 0;
+	select_value = -1;
+}
+LoadMenu::~LoadMenu() {
+	if (awk_dialog) delete awk_dialog;
+}
+void LoadMenu::InitPanel(Event::Container& event, PicContainer& parent) {
+	pevent = &event;
+	pparent = &parent;
+
+	if (menu) delete menu;
+	menu = 0;
+	menu = new MenuItem(&parent, Rect(80,30,560, 450), 1, 3, 0);
+	Surface* surface = parent.Root().NewSurface(menu->Pic()->Width(), menu->Pic()->Height(), ALPHA_MASK);
+	if (interface.type == Scn2kMenu::MENU_LOAD) {
+		menu->SetLabelTop(new Label(menu->PicNode(), Rect(0,0), true, "Load", 26), Rect(0,0,10,0), Rect(0,0,0,20));
+		DSurfaceFill(surface, Rect(*surface), 0, 0, 0x80, 0x80);
+	} else {
+		menu->SetLabelTop(new Label(menu->PicNode(), Rect(0,0), true, "Save", 26), Rect(0,0,10,0), Rect(0,0,0,20));
+		DSurfaceFill(surface, Rect(*surface), 0, 0x80, 0, 0x80);
+	}
+	menu->Pic()->SetSurface(surface, 0, 0);
+	menu->Pic()->SetSurfaceFreeFlag();
+
+	btn_page = new RadioButton(event, menu->PicNode(), Rect(0, 0, 480, 40), 10, 1, &btn_page_val,
+		Rect(0,0,0,0), 18, Color(0,0,0),Color(0xff,0,0),Color(0xff,0x80,0));
+	btn_page->set_func = &ChangeBtnPage;
+	btn_page->set_pointer = this;
+	btn_page->SetLabelLeft(new Label(btn_page->PicNode(), Rect(0,0), true, "Page", 18), Rect(0, 0, 180, 0), Rect(0,0));
+	btn_page->Add(" 1 ");
+	btn_page->Add(" 2 ");
+	btn_page->Add(" 3 ");
+	btn_page->Add(" 4 ");
+	btn_page->Add(" 5 ");
+	btn_page->Add(" 6 ");
+	btn_page->Add(" 7 ");
+	btn_page->Add(" 8 ");
+	btn_page->Add(" 9 ");
+	btn_page->Add(" 10 ");
+	btn_page->pack();
+/*
+	surface = parent.Root().NewSurface(btn_page->Pic()->Width(), btn_page->Pic()->Height(), ALPHA_MASK);
+	DSurfaceFill(surface, Rect(*surface), 0xff, 0, 0, 0x80);
+	btn_page->Pic()->SetSurface(surface, 0, 0);
+	btn_page->Pic()->SetSurfaceFreeFlag();
+*/
+	menu->item[0] = btn_page;
+	btn_set = new RadioButton(event, menu->PicNode(), Rect(0, 0, 480, 40), 2, 1, &btn_set_val,
+		Rect(0,0,0,0), 18, Color(0,0,0),Color(0xff,0,0),Color(0xff,0x80,0));
+	btn_set->set_func = &ChangeBtnSet;
+	btn_set->set_pointer = this;
+	btn_set->SetLabelLeft(new Label(btn_set->PicNode(), Rect(0,0)), Rect(0,0,200,0), Rect(0,0));
+	if (interface.type == Scn2kMenu::MENU_LOAD) {
+		btn_set->Add(" Load ");
+	} else {
+		btn_set->Add(" Save ");
+	}
+	btn_set->Add(" Cancel ");
+	btn_set->pack();
+/*
+	surface = parent.Root().NewSurface(btn_set->Pic()->Width(), btn_set->Pic()->Height(), ALPHA_MASK);
+	DSurfaceFill(surface, Rect(*surface), 0, 0, 0xff, 0x80);
+	btn_set->Pic()->SetSurface(surface, 0, 0);
+	btn_set->Pic()->SetSurfaceFreeFlag();
+*/
+	menu->item[2] = btn_set;
+	// void btn_set_press(void* pointer, MenuItem* widget);
+	// btn_set->set_func = btn_set_press;
+	// btn_set->set_pointer = this;
+	btn_local = new RadioButton(*pevent, menu->PicNode(), Rect(0, 0, 480, 300), 1, 100, &btn_local_val,
+		Rect(0,0,300,30), 18, Color(0,0,0),Color(0xff,0,0),Color(0xff,0x80,0));
+	btn_local->set_func = &ChangeBtnLocal;
+	btn_local->set_pointer = this;
+/*
+	surface = pparent->Root().NewSurface(btn_local->Pic()->Width(), btn_local->Pic()->Height(), ALPHA_MASK);
+	DSurfaceFill(surface, Rect(*surface), 0, 0xff, 0, 0x80);
+	btn_local->Pic()->SetSurface(surface, 0, 0);
+	btn_local->Pic()->SetSurfaceFreeFlag();
+*/
+	menu->item[1] = btn_local;
+	int i;
+	for (i=0; i<12; i++)
+		btn_local->Add("",false);
+	btn_local->pack();
+	btn_local->show_all();
+	menu->pack();
+
+	PicBase* local_pic = btn_local->Pic();
+	int local_x2 = local_pic->PosX() + local_pic->Width();
+	int local_y2 = local_pic->PosY() + local_pic->Height();
+	btn_scale = new Scale(*pevent, menu->PicNode(), Rect(local_x2-16, local_pic->PosY(), local_x2, local_y2), Color(0xff, 0x80, 0), true);
+	btn_scale->SetRange(0, 900);
+	btn_scale->InitCursor(1024/10);
+	btn_scale->SetValue(0);
+	btn_scale->change_func = &ChangeBtnScale;
+	btn_scale->change_pointer = this;
+
+	menu->PicNode()->show_all();
+}
+
+void LoadMenu::InitTitle(const SaveTitle& title_op) {
+	title.clear();
+	int i;
+	for (i=1; i<=100; i++) {
+		char buf[100];
+		sprintf(buf,"%2d:",i);
+		string t = title_op(i);
+		string s = string(buf) + t;
+		if (t.length() == 0) {
+			string s = string(buf) + "--------";
+			title_valid.push_back(0);
+		} else {
+			title_valid.push_back(1);
+		}
+		title.push_back(s);
+	}
+	if (btn_local==0) return;
+	for (i=0; i<10; i++) {
+		TextButton* button = dynamic_cast<TextButton*>(btn_local->item[i]);
+		if (button) button->SetText(title[i].c_str());
+	}
+}
+
+void LoadMenu::SetPage(int new_page) {
+	if (new_page < 0) new_page = 0;
+	if (new_page > 900) new_page = 900;
+	if (select_page == new_page) return;
+	if (in_setpage) return;
+	in_setpage = true;
+	
+	int prev_page = select_page / 10;
+	int cur_page = new_page / 10;
+	int prev_point = select_page%10;
+	int new_point = new_page%10;
+	select_page = new_page;
+	if (prev_page != cur_page) {
+		int i;
+		for (i=0; i<12; i++) {
+			TextButton* button = dynamic_cast<TextButton*>(btn_local->item[i]);
+			if (button) {
+				if (cur_page+i < title.size()) button->SetText(title[cur_page+i].c_str());
+				else button->SetText("----");
+			}
+		}
+		// ボタンの内容を変更する
+		if (select_value < cur_page || select_value > cur_page+12)
+			btn_local->SetValue(-1);
+		else
+			btn_local->SetValue(select_value - cur_page);
+	}
+	if (prev_point != new_point) {
+		int i;
+		for (i=0; i<12; i++) {
+			int old_x = btn_local->item[i]->Pic()->PosX();
+			btn_local->item[i]->Pic()->Move(old_x, i*30-new_point*3);
+		}
+	}
+	if (btn_page) {
+		if (select_page%100 == 0) btn_page->SetValue(select_page/100);
+		else btn_page->SetValue(-1);
+	}
+	if (btn_scale) {
+		btn_scale->SetValue(select_page);
+	}
+	in_setpage = false;
+	return;
+}
+void LoadMenu::SetValue(int new_value) {
+	if (in_setpage) return;
+	in_setpage = true;
+
+	if (new_value < 0 || new_value > title.size() ||
+	    (interface.type == Scn2kMenu::MENU_LOAD && title_valid[new_value] == 0) ) { // 無効な選択肢
+		if (select_value < select_page/10 || select_value > select_page/10+12)
+			btn_local->SetValue(-1);
+		else
+			btn_local->SetValue(select_value-select_page/10);
+	} else { // 選択肢を変更する
+		if (select_value == new_value) {
+			PressOk(); // ダブルクリック
+		} else {
+			select_value = new_value;
+			if (interface.type == Scn2kMenu::MENU_SAVE && title_valid[select_value] == 0) {
+				PressOk(); // 新しいセーブデータなら無条件に選択
+			}
+		}
+	}
+
+	in_setpage = false;
+	return;
+}
+void LoadMenu::PressOk(void) {
+	if (select_value == -1) {
+		btn_set->SetValue(-1); // なにもしない
+		return;
+	}
+	menu->deactivate();
+	if (interface.type == Scn2kMenu::MENU_LOAD) {
+		interface.cmd.cmd_type = CMD_LOAD;
+		interface.cmd.args.push_back(VarInfo(select_value));
+		awk_dialog = new Dialog(*pevent, pparent, "ファイルをロードしますか?", true);
+		awk_dialog->set_pointer = this;
+		awk_dialog->set_func = ChangeDialog;
+	} else {// MENU_SAVE
+		interface.cmd.cmd_type = CMD_SAVE;
+		interface.cmd.args.push_back(VarInfo(select_value));
+		if (title_valid[select_value] == 0) { // 新しいセーブデータ
+			interface.status = Scn2kMenu::MenuStatus(Scn2kMenu::MENU_CMD | Scn2kMenu::MENU_DELETE);
+		} else { // セーブデータを上書き:確認
+			awk_dialog = new Dialog(*pevent, pparent, "データを上書きしますか?", true);
+			awk_dialog->set_pointer = this;
+			awk_dialog->set_func = ChangeDialog;
+		}
+	}
+}
+void LoadMenu::Cancel(void) {
+	if (awk_dialog) { // ダイアログのキャンセル
+		awk_dialog->status = Dialog::CANCEL;
+		ChangeDialog(this, awk_dialog);
+	} else { // 一般キャンセル
+		btn_set->SetValue(1);
+	}
+}
+void LoadMenu::Exec(Cmd& cmd) {
+}
+void LoadMenu::ChangeBtnPage(void* pointer, MenuItem* widget) {
+	LoadMenu* instance = (LoadMenu*)pointer;
+	if (instance->btn_page_val == -1) return;
+	instance->SetPage(instance->btn_page_val*100);
+}
+void LoadMenu::ChangeBtnScale(void* pointer, Scale* from) {
+	LoadMenu* instance = (LoadMenu*)pointer;
+	int value = from->GetValue();
+	instance->SetPage(value);
+}
+void LoadMenu::ChangeBtnSet(void* pointer, MenuItem* widget) {
+	LoadMenu* instance = (LoadMenu*)pointer;
+	if (instance->btn_set_val == 1) { // cancel
+		instance->interface.status = Scn2kMenu::MENU_DELETE;
+		return;
+	} else if (instance->btn_set_val == 0) { // OK
+		instance->PressOk();
+	}
+}
+void LoadMenu::ChangeDialog(void* pointer, Dialog* widget) {
+	LoadMenu* instance = (LoadMenu*)pointer;
+	if (widget->status == Dialog::CANCEL) {
+		// ダイアログ消去、OK ボタン復帰
+		delete instance->awk_dialog;
+		instance->awk_dialog = 0;
+		instance->menu->activate();
+		instance->btn_set->SetValue(-1);
+		return;
+	} else if (widget->status == Dialog::OK) {
+		instance->interface.status = Scn2kMenu::MenuStatus(Scn2kMenu::MENU_CMD | Scn2kMenu::MENU_DELETE);
+		return;
+	}
+}
+void LoadMenu::ChangeBtnLocal(void* pointer, MenuItem* widget) {
+	LoadMenu* instance = (LoadMenu*)pointer;
+	if (instance->btn_local_val == -1) return;
+	instance->SetValue( (instance->select_page/10) + instance->btn_local_val);
+}
+
+struct BacklogMenu : Scn2kMenuImpl {
+	Scn2k& scn_impl;
+	Text& text_exec;
+	bool backlog_update;
+	int backlog_cnt;
+	BacklogMenu(Scn2kMenu& _interface, Scn2k& scn_impl, Text& text_exec);
+	~BacklogMenu();
+	void InitPanel(Event::Container& event, PicContainer& parent);
+	void InitTitle(const SaveTitle&);
+	void Cancel(void);
+	void Exec(Cmd& cmd);
+};
+BacklogMenu::BacklogMenu(Scn2kMenu& _interface, Scn2k& _scn, Text& parent_text_exec) : Scn2kMenuImpl(_interface), scn_impl(_scn), text_exec(parent_text_exec) {
+	backlog_cnt = -1;
+	backlog_update = false;
+}
+BacklogMenu::~BacklogMenu() {
+}
+void BacklogMenu::InitPanel(Event::Container& event, PicContainer& parent) {
+	pevent = &event;
+}
+
+void BacklogMenu::InitTitle(const SaveTitle& title_op) {
+}
+void BacklogMenu::Cancel(void) {
+	interface.status = Scn2kMenu::MenuStatus(Scn2kMenu::MENU_DELETE);
+}
+void BacklogMenu::Exec(Cmd& cmd) {
+	int command_direction = 0; // forward
+	if (cmd.cmd_type == CMD_NOP) text_exec.Wait(0xffffffffUL, cmd);
+	if (cmd.cmd_type == CMD_BACKLOGREQ || pevent->presscount(MOUSE_UP)) {
+		if (cmd.cmd_type == CMD_BACKLOGREQ) cmd.clear();
+		backlog_cnt++;
+		backlog_update = false;
+		command_direction = 1;
+	}
+	if (cmd.cmd_type == CMD_BACKLOGREQ_FWD || pevent->presscount(MOUSE_DOWN)) {
+		if (cmd.cmd_type == CMD_BACKLOGREQ_FWD) cmd.clear();
+		backlog_cnt--;
+		backlog_update = false;
+		if (backlog_cnt == -2 || (
+		   (backlog_cnt == -1 && text_exec.backlog_item.scn == -1 && text_exec.backlog_item.pos == -1)) ){
+			Cancel();
+			return;
+		}
+		command_direction = -1;
+	}
+	if (cmd.cmd_type != CMD_NOP) return;
+	if (backlog_update) return;
+	// backlog を最新の状態に更新
+	cmd.clear();
+	BacklogItem item;
+
+retry:
+	if (backlog_cnt < -1) backlog_cnt = -1;
+	if (backlog_cnt >= int(text_exec.backlog.size())) backlog_cnt = text_exec.backlog.size() - 1;
+
+	if (backlog_cnt == -1) {
+		if (text_exec.backlog_item.scn == -1 && text_exec.backlog_item.pos == -1) {
+			if (text_exec.backlog.size() == 0 || command_direction < 0) {
+				Cancel();
+				return;
+			}
+			item = text_exec.backlog.back();
+			backlog_cnt = 0;
+		} else {
+			// item = text_exec.backlog.back();
+			item = text_exec.backlog_item;
+		}
+	} else {
+		item = text_exec.backlog[text_exec.backlog.size()-1-backlog_cnt];
+	}
+	if (item.scn ==  BacklogItem::SaveSelect) { // select marker ; skip this item
+		if (command_direction == 0) command_direction = 1;
+		backlog_cnt += command_direction;
+		goto retry;
+	}
+	if (item.scn == 0 && item.pos == -1) ; // not read cmd
+	else {
+		scn_impl.ReadCmdAt(cmd, item.scn, item.pos);
+	}
+	text_exec.DrawBacklog(item, cmd);
+	cmd.clear();
+	backlog_update = true;
+}
+
+/*******************************************************************************
+**
+**
+*/
+
+Scn2kMenu::Scn2kMenu(MenuType _type, Scn2k& scn_impl, const Flags& flags, Text& text_exec, int system_version) :
+	cmd(flags, system_version), type(_type) {
+	pimpl = 0;
+	status = MENU_CONTINUE;
+	switch(type) {
+	case MENU_LOAD: pimpl = new LoadMenu(*this); break;
+	case MENU_SAVE: pimpl = new LoadMenu(*this); break;
+	case MENU_BACKLOG: pimpl = new BacklogMenu(*this, scn_impl, text_exec); break;
+	}
+	return;
+}
+Scn2kMenu::~Scn2kMenu() {
+	if (pimpl) delete pimpl;
+	pimpl = 0;
+}
+void Scn2kMenu::InitPanel(Event::Container& event, PicContainer& parent) {
+	if (pimpl) pimpl->InitPanel(event, parent);
+}
+void Scn2kMenu::InitTitle(const SaveTitle& t) {
+	if (pimpl) pimpl->InitTitle(t);
+}
+void Scn2kMenu::Cancel(void) {
+	if (pimpl) pimpl->Cancel();
+}
+void Scn2kMenu::Exec(Cmd& ret_cmd) {
+	if (pimpl == 0) return;
+	pimpl->Exec(ret_cmd);
+	if (pimpl->pevent->presscount(MOUSE_RIGHT)) {
+		Cancel();
+	}
+	if (status & MENU_CMD && cmd.cmd_type != CMD_NOP) {
+		status = Scn2kMenu::MenuStatus(status & (~Scn2kMenu::MENU_CMD) );
+		CmdSimplified tmp_cmd;
+		char cmd_str[32768];
+		char* tmp_cmd_str = cmd_str;
+		cmd.write(tmp_cmd, tmp_cmd_str);
+		ret_cmd.read(tmp_cmd);
+	}
+}
+void Scn2kMenu::activate(void) {
+	if (pimpl && pimpl->menu) pimpl->menu->activate();
+}
+void Scn2kMenu::deactivate(void) {
+	if (pimpl && pimpl->menu) pimpl->menu->deactivate();
+}
+