#include #include "platform.h" #include "x86real.h" #include "pc98.h" #include "planar.h" #include "master.hpp" #include "shiftjis.hpp" #include "th01/hardware/egc.h" #include "th01/hardware/grppsafx.h" #include "th01/formats/cutscene.hpp" #include "th02/score.h" #include "th02/v_colors.hpp" #include "th02/hardware/frmdelay.h" #include "th02/hardware/input.hpp" #include "th02/formats/end.hpp" #include "th02/gaiji/gaiji.h" #include "th02/gaiji/score_p.hpp" #include "th02/sprites/verdict.hpp" // Coordinates // ----------- static const screen_y_t CUTSCENE_PIC_TOP = ((RES_Y / 2) - (CUTSCENE_PIC_H / 2)); // ----------- // State // ----- shiftjis_t end_text[100][END_LINE_SIZE]; int8_t line_col_and_fx; bool line_type_allow_fast_forward_and_automatically_clear_end_line; // ----- // Function ordering fails // ----------------------- void near end_line_clear(void); // ----------------------- void pascal near end_load(const char *fn) { file_ropen(fn); // ZUN landmine: No check to ensure that the size is ≤ sizeof(end_text). // Dynamic allocation would have made more sense... size_t size = file_size(); file_read(end_text, size); file_close(); } void pascal near verdict_value_score_put( screen_x_t left, screen_y_t top, score_t score ) { #define on_digit(i, gaiji) { \ graph_gaiji_putc((left + (GAIJI_W * i)), top, gaiji, V_WHITE); \ } gaiji_score_put(score, on_digit, false); #undef on_digit } void pascal near line_type( screen_x_t left, screen_y_t top, shiftjis_ank_amount_t len, shiftjis_t* str, int frames_per_kanji ) { // ZUN landmine: Susceptible to buffer overflows if [len] is larger. shiftjis_t buf[RES_X / GLYPH_HALF_W] = { '\0' }; int i = 0; for(int loop = 0; loop < len; loop += int(sizeof(shiftjis_kanji_t))) { // ZUN bug: Does not address the PC-98 keyboard quirk documented in the // `Research/HOLDKEY` example. As a result, the eventual key release // scancode won't be filtered and will get through to [key_det], ... input_reset_sense(); buf[i] = str[i]; i++; buf[i] = str[i]; i++; buf[i] = '\0'; graph_putsa_fx(left, top, line_col_and_fx, buf); // …which leads to the code taking the branch with the longer delay // every once in a while, even if the player still holds a key. While // the condition checks for *any* key being held, the probability of // taking the wrong branch remains identical regardless of how many // keys are held down, as the key release scancodes are only sent for // the last key that was pressed. if( line_type_allow_fast_forward_and_automatically_clear_end_line && (key_det != INPUT_NONE) ) { // As [loop] increases by 2 on each iteration, the loop will // alternate between this delay and no delay at all, resulting in // two kanji every ([frames_per_kanji] / 3) frames. if(loop & 3) { frame_delay(frames_per_kanji / 3); } } else { frame_delay(frames_per_kanji); } } for(i = 0; i < 20; i++) { input_reset_sense(); if( line_type_allow_fast_forward_and_automatically_clear_end_line && (key_det != INPUT_NONE) ) { // ZUN bloat: Technically, a 0-frame delay is not a no-op. Because // it still resets [vsync_Count1], it forms a frame boundary in // case the current frame didn't finish rendering within a single // VSync interrupt. // In this context though, this function call might as well be a // `break`. Without the 614.4 µs delay to address the PC-98 // keyboard quirk documented in the `Research/HOLDKEY` example, a // single call to input_reset_sense() encompasses // // • 8 INT 18h, AH=04h calls, // • 18 comparisons, and // • 14 branches with OR assignments. // // The exact implementation of INT 18h varies between PC-98 models, // so there's no point in precisely counting CPU cycles here. But // given that this blocking loop does nothing else *and* we're sure // to get here at the very start of a frame, it makes sense to // assume that 20 of those calls can easily fit within the ≈600,000 // cycles we have for every frame on the game's target 33 MHz CPUs. frame_delay(0); } else { frame_delay(2); } } if(line_type_allow_fast_forward_and_automatically_clear_end_line) { end_line_clear(); } } void verdict_kanji_1_to_0_masked( screen_x_t left, screen_y_t top, const dots16_t mask[VERDICT_MASK_H] ) { static_assert(VERDICT_MASK_H == GLYPH_H); Planar dots; vram_offset_t vo = vram_offset_shift(left, top); for(pixel_t row = 0; row < VERDICT_MASK_H; row++) { // ZUN bloat: Thanks to the blit functions being macros, `mask[row]` is // evaluated a total of 5 times. Once would be enough. graph_accesspage(1); VRAM_SNAP_PLANAR(dots, vo, VERDICT_MASK_W); graph_accesspage(0); grcg_setcolor(GC_RMW, 0); grcg_put(vo, mask[row], VERDICT_MASK_W); grcg_off(); vram_or_planar_masked(vo, dots, VERDICT_MASK_W, mask[row]); vo += ROW_SIZE; } } void verdict_row_1_to_0_animate( screen_x_t left, screen_y_t top, shiftjis_kanji_amount_t len ) { // ZUN bloat: This array is not `static`, and will be needlessly copied // into a local variable at every call to the function. #include "th02/sprites/verdict.csp" shiftjis_kanji_amount_t i; for(int mask = 0; mask < VERDICT_MASK_COUNT; mask++) { for(i = 0; i < len; i++) { verdict_kanji_1_to_0_masked( (left + (i * GLYPH_FULL_W)), top, &sVERDICT_MASKS[mask][0] ); } frame_delay(10); } } void pascal near gaiji_boldfont_str_from_positive_3_digit_value( int value, // ZUN bloat: Not meant to support unsigned values. gaiji_th02_t str[4] ) { enum { DIGITS = 3, }; int divisor = 100; // Must match DIGITS! int8_t digit; uint8_t past_leading_zeroes = 0; int i = 0; while(i < DIGITS) { digit = (value / divisor); if(past_leading_zeroes == 0) { past_leading_zeroes = digit; } if(past_leading_zeroes || (i == (DIGITS - 1))) { str[i] = gaiji_th02_t(gb_0_ + digit); } else { str[i] = gb_SP; } value -= (digit * divisor); i++; divisor /= 10; } str[i] = gs_NULL; } // ZUN bloat: Same algorithm as in TH01, same problems. Also could be a // single proper function. #define pic_put(left, top, rows, quarter, quarter_offset_y) { \ uvram_offset_t vram_offset_src = ( \ (quarter == 0) ? vram_offset_shift(0, 0) : \ (quarter == 1) ? vram_offset_shift(CUTSCENE_PIC_W, 0) : \ (quarter == 2) ? vram_offset_shift(0, CUTSCENE_PIC_H) : \ /*quarter == 3*/ vram_offset_shift(CUTSCENE_PIC_W, CUTSCENE_PIC_H) \ ); \ uvram_offset_t vram_offset_dst = vram_offset_shift(left, top); \ vram_offset_src += (quarter_offset_y * ROW_SIZE); \ \ egc_start_copy(); \ \ pixel_t y = quarter_offset_y; \ vram_byte_amount_t vram_x; \ while(y < (rows + quarter_offset_y)) { \ vram_x = 0; \ while(vram_x < CUTSCENE_PIC_VRAM_W) { \ egc_temp_t d; \ \ graph_accesspage(1); d = egc_chunk(vram_offset_src); \ graph_accesspage(0); egc_chunk(vram_offset_dst) = d; \ \ vram_x += EGC_REGISTER_SIZE; \ vram_offset_src += EGC_REGISTER_SIZE; \ vram_offset_dst += EGC_REGISTER_SIZE; \ } \ y++; \ vram_offset_dst += (ROW_SIZE - CUTSCENE_PIC_VRAM_W); \ vram_offset_src += (ROW_SIZE - CUTSCENE_PIC_VRAM_W); \ } \ egc_off(); \ } void pascal near end_pic_show(int quarter) { pic_put(CUTSCENE_PIC_LEFT, CUTSCENE_PIC_TOP, CUTSCENE_PIC_H, quarter, 0); } void pascal near staffroll_pic_put(screen_x_t left, screen_y_t top, int quarter) { pic_put(left, top, CUTSCENE_PIC_H, quarter, 0); } void pascal near end_pic_put_rows( int quarter, pixel_t quarter_offset_y, pixel_t rows ) { pic_put( CUTSCENE_PIC_LEFT, CUTSCENE_PIC_TOP, rows, quarter, quarter_offset_y ); }