Files
kichikuou_system3-sdl2/src/sys/nact.cpp
T
kichikuou ccd99dab07 Apply character encoding conversion to hard-coded strings
This fixes an issue where menu items like "戻る" were garbled in Unicode
mode.
2021-09-05 15:10:22 +09:00

572 lines
11 KiB
C++

/*
ALICE SOFT SYSTEM 3 for Win32
[ NACT ]
*/
#include <stdarg.h>
#include "nact.h"
#include "encoding.h"
#include "ags.h"
#include "mako.h"
#include "msgskip.h"
#include "dri.h"
#include "../config.h"
#include "../fileio.h"
#include "crc32.h"
extern SDL_Window* g_window;
// 初期化
NACT::NACT(int sys_ver, uint32 crc32_a, uint32 crc32_b, const Config& config)
: sys_ver(sys_ver),
crc32_a(crc32_a),
crc32_b(crc32_b),
encoding(Encoding::create(get_encoding_name(config))),
strings(get_language(), encoding.get())
{
platform_initialize();
// AG00.DAT読み込み
FILEIO* fio = new FILEIO();
if(fio->Fopen("AG00.DAT", FILEIO_READ_BINARY)) {
int d0, d1, d2, d3;
char string[MAX_CAPTION];
fio->Fgets(string, MAX_CAPTION);
sscanf_s(string, "%d,%d,%d,%d", &d0, &d1, &d2, &d3);
for(int i = 0; i < d1; i++) {
// 動詞の読み込み
fio->Fgets(string, MAX_CAPTION);
memcpy(caption_verb[i], string, sizeof(string));
}
for(int i = 0; i < d2; i++) {
// 目的語の読み込み
fio->Fgets(string, MAX_CAPTION);
memcpy(caption_obj[i], string, sizeof(string));
}
fio->Fclose();
}
delete fio;
// ADISK.DAT
if (crc32_a == CRC32_PROG_OMAKE)
strcpy_s(adisk, 16, "AGAME.DAT");
else
strcpy_s(adisk, 16, "ADISK.DAT");
// シナリオ管理
scenario_data = NULL;
load_scenario(0);
scenario_page = 0;
scenario_addr = 2;
label_depth = page_depth = 0;
// 変数初期化
memset(var, 0, sizeof(var));
memset(var_stack, 0, sizeof(var_stack));
memset(tvar, 0, sizeof(tvar));
memset(tvar_stack, 0, sizeof(tvar_stack));
// コマンド初期化
column = true; // 座標モード
wait_keydown = true; // ウェイト時のキー受付
seed = SDL_GetTicks(); // 乱数の種
text_wait_time = 100; // メッセージ表示のウェイト
text_wait_enb = false;
mouse_sence = 16; // マウス移動感知
menu_window = text_window = 1;
menu_index = 0;
show_push = true;
clear_text = true;
verb_obj = false;
set_palette = false;
tvar_index = 0;
pcm_index = 0;
memset(pcm, 0, sizeof(pcm));
// 各種クラス生成
ags = new AGS(this, config);
mako = new MAKO(this, config);
msgskip = new MsgSkip(this);
#ifdef USE_JOY
// 入力初期化
joy_num = joyGetNumDevs();
if(joy_num) {
joyGetDevCaps(JOYSTICKID1, &joycaps, sizeof(JOYCAPS));
}
#endif
terminate = false;
restart_after_terminate = false;
}
NACT::~NACT()
{
delete ags;
delete mako;
delete msgskip;
// シナリオ開放
if(scenario_data) {
free(scenario_data);
}
platform_finalize();
}
bool NACT::mainloop()
{
msgskip->load_from_file();
int sleep_cnt = 0;
while(!terminate) {
execute();
// 512コマンド実行毎にSleep(10)
if(!(sleep_cnt = (sleep_cnt + 1) & 0x1ff)) {
sys_sleep(10);
}
}
return restart_after_terminate;
}
// コマンドパーサ
EMSCRIPTEN_KEEPALIVE // Prevent inlining, because this function is listed in ASYNCIFY_ADD
void NACT::execute()
{
// アドレスの確認
if(scenario_addr < 2 || scenario_addr >= scenario_size) {
fatal("Scenario error");
}
// 致命的なエラー発生 or 正常終了
if(fatal_error) {
if(!post_quit) {
terminate = true;
}
post_quit = true;
return;
}
if(sys_ver == 1 && scenario_page == 0 && scenario_addr == 2) {
opening();
}
// Skip SysEng's "new style" marker
if (scenario_page == 0 && scenario_addr == 2 &&
memcmp(&scenario_data[2], "REV", 3) == 0) {
scenario_addr = 5;
}
// 1コマンド実行
prev_addr = scenario_addr;
uint8 cmd = getd();
if(set_palette && cmd != 'P') {
// パレット設定が終わった
ags->flush_screen(true);
set_palette = false;
}
if(verb_obj && cmd != '[' && cmd != ':') {
// 動詞-目的語メニューの表示
scenario_addr--;
cmd_open_verb();
return;
}
switch(cmd) {
case '!':
cmd_calc();
break;
case '{':
cmd_branch();
break;
case '}':
break;
case '@':
cmd_label_jump();
break;
case '\\':
cmd_label_call();
break;
case '&':
cmd_page_jump();
break;
case '%':
cmd_page_call();
break;
case '$':
cmd_set_menu();
break;
case '[':
cmd_set_verbobj();
break;
case ':':
cmd_set_verbobj2();
break;
case ']':
cmd_open_menu();
break;
case 'A':
cmd_a();
break;
case 'B':
cmd_b();
break;
case 'D':
cmd_d();
break;
case 'E':
cmd_e();
break;
case 'F':
cmd_f();
break;
case 'G':
cmd_g();
break;
case 'H':
cmd_h();
break;
case 'I':
cmd_i();
break;
case 'J':
cmd_j();
break;
case 'K':
cmd_k();
break;
case 'L':
cmd_l();
break;
case 'M':
cmd_m();
break;
case 'N':
cmd_n();
break;
case 'O':
cmd_o();
break;
case 'P':
cmd_p();
break;
case 'Q':
cmd_q();
break;
case 'R':
cmd_r();
break;
case 'S':
cmd_s();
break;
case 'T':
cmd_t();
break;
case 'U':
cmd_u();
break;
case 'V':
cmd_v();
break;
case 'W':
cmd_w();
break;
case 'X':
cmd_x();
break;
case 'Y':
cmd_y();
break;
case 'Z':
cmd_z();
break;
case '\'': case '"':
message(cmd);
break;
default:
if (is_message(cmd)) {
ungetd();
message(0);
} else if (cmd >= 0x20 && cmd < 0x7f) {
fatal("Unknown Command: '%c' at page = %d, addr = %d", cmd, scenario_page, prev_addr);
} else {
fatal("Unknown Command: %02x at page = %d, addr = %d", cmd, scenario_page, prev_addr);
}
break;
}
}
void NACT::skip_string(uint8 terminator)
{
for (uint8 c = getd(); c != terminator; c = getd()) {
if (c != '\\')
ungetd();
scenario_addr += encoding->mblen(scenario_data + scenario_addr);
}
}
void NACT::get_string(char* buf, int size, uint8 terminator)
{
int start_addr = scenario_addr;
int i = 0;
for (uint8 c = getd(); c != terminator; c = getd()) {
if (c != '\\')
ungetd();
int len = encoding->mblen(scenario_data + scenario_addr);
if (i + len >= size)
fatal("String buffer overrun. page = %d, addr = %d", scenario_page, start_addr);
memcpy(&buf[i], &scenario_data[scenario_addr], len);
i += len;
scenario_addr += len;
}
buf[i] = '\0';
}
void NACT::message(uint8 terminator)
{
char buf[200];
if (terminator) { // SysEng
get_string(buf, sizeof(buf), terminator);
} else {
uint8* begin = &scenario_data[scenario_addr];
uint8* p = begin;
while (is_message(*p))
p += encoding->mblen(p);
int len = p - begin;
scenario_addr += len;
strncpy(buf, reinterpret_cast<char*>(begin), len);
buf[len] = '\0';
}
ags->draw_text(buf, text_wait_enb);
if(crc32_a == CRC32_DPS || crc32_a == CRC32_DPS_SG || crc32_a == CRC32_DPS_SG2 || crc32_a == CRC32_DPS_SG3) {
if(!ags->draw_menu) {
text_refresh = false;
}
}
if (!ags->draw_menu)
msgskip->on_message(scenario_page, scenario_addr);
// TODO: Convert hankaku to zenkaku
output_console(buf);
}
void NACT::text_wait()
{
Uint32 dwTime = SDL_GetTicks() + text_wait_time;
for(;;) {
if(terminate) {
return;
}
RND = get_key();
if(RND && wait_keydown) {
break;
}
if(dwTime <= SDL_GetTicks()) {
break;
}
sys_sleep(16);
}
}
void NACT::load_scenario(int page)
{
if(scenario_data) {
free(scenario_data);
}
DRI* dri = new DRI();
if((scenario_data = dri->load(adisk, page + 1, &scenario_size)) == NULL) {
fatal("Cannot load scenario %d", page);
}
delete dri;
}
// 下位関数
int NACT::menu_select(int num_items)
{
if (msgskip->get_flags() & MSGSKIP_STOP_ON_MENU)
msgskip->activate(false);
// メニュー表示
ags->open_menu_window(menu_window);
// マウス移動
int sx = ags->menu_w[menu_window - 1].sx;
int sy = ags->menu_w[menu_window - 1].sy;
int ex = ags->menu_w[menu_window - 1].ex;
int mx = ex - 16;
int my = sy + 10;
int height = ags->menu_font_size + 4;
int current_index = 0;
set_cursor(mx, my);
wait_after_open_menu();
// メニュー選択
for(bool selectable = true;;) {
// 入力待機
int val = 0, current_mx = mx, current_my = my;
for(;;) {
if(terminate) {
return -1;
}
get_cursor(&current_mx, &current_my);
int dx = mx - current_mx;
int dy = my - current_my;
if (dx*dx + dy*dy > 10)
break;
if((val = get_key()) != 0) {
sys_sleep(100);
break;
}
sys_sleep(16);
}
if(val) {
for(;;) {
if(terminate) {
return -1;
}
if(!get_key()) {
break;
}
sys_sleep(16);
}
}
if(val == 0) {
// マウス操作
mx = current_mx; my = current_my;
int index = (my - sy) / height;
if(sx <= mx && mx <= ex && 0 <= index && index < num_items) {
current_index = index;
ags->redraw_menu_window(menu_window, current_index);
selectable = true;
} else {
selectable = false;
}
} else if(val == 1 || val == 2 || val == 4 || val == 8) {
if(val == 1) {
current_index = current_index ? current_index - 1 : num_items - 1;
} else if(val == 2) {
current_index = (current_index < num_items - 1) ? current_index + 1 : 0;
} else if(val == 4) {
current_index = 0;
} else if(val == 8) {
current_index = num_items - 1;
}
ags->redraw_menu_window(menu_window, current_index);
selectable = true;
} else if(val == 16 && selectable) {
break;
} else if(val == 32) {
current_index = -1;
break;
}
}
// 画面更新
ags->close_menu_window(menu_window);
if(clear_text) {
ags->clear_text_window(text_window, true);
}
return current_index;
}
uint16 NACT::random(uint16 range)
{
// xorshift32
seed = seed ^ (seed << 13);
seed = seed ^ (seed >> 17);
seed = seed ^ (seed << 15);
return (uint16)(((uint32)range * (seed & 0xffff)) >> 16) + 1;
}
void NACT::wait_after_open_menu()
{
// 連打による誤クリック防止
Uint32 dwTime = SDL_GetTicks();
Uint32 dwWait = dwTime + 400;
while(dwTime < dwWait) {
if(terminate) {
return;
}
/*
if(get_key() == 16) {
break;
}
*/
sys_sleep(10);
dwTime = SDL_GetTicks();
}
// クリック中の間は待機
for(;;) {
if(terminate) {
return;
}
if(!get_key()) {
break;
}
sys_sleep(10);
}
}
void NACT::sys_sleep(int ms) {
pump_events();
ags->update_screen();
#ifdef __EMSCRIPTEN__
emscripten_sleep(ms);
#else
SDL_Delay(ms);
#endif
}
// WinMainとのインターフェース
int NACT::get_screen_height()
{
return ags->screen_height;
}
void NACT::select_cursor()
{
ags->select_cursor();
}
void NACT::fatal(const char* format, ...) {
char buf[512];
va_list args;
va_start(args, format);
vsnprintf(buf, sizeof buf, format, args);
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "system3", buf, g_window);
fatal_error = true;
}
NACT* NACT::create(const Config& config) {
uint32 crc32_a = NACT::calc_crc32("ADISK.DAT", config.game_id);
uint32 crc32_b = NACT::calc_crc32("BDISK.DAT", config.game_id);
int sys_ver = NACT::get_sys_ver(crc32_a, crc32_b);
switch (sys_ver) {
case 1:
return new NACT_Sys1(crc32_a, crc32_b, config);
case 2:
return new NACT_Sys2(crc32_a, crc32_b, config);
default:
return new NACT_Sys3(crc32_a, crc32_b, config);
}
}