diff --git a/Makefile b/Makefile index e632707..ba094e3 100644 --- a/Makefile +++ b/Makefile @@ -4,16 +4,18 @@ SDL_LIBS := `sdl2-config --libs` BB := decode.c fileio.c game.c level.c objects.c resource.c screen.c sound.c staticres.c tiles.c unpack.c JA := game.c level.c resource.c screen.c sound.c staticres.c unpack.c +P2 := game.c level.c resource.c screen.c sound.c staticres.c unpack.c BB_SRCS := $(foreach f,$(BB),bb/$f) JA_SRCS := $(foreach f,$(JA),ja/$f) -SRCS := $(BB_SRCS) $(JA_SRCS) +P2_SRCS := $(foreach f,$(P2),p2/$f) +SRCS := $(BB_SRCS) $(JA_SRCS) $(P2_SRCS) OBJS := $(SRCS:.c=.o) DEPS := $(SRCS:.c=.d) -CPPFLAGS := -Wall -Wpedantic -MMD $(SDL_CFLAGS) -I. -g +CPPFLAGS += -Wall -Wpedantic -MMD $(SDL_CFLAGS) -I. -g -all: blues bbja +all: blues bbja pre2 blues: main.o sys_sdl2.o util.o $(BB_SRCS:.c=.o) $(CC) $(LDFLAGS) -o $@ $^ $(SDL_LIBS) -lmodplug @@ -21,7 +23,10 @@ blues: main.o sys_sdl2.o util.o $(BB_SRCS:.c=.o) bbja: main.o sys_sdl2.o util.o $(JA_SRCS:.c=.o) $(CC) $(LDFLAGS) -o $@ $^ $(SDL_LIBS) -lmodplug +pre2: main.o sys_sdl2.o util.o $(P2_SRCS:.c=.o) + $(CC) $(LDFLAGS) -o $@ $^ $(SDL_LIBS) -lmodplug + clean: - rm -f $(OBJS) $(DEPS) + rm -f $(OBJS) $(DEPS) *.o -include $(DEPS) diff --git a/README.md b/README.md index e5035e2..2aa0044 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ This is a rewrite of the [Blues Brothers](https://www.mobygames.com/game/blues-b ![Screenshot1](blues1.png) ![Screenshot2](bbja2.png) +There is also early support for [Prehistork 2](https://www.mobygames.com/game/prehistorik-2). ## Requirements @@ -26,6 +27,14 @@ The game data files of the DOS version are required. *.EAT, *.MOD ``` +### Prehistorik 2 + +The game data files of the DOS version are required. + +``` +*.SQZ, *.TRK +``` + ## Running diff --git a/intern.h b/intern.h index 55c9760..5b235b5 100644 --- a/intern.h +++ b/intern.h @@ -3,6 +3,7 @@ #define INTERN_H__ #include +#include #include #include #include diff --git a/main.c b/main.c index 0b86846..f50ab77 100644 --- a/main.c +++ b/main.c @@ -29,9 +29,11 @@ static struct game_t *detect_game(const char *data_path) { #if 0 extern struct game_t bb_game; extern struct game_t ja_game; + extern struct game_t p2_game; static struct game_t *games[] = { &bb_game, &ja_game, + &p2_game, 0 }; for (int i = 0; games[i]; ++i) { diff --git a/p2/game.c b/p2/game.c new file mode 100644 index 0000000..9c345a7 --- /dev/null +++ b/p2/game.c @@ -0,0 +1,230 @@ + +#include +#include "game.h" +#include "resource.h" +#include "sys.h" + +static const bool _logos = false; + +struct vars_t g_vars; + +void update_input() { + g_sys.process_events(); + + g_vars.input.key_left = (g_sys.input.direction & INPUT_DIRECTION_LEFT) != 0 ? 0xFF : 0; + g_vars.input.key_right = (g_sys.input.direction & INPUT_DIRECTION_RIGHT) != 0 ? 0xFF : 0; + g_vars.input.key_up = (g_sys.input.direction & INPUT_DIRECTION_UP) != 0 ? 0xFF : 0; + g_vars.input.key_down = (g_sys.input.direction & INPUT_DIRECTION_DOWN) != 0 ? 0xFF : 0; + g_vars.input.key_space = g_sys.input.space ? 0xFF : 0; + + g_vars.input.keystate[2] = g_sys.input.digit1; + g_vars.input.keystate[3] = g_sys.input.digit2; + g_vars.input.keystate[4] = g_sys.input.digit3; +} + +static void wait_input(int timeout) { + const uint32_t end = g_sys.get_timestamp() + timeout * 10; + while (g_sys.get_timestamp() < end) { + g_sys.process_events(); + if (g_sys.input.quit || g_sys.input.space) { + break; + } + g_sys.sleep(20); + } +} + +static void do_programmed_in_1992_screen() { + time_t now; + time(&now); + struct tm *t = localtime(&now); + if (t->tm_year + 1900 < 1996) { + return; + } + g_sys.set_screen_palette(credits_palette_data, 0, 16, 6); + int offset = 0x960; + video_draw_string(offset, 5, "YEAAA > > >"); + char str[64]; + snprintf(str, sizeof(str), "MY GAME IS STILL WORKING IN %04d <<", 1900 + t->tm_year); + offset += 0x1E0; + video_draw_string(offset, 0, str); + offset = 0x1680; + video_draw_string(offset, 1, "PROGRAMMED IN 1992 ON AT >286 12MHZ>"); + offset += 0x1E0; + video_draw_string(offset, 3, "> > > ENJOY OLDIES<<"); + g_sys.update_screen(g_res.vga, 1); + wait_input(100); +} + +static void do_credits() { + g_sys.set_screen_palette(credits_palette_data, 0, 16, 6); + int offset = 0x140; + video_draw_string(offset, 1, "CODER> DESIGNER AND ARTIST DIRECTOR>"); + offset += 0x230; + video_draw_string(offset, 14, "ERIC ZMIRO"); + offset += 0x460; + video_draw_string(offset, 4, ">MAIN GRAPHICS AND BACKGROUND>"); + offset += 0x230; + video_draw_string(offset, 11, "FRANCIS FOURNIER"); + offset += 0x460; + video_draw_string(offset, 9, ">MONSTERS AND HEROS>"); + offset += 0x230; + video_draw_string(offset, 11, "LYES BELAIDOUNI"); + offset = 0x1770; + video_draw_string(offset, 15, "THANKS TO"); + offset = 0x1A40; + video_draw_string(offset, 2, "CRISTELLE> GIL ESPECHE AND CORINNE>"); + offset += 0x1E0; + video_draw_string(offset, 0, "SEBASTIEN BECHET AND OLIVIER AKA DELTA>"); + g_sys.update_screen(g_res.vga, 1); +} + +static void do_titus_screen() { + uint8_t *data = load_file("TITUS.SQZ"); + if (data) { + g_sys.set_screen_palette(data, 0, 256, 6); + g_sys.update_screen(data + 768, 0); + fade_in_palette(); + wait_input(70); + fade_out_palette(); + free(data); + } +} + +static void do_present_screen() { + uint8_t *data = load_file("PRESENT.SQZ"); + if (data) { + g_sys.set_screen_palette(data, 0, 256, 6); + g_sys.update_screen(data + 768, 0); + fade_in_palette(); + free(data); + } +} + +static void do_demo_screen() { + uint8_t *data = load_file("JOYSTICK.SQZ"); + if (data) { + video_copy_img(data); + free(data); + } +} + +static void do_menu() { + uint8_t *data = load_file("MENU.SQZ"); + if (data) { + g_sys.set_screen_palette(data, 0, 256, 6); + g_sys.update_screen(data + 768, 0); + fade_in_palette(); + free(data); + memset(g_vars.input.keystate, 0, sizeof(g_vars.input.keystate)); + while (!g_sys.input.quit) { + update_input(); + if (g_vars.input.keystate[2] || g_vars.input.keystate[0x4F] || g_sys.input.space) { + g_sys.input.space = 0; + fade_out_palette(); + break; + } + if (g_vars.input.keystate[3] || g_vars.input.keystate[0x50]) { + fade_out_palette(); + break; + } + if (g_vars.input.keystate[4] || g_vars.input.keystate[0x51]) { + break; + } + g_sys.sleep(30); + } + } +} + +void input_check_ctrl_alt_w() { + if (g_vars.input.keystate[0x1D] && g_vars.input.keystate[0x38] && g_vars.input.keystate[0x11]) { + do_credits(); + wait_input(60); + } +} + +uint32_t timer_get_counter() { + const uint32_t current = g_sys.get_timestamp(); + return ((current - g_vars.starttime) * 1193182 / 0x4000) / 1000; +} + +void random_reset() { + g_vars.random.a = 5; + g_vars.random.b = 34; + g_vars.random.c = 134; + g_vars.random.d = 58765; +} + +uint8_t random_get_number() { + g_vars.random.d += g_vars.random.a; + g_vars.random.a += 3 + (g_vars.random.d >> 8); + + g_vars.random.b += g_vars.random.c; + g_vars.random.b *= 2; + g_vars.random.b += g_vars.random.a; + + g_vars.random.c ^= g_vars.random.a; + g_vars.random.c ^= g_vars.random.b; + + return g_vars.random.b; +} + +static uint16_t ror16(uint16_t x, int c) { + return (x >> c) | (x << (16 - c)); +} + +uint16_t random_get_number2() { + const uint16_t x = g_vars.random.e + 0x9248; + g_vars.random.e = ror16(x, 3); + return g_vars.random.e; +} + +static uint16_t rol16(uint16_t x, int c) { + return (x << c) | (x >> (16 - c)); +} + +/* original code returns a machine specific value, based on BIOS and CPU */ +uint16_t random_get_number3(uint16_t x) { + x ^= 0x55a3; + x *= 0xb297; /* to match dosbox */ + return rol16(x, 3); +} + +static void game_run(const char *data_path) { + res_init(data_path, GAME_SCREEN_W * GAME_SCREEN_H); + sound_init(); + video_convert_tiles(g_res.uniondat, g_res.unionlen); + g_vars.level_num = g_options.start_level; + if (_logos) { + do_programmed_in_1992_screen(); + do_titus_screen(); + play_music(3); + do_present_screen(); + } + g_sys.render_set_sprites_clipping_rect(0, 0, TILEMAP_SCREEN_W, TILEMAP_SCREEN_H); + g_vars.random.e = 0x1234; + g_vars.starttime = g_sys.get_timestamp(); + while (!g_sys.input.quit) { + if (1) { + video_set_palette(); + g_vars.player_lifes = 2; + g_vars.player_bonus_letters_mask = 0; + g_vars.player_club_power = 20; + g_vars.player_club_type = 0; + if (g_res.dos_demo) { + do_demo_screen(); + } + do_menu(); + } + video_set_palette(); + video_set_palette(); + do_level(); + } + sound_fini(); + res_fini(); +} + +struct game_t game = { + "Prehistorik 2", + game_run +}; + diff --git a/p2/game.h b/p2/game.h new file mode 100644 index 0000000..82d737c --- /dev/null +++ b/p2/game.h @@ -0,0 +1,247 @@ + +#ifndef GAME_H__ +#define GAME_H__ + +#include "intern.h" + +extern struct options_t g_options; + +#define PANEL_H 24 + +#define TILEMAP_SCREEN_W GAME_SCREEN_W +#define TILEMAP_SCREEN_H (GAME_SCREEN_H - PANEL_H) + +#define CHEATS_NO_HIT (1 << 0) +#define CHEATS_UNLIMITED_LIFES (1 << 1) +#define CHEATS_UNLIMITED_ENERGY (1 << 2) + +struct club_anim_t { + const uint8_t *anim; /* uint16_t[4] : player spr, club spr, x, y */ + uint8_t a; + uint8_t power; /* damage */ + uint8_t c; +}; + +struct player_t { + int16_t hdir; /* left:-1 right:1 */ // 0x9 + uint8_t current_anim_num; // 0xB + const uint8_t *anim; // 0xC + int16_t y_velocity; // 0xE + uint8_t special_anim_num; /* idle, exhausted */ // 0x10 +}; + +struct club_projectile_t { + const uint8_t *anim; // 0xC + int16_t y_velocity; // 0xE +}; + +struct thing_t { + void *ref; // 0x9 + int16_t counter; // 0xC + int16_t unkE; // 0xE +}; + +struct object_t { + int16_t x_pos, y_pos; + uint16_t spr_num; + int16_t x_velocity; + uint8_t x_friction; + union { // 0x9 - 0x10 + struct player_t p; /* objects[1] */ + struct club_projectile_t c; /* objects[2..5] */ + struct thing_t t; + } data; + uint8_t hit_counter; // 0x11 +}; // sizeof == 18 + +#define OBJECTS_COUNT 108 +// offset count +// 0 1 : club +// 1 1 : player +// 2 4 : axe +// 6 5 : club hitting decor frames +// 11 12 : monsters +// 23 32 : secret bonuses +// 55 20 : items +// 75 16 : bonus scores +// 91 7 : decor + +struct rotation_t { + uint16_t x_pos; + uint16_t y_pos; + uint8_t index_tbl; /* cos_/sin_tbl */ + uint8_t radius; +}; // sizeof == 6 + +struct collision_t { + uint16_t x_pos; + uint16_t y_pos; + uint8_t unk4; + uint8_t unk5; + uint8_t unk6; + uint8_t unk7; +}; // sizeof == 8 + +struct vars_t { + uint32_t starttime; + uint32_t timestamp; + struct { + uint8_t a, b, c; + uint16_t d, e; + } random; + struct { + bool keystate[128]; + uint8_t key_left, key_right, key_down, key_up, key_space; + uint8_t key_vdir, key_hdir; + } input; + + uint8_t level_num; + uint32_t score; + uint16_t score_extra_life; + + uint8_t level_completed_flag; + uint8_t restart_level_flag; + + uint16_t level_draw_counter; + + uint8_t player_lifes; + uint8_t player_energy; + uint8_t player_death_flag; + uint8_t player_flying_flag; + uint8_t player_flying_counter; + uint8_t player_flying_anim_index; + uint8_t player_bonus_letters_mask; + uint8_t player_utensils_mask; + uint8_t player_gravity_flag; /* 0, 1 or 2 */ + uint8_t player_unk_counter1; + uint8_t player_moving_counter; + uint8_t player_anim_0x40_flag; + uint8_t player_anim2_counter; + int16_t player_prev_y_pos; + uint8_t player_bonus_letters_blinking_counter; + int16_t player_monsters_unk_counter; + uint8_t player_nojump_counter; + uint8_t player_jumping_counter; + uint8_t player_action_counter; + uint8_t player_club_type; + uint8_t player_club_power; + uint8_t player_club_powerup_duration; + uint8_t player_club_anim_duration; + bool player_using_club_flag; + uint16_t player_update_counter; + uint8_t player_platform_counter; + uint8_t player_current_anim_type; + uint8_t player_tile_flags; + + int16_t level_current_bonus_x_pos, level_current_bonus_y_pos, level_current_bonus_spr_num; + uint8_t level_items_count_tbl[140]; /* bonuses and items collected in the level */ + uint8_t level_items_total_count; + uint8_t level_bonuses_count_tbl[80]; + uint8_t bonus_energy_counter; + + int16_t level_current_object_decor_x_pos, level_current_object_decor_y_pos; + uint16_t decor_tile0_offset; /* decor tile below the player */ + + struct collision_t collision_tbl[20]; /* reset by bonus 145 */ + struct rotation_t rotation_tbl[20]; + struct object_t *current_hit_object; + struct object_t objects_tbl[OBJECTS_COUNT]; + + uint8_t level_xscroll_center_flag, level_yscroll_center_flag; + uint8_t level_force_x_scroll_flag; + bool tilemap_adjust_player_pos_flag; + uint8_t level_noscroll_flag; + int16_t tilemap_yscroll_diff; + uint16_t tilemap_x, tilemap_y; + uint16_t tilemap_prev_x, tilemap_prev_y; + int8_t tilemap_scroll_dx, tilemap_scroll_dy; + uint8_t tilemap_h; + uint16_t tilemap_size; /* tilemap size h*256 */ + uint16_t tilemap_start_x_pos, tilemap_start_y_pos; /* tilemap restart position */ + uint8_t tile_attr2_flags; /* current tilemap tile types (eg. front) */ + uint8_t animated_tiles_flag; /* current tilemap has animated tiles */ + uint8_t level_animated_tiles_counter; /* animated tiles update counter */ + uint8_t *level_animated_tiles_current_tbl; /* pointer to current tile_tbl */ + uint8_t tile_tbl1[256]; /* animated tile state 1 */ + uint8_t tile_tbl2[256]; /* animated tile state 2 */ + uint8_t tile_tbl3[256]; /* animated tile state 3 */ + uint8_t animated_tile_flag_tbl[256]; /* 1 if tile is animated */ + uint8_t tilemap_redraw_flag2; /* tilemap needs redraw */ + uint8_t tilemap_redraw_flag1; /* force redraw even if tilemap origin did not change */ + + struct { + uint16_t value; + uint16_t counter; + uint16_t random_tbl[256]; + } snow; + struct { + uint8_t state; /* 1: lights off, 0: lights on */ + uint8_t palette_flag1; /* palette day time */ + uint8_t palette_flag2; /* palette night time */ + uint8_t palette_counter; + } light; + struct { + uint32_t score; + uint8_t lifes; + uint8_t energy; + uint8_t bonus_letters_mask; + } panel; +}; + +extern struct vars_t g_vars; + +/* staticres.c */ +extern const uint8_t *palettes_tbl[16]; +extern const uint8_t credits_palette_data[16 * 3]; +extern const uint8_t light_palette_data[16 * 3]; +extern const uint8_t spr_offs_tbl[892]; +extern const uint8_t spr_size_tbl[892]; +extern const uint16_t score_tbl[17]; +extern const uint8_t score_spr_lut[110]; +extern const uint8_t *object_anim_tbl[]; +extern const struct club_anim_t club_anim_tbl[4]; +extern const uint8_t player_anim_lut[32]; +extern const uint8_t player_anim_data[100]; +extern const uint8_t vscroll_offsets_data[132]; +extern const uint8_t cos_tbl[256]; +extern const uint8_t sin_tbl[256]; + +/* game.c */ +extern void update_input(); +extern void input_check_ctrl_alt_w(); +extern uint32_t timer_get_counter(); +extern void random_reset(); +extern uint8_t random_get_number(); +extern uint16_t random_get_number2(); +extern uint16_t random_get_number3(uint16_t x); +extern void game_main(); + +/* level.c */ +extern void do_level(); + +/* screen.c */ +extern void video_draw_string(int offset, int hspace, const char *s); +extern void video_clear(); +extern void video_copy_img(const uint8_t *src); +extern void video_draw_panel(const uint8_t *src); +extern void video_draw_panel_number(int offset, int num); +extern void video_draw_number(int offset, int num); +extern void video_draw_tile(const uint8_t *src, int x, int y); +extern void video_convert_tiles(uint8_t *data, int len); +extern void video_load_front_tiles(); +extern void video_set_palette(); +extern void fade_in_palette(); +extern void fade_out_palette(); +extern void video_wait_vbl(); +extern void video_transition_close(); +extern void video_transition_open(); +extern void video_load_sprites(); +extern void video_draw_sprite(int num, int x, int y, int flag); + +/* sound.c */ +extern void sound_init(); +extern void sound_fini(); +extern void play_sound(int num); +extern void play_music(int num); + +#endif /* GAME_H__ */ diff --git a/p2/level.c b/p2/level.c new file mode 100644 index 0000000..68b4cab --- /dev/null +++ b/p2/level.c @@ -0,0 +1,2969 @@ + +#include "game.h" +#include "resource.h" +#include "sys.h" +#include "util.h" + +static const bool _redraw_tilemap = true; +static const bool _redraw_panel = true; + +static const uint16_t _undefined = 0x55AA; + +static void level_completed_bonuses_animation(); +static void level_player_death_animation(); + +static void set_level_palette() { + g_sys.set_screen_palette(palettes_tbl[g_vars.level_num], 0, 16, 6); +} + +static int load_level_data_get_tilemap_size(int num, const uint8_t *lev, const uint8_t *uni) { + static const uint8_t level_height_tbl[] = { 0x31, 0x68, 0x31, 0x2D, 0x80, 0x80, 0x80, 0x56, 0x6E, 0x0C, 0x18, 0x33, 0x33, 0x26, 0xAD, 0x54 }; + g_vars.tilemap_h = level_height_tbl[num]; + const int tilemap_size = level_height_tbl[num] << 8; + g_vars.tilemap_size = tilemap_size; + int offset = 0; + for (int i = 0; i < 512; i += 2) { + const uint16_t num = READ_LE_UINT16(lev + tilemap_size + i); + if (num == 0xFFFF) { + continue; + } + if (num < 0x100) { + offset += 128; + } else { + /* 'UNION' tile */ + } + } + return tilemap_size + 512 + offset; +} + +static bool level_check_tilemap_offset(uint16_t offset) { + const uint8_t x = offset & 255; + const uint8_t y = offset >> 8; + if (y >= g_vars.tilemap_h) { + print_debug(DBG_GAME, "invalid tilemap offset %d (%d,%d)", (int16_t)offset, x, y); + return false; + } + return true; +} + +static uint8_t level_get_tile(uint16_t offset) { + if (level_check_tilemap_offset(offset)) { + return g_res.leveldat[offset]; + } + return 0; +} + +static void level_set_tile(uint16_t offset, uint8_t tile_num) { + if (level_check_tilemap_offset(offset)) { + g_res.leveldat[offset] = tile_num; + } +} + + +static void load_level_data_init_animated_tiles() { + for (int i = 0; i < 256; ++i) { + if ((g_res.level.tile_attributes2[i] & 0x80) == 0) { /* not animated */ + g_vars.tile_tbl1[i] = i; + g_vars.tile_tbl2[i] = i; + g_vars.tile_tbl3[i] = i; + g_vars.animated_tile_flag_tbl[i] = 0; + } else { + g_vars.animated_tiles_flag = 1; + int j = i; + g_vars.tile_tbl1[i] = j; + g_vars.tile_tbl2[i + 2] = j; + g_vars.tile_tbl3[i + 1] = j; + ++j; + g_vars.tile_tbl1[i + 1] = j; + g_vars.tile_tbl2[i] = j; + g_vars.tile_tbl3[i + 2] = j; + ++j; + g_vars.tile_tbl1[i + 2] = j; + g_vars.tile_tbl2[i + 1] = j; + g_vars.tile_tbl3[i] = j; + + g_vars.animated_tile_flag_tbl[i] = 1; + g_vars.animated_tile_flag_tbl[i + 1] = 1; + g_vars.animated_tile_flag_tbl[i + 2] = 1; + + i += 2; + } + } + g_vars.level_animated_tiles_current_tbl = g_vars.tile_tbl1; +} + +static void load_level_data_init_transparent_tiles() { + int count = 1; + for (int i = 0; i < 256; ++i) { + const uint8_t num = i; + const uint8_t *tiledat = g_res.leveldat + g_vars.tilemap_size + 512 + num * 128; + uint8_t mask_opaque = 0xFF; + uint8_t mask_transparent = 0; + for (int j = 0; j < 128; ++j) { + mask_opaque &= tiledat[j]; + mask_transparent |= tiledat[j]; + } + if (mask_transparent == 0) { + } else if (mask_opaque == 0xFF) { + g_vars.tilemap_redraw_flag2 = 1; + } else { + ++count; + g_vars.tilemap_redraw_flag2 = 1; + } + } +} + +static void load_level_data_fix_items_spr_num() { + const int16_t offset = g_res.level.items_spr_num_offset; + for (int i = 0; i < MAX_LEVEL_ITEMS; ++i) { + const uint16_t num = g_res.level.items_tbl[i].spr_num; + if (num != 0xFFFF) { + g_res.level.items_tbl[i].spr_num = num - offset + 53; + } + } + for (int i = 0; i < MAX_LEVEL_TRIGGERS; ++i) { + const uint16_t num = g_res.level.triggers_tbl[i].spr_num; + if (num != 0xFFFF) { + g_res.level.triggers_tbl[i].spr_num = num - offset + 53; + } + } +} + +static void load_level_data_fix_monsters_spr_num() { + if (g_res.level.items_spr_num_offset != 0xFFFF) { + uint8_t *p = g_res.level.monsters_attributes; + while (*p <= 50) { + int16_t num = READ_LE_UINT16(p + 2); + if (num != -1) { + if (num >= g_res.level.monsters_spr_num_offset) { + num -= g_res.level.monsters_spr_num_offset; + num += 305; + WRITE_LE_UINT16(p + 2, num); + } else if (num >= g_res.level.items_spr_num_offset) { + num -= g_res.level.items_spr_num_offset; + num += 53; + WRITE_LE_UINT16(p + 2, num); + } + } + const int8_t len = *p; + p += len; + } + } + g_res.level.items_spr_num_offset = 53; + g_res.level.monsters_spr_num_offset = 305; +} + +static void load_level_data_init_secret_bonus_tiles() { + for (int i = 0; i < MAX_LEVEL_BONUSES; ++i) { + struct level_bonus_t *bonus = &g_res.level.bonuses_tbl[i]; + const uint16_t offset = bonus->pos; + if (offset != 0xFFFF) { + const uint8_t tile_num = level_get_tile(offset); + level_set_tile(offset, bonus->tile_num0); + bonus->tile_num1 = tile_num; + } + } +} + +static int compare_level_item_x_pos(const void *item1, const void *item2) { + return ((const struct level_item_t *)item1)->x_pos < ((const struct level_item_t *)item2)->x_pos; +} + +static void load_level_data_init_password_items() { + struct level_item_t *items_ptr_tbl[MAX_LEVEL_ITEMS]; + int count = 0; + for (int i = 0; i < MAX_LEVEL_ITEMS; ++i) { + struct level_item_t *item = &g_res.level.items_tbl[i]; + const uint16_t num = item->spr_num - 283; + if (num > 15) { /* not 0-9A-F */ + continue; + } + items_ptr_tbl[count++] = item; + } + if (count == 0 || (count & 3) != 0) { + return; + } + qsort(items_ptr_tbl, count, sizeof(struct level_item_t *), compare_level_item_x_pos); + const uint16_t r = random_get_number3(g_vars.level_num); + for (int j = 0; j < count; j += 4) { + for (int i = 0; i < 4; ++i) { + struct level_item_t *item = items_ptr_tbl[j + i]; + const int num = r >> ((3 - i) * 4); + item->spr_num = 283 + (num & 15); + } + } +} + + +static void load_level_data(int num) { + static const uint8_t num_tbl[] = { 0, 0, 0, 1, 1, 1, 2, 3, 3, 0, 4, 4, 4, 5, 0, 2 }; + char name[16]; + snprintf(name, sizeof(name), "BACK%d.SQZ", num_tbl[num]); + uint8_t *data = load_file(name); + if (data) { + video_copy_img(data); + free(data); + } + memset(&g_vars.objects_tbl[0], 0, sizeof(struct object_t) * OBJECTS_COUNT); + const int level = (num < 9) ? ('1' + num) : ('A' + num - 9); + snprintf(name, sizeof(name), "LEVEL%c.SQZ", level); + free(g_res.leveldat); + g_res.leveldat = load_file(name); + g_res.levellen = g_uncompressed_size; + const int offset = load_level_data_get_tilemap_size(num, g_res.leveldat, data); + print_debug(DBG_GAME, "tilemap offset %d", offset); + load_leveldat(g_res.leveldat + offset, 0); + video_convert_tiles(g_res.leveldat + g_vars.tilemap_size + 512, offset - (g_vars.tilemap_size + 512)); + print_debug(DBG_GAME, "start_pos %d,%d end_pos %d,%d", g_res.level.start_x_pos, g_res.level.start_y_pos, g_res.level.end_x_pos, g_res.level.end_y_pos); + load_level_data_init_animated_tiles(); + load_level_data_init_transparent_tiles(); + g_vars.snow.counter = 0; + g_vars.tilemap_start_x_pos = g_res.level.start_x_pos; + g_vars.tilemap_start_y_pos = g_res.level.start_y_pos; + load_level_data_fix_items_spr_num(); + load_level_data_fix_monsters_spr_num(); + load_level_data_init_secret_bonus_tiles(); + load_level_data_init_password_items(); + g_res.restart = g_res.level; +} + +static void level_draw_tile(int tile_num, int x, int y) { + const int num = READ_LE_UINT16(g_res.leveldat + g_vars.tilemap_size + tile_num * 2); + const uint8_t *tiledat; + if (num < 0x100) { + tiledat = g_res.leveldat + g_vars.tilemap_size + 512 + num * 128; + } else { + tiledat = g_res.uniondat + (num - 256) * 128; + } + video_draw_tile(tiledat, x * 16 - g_vars.tilemap_scroll_dx, y * 16 - g_vars.tilemap_scroll_dy); +} + +static void level_update_tilemap() { + if (!_redraw_tilemap && g_vars.animated_tiles_flag == 0) { + return; + } + const uint8_t mask = (g_vars.snow.value >= 20) ? 1 : 3; + ++g_vars.level_animated_tiles_counter; + if ((g_vars.level_animated_tiles_counter & mask) == 0) { + if (g_vars.level_animated_tiles_current_tbl == g_vars.tile_tbl1) { + g_vars.level_animated_tiles_current_tbl = g_vars.tile_tbl2; + } else if (g_vars.level_animated_tiles_current_tbl == g_vars.tile_tbl2) { + g_vars.level_animated_tiles_current_tbl = g_vars.tile_tbl3; + } else if (g_vars.level_animated_tiles_current_tbl == g_vars.tile_tbl3) { + g_vars.level_animated_tiles_current_tbl = g_vars.tile_tbl1; + } + } else { + if (!_redraw_tilemap) { + return; + } + } + memcpy(g_res.vga, g_res.background, 320 * 200); + g_vars.tile_attr2_flags = 0; + g_vars.animated_tiles_flag = 0; + uint16_t offset = (g_vars.tilemap_y << 8) | g_vars.tilemap_x; + for (int y = 0; y < (TILEMAP_SCREEN_H / 16) + 1; ++y) { + for (int x = 0; x < TILEMAP_SCREEN_W / 16; ++x) { + const uint8_t tile_num = level_get_tile(offset + x); + g_vars.tile_attr2_flags |= g_res.level.tile_attributes2[tile_num]; + if (_redraw_tilemap || g_vars.animated_tile_flag_tbl[tile_num] != 0) { + g_vars.animated_tiles_flag = 1; + const uint8_t num = g_vars.level_animated_tiles_current_tbl[tile_num]; + level_draw_tile(num, x, y); + } + } + offset += 256; + } +} + +static void level_draw_tilemap() { + if (_redraw_tilemap) { + return; + } + if (g_vars.tilemap_redraw_flag1 == 0) { + const bool changed = (g_vars.tilemap_x != g_vars.tilemap_prev_x) || (g_vars.tilemap_y != g_vars.tilemap_prev_y); + if (!changed) { + return; + } + g_vars.tilemap_prev_x = g_vars.tilemap_x; + g_vars.tilemap_prev_y = g_vars.tilemap_y; + if (g_vars.tilemap_redraw_flag2 == 0) { + return; + } + } + g_vars.tilemap_redraw_flag1 = 0; + g_vars.tilemap_redraw_flag2 = 0; + g_vars.tile_attr2_flags = 0; + uint16_t offset = (g_vars.tilemap_y << 8) | g_vars.tilemap_x; + for (int y = 0; y < (TILEMAP_SCREEN_H / 16) + 1; ++y) { + for (int x = 0; x < TILEMAP_SCREEN_W / 16; ++x) { + const uint8_t tile_num = level_get_tile(offset + x); + g_vars.tile_attr2_flags |= g_res.level.tile_attributes2[tile_num]; + level_draw_tile(tile_num, x, y); + } + offset += 256; + } +} + +static bool level_adjust_hscroll_left() { + if (!g_options.dos_scrolling && 0) { + if (g_vars.tilemap_scroll_dx >= 0) { + return false; + } + g_vars.tilemap_scroll_dx += 4; + --g_vars.tilemap_x; + if (g_vars.tilemap_x < 0) { + g_vars.tilemap_x = 0; + g_vars.tilemap_scroll_dx = 0; + } + return true; + } + if (g_vars.tilemap_x == 0) { + return true; + } + --g_vars.tilemap_x; + return false; +} + +static bool level_adjust_hscroll_right() { + if (!g_options.dos_scrolling && 0) { + if (g_vars.tilemap_scroll_dx < 4) { + return false; + } + g_vars.tilemap_scroll_dx -= 4; + ++g_vars.tilemap_x; + return true; + } + int end_x = (g_vars.objects_tbl[1].x_pos >> 4) - (TILEMAP_SCREEN_W / 16); + if (g_res.level.tilemap_w < end_x) { + end_x = 256 - (TILEMAP_SCREEN_W / 16); + } else { + end_x = g_res.level.tilemap_w; + } + if (g_vars.tilemap_x >= end_x) { + return true; + } + ++g_vars.tilemap_x; + return false; +} + +static void level_adjust_x_scroll() { + const int x_pos = (g_vars.objects_tbl[1].x_pos >> 4) - g_vars.tilemap_x; + if (x_pos >= (TILEMAP_SCREEN_W / 16) || g_vars.level_force_x_scroll_flag != 0 || g_vars.objects_tbl[1].x_velocity != 0) { + if (g_vars.level_xscroll_center_flag != 0) { + if (g_vars.level_xscroll_center_flag != 1) { + if (x_pos < 15) { + if (!level_adjust_hscroll_left()) { + return; + } + } + } else { + if (x_pos > 5) { + if (!level_adjust_hscroll_right()) { + return; + } + } + } + g_vars.level_xscroll_center_flag = 0; + } else { + if (g_vars.objects_tbl[1].x_velocity != 0) { + if (g_vars.objects_tbl[1].x_velocity < 0) { + if (x_pos <= 4) { + g_vars.level_xscroll_center_flag = 2; + } + } else { + if (x_pos >= 16) { + g_vars.level_xscroll_center_flag = 1; + } + } + } else { + if (x_pos < 10) { + if (x_pos <= 4) { + g_vars.level_xscroll_center_flag = 2; + } + } else { + if (x_pos >= 16) { + g_vars.level_xscroll_center_flag = 1; + } + } + } + } + } else { + g_vars.level_xscroll_center_flag = 0; + } +} + +static bool level_adjust_vscroll_down(int dl) { + const int end_y = g_vars.tilemap_h - (TILEMAP_SCREEN_H / 16); + if (g_vars.tilemap_y >= end_y) { + return false; + } + ++g_vars.tilemap_redraw_flag1; + g_vars.tilemap_scroll_dy += dl; + if (g_vars.tilemap_scroll_dy < 16) { + return true; + } + g_vars.tilemap_scroll_dy -= 16; + ++g_vars.tilemap_y; + if (g_vars.tilemap_y >= end_y) { + g_vars.tilemap_scroll_dy = 0; + } + return true; +} + +static bool level_adjust_vscroll_up(int dl) { + if (g_vars.tilemap_y == 0) { + return false; + } + ++g_vars.tilemap_redraw_flag1; + g_vars.tilemap_scroll_dy -= dl; + if (g_vars.tilemap_scroll_dy >= 0) { + return true; + } + g_vars.tilemap_scroll_dy += 16; + --g_vars.tilemap_y; + return true; +} + +static void level_adjust_y_scroll() { + if (!g_vars.tilemap_adjust_player_pos_flag && (g_res.level.scrolling_mask & 4) != 0) { + level_adjust_vscroll_down(1); + return; + } + if (g_vars.objects_tbl[1].data.p.y_velocity == 0) { + g_vars.level_yscroll_center_flag = 0; + } + const int player_yorigin_diff = (g_vars.objects_tbl[1].y_pos >> 4) - g_vars.tilemap_y; + if (g_vars.objects_tbl[1].data.p.y_velocity != 0) { + if (player_yorigin_diff >= 9) { + g_vars.tilemap_yscroll_diff = 3; + ++g_vars.level_yscroll_center_flag; + } else if (player_yorigin_diff <= 2) { + g_vars.tilemap_yscroll_diff = 8; + ++g_vars.level_yscroll_center_flag; + } + } else { + if ((g_res.level.scrolling_mask & 1) == 0) { + if (player_yorigin_diff >= 10) { + g_vars.tilemap_yscroll_diff = 9; + ++g_vars.level_yscroll_center_flag; + } else if (player_yorigin_diff <= 3) { + g_vars.tilemap_yscroll_diff = 8; + ++g_vars.level_yscroll_center_flag; + } + } else { + if (player_yorigin_diff >= 8) { + g_vars.tilemap_yscroll_diff = 7; + ++g_vars.level_yscroll_center_flag; + } else if (player_yorigin_diff <= 5) { + g_vars.tilemap_yscroll_diff = 6; + ++g_vars.level_yscroll_center_flag; + } + } + } + if (g_vars.level_yscroll_center_flag == 0) { + return; + } + const int bottom = (g_res.level.scrolling_top + (TILEMAP_SCREEN_H / 16)) << 4; + if (g_vars.objects_tbl[1].y_pos > bottom || g_res.level.scrolling_top >= g_vars.tilemap_y - 1) { + if (g_vars.level_yscroll_center_flag == 0) { + return; + } + if (g_vars.tilemap_yscroll_diff == player_yorigin_diff) { + g_vars.level_yscroll_center_flag = 0; + return; + } + if (g_vars.tilemap_yscroll_diff < player_yorigin_diff) { + if (g_vars.objects_tbl[1].y_pos <= bottom && g_vars.tilemap_y > g_res.level.scrolling_top) { + g_vars.level_yscroll_center_flag = 0; + return; + } + int dl = 16; + if (!g_vars.tilemap_adjust_player_pos_flag) { + const int dy = g_vars.objects_tbl[1].y_pos - g_vars.tilemap_scroll_dy; + const int index = dy - ((g_vars.tilemap_y + g_vars.tilemap_yscroll_diff) << 4); + if (index >= 0 && index < 132) { + dl = vscroll_offsets_data[index]; + } else { + print_warning("Invalid scroll down delta %d", index); + return; + } + } + if (!level_adjust_vscroll_down(dl)) { + g_vars.level_yscroll_center_flag = 0; + } + return; + } + } + int dl = 16; + if (!g_vars.tilemap_adjust_player_pos_flag) { + const int dy = g_vars.objects_tbl[1].y_pos - g_vars.tilemap_scroll_dy; + const int index = ((g_vars.tilemap_y + g_vars.tilemap_yscroll_diff) << 4) - dy; + if (index >= 0 && index < 132) { + dl = vscroll_offsets_data[index]; + } else { + print_warning("Invalid scroll up delta %d", index); + return; + } + } + if (!level_adjust_vscroll_up(dl)) { + g_vars.level_yscroll_center_flag = 0; + } +} + +static void level_update_scrolling() { + if (g_vars.level_noscroll_flag != 0) { + return; + } + if ((g_res.level.scrolling_mask & 2) == 0) { + level_adjust_x_scroll(); + } + level_adjust_y_scroll(); +} + +static void level_init_tilemap() { + video_transition_close(); + g_vars.tilemap_x = 0; + g_vars.tilemap_y = 0; + g_vars.tilemap_scroll_dx = 0; + g_vars.tilemap_scroll_dy = 0; + g_vars.level_animated_tiles_current_tbl = g_vars.tile_tbl1; + do { + g_vars.tilemap_adjust_player_pos_flag = true; + level_update_scrolling(); + g_vars.tilemap_adjust_player_pos_flag = false; + } while ((g_vars.level_yscroll_center_flag | g_vars.level_xscroll_center_flag) != 0); + print_debug(DBG_GAME, "init_tilemap x_pos %d y_pos %d scrolling 0x%x", g_vars.tilemap_x, g_vars.tilemap_y, g_res.level.scrolling_mask); + if ((g_res.level.scrolling_mask & 2) == 0) { + const int x = (g_vars.objects_tbl[1].x_pos >> 4) - g_vars.tilemap_x; + if (x >= 12) { + for (int i = 0; i < 10; ++i) { + level_adjust_hscroll_right(); + } + } + } + g_vars.tilemap_redraw_flag2 = 1; + g_vars.tilemap_prev_x = _undefined; + level_draw_tilemap(); + video_transition_open(); +} + +static void level_player_die() { + g_vars.restart_level_flag = 2; + if (g_vars.player_lifes != 0) { + if (g_options.cheats & CHEATS_UNLIMITED_LIFES) { + return; + } + --g_vars.player_lifes; + g_vars.player_energy = 0; + } else { + g_vars.player_death_flag = 1; + } +} + +static void level_player_reset() { + if (g_vars.player_nojump_counter > 0) { + --g_vars.player_nojump_counter; + } + g_vars.player_anim2_counter = 0; + g_vars.player_tile_flags = 2; + g_vars.player_prev_y_pos = g_vars.objects_tbl[1].y_pos; +} + +static void level_init_object_hit_from_player_pos() { + struct object_t *obj = g_vars.current_hit_object; + obj->x_pos = g_vars.objects_tbl[1].x_pos; + obj->y_pos = g_vars.objects_tbl[1].y_pos; + obj->spr_num = 53; + --obj; + if (obj < &g_vars.objects_tbl[6]) { + obj = &g_vars.objects_tbl[10]; + } + g_vars.current_hit_object = obj; +} + +static void level_init_object_hit_from_xy_pos(int16_t x_pos, int16_t y_pos) { + struct object_t *obj = g_vars.current_hit_object; + obj->x_pos = x_pos; + obj->y_pos = y_pos; + obj->spr_num = 53; + --obj; + if (obj < &g_vars.objects_tbl[6]) { + obj = &g_vars.objects_tbl[10]; + } + g_vars.current_hit_object = obj; +} + +static void level_update_tile_type_2() { + level_player_die(); + g_vars.restart_level_flag = 2; +} + +static int level_get_tile_player_offset(uint8_t attr) { + if ((attr & 0x30) == 0) { + return attr; + } + int x = (g_vars.objects_tbl[1].x_pos & 15) / 3; + if (attr & 0x10) { + x = (attr & 15) + x; + } else { + x = (attr & 15) - x; + } + return x; +} + +static void level_update_tile_attr1_helper(uint16_t offset) { + g_vars.objects_tbl[1].x_friction = 0; + if (g_vars.objects_tbl[1].data.p.y_velocity < 0) { + g_vars.player_tile_flags |= 1; + return; + } + g_vars.player_gravity_flag = 0; + g_vars.objects_tbl[1].y_pos &= ~15; + uint8_t al = level_get_tile(offset); + al = g_res.level.tile_attributes3[al]; + if (al != 0) { + int dy = level_get_tile_player_offset(al); + int bx = g_vars.objects_tbl[1].data.p.y_velocity >> 4; + if (bx > 0 && dy >= bx) { + dy = bx; + } + g_vars.objects_tbl[1].y_pos += dy; + } else { + al = level_get_tile(offset - 0x100); + al = g_res.level.tile_attributes3[al]; + if (al != 0) { + int ax = level_get_tile_player_offset(al); + if (ax < 16) { + ax -= 16; + g_vars.objects_tbl[1].y_pos += ax; + } + } + } + if (g_vars.player_jumping_counter > 4) { + level_init_object_hit_from_player_pos(); + const int dy = g_vars.objects_tbl[1].y_pos - g_vars.player_prev_y_pos; + if (dy >= 32 && g_vars.objects_tbl[1].data.p.y_velocity >= 80) { + g_vars.player_prev_y_pos = g_vars.objects_tbl[1].y_pos; + if (g_vars.player_jumping_counter >= 20 && g_vars.objects_tbl[1].data.p.y_velocity > 160) { + g_vars.player_platform_counter = 8; + } + if (g_vars.player_jumping_counter > 10) { + if ((g_res.level.scrolling_mask & 1) == 0) { + g_vars.objects_tbl[1].data.p.y_velocity = -32; + } + g_vars.objects_tbl[1].spr_num = (g_vars.objects_tbl[1].spr_num & 0xE000) | 12; + g_vars.player_jumping_counter = 0; + return; + } + } + } + g_vars.objects_tbl[1].data.p.y_velocity = 0; + level_player_reset(); +} + +static void level_update_tile_attr1_type_1(uint16_t offset) { + level_update_tile_attr1_helper(offset); +} + +static void level_update_tile_attr1_type_2(uint16_t offset) { + level_update_tile_attr1_helper(offset); + g_vars.objects_tbl[1].x_friction = 1; +} + +static void level_update_tile_attr1_type_3(uint16_t offset) { + level_update_tile_attr1_helper(offset); + g_vars.objects_tbl[1].x_friction = 2; +} + +static void level_update_tile_attr1_type_4(uint16_t offset) { + level_update_tile_attr1_helper(offset); + g_vars.objects_tbl[1].x_friction = 3; +} + +static void level_update_tile_attr1_type_5(uint16_t offset) { + g_vars.objects_tbl[1].x_friction = 0; + if (g_vars.player_action_counter == 0) { + level_update_tile_attr1_helper(offset); + } else { + g_vars.player_tile_flags |= 1; + } +} + +static void level_update_tile_attr1_type_0(uint16_t offset) { + if (g_vars.objects_tbl[1].data.p.y_velocity == 0) { + uint8_t al = level_get_tile(offset + 0x100); + al = g_res.level.tile_attributes3[al]; + if (al != 0 && level_get_tile_player_offset(al) < 16) { + offset += 0x100; + g_vars.objects_tbl[1].y_pos += 16; + level_update_tile_attr1_type_1(offset); + return; + } + } + g_vars.player_tile_flags |= 1; +} + +static void level_update_tile_attr2_type1() { + if (g_vars.objects_tbl[1].data.p.y_velocity != 0) { + g_vars.objects_tbl[1].data.p.y_velocity = 0; + g_vars.objects_tbl[1].y_pos &= ~15; + g_vars.objects_tbl[1].y_pos += 16; + return; + } + const uint8_t spr_num = g_vars.objects_tbl[1].spr_num; + if (spr_num == 10 || spr_num == 21) { + return; + } + static int16_t data[6]; + const uint8_t bh = (data[1] >> 4) & 255; + const uint8_t bl = (data[0] >> 4) & 255; + uint16_t offset = (bh << 8) | bl; + uint8_t tile_num = level_get_tile(offset); + if (g_res.level.tile_attributes0[tile_num] & 1) { + int dx = 16; + tile_num = level_get_tile(offset - 1); + if ((g_res.level.tile_attributes0[tile_num] & 1) == 0) { + dx = -dx; + } + data[0] += dx; + } +} + +static void level_update_player_jump(); + +static void level_update_tile0(uint16_t offset) { + if (g_vars.objects_tbl[1].y_pos <= -1) { + level_update_player_jump(); + g_vars.player_tile_flags = 0xFF; + return; + } + const uint16_t offset_nextline = offset + 0x100; + int tile_num = level_get_tile(offset_nextline); + const uint8_t attr1 = g_res.level.tile_attributes1[tile_num]; + if (g_vars.decor_tile0_offset != offset_nextline) { + /* update tiles below the player */ + if (g_vars.decor_tile0_offset != _undefined) { + tile_num = level_get_tile(g_vars.decor_tile0_offset); + while (1) { + --tile_num; + if ((g_res.level.tile_attributes2[tile_num] & 0x20) == 0) { + g_vars.decor_tile0_offset = _undefined; + break; + } + level_set_tile(g_vars.decor_tile0_offset, tile_num); + } + } + tile_num = level_get_tile(offset_nextline); + if (g_res.level.tile_attributes2[tile_num] & 0x20) { + g_vars.decor_tile0_offset = offset_nextline; + ++tile_num; + level_set_tile(g_vars.decor_tile0_offset, tile_num); + } + } + switch (attr1) { + case 0: + level_update_tile_attr1_type_0(offset_nextline); + break; + case 1: + level_update_tile_attr1_type_1(offset_nextline); + break; + case 2: + level_update_tile_attr1_type_2(offset_nextline); + break; + case 3: + level_update_tile_attr1_type_3(offset_nextline); + break; + case 4: + level_update_tile_attr1_type_4(offset_nextline); + break; + case 5: + level_update_tile_attr1_type_5(offset_nextline); + break; + case 6: + /* spikes */ + level_update_tile_type_2(); + break; + default: + print_warning("Unhandled level_update_tile0 attr1 %d", attr1); + break; + } + if (offset < 0x100 || g_vars.objects_tbl[1].data.p.y_velocity >= 0) { + return; + } + offset -= 0x100; + uint8_t ah = level_get_tile(offset); + uint8_t al = level_get_tile(offset + 0x100); + al = g_res.level.tile_attributes0[al]; + SWAP(ah, al); + al = g_res.level.tile_attributes2[al] & 15; + switch (al) { + case 0: + break; + case 1: + level_update_tile_attr2_type1(); + break; + default: + print_warning("Unhandled level_update_tile0 attr2 %d", al); + break; + } + if ((ah & 1) == 0 || g_vars.objects_tbl[1].y_pos <= 0) { + return; + } + int dx = (g_vars.objects_tbl[1].x_velocity > 0) ? -1 : 1; + offset += dx; + al = level_get_tile(offset + 0x100); + al = g_res.level.tile_attributes0[al]; + if (al != 0) { + dx = -dx; + offset += 2 * dx; + al = level_get_tile(offset + 0x100); + al = g_res.level.tile_attributes0[al]; + if (al != 0) { + return; + } + } + g_vars.objects_tbl[1].x_pos += 2 * dx; +} + +static void level_update_tile_type_0(uint16_t offset) { + const int tile_num = level_get_tile(offset); + if ((g_res.level.tile_attributes2[tile_num] & 0x10) == 0) { + return; + } + for (int i = 0; i < 20; ++i) { + struct collision_t *p = &g_vars.collision_tbl[i]; + if (p->x_pos == _undefined) { + continue; + } + p->x_pos = g_vars.objects_tbl[1].x_pos << 3; + p->y_pos = g_vars.objects_tbl[1].y_pos << 3; + p->unk4 = 0; + p->unk5 = 0; + p->unk7 = 0; + break; + } +} + +static void level_update_tile_type_1(uint16_t offset) { + g_vars.objects_tbl[1].x_pos -= g_vars.objects_tbl[1].x_velocity >> 4; + g_vars.objects_tbl[1].x_velocity = 0; +} + +static void level_update_tile1(uint16_t offset) { + const int tile_num = level_get_tile(offset); + const uint8_t type = g_res.level.tile_attributes0[tile_num]; + switch (type) { + case 0: + level_update_tile_type_0(offset); + break; + case 1: + level_update_tile_type_1(offset); + break; + default: + print_warning("Unhandled level_update_tile1 type %d", type); + break; + } +} + +static void level_update_tile2(uint16_t offset) { + const int tile_num = level_get_tile(offset); + const uint8_t type = g_res.level.tile_attributes0[tile_num]; + if (type == 2 || type == 4) { + switch (type) { + case 2: + level_update_tile_type_2(); + break; + default: + print_warning("Unhandled level_update_tile2 type %d", type); + break; + } + } +} + +static void level_reset_objects() { + memset(&g_vars.objects_tbl[1], 0, sizeof(struct object_t) * (OBJECTS_COUNT - 1)); + for (int i = 0; i < OBJECTS_COUNT; ++i) { + struct object_t *obj = &g_vars.objects_tbl[i]; + obj->spr_num = 0xFFFF; + obj->hit_counter = 0; + } + g_vars.objects_tbl[1].x_pos = g_res.level.start_x_pos; + g_vars.objects_tbl[1].y_pos = g_res.level.start_y_pos; +} + +static void level_reset() { + g_vars.objects_tbl[1].data.p.special_anim_num = 0; + g_vars.tilemap_prev_x = _undefined; + g_vars.current_hit_object = &g_vars.objects_tbl[6]; + level_reset_objects(); + set_level_palette(); + for (int i = 0; i < 256; ++i) { + /* const uint8_t dh = */ random_get_number(); + const uint8_t al = random_get_number(); + /* this matches disassembly but is likely a typo and should rather be '(dh << 8) | al' */ + g_vars.snow.random_tbl[i] = (al << 8) | al; + } + for (int i = 0; i < 20; ++i) { + g_vars.collision_tbl[i].x_pos = _undefined; + } + for (int i = 0; i < 20; ++i) { + g_vars.rotation_tbl[i].x_pos = 0xFFFF; + } + g_vars.player_energy = 3; + + g_vars.decor_tile0_offset = _undefined; + g_vars.tilemap_yscroll_diff = 0; +} + +static uint16_t level_get_player_tile_pos() { + const int y = (g_vars.objects_tbl[1].y_pos) >> 4; + const int x = (g_vars.objects_tbl[1].x_pos) >> 4; + return (y << 8) | x; +} + +static void level_add_object75_score(struct object_t *ref_obj, int num) { + const int score_index = num - 74; + if (score_index >= 0 && score_index <= 16) { + g_vars.score += score_tbl[score_index]; + } + for (int i = 0; i < 16; ++i) { + struct object_t *obj = &g_vars.objects_tbl[75 + i]; + if (obj->spr_num == 0xFFFF) { + obj->spr_num = num; + obj->x_pos = ref_obj->x_pos; + obj->y_pos = ref_obj->y_pos; + obj->data.t.counter = 44; + if (ref_obj >= &g_vars.objects_tbl[23]) { + struct level_item_t *item = ref_obj->data.t.ref; + if (item) { + assert(item >= &g_res.level.items_tbl[0] && item < &g_res.level.items_tbl[MAX_LEVEL_ITEMS]); + item->spr_num = 0xFFFF; + } + } + break; + } + } +} + +static void level_update_object_anim(const uint8_t *anim) { + int16_t num = READ_LE_UINT16(anim); + if (num < 0) { + anim += num; + num = READ_LE_UINT16(anim); + } + g_vars.player_current_anim_type = num >> 8; + g_vars.objects_tbl[1].spr_num = (num & 0x1FFF) | ((g_vars.objects_tbl[1].data.p.hdir < 0) ? 0x8000 : 0); + g_vars.objects_tbl[1].data.p.anim = anim + 2; +} + +static void level_update_objects_hit_animation() { + struct object_t *obj = g_vars.current_hit_object; + for (int i = 0; i < 5; ++i) { + const int num = (obj->spr_num & 0x1FFF) + 1; + --obj->y_pos; + if (num < 58) { + obj->spr_num = num; + } else { + obj->spr_num = 0xFFFF; + } + --obj; + if (obj < &g_vars.objects_tbl[6]) { + obj = &g_vars.objects_tbl[10]; + } + } +} + +static void level_add_object23_bonus(int x_vel, int y_vel, int count) { + int counter = 0; + for (int i = 0; i < 32; ++i) { + struct object_t *obj = &g_vars.objects_tbl[23 + i]; + if (obj->spr_num == 0xFFFF) { + obj->spr_num = g_vars.level_current_bonus_spr_num; + obj->hit_counter = 0; + obj->data.t.counter = 198; + obj->x_pos = g_vars.level_current_bonus_x_pos; + obj->y_pos = g_vars.level_current_bonus_y_pos; + obj->x_velocity = x_vel; + obj->data.p.y_velocity = y_vel; + x_vel = -x_vel; + ++counter; + if ((counter & 1) == 0) { + if (counter > 12) { + x_vel = 0; + } + x_vel -= 16; + y_vel -= 16; + } + --count; + if (count == 0) { + return; + } + } + } +} + +static bool level_objects_collide(const struct object_t *si, const struct object_t *di) { + const int dx = abs(si->x_pos - di->x_pos); + if (dx >= 64) { + return false; + } + const int dy = abs(si->y_pos - di->y_pos); + if (dy >= 70) { + return false; + } + int num = si->spr_num; + int a = si->y_pos; + int d = di->y_pos; + if (a < d) { + num = di->spr_num; + SWAP(a, d); + SWAP(si, di); + } + num &= 0x1FFF; + int b = spr_size_tbl[num * 2 + 1]; + a -= b; + if (a >= d) { + return false; + } + if (g_vars.player_using_club_flag == 0) { + d -= a; + if (g_vars.objects_tbl[1].data.p.y_velocity >= 128 || (d <= (b >> 1) && si != &g_vars.objects_tbl[1])) { + } + } + d = si->x_pos - spr_offs_tbl[(si->spr_num & 0x1FFF) * 2]; + a = di->x_pos - spr_offs_tbl[(di->spr_num & 0x1FFF) * 2]; + b = spr_size_tbl[(di->spr_num & 0x1FFF) * 2]; + if (a >= d) { + SWAP(a, d); + b = spr_size_tbl[(si->spr_num & 0x1FFF) * 2]; + } + if (g_vars.player_using_club_flag == 0) { + b >>= 1; + } + return (a + b > d); +} + +static bool level_collide_axe_monsters(struct object_t *obj) { + for (int i = 0; i < 12; ++i) { + struct object_t *obj = &g_vars.objects_tbl[11 + i]; + if (obj->spr_num == 0xFFFF) { + continue; + } + print_warning("Unhandled level_collide_axe_monsters object %d", 11 + i); + } + return false; +} + +static uint16_t level_get_random_bonus_spr_num() { + uint8_t num; + while (1) { + num = random_get_number() & 0x7F; + if (num < 0x5F) { + break; + } + } + return 0x2080 + num; +} + +static bool level_update_found_bonus_tile(struct level_bonus_t *bonus) { + const uint16_t pos = bonus->pos; + bonus->pos = 0xFFFF; + level_set_tile(pos, bonus->tile_num1); + int8_t al = (pos & 0xFF) - g_vars.tilemap_x; + if (al < 0 || al >= (TILEMAP_SCREEN_W / 16)) { + return false; + } + int8_t ah = (pos >> 8) - g_vars.tilemap_y; + if (ah < 0 || ah >= (TILEMAP_SCREEN_H / 16) + 1) { + return false; + } + return false; +} + +static bool level_handle_bonuses_found(struct object_t *obj, struct level_bonus_t *bonus) { + const uint16_t pos = bonus->pos; + const int y_pos = ((pos >> 8) << 4) + 8; + const int x_pos = ((pos & 0xFF) << 4) + 12; + level_init_object_hit_from_xy_pos(x_pos, y_pos); + if (bonus->count & 0x80) { + --bonus->count; + if (bonus->count & 0x80) { + return false; + } + g_vars.level_current_bonus_x_pos = (pos & 0xFF) << 4; + g_vars.level_current_bonus_y_pos = (pos >> 8) << 4; + int num = 0; + while (1) { + num = random_get_number() & 7; + if (num != 0) { + break; + } + } + g_vars.level_current_bonus_spr_num = 110 + num; + g_vars.level_current_bonus_y_pos -= 112; + level_add_object23_bonus(0, 0, 1); + } else { + static uint8_t draw_counter = 0; + const int diff = abs(g_vars.level_draw_counter - draw_counter); + draw_counter = g_vars.level_draw_counter; + if (diff < 6) { + return true; + } + g_vars.level_current_bonus_x_pos = (pos & 0xFF) << 4; + g_vars.level_current_bonus_y_pos = (pos >> 8) << 4; + int ax, count = 1; + if (bonus->count & 0x40) { + if (bonus->count == 64) { + bonus->count = 0; + if (g_vars.level_num == 7 || g_vars.level_num == 6) { + g_vars.level_current_bonus_spr_num = 300; + level_add_object23_bonus(32, -48, 2); + g_vars.level_current_bonus_spr_num = 229; + count = 4; + } else { + g_vars.level_current_bonus_spr_num = 229; + count = 1; + } + } else { + if (g_vars.level_num == 7 || g_vars.level_num == 6) { + g_vars.level_current_bonus_spr_num = 300; + level_add_object23_bonus(48, -96, 4); + } + goto decrement_bonus; + } + } else { + g_vars.level_current_bonus_spr_num = level_get_random_bonus_spr_num(); + } + ax = 48; + if (obj < &g_vars.objects_tbl[1]) { + if (g_vars.objects_tbl[1].data.p.hdir >= 0) { + ax = -ax; + } + } else if (obj->x_velocity >= 0) { + ax = -ax; + } + level_add_object23_bonus(ax, -112, count); +decrement_bonus: + --bonus->count; + if ((bonus->count & 0x80) == 0) { + return true; + } + } + return level_update_found_bonus_tile(bonus); +} + +static bool level_collide_axe_bonuses(struct object_t *obj) { + const int obj_x = obj->x_pos >> 4; + const int obj_y = obj->y_pos - 16; + for (int i = 0; i < MAX_LEVEL_BONUSES; ++i) { + struct level_bonus_t *bonus = &g_res.level.bonuses_tbl[i]; + if (bonus->pos == 0xFFFF) { + continue; + } + const int bonus_x = bonus->pos & 0xFF; + if (abs(bonus_x - obj_x) > 1) { + continue; + } + const int bonus_y = bonus->pos >> 8; + if (abs((bonus_y << 4) - obj_y) >= 16) { + continue; + } + obj->spr_num = 0xFFFF; + if (level_handle_bonuses_found(obj, bonus)) { + continue; + } + memset(g_vars.level_bonuses_count_tbl, 0, 80); + const uint16_t pos = bonus->pos; + for (int j = 0; j < MAX_LEVEL_BONUSES; ++j) { + struct level_bonus_t *bonus2 = &g_res.level.bonuses_tbl[j]; + if (bonus2->pos == 0xFFFF || bonus2->pos == pos) { + continue; + } + if (abs((bonus2->pos & 0xFF) - bonus_x) > 1) { + continue; + } + if (abs((bonus2->pos >> 8) - bonus_y) > 1) { + continue; + } + if (g_vars.level_bonuses_count_tbl[j] == 0) { + ++g_vars.level_bonuses_count_tbl[j]; + } + } + } + return false; +} + +static void level_update_objects_axe() { + g_vars.player_using_club_flag = true; + for (int i = 0; i < 4; ++i) { + struct object_t *obj = &g_vars.objects_tbl[2 + i]; + if (obj->spr_num == 0xFFFF) { + continue; + } + if (level_collide_axe_monsters(obj) || level_collide_axe_bonuses(obj)) { + continue; + } + } + if (g_vars.player_flying_flag == 0) { + struct object_t *obj = &g_vars.objects_tbl[0]; + if (obj->spr_num != 0xFFFF) { + if (level_collide_axe_monsters(obj) || level_collide_axe_bonuses(obj)) { + if (g_vars.objects_tbl[1].data.p.y_velocity != 0) { + g_vars.objects_tbl[1].data.p.y_velocity = -80; + } + } + } + g_vars.player_using_club_flag = false; + } +} + +static void level_update_monsters_state() { +} + +static void level_update_objects_monsters() { + if (g_res.level.monsters_state != 0xFF) { + level_update_monsters_state(); + } + if (g_vars.level_num == 5 && (g_res.level.scrolling_mask & ~1) == 0) { + } + for (int i = 0; i < 12; ++i) { + struct object_t *obj = &g_vars.objects_tbl[11 + i]; + if (obj->spr_num == 0xFFFF) { + continue; + } + print_warning("Unhandled level_update_objects_monsters object %d", 11 + i); + } + const uint8_t *p = g_res.level.monsters_attributes; + while (*p < 50) { + if (READ_LE_UINT16(p + 2) != 0xFFFF && (p[4] & 4) == 0) { + } + const uint8_t len = p[0]; + p += len; + } +} + +static void level_update_objects_club_projectiles() { + for (int i = 0; i < 4; ++i) { + struct object_t *obj = &g_vars.objects_tbl[2 + i]; + if (obj->spr_num == 0xFFFF) { + continue; + } + if ((obj->spr_num & 0x2000) == 0) { + obj->spr_num = 0xFFFF; + continue; + } + obj->x_pos += obj->x_velocity >> 4; + obj->y_pos += obj->data.c.y_velocity >> 4; + const uint8_t *anim = obj->data.c.anim; + int16_t num = READ_LE_UINT16(anim); + if (num < 0) { + anim += num; + num = READ_LE_UINT16(anim); + } + obj->data.c.anim = anim + 2; + obj->spr_num = num | ((obj->x_velocity < 0) ? 0x8000 : 0); + switch (obj->x_friction) { + case 0: + obj->data.c.y_velocity += 32; + break; + case 1: + obj->data.c.y_velocity -= 16; + break; + default: + print_warning("Unhandled level_update_objects_club_projectiles type %d", obj->x_friction); + break; + } + } +} + +static void level_update_objects_bonuses() { + for (int i = 0; i < 32; ++i) { + struct object_t *obj = &g_vars.objects_tbl[23 + i]; + if (obj->spr_num == 0xFFFF) { + continue; + } + --obj->data.t.counter; + if (obj->data.t.counter == 0) { + obj->hit_counter = 15; + continue; + } + if (obj->data.t.counter < 0) { + if (obj->hit_counter == 0) { + obj->spr_num = 0xFFFF; + } + continue; + } + obj->x_pos += obj->x_velocity >> 4; + if (obj->x_pos < 0) { + obj->x_pos = 0; + obj->x_velocity = -obj->x_velocity; + } + const int dy = obj->data.p.y_velocity >> 4; + obj->y_pos += dy; + int dx = obj->data.p.y_velocity + 9; + if (dx < 256) { + obj->data.p.y_velocity = dx; + } + const int num = obj->spr_num & 0x1FFF; + if (num == 229) { + continue; + } + if (num == 300) { + if (obj->data.t.counter > 50) { + obj->data.t.counter = 50; + } + continue; + } + uint8_t bl = obj->x_pos >> 4; + uint8_t bh = obj->y_pos >> 4; + if (dy > 0) { + const int tile_num = level_get_tile(bh * 256 + bl); + uint8_t al = g_res.level.tile_attributes1[tile_num]; + if (al != 0) { + obj->data.p.y_velocity = -obj->data.p.y_velocity; + obj->data.p.y_velocity >>= 1; + const bool x_direction = (obj->x_velocity < 0); + const int dx = (obj->x_velocity < 0) ? -8 : 8; + obj->x_velocity -= dx; + if (x_direction != (obj->x_velocity < 0)) { + obj->x_velocity = 0; + } + } + } else { + bh -= 1; + const int tile_num = level_get_tile(bh * 256 + bl); + uint8_t al = g_res.level.tile_attributes0[tile_num]; + if (al != 0) { + obj->x_velocity = -obj->x_velocity; + obj->x_pos += obj->x_velocity >> 4; + } + } + if (g_vars.level_draw_counter & 1) { + continue; + } + if (obj->x_velocity == 0) { + continue; + } + if (num < 73) { + obj->spr_num = num + 1; + } else if (num == 73) { + obj->spr_num = 70; + } + } +} + +static void level_update_objects_bonus_scores() { + for (int i = 0; i < 16; ++i) { + struct object_t *obj = &g_vars.objects_tbl[75 + i]; + if (obj->spr_num != 0xFFFF) { + --obj->y_pos; + --obj->data.t.counter; + if (obj->data.t.counter == 0) { + obj->spr_num = 0xFFFF; + } + } + } +} + +static bool level_update_objects_decors_helper(struct object_t *obj) { + if (g_vars.level_force_x_scroll_flag != 0) { + return false; + } + if (obj->y_pos <= g_vars.objects_tbl[1].y_pos) { + return false; + } + const int prev_y_pos = g_vars.objects_tbl[1].y_pos; + if (g_vars.level_current_object_decor_y_pos >= 0) { + g_vars.objects_tbl[1].y_pos += g_vars.level_current_object_decor_y_pos; + } + const int prev_spr_num = g_vars.objects_tbl[1].spr_num; + g_vars.objects_tbl[1].spr_num = 7; + if (!level_objects_collide(&g_vars.objects_tbl[1], obj)) { + return false; + } + g_vars.objects_tbl[1].y_pos = prev_y_pos; + g_vars.objects_tbl[1].spr_num = prev_spr_num; + g_vars.level_force_x_scroll_flag = 1; + g_vars.objects_tbl[1].x_friction = 0; + g_vars.objects_tbl[1].x_pos += g_vars.level_current_object_decor_x_pos; + level_player_reset(); + const int y = obj->y_pos - spr_size_tbl[(obj->spr_num & 0x1FFF) * 2 + 1]; + if (g_vars.objects_tbl[1].y_pos > y) { + g_vars.objects_tbl[1].y_pos = y + 1; + g_vars.objects_tbl[1].data.p.y_velocity = 1; + } else { + g_vars.objects_tbl[1].data.p.y_velocity = g_vars.level_current_object_decor_y_pos << 4; + } + return true; +} + +static void level_update_objects_decors() { + g_vars.level_force_x_scroll_flag = 0; + int count = 7; + struct object_t *obj = &g_vars.objects_tbl[91]; + for (int i = 0; i < MAX_LEVEL_TRIGGERS; ++i) { + struct level_trigger_t *trigger = &g_res.level.triggers_tbl[i]; + if (trigger->spr_num == 0xFFFF) { + continue; + } + if ((trigger->flags & 15) == 8) { + g_vars.level_current_object_decor_x_pos = 0; + trigger->y_pos -= trigger->unkB; + if (trigger->state == 0) { + int a = trigger->unkB - 8; + if (a < 0) { + a = 0; + trigger->unk7 = 0; + } + trigger->unkB = a; + } + if (trigger->flags & 0x40) { + if (trigger->unk7 != 0 && --trigger->counter == 0) { + trigger->state = 1; + trigger->unk7 = 0; + } + } else { + if (trigger->state == 1) { + int y = trigger->unk7; + if (y < 192) { + trigger->unk7 += 8; + } + y >>= 4; + g_vars.level_current_object_decor_y_pos = y; + trigger->unkB += y; + int bx = trigger->x_pos >> 4; + int dx = (trigger->y_pos + trigger->unkB) >> 4; + int ax = g_vars.tilemap_h - 1 - dx; + if (ax < 0) { + ax = -ax; + if (ax >= 3) { + trigger->state = 2; + trigger->counter = 22; + } + } else { + const uint8_t tile_num = level_get_tile((dx << 8) | bx); + const uint8_t tile_attr1 = g_res.level.tile_attributes1[tile_num]; + if (tile_attr1 != 0 && tile_attr1 != 6) { + trigger->state = 2; + trigger->counter = 22; + } + } + } else { + if (trigger->state == 2 && (trigger->flags & 0x40) == 0 && --trigger->counter == 0) { + trigger->state = 0; + trigger->counter = trigger->unk9; + } + } + } + trigger->y_pos += trigger->unkB; + } else { + g_vars.level_current_object_decor_x_pos = 0; + g_vars.level_current_object_decor_y_pos = 0; + if (trigger->unkE != 0 || trigger->unk7 < 0 || (trigger->flags & 0xC0) != 0) { + if (trigger->unk7 > trigger->unkE) { + ++trigger->unkE; + } else if (trigger->unk7 < trigger->unkE) { + --trigger->unkE; + } + int al = trigger->unkE; + int ah = trigger->unkE; + int dl = trigger->flags & 7; + if (dl == 0) { + al = 0; + ah = -ah; + } else if (dl == 1) { + ah = -ah; + } else if (dl == 2) { + ah = 0; + } + if (dl != 3) { + if (dl == 4) { + al = 0; + } else if (dl == 5) { + al = -al; + } else if (dl == 6) { + al = -al; + ah = 0; + } else if (dl == 7) { + al = -al; + ah = -ah; + } + } + dl = ah; + g_vars.level_current_object_decor_x_pos = (int8_t)al; + trigger->x_pos += al; + g_vars.level_current_object_decor_y_pos = (int8_t)dl; + trigger->y_pos += dl; + if (trigger->unk7 == trigger->unkE) { + } + } + } + const int x_pos = trigger->x_pos - (g_vars.tilemap_x << 4); + if (x_pos >= TILEMAP_SCREEN_W + 32 || x_pos <= -32) { + continue; + } + const int y_pos = trigger->y_pos - (g_vars.tilemap_y << 4); + if (y_pos >= TILEMAP_SCREEN_H + 32 || y_pos <= -32) { + continue; + } + obj->x_pos = trigger->x_pos; + obj->y_pos = trigger->y_pos; + obj->spr_num = trigger->spr_num; + obj->data.t.ref = trigger; + if (g_vars.objects_tbl[1].data.p.y_velocity > -16) { + trigger->flags |= 0x40; + if (level_update_objects_decors_helper(obj)) { + ++obj; + --count; + if (count == 0) { + return; + } + continue; + } + } + trigger->flags &= ~0x40; + obj->y_pos -= 2; + ++obj; + --count; + if (count == 0) { + return; + } + } + do { + obj->spr_num = 0xFFFF; + ++obj; + } while (--count != 0); +} + +static void level_update_player_x_velocity() { + bool left = false; + int x_vel = g_vars.objects_tbl[1].x_velocity; + if (x_vel < 0) { + x_vel = -x_vel; + left = true; + } + x_vel -= 12 >> g_vars.objects_tbl[1].x_friction; + if (x_vel < 0) { + x_vel = 0; + } + if (left) { + x_vel = -x_vel; + } + g_vars.objects_tbl[1].x_velocity = x_vel; +} + +static void level_update_player_hdir_x_velocity(int x_vel) { + int16_t f = 0; + if (g_vars.input.key_hdir) { + f = g_vars.objects_tbl[1].data.p.hdir << 4; + } + f >>= g_vars.objects_tbl[1].x_friction; + const int16_t dx = g_vars.objects_tbl[1].x_velocity + f; + if (dx < x_vel) { + x_vel = -x_vel; + if (dx > x_vel) { + x_vel = dx; + } + } + g_vars.objects_tbl[1].x_velocity = x_vel; +} + +static void level_update_screen_x_velocity() { + g_vars.objects_tbl[1].x_velocity -= g_vars.snow.value >> 3; + if (g_vars.objects_tbl[1].x_velocity < -96) { + g_vars.objects_tbl[1].x_velocity = -96; + } +} + +static void level_update_player_y_velocity(int y_vel) { + int f = 16; + if (g_vars.player_gravity_flag == 1) { + f = 4; + y_vel >>= 3; + } + const int y = g_vars.objects_tbl[1].data.p.y_velocity + f; + if (y < y_vel) { + y_vel = y; + } + g_vars.objects_tbl[1].data.p.y_velocity = y_vel; +} + +static void level_update_player_jump() { + level_update_player_hdir_x_velocity(80); + level_update_player_y_velocity(192); + uint8_t num; + if (g_vars.player_flying_flag != 0) { + num = 45; + if (g_vars.objects_tbl[1].data.p.y_velocity >= 0) { + ++num; + } + } else { + if (g_vars.objects_tbl[1].data.p.y_velocity <= 0) { + return; + } + g_vars.player_nojump_counter = 6; + if (g_vars.player_anim_0x40_flag != 0) { + return; + } + num = 12; + if (g_vars.player_jumping_counter >= 12) { + ++num; + } + } + g_vars.objects_tbl[1].spr_num = num | ((g_vars.objects_tbl[1].data.p.hdir < 0) ? 0x8000 : 0); +} + +static const uint8_t *level_update_player_anim1_num(uint8_t num, uint16_t anim) { + assert((anim & 1) == 0); + assert(anim == num * 2); + if (g_vars.objects_tbl[1].data.p.special_anim_num != num) { + g_vars.objects_tbl[1].data.p.special_anim_num = num; + g_vars.objects_tbl[1].data.p.anim = object_anim_tbl[anim / 2]; + } + return g_vars.objects_tbl[1].data.p.anim; +} + +static const uint8_t *level_update_player_anim2_num(uint8_t num, uint16_t anim) { + assert((anim & 1) == 0); + assert(anim == num * 2); + if (g_vars.objects_tbl[1].data.p.current_anim_num != num) { + g_vars.objects_tbl[1].data.p.current_anim_num = num; + g_vars.objects_tbl[1].data.p.anim = object_anim_tbl[anim / 2]; + } + return g_vars.objects_tbl[1].data.p.anim; +} + +static struct object_t *level_find_object_club_projectile() { + for (int i = 0; i < 4; ++i) { + struct object_t *obj = &g_vars.objects_tbl[2 + i]; + if (obj->spr_num == 0xFFFF) { + return obj; + } + } + return 0; +} + +static void level_update_player_anim_3_6_7(uint8_t al) { + const uint8_t *p = level_update_player_anim2_num(al, al * 2); + level_update_object_anim(p); + level_update_player_x_velocity(); + if (g_vars.player_moving_counter < UCHAR_MAX) { + ++g_vars.player_moving_counter; + } + al = club_anim_tbl[g_vars.player_club_type].power; + if (g_vars.player_club_powerup_duration != 0) { + al <<= 2; + } + g_vars.player_club_power = al; + g_vars.player_anim_0x40_flag = (g_vars.player_current_anim_type & 0x40) ^ 0x40; + if (g_vars.player_anim_0x40_flag == 0) { + g_vars.player_club_anim_duration = club_anim_tbl[g_vars.player_club_type].a; + int16_t dy = 0; + al = g_vars.objects_tbl[1].data.p.current_anim_num; + if (al != 6) { + dy = -32; + if (al != 3) { + dy = -48; + if (g_vars.level_draw_counter & 3) { + level_init_object_hit_from_player_pos(); + } + } + } + if (g_vars.level_force_x_scroll_flag == 0) { + /* off the ground when using the club */ + g_vars.objects_tbl[1].data.p.y_velocity += dy; + } + al = club_anim_tbl[g_vars.player_club_type].c; + if (al & 1) { + /* club with projectile */ + struct object_t *obj = level_find_object_club_projectile(); + if (obj) { + obj->x_friction = (al >> 1) & 3; + const uint8_t *p = club_anim_tbl[g_vars.player_club_type].anim; + do { + p += 2; + } while (READ_LE_UINT16(p) != 0x55AA); + p += 6; + obj->data.c.anim = p; + int num = READ_LE_UINT16(p); + int16_t y_vel = READ_LE_UINT16(p - 2); + int16_t x_vel = READ_LE_UINT16(p - 4); + + obj->data.c.y_velocity = y_vel; + if (g_vars.objects_tbl[1].data.p.hdir < 0) { + num |= 0x8000; + x_vel = -x_vel; + } + obj->spr_num = num; + obj->x_velocity = x_vel; + obj->x_pos = g_vars.objects_tbl[1].x_pos + (x_vel >> 4); + obj->y_pos = g_vars.objects_tbl[1].y_pos + (y_vel >> 4); + g_vars.objects_tbl[0].spr_num = 0xFFFF; + return; + } + } + if (g_vars.player_jumping_counter != 0) { + g_vars.objects_tbl[0].spr_num = 0xFFFF; + return; + } + } + const uint16_t spr_num = g_vars.objects_tbl[1].spr_num; + p = club_anim_tbl[g_vars.player_club_type].anim; + while (READ_LE_UINT16(p) != 0x55AA) { + if (READ_LE_UINT16(p) == (spr_num & 0x1FFF)) { + int16_t num = READ_LE_UINT16(p + 2); + int16_t x = READ_LE_UINT16(p + 4); + if (spr_num & 0x8000) { + x = -x; + } + int16_t y = READ_LE_UINT16(p + 6); + g_vars.objects_tbl[0].spr_num = num | (spr_num & 0x8000); + g_vars.objects_tbl[0].x_pos = g_vars.objects_tbl[1].x_pos + (g_vars.objects_tbl[1].x_velocity >> 4) - x; + g_vars.objects_tbl[0].y_pos = g_vars.objects_tbl[1].y_pos + (g_vars.objects_tbl[1].data.p.y_velocity >> 4) - y; + break; + } + p += 8; + } +} + +static void level_update_player_anim_0(uint8_t al) { + if (g_vars.player_anim_0x40_flag != 0) { + level_update_player_anim_3_6_7(g_vars.objects_tbl[1].data.p.current_anim_num); + return; + } + g_vars.player_unk_counter1 = 0; + level_update_screen_x_velocity(); + level_update_player_x_velocity(); + if (g_vars.level_force_x_scroll_flag == 0 && g_vars.objects_tbl[1].data.p.y_velocity != 0) { + if (g_vars.player_anim2_counter > 4) { + level_update_screen_x_velocity(); + } + return; + } + const int x_vel = abs(g_vars.objects_tbl[1].x_velocity); + if (x_vel >= 8) { + const bool same_direction = ((g_vars.objects_tbl[1].spr_num & 0x8000) != 0) && (g_vars.objects_tbl[1].x_velocity < 0); + if (same_direction) { + const uint8_t *anim = level_update_player_anim1_num(18, 36); + level_update_object_anim(anim); + if ((g_vars.level_draw_counter & 3) == 0) { + level_init_object_hit_from_player_pos(&g_vars.objects_tbl[1]); + } + g_vars.objects_tbl[1].data.p.current_anim_num = 0; + return; + } + + } else if (x_vel == 0) { + if (g_vars.player_moving_counter >= 30) { + if ((g_vars.input.key_right & g_vars.input.key_left) == 0) { + /* player stopped moving, show exhausted animation */ + g_vars.player_moving_counter -= 3; + const uint8_t *anim = level_update_player_anim1_num(16, 32); + level_update_object_anim(anim); + g_vars.objects_tbl[1].data.p.current_anim_num = 0; + return; + } + } else { + if ((g_vars.input.key_right & g_vars.input.key_left) != 0) { + const uint8_t *anim = level_update_player_anim1_num(19, 38); + level_update_object_anim(anim); + if ((g_res.level.scrolling_mask & 2) == 0 && g_vars.level_noscroll_flag == 0) { + const int dx = (g_vars.objects_tbl[1].x_pos >> 4) - g_vars.tilemap_x; + if ((g_vars.objects_tbl[1].spr_num & 0x8000) == 0) { + if (dx > 2) { + level_adjust_hscroll_right(); + } + } else { + if (dx < 17) { + level_adjust_hscroll_left(); + } + } + } + g_vars.objects_tbl[1].data.p.current_anim_num = 0; + return; + } + /* show idle animation */ + static const uint16_t data[] = { 16, 96, 128, 176, 256, 512 }; + const uint16_t timer = timer_get_counter() & 511; + int offset = 0; + while (1) { + if (timer < data[offset]) { + break; + } + ++offset; + if (timer < data[offset]) { + const uint8_t *anim = level_update_player_anim1_num(17, 34); + level_update_object_anim(anim); + g_vars.objects_tbl[1].data.p.current_anim_num = 0; + return; + } + ++offset; + } + } + } + const uint8_t *p = object_anim_tbl[al]; + g_vars.objects_tbl[1].data.p.anim = p; + g_vars.objects_tbl[1].spr_num = READ_LE_UINT16(p) | ((g_vars.objects_tbl[1].data.p.hdir < 0) ? 0x8000 : 0); + g_vars.objects_tbl[1].data.p.special_anim_num = 0; + g_vars.objects_tbl[1].data.p.current_anim_num = 0; +} + +static void level_update_player_anim_1(uint8_t al) { + if (g_vars.player_anim_0x40_flag != 0) { + level_update_player_anim_3_6_7(g_vars.objects_tbl[1].data.p.current_anim_num); + return; + } + const int x_vel = abs(g_vars.objects_tbl[1].x_velocity); + if (x_vel >= 64 && g_vars.player_flying_flag != 0) { + if (g_vars.player_unk_counter1 < UCHAR_MAX) { + ++g_vars.player_unk_counter1; + } + if (g_vars.player_unk_counter1 == 23) { + level_init_object_hit_from_player_pos(); + } + } + if (g_vars.player_moving_counter < UCHAR_MAX) { + ++g_vars.player_moving_counter; + } + level_update_player_hdir_x_velocity(80); + level_update_screen_x_velocity(); + const uint8_t *p = level_update_player_anim2_num(al, al * 2); + level_update_object_anim(p); +} + +static void level_update_player_anim_2(uint8_t al) { + if (g_vars.player_anim_0x40_flag != 0) { + level_update_player_anim_3_6_7(g_vars.objects_tbl[1].data.p.current_anim_num); + return; + } + if (g_vars.player_nojump_counter != 0) { + level_update_player_anim_0(al); + return; + } + g_vars.level_force_x_scroll_flag = 0; + uint8_t value = g_vars.player_anim2_counter; + ++g_vars.player_anim2_counter; + if (value < 9) { + static const int8_t y_tbl[] = { -65, -51, -35, -20, -10, -5, -2, -1, 0 }; + int16_t ax = y_tbl[value]; + if (g_vars.player_flying_flag != 0) { + ax >>= 1; + } + g_vars.objects_tbl[1].data.p.y_velocity += ax; + } else { + level_update_player_y_velocity(192); + } + if (g_vars.objects_tbl[1].x_velocity >= 48) { + level_update_player_x_velocity(); + } else { + level_update_player_hdir_x_velocity(48); + } + const uint8_t *p = level_update_player_anim2_num(2, 4); + level_update_object_anim(p); + level_update_screen_x_velocity(); + level_update_screen_x_velocity(); +} + +static void level_update_player_club_power() { + if (g_vars.player_club_powerup_duration <= 48) { + g_vars.player_club_powerup_duration += 2; + } +} + +static void level_update_player_anim_4(uint8_t al) { + if (g_vars.player_anim_0x40_flag != 0) { + level_update_player_anim_3_6_7(g_vars.objects_tbl[1].data.p.current_anim_num); + return; + } + g_vars.player_moving_counter = 0; + g_vars.player_action_counter = 4; + level_update_player_club_power(); + const int x_vel = abs(g_vars.objects_tbl[1].x_velocity); + if (x_vel > 32) { + level_update_player_anim_0(al); + } else { + level_update_player_hdir_x_velocity(32); + const uint8_t *p = level_update_player_anim2_num(al, al * 2); + level_update_object_anim(p); + } +} + +static void level_update_player_anim_5(uint8_t al) { + if (g_vars.player_anim_0x40_flag != 0) { + level_update_player_anim_3_6_7(g_vars.objects_tbl[1].data.p.current_anim_num); + return; + } + g_vars.player_unk_counter1 = 0; + g_vars.player_action_counter = 4; + level_update_player_club_power(); + const uint8_t *p = level_update_player_anim2_num(al, al * 2); + level_update_object_anim(p); + level_update_player_x_velocity(); + level_update_player_club_power(); +} + +static void level_update_player_decor() { + const int y_pos = (g_vars.objects_tbl[1].y_pos >> 4) - 1; + const int spr_num = g_vars.objects_tbl[1].spr_num & 0x1FFF; + int spr_h = spr_size_tbl[spr_num * 2 + 1]; + int dx = 9; + if (g_vars.objects_tbl[1].x_velocity < 0) { + dx = -9; + } else if (g_vars.objects_tbl[1].x_velocity == 0) { + dx = 0; + } + const int x_pos = g_vars.objects_tbl[1].x_pos >> 4; + const int player_ypos_diff = abs((g_vars.objects_tbl[1].y_pos >> 4) - g_vars.tilemap_y); + if (player_ypos_diff > (TILEMAP_SCREEN_H / 16)) { + level_update_tile_type_2(); + } else { + const int player_xpos_diff = abs((g_vars.objects_tbl[1].x_pos >> 4) - g_vars.tilemap_x); + if (player_xpos_diff > (TILEMAP_SCREEN_W / 16)) { + level_update_tile_type_2(); + } else if ((g_res.level.scrolling_mask & 4) != 0 && g_vars.objects_tbl[1].y_pos < (g_vars.tilemap_y << 4)) { + level_update_tile_type_2(); + } else if (g_vars.objects_tbl[1].y_pos >= 0 && g_vars.objects_tbl[1].y_pos > ((g_vars.tilemap_h + 1) << 4)) { + level_update_tile_type_2(); + } + } + int bx = (y_pos << 8) | x_pos; + g_vars.player_tile_flags = 0; + level_update_tile0(bx); + if (g_vars.player_tile_flags == 1) { + if (g_vars.level_force_x_scroll_flag != 0) { + level_player_reset(); + g_vars.player_jumping_counter = 0; + } else { + level_update_player_jump(); + if (g_vars.objects_tbl[1].data.p.y_velocity > 0) { + ++g_vars.player_jumping_counter; + } + } + } else { + g_vars.player_jumping_counter = 0; + } + if (g_vars.objects_tbl[1].y_pos <= 0) { + return; + } + bx = ((dx + g_vars.objects_tbl[1].x_pos) >> 4) + (y_pos << 8); + level_update_tile1(bx); + while (1) { + bx -= 0x100; + if (bx < 0) { + break; + } + spr_h -= 16; + if (spr_h <= 0) { + break; + } + level_update_tile2(bx); + } +} + +static void level_update_player_flying() { + if (g_vars.player_flying_flag == 0) { + return; + } + print_warning("Unimplemented level_update_player_flying"); +} + +static void level_update_player() { + g_vars.objects_tbl[0].spr_num = 0xFFFF; + if (g_vars.input.keystate[0x3B]) { + if (g_vars.restart_level_flag == 0) { + level_player_die(); + g_vars.restart_level_flag = 1; + return; + } + } + if (g_vars.input.keystate[0x3C]) { + g_vars.player_death_flag = 1; + return; + } + input_check_ctrl_alt_w(); + g_vars.input.key_vdir = g_vars.input.key_up | g_vars.input.key_down; + g_vars.input.key_hdir = g_vars.input.key_left | g_vars.input.key_right; + if (g_vars.input.key_right != 0) { + if (g_vars.input.key_left == 0) { + if (g_vars.objects_tbl[1].data.p.hdir != 1) { + g_vars.objects_tbl[1].data.p.hdir = 1; + g_vars.player_update_counter = 0; + } + } + } else { + if (g_vars.input.key_left != 0) { + if (g_vars.objects_tbl[1].data.p.hdir != -1) { + g_vars.objects_tbl[1].data.p.hdir = -1; + g_vars.player_update_counter = 0; + } + } + } + uint16_t mask = 0; + mask |= g_vars.input.key_right & 1; + mask <<= 1; + mask |= g_vars.input.key_left & 1; + mask <<= 1; + mask |= g_vars.input.key_up & 1; + mask <<= 1; + mask |= g_vars.input.key_down & 1; + mask <<= 1; + mask |= g_vars.input.key_space & 1; + if (g_vars.player_club_anim_duration != 0) { + mask = 0; + } + uint8_t dl = g_vars.objects_tbl[1].hit_counter; + g_vars.objects_tbl[0].hit_counter = dl; + uint8_t al = player_anim_lut[mask]; + if (dl >= 22) { + al = 8; + } + if (g_vars.objects_tbl[1].data.p.current_anim_num != al) { + g_vars.player_update_counter = 0; + g_vars.objects_tbl[1].data.p.special_anim_num = 0; + } + if (g_vars.player_update_counter < USHRT_MAX) { + ++g_vars.player_update_counter; + } + if (g_vars.player_flying_flag != 0) { + mask += 0x40; + if ((g_vars.player_flying_flag &= 1) != 0) { + if (g_vars.input.key_up) { + if (g_vars.player_flying_anim_index == 6) { + if (g_vars.objects_tbl[1].data.p.y_velocity > 16) { + g_vars.player_flying_flag |= 2; + } + } else { + ++g_vars.player_flying_anim_index; + } + if (g_vars.player_flying_counter != 0) { + --g_vars.player_flying_counter; + if (g_vars.player_flying_anim_index >= 4) { + g_vars.objects_tbl[1].data.p.y_velocity = -64; + } + goto update_pos; + } + } + if (g_vars.input.key_down) { + if (g_vars.player_flying_counter != 0) { + --g_vars.player_flying_counter; + } + goto update_pos; + } else { + if (g_vars.player_flying_anim_index != 0) { + --g_vars.player_flying_anim_index; + } + g_vars.player_flying_flag |= 2; + if (g_vars.player_flying_anim_index <= 1) { + const int x = abs(g_vars.objects_tbl[1].x_velocity >> 4); + if (g_vars.player_flying_counter < x * 5) { + g_vars.player_flying_counter += x; + } + } + goto update_pos; + } + } else { + if (g_vars.objects_tbl[1].data.p.y_velocity > 160) { + g_vars.player_flying_flag = 1; + } + } + } + switch (al) { + case 0: + level_update_player_anim_0(al); + break; + case 1: + level_update_player_anim_1(al); + break; + case 2: + level_update_player_anim_2(al); + break; + case 3: + case 6: + case 7: + level_update_player_anim_3_6_7(al); + break; + case 4: + level_update_player_anim_4(al); + break; + case 5: + level_update_player_anim_5(al); + break; + default: + print_warning("Unhandled anim_lut %d mask %d", al, mask); + break; + } +update_pos: + { + const int x_pos = (g_vars.objects_tbl[1].x_velocity >> 4) + g_vars.objects_tbl[1].x_pos; + const int end_x = (g_res.level.tilemap_w + 20) << 4; + if (end_x > x_pos && x_pos >= 8 && x_pos < 511 * 8) { + g_vars.objects_tbl[1].x_pos = x_pos; + } + } + g_vars.objects_tbl[1].y_pos += g_vars.objects_tbl[1].data.p.y_velocity >> 4; + level_update_player_decor(); + level_update_player_flying(); + if (g_vars.player_club_powerup_duration > 0) { + --g_vars.player_club_powerup_duration; + } + if (g_vars.player_club_anim_duration > 0) { + --g_vars.player_club_anim_duration; + } + if (g_vars.player_platform_counter > 0) { + --g_vars.player_platform_counter; + } + if (g_vars.restart_level_flag > 0) { + --g_vars.restart_level_flag; + } + if (g_vars.player_action_counter > 0) { + --g_vars.player_action_counter; + } + if (g_vars.player_bonus_letters_blinking_counter > 0) { + --g_vars.player_bonus_letters_blinking_counter; + } + if (g_vars.player_monsters_unk_counter > 0) { + --g_vars.player_monsters_unk_counter; + } +} + +static void level_clear_item(struct object_t *obj) { + struct level_item_t *item = obj->data.t.ref; + if (item) { + assert(item >= &g_res.level.items_tbl[0] && item < &g_res.level.items_tbl[MAX_LEVEL_ITEMS]); + item->spr_num = 0xFFFF; + } +} + +static void level_update_player_collision() { + struct object_t *obj_player = &g_vars.objects_tbl[1]; + if (g_vars.objects_tbl[1].hit_counter == 0) { + for (int i = 0; i < 12; ++i) { + struct object_t *obj = &g_vars.objects_tbl[11 + i]; + if (obj->spr_num == 0xFFFF || (obj->spr_num & 0x2000) == 0) { + continue; + } + print_warning("Unhandled level_update_player_collision 6A2F"); + } + } + for (int i = 0; i < 52; ++i) { + struct object_t *obj = &g_vars.objects_tbl[23 + i]; + if (obj->spr_num == 0xFFFF) { + continue; + } + if (obj->data.t.counter > 188) { + continue; + } + if ((obj->spr_num & 0x2000) == 0) { + continue; + } + if (!level_objects_collide(obj, obj_player)) { + continue; + } + g_vars.level_current_bonus_spr_num = obj->spr_num; + const int num = (obj->spr_num & 0x1FFF) - 53; + obj->spr_num = 0xFFFF; + if (num == 226) { + g_vars.level_completed_flag = 1; + return; + } else if (num == 228) { /* checkpoint */ + g_vars.tilemap_start_x_pos = g_vars.objects_tbl[1].x_pos; + g_vars.tilemap_start_y_pos = g_vars.objects_tbl[1].y_pos; + for (int i = 0; i < MAX_LEVEL_ITEMS; ++i) { + struct level_item_t *item = &g_res.level.items_tbl[i]; + if (item->spr_num == 280) { + ++item->spr_num; + } + } + struct level_item_t *item = obj->data.t.ref; + if (item) { + assert(item >= &g_res.level.items_tbl[0] && item < &g_res.level.items_tbl[MAX_LEVEL_ITEMS]); + item->spr_num -= 1; + } + } else if (num == 174) { + ++g_vars.player_lifes; + play_sound(4); + level_add_object75_score(&g_vars.objects_tbl[1], 227); + level_clear_item(obj); + } else if (num == 13) { + play_sound(8); + g_vars.player_club_type = 0; + level_clear_item(obj); + } else if (num == 182) { + play_sound(8); + g_vars.player_club_type = 1; + level_clear_item(obj); + } else if (num == 44) { + play_sound(8); + g_vars.player_club_type = 2; + level_clear_item(obj); + } else if (num == 224) { + play_sound(8); + g_vars.player_club_type = 3; + level_clear_item(obj); + } else if (num <= 20) { + play_sound(8); + level_clear_item(obj); + ++g_vars.bonus_energy_counter; + if (g_vars.bonus_energy_counter >= 6) { + if (g_vars.player_energy != 3) { + ++g_vars.player_energy; + g_vars.bonus_energy_counter = 0; + level_add_object75_score(obj, 226); + } + } + } else if (num <= 44) { + play_sound(8); + const int index = num - 39; + if (index >= 0 && index <= 4) { + g_vars.player_bonus_letters_mask |= (1 << index); + level_clear_item(obj); + } + } else if (num <= 50) { + play_sound(8); + g_vars.player_utensils_mask |= 1 << (num - 45); + for (int j = 0; j < MAX_LEVEL_ITEMS; ++j) { + struct level_item_t *item = &g_res.level.items_tbl[j]; + if (item->spr_num == 278) { /* end of level semaphore */ + ++item->spr_num; + } + } + level_clear_item(obj); + } else if (num <= 64) { /* food */ + play_sound(4); + if (obj->data.t.unkE >= 128) { + obj->data.t.unkE = -obj->data.t.unkE; + int x = 32; + if ((random_get_number() & 1) != 0) { + x = -x; + g_vars.player_platform_counter = 7; + } + obj->x_velocity = x; + obj->spr_num = g_vars.level_current_bonus_spr_num; + } else { + const int index = num - 57; + ++g_vars.level_items_count_tbl[index]; + ++g_vars.level_items_total_count; + level_add_object75_score(obj, score_spr_lut[index] + 74); + } + } else if (num <= 74) { + play_sound(8); + if (g_vars.player_flying_flag == 0) { + g_vars.player_flying_flag = 1; + g_vars.objects_tbl[1].data.p.current_anim_num = 0xFF; + level_clear_item(obj); + } + } else if (num <= 166) { + play_sound(8); + if (num == 145) { /* tap */ + for (int i = 0; i < 20; ++i) { + g_vars.collision_tbl[i].x_pos = _undefined; + } + } + const int index = num - 57; + ++g_vars.level_items_count_tbl[index]; + ++g_vars.level_items_total_count; + level_add_object75_score(obj, score_spr_lut[index] + 74); + } else if (num == 167 || num == 168) { + play_sound(1); + g_vars.objects_tbl[1].hit_counter = 44; + g_vars.objects_tbl[1].data.p.special_anim_num = 0; + g_vars.objects_tbl[1].data.p.current_anim_num = 8; + g_vars.level_current_bonus_x_pos = g_vars.objects_tbl[1].x_pos; + g_vars.level_current_bonus_y_pos = g_vars.objects_tbl[1].y_pos - 48; + const int count = g_vars.player_energy * 6 + g_vars.bonus_energy_counter; + if (count != 0) { + g_vars.player_energy = 0; + g_vars.bonus_energy_counter = 0; + g_vars.level_current_bonus_spr_num = 0x2046; + level_add_object23_bonus(48, -128, count); + } + g_vars.player_platform_counter = 7; + level_clear_item(obj); + level_add_object75_score(obj, 228); + } else if (num == 173) { + play_sound(4); + if (g_vars.player_energy < 3) { + ++g_vars.player_energy; + level_clear_item(obj); + } + } else if (num == 169) { + play_sound(0); + print_warning("Unhandled bonus num 169"); + } else if (num == 170) { /* bomb */ + play_sound(0); + print_warning("Unhandled bonus num 170"); + } else if (num == 181) { /* light off */ + if (g_vars.light.state != 1) { + play_sound(1); + g_vars.light.palette_flag2 = 0; + g_vars.light.palette_flag1 = 1; + g_vars.light.palette_counter = 0; + g_vars.light.state = 1; + } + level_clear_item(obj); + } else if (num == 180) { /* light on */ + if (g_vars.light.state != 0) { + g_vars.light.palette_flag2 = 0; + g_vars.light.palette_flag1 = 1; + g_vars.light.palette_counter = 0; + g_vars.light.state = 0; + } + level_clear_item(obj); + } + } +} + +static void level_update_objects_items() { + struct object_t *obj = &g_vars.objects_tbl[55]; + int count = 20; + for (int i = 0; i < MAX_LEVEL_ITEMS; ++i) { + struct level_item_t *level_item = &g_res.level.items_tbl[i]; + if (level_item->spr_num == 0xFFFF) { + continue; + } + const int16_t x = (level_item->x_pos >> 4) - g_vars.tilemap_x; + if (x < 0 || x > 22) { + continue; + } + const int16_t y = (level_item->y_pos >> 4) - g_vars.tilemap_y; + if (y < 0 || y > 43) { + continue; + } + obj->x_pos = level_item->x_pos; + int16_t dy_pos = 0; + if (g_vars.level_draw_counter & 1) { + dy_pos = level_item->y_delta + 1; + level_item->y_delta = dy_pos; + if (dy_pos >= 4) { + dy_pos = 1 - dy_pos; + level_item->y_delta = dy_pos; + } + } + level_item->y_pos += dy_pos; + obj->y_pos = level_item->y_pos; + obj->spr_num = level_item->spr_num; + obj->data.t.ref = level_item; + ++obj; + if (--count == 0) { + return; + } + } + do { + obj->spr_num = 0xFFFF; + ++obj; + } while (--count != 0); +} + +static void level_update_gates() { + if (g_vars.player_action_counter == 0) { + return; + } + if (g_vars.player_flying_flag != 0) { + return; + } + uint16_t pos = level_get_player_tile_pos(); + for (int i = 0; i < MAX_LEVEL_GATES; ++i) { + struct level_gate_t *gate = &g_res.level.gates_tbl[i]; + if (gate->enter_pos == pos) { + g_vars.objects_tbl[1].x_velocity = 0; + g_vars.objects_tbl[1].data.p.y_velocity = 0; + video_transition_close(); + g_vars.tilemap_scroll_dy = 0; + const uint8_t tmp = g_res.level.tilemap_w; + g_res.level.tilemap_w = 256 - 20; + const uint16_t dst_pos = gate->dst_pos; + g_vars.objects_tbl[1].x_pos = (dst_pos & 255) << 4; + g_vars.objects_tbl[1].y_pos = (dst_pos >> 8) << 4; + const uint8_t tilemap_y = gate->tilemap_pos >> 8; + while (g_vars.tilemap_y != tilemap_y) { + if (g_vars.tilemap_y > tilemap_y) { + level_adjust_vscroll_up(16); + } else { + level_adjust_vscroll_down(16); + } + } + const uint8_t tilemap_x = gate->tilemap_pos & 255; + while (g_vars.tilemap_x != tilemap_x) { + if (g_vars.tilemap_x > tilemap_x) { + level_adjust_hscroll_left(); + } else { + level_adjust_hscroll_right(); + } + } + g_res.level.tilemap_w = tmp; + g_vars.level_noscroll_flag = gate->scroll_flag; + g_vars.player_action_counter = 0; + if (g_vars.level_num == 5) { + } + level_draw_tilemap(); + level_update_objects_decors(); + level_update_objects_items(); + level_update_objects_monsters(); + video_transition_open(); + break; + } + } +} + + +static void level_draw_front_tile(uint8_t tile_num, int x, int y) { + const int num = g_res.level.front_tiles_lut[tile_num]; + assert(num < (g_res.frontlen / 128)); + g_sys.render_add_sprite(RENDER_SPR_FG, num, x * 16 - g_vars.tilemap_scroll_dx, y * 16 - g_vars.tilemap_scroll_dy, 0); +} + +static void level_draw_front_tiles() { + if ((g_vars.tile_attr2_flags & 0x40) == 0) { /* no front tiles */ + return; + } + uint16_t offset = (g_vars.tilemap_y << 8) | g_vars.tilemap_x; + for (int y = 0; y < (TILEMAP_SCREEN_H / 16) + 1; ++y) { + for (int x = 0; x < (TILEMAP_SCREEN_W / 16) + 1; ++x) { + const uint8_t tile_num = level_get_tile(offset + x); + if (g_res.level.tile_attributes2[tile_num] & 0x40) { + level_draw_front_tile(tile_num, x, y); + } + } + offset += 256; + } +} + +static void level_clear_items_spr_num_tbl() { + memset(g_vars.level_items_count_tbl, 0, sizeof(g_vars.level_items_count_tbl)); + g_vars.level_items_total_count = 0; +} + +static bool level_update_objects_anim() { + if (g_vars.level_completed_flag == 1) { + level_completed_bonuses_animation(); + ++g_vars.level_num; + level_reset(); + level_clear_items_spr_num_tbl(); + } else if (g_vars.restart_level_flag == 1) { + level_player_death_animation(); + if (g_vars.player_death_flag == 0) { + uint16_t spr_num_tbl[MAX_LEVEL_ITEMS]; + for (int i = 0; i < MAX_LEVEL_ITEMS; ++i) { + spr_num_tbl[i] = g_res.level.items_tbl[i].spr_num; + } + for (int i = 0; i < MAX_LEVEL_BONUSES; ++i) { + g_vars.level_bonuses_count_tbl[i] = (g_res.level.bonuses_tbl[i].pos != 0xFFFF) ? 1 : 0; + } + level_reset(); + g_vars.objects_tbl[1].x_pos = g_vars.tilemap_start_x_pos; + g_vars.objects_tbl[1].y_pos = g_vars.tilemap_start_y_pos; + level_init_tilemap(); + for (int i = 0; i < MAX_LEVEL_ITEMS; ++i) { + const uint16_t spr_num = spr_num_tbl[i]; + const uint16_t num = spr_num - 53; + if (num == 13 || num == 44 || num == 65 || num == 169 || num == 170 || num == 182 || num == 224) { + continue; + } + g_res.level.items_tbl[i].spr_num = spr_num; + } + for (int i = 0; i < MAX_LEVEL_BONUSES; ++i) { + if (g_vars.level_bonuses_count_tbl[i] == 0) { + g_res.level.bonuses_tbl[i].pos = 0xFFFF; + } + } + level_clear_items_spr_num_tbl(); + return true; + } + } else if (g_vars.player_death_flag == 1) { + level_player_death_animation(); + } else { + return true; + } + g_vars.objects_tbl[1].spr_num = 13; + g_vars.level_num = 0; + g_vars.score = 0; + g_vars.score_extra_life = 0; + level_clear_items_spr_num_tbl(); + return false; +} + +static void level_clear_panel() { + g_vars.panel.score = 0xFFFFFFFF; + g_vars.panel.lifes = 0xFF; + g_vars.panel.energy = 0xFF; + g_vars.panel.bonus_letters_mask = 0xFF; + const uint8_t *src = g_res.allfonts + 41 * 48; + video_draw_panel(src); +} + +static void level_draw_panel() { + const uint8_t *src = g_res.allfonts + 41 * 48; + video_draw_panel(src); + if (g_vars.player_lifes != g_vars.panel.lifes || _redraw_panel) { + g_vars.panel.lifes = g_vars.player_lifes; + video_draw_panel_number(0x1CED, MIN(g_vars.panel.lifes, 9)); + } + if (g_vars.score != g_vars.panel.score || _redraw_panel) { + g_vars.panel.score = g_vars.score; + int score = g_vars.score * 10; + for (int i = 0; i < 7; ++i) { + const int digit = score % 10; + score /= 10; + video_draw_panel_number(0x1CF1 + (6 - i) * 16 / 8, digit); + } + } + if (g_vars.player_energy != g_vars.panel.energy || _redraw_panel) { + g_vars.panel.energy = g_vars.player_energy; + for (int i = 0; i < g_vars.player_energy; ++i) { + video_draw_panel_number(0x1D01 + i * 16 / 8, 10); + } + if (g_vars.player_energy > 3) { + video_draw_panel_number(0x1D01 + 3 * 16 / 8, 11); + } + } + uint8_t mask; + if (g_vars.player_bonus_letters_blinking_counter != 0) { + g_vars.panel.bonus_letters_mask = 0xFF; + mask = 0x1F; + if ((g_vars.level_draw_counter & 1) == 0) { + mask = 0; + } + } else { + mask = g_vars.player_bonus_letters_mask; + if (g_vars.player_bonus_letters_mask != g_vars.panel.bonus_letters_mask) { + g_vars.panel.bonus_letters_mask = g_vars.player_bonus_letters_mask; + } + } + static const uint16_t panel_bonus_letters_pos_tbl[] = { 0x1C91, 0x1BF2, 0x1CE3, 0x1C6C, 0x1C1D }; + for (int i = 0; i < 5; ++i) { + if ((mask & (1 << i)) != 0) { + video_draw_panel_number(panel_bonus_letters_pos_tbl[i], 12 + i); + } + } +} + +static void level_update_panel() { + int count = g_vars.score / 25000; + count -= g_vars.score_extra_life; + if (count != 0) { + g_vars.score_extra_life += count; + g_vars.player_lifes += count; + level_add_object75_score(&g_vars.objects_tbl[1], 227); + } + level_draw_panel(); +} + +static void level_update_light_palette() { + if ((g_vars.light.palette_flag1 | g_vars.light.palette_flag2) != 0) { + ++g_vars.light.palette_counter; + uint8_t palette[16 * 3]; + const uint8_t *si = palettes_tbl[g_vars.level_num]; + const uint8_t *bx = light_palette_data; + bool changed = false; + if (g_vars.light.palette_flag2 != 0) { + SWAP(si, bx); + } + for (int i = 0; i < 16 * 3; ++i) { + int al = *si++; + const int ah = *bx++; + int dh = g_vars.light.palette_counter; + al -= ah; + if (al < 0) { + al = -al; + dh = -dh; + } + if (al <= g_vars.light.palette_counter) { + al = ah; + } else { + al = si[-1] - dh; + changed = true; + } + palette[i] = al; + } + if (!changed) { + g_vars.light.palette_flag1 = 0; + g_vars.light.palette_flag2 = 0; + } + g_sys.set_screen_palette(palette, 0, 16, 6); + } +} + +static void level_sync() { + update_input(); + g_sys.update_screen(g_res.vga, 1); + g_sys.render_clear_sprites(); + const int diff = (g_vars.timestamp + (1000 / 30)) - g_sys.get_timestamp(); + g_sys.sleep(MAX(diff, 10)); + g_vars.timestamp = g_sys.get_timestamp(); +} + +static void level_draw_objects() { + ++g_vars.level_draw_counter; + for (int i = OBJECTS_COUNT - 1; i >= 0; --i) { + struct object_t *obj = &g_vars.objects_tbl[i]; + if (obj->spr_num == 0xFFFF) { + continue; + } + if (obj->hit_counter != 0) { + --obj->hit_counter; + } + if (obj->hit_counter != 0 && (g_vars.level_draw_counter & 3) != 0) { + continue; + } + const int spr_num = obj->spr_num & 0x1FFF; + const int spr_hflip = ((obj->spr_num & 0x8000) != 0) ? 1 : 0; + const int spr_y_pos = obj->y_pos - ((g_vars.tilemap_y << 4) + g_vars.tilemap_scroll_dy) - spr_size_tbl[2 * spr_num + 1]; + int spr_x_pos = obj->x_pos - ((g_vars.tilemap_x << 4) + (g_vars.tilemap_scroll_dx << 2)); + int spr_w = spr_offs_tbl[2 * spr_num]; + if (spr_hflip) { + spr_w = spr_size_tbl[2 * spr_num] - spr_w; + } + spr_x_pos -= spr_w; + video_draw_sprite(spr_num, spr_x_pos, spr_y_pos, spr_hflip); + obj->spr_num |= 0x2000; + obj->spr_num &= ~0x4000; + } +} + +static void level_draw_rotated_tiles() { + for (int i = 0; i < 20; ++i) { + struct rotation_t *p = &g_vars.rotation_tbl[i]; + if (p->x_pos == 0xFFFF) { + continue; + } + + const int rx = ((int8_t)cos_tbl[p->index_tbl]) >> 2; + p->x_pos += (rx * p->radius) >> 4; + + const int ry = ((int8_t)sin_tbl[p->index_tbl]) >> 2; + p->y_pos += (ry * p->radius) >> 4; + + const int dy = p->y_pos - g_vars.tilemap_scroll_dy - (g_vars.tilemap_y << 4); + if (dy < TILEMAP_SCREEN_H) { + const int dx = p->x_pos - g_vars.tilemap_scroll_dx - (g_vars.tilemap_x << 4); + if (dx < TILEMAP_SCREEN_W) { + } + } + p->x_pos = 0xFFFF; + } +} + + +static void level_draw_snow() { + ++g_vars.snow.counter; + if ((g_vars.level_draw_counter & 3) == 0) { + } + if (g_vars.snow.value == 0) { + return; + } + const uint8_t num = random_get_number(); + const uint8_t hi = random_get_number(); + const uint8_t lo = random_get_number(); + g_vars.snow.random_tbl[num] = (hi << 8) | lo; +} + +static void level_update_player_bonuses() { + if (g_vars.player_bonus_letters_mask == 0x1F) { /* found the 5 bonuses */ + g_vars.level_current_bonus_x_pos = g_vars.objects_tbl[1].x_pos; + g_vars.level_current_bonus_y_pos = g_vars.objects_tbl[1].y_pos - 112; + g_vars.level_current_bonus_spr_num = 110; + level_add_object23_bonus(0, 0, 1); + g_vars.player_bonus_letters_mask = 0; + g_vars.player_bonus_letters_blinking_counter = 44; /* blink the letters in the panel */ + } else if ((g_vars.player_utensils_mask & 0x38) == 0x38) { + g_vars.player_utensils_mask &= ~0x38; + g_vars.player_monsters_unk_counter = 660; + } +} + +static void level_fixup_player_y_pos() { + if (g_vars.player_platform_counter < 1) { + return; + } else if (g_vars.player_platform_counter == 1) { + } else if ((g_vars.level_draw_counter & 1) == 0) { + } else { + if (g_vars.objects_tbl[1].data.p.current_anim_num != 5 && g_vars.objects_tbl[1].data.p.current_anim_num != 32) { + g_vars.objects_tbl[1].y_pos -= 12; + } + ++g_vars.player_platform_counter; + } +} + +static void level_player_death_animation() { + g_vars.objects_tbl[1].hit_counter = 0; + g_vars.objects_tbl[1].spr_num = 33; + g_vars.objects_tbl[1].data.p.y_velocity = 15; + g_vars.objects_tbl[1].x_velocity = (g_vars.objects_tbl[1].x_pos < ((g_vars.tilemap_x + 10) << 4)) ? 5 : -5; + for (int i = 0; i < 60; ++i) { + g_vars.objects_tbl[0].spr_num = 0xFFFF; + level_update_objects_hit_animation(); + level_update_objects_monsters(); + level_update_objects_club_projectiles(); + level_update_objects_bonuses(); + level_update_objects_bonus_scores(); + level_update_tilemap(); + level_draw_tilemap(); + level_draw_panel(); + level_draw_rotated_tiles(); + level_draw_objects(); + level_draw_front_tiles(); + level_draw_snow(); + level_sync(); + g_vars.objects_tbl[1].x_pos += g_vars.objects_tbl[1].x_velocity; + const int y_velocity = MAX(g_vars.objects_tbl[1].data.p.y_velocity - 1, -16); + g_vars.objects_tbl[1].y_pos -= y_velocity; + g_vars.objects_tbl[1].data.p.y_velocity = y_velocity; + } + video_transition_close(); +} + +static void level_completed_bonuses_animation_draw_score() { + int score = g_vars.score * 10; + for (int i = 0; i < 7; ++i) { + const int digit = score % 10; + score /= 10; + video_draw_number(0x247 + (7 - i) * 16 / 8, digit); + } +} + +static void level_completed_bonuses_animation_fixup_object4_spr_num() { + if (g_vars.level_draw_counter & 3) { + return; + } + int spr_num = (g_vars.objects_tbl[4].spr_num & 0x1FFF) + 1; + if (spr_num >= 110) { + spr_num = 104; + } + g_vars.objects_tbl[4].spr_num = spr_num; +} + +static int level_completed_bonuses_animation_helper() { + int bp = 0; + do { + level_update_object_anim(g_vars.objects_tbl[1].data.p.anim); + video_clear(); + level_completed_bonuses_animation_fixup_object4_spr_num(); + level_draw_objects(); + level_completed_bonuses_animation_draw_score(); + level_sync(); + for (int i = 0; i < 20; ++i) { + struct object_t *obj = &g_vars.objects_tbl[55 + i]; + if (obj->spr_num == 0xFFFF) { + continue; + } + int y_velocity = obj->data.p.y_velocity; + if (y_velocity < 128) { + y_velocity += 8; + obj->data.p.y_velocity = y_velocity; + } + obj->y_pos += y_velocity >> 4; + ++bp; + if (obj->y_pos >= 145) { + const int spr_num = obj->spr_num & 0x1FFF; + g_vars.score += score_tbl[score_spr_lut[spr_num - 110]]; + obj->spr_num = 0xFFFF; + play_sound(8); + } + } + } while ((g_vars.level_draw_counter & 7) != 0); + return bp; +} + +static void level_completed_bonuses_animation() { + if (g_vars.light.state != 0) { + g_vars.light.palette_flag1 = 0; + g_vars.light.palette_flag2 = 1; + g_vars.light.palette_counter = 0; + } + g_vars.objects_tbl[1].y_pos -= g_vars.tilemap_y << 4; + g_vars.objects_tbl[1].x_pos -= g_vars.tilemap_x << 4; + g_vars.tilemap_y = 0; + g_vars.tilemap_x = 0; + g_vars.objects_tbl[1].data.p.hdir = 0; + g_vars.objects_tbl[1].x_velocity = 0; + level_completed_bonuses_animation_draw_score(); + g_vars.player_flying_flag = 0; + g_vars.objects_tbl[1].data.p.anim = object_anim_tbl[1]; + while (1) { + level_update_object_anim(g_vars.objects_tbl[1].data.p.anim); + int _bx, _ax, flag = 0; + + int x_pos = 60; + _bx = 2; + _ax = g_vars.objects_tbl[1].x_pos - x_pos; + if (_ax < 0) { + _bx = -_bx; + } + if (_ax >= 2) { + x_pos = g_vars.objects_tbl[1].x_pos - _bx; + ++flag; + } + g_vars.objects_tbl[1].x_pos = x_pos; + + int y_pos = 175; + _bx = 2; + _ax = g_vars.objects_tbl[1].y_pos - y_pos; + if (_ax < 0) { + _bx = -_bx; + } + if (_ax >= 2) { + y_pos = g_vars.objects_tbl[1].y_pos - _bx; + ++flag; + } + g_vars.objects_tbl[1].y_pos = y_pos; + video_clear(); + level_completed_bonuses_animation_draw_score(); + level_draw_objects(); + level_sync(); + if (g_sys.input.quit) { + return; + } + if (!flag) { + break; + } + } + g_vars.objects_tbl[2].x_pos = 360; + g_vars.objects_tbl[2].y_pos = 175; + g_vars.objects_tbl[2].spr_num = 100; + g_vars.objects_tbl[3].x_pos = 360; + g_vars.objects_tbl[3].y_pos = 148; + g_vars.objects_tbl[3].spr_num = 98; + g_vars.objects_tbl[4].x_pos = 360; + g_vars.objects_tbl[4].y_pos = 155; + g_vars.objects_tbl[4].spr_num = 104; + do { + level_update_object_anim(g_vars.objects_tbl[1].data.p.anim); + video_clear(); + level_completed_bonuses_animation_fixup_object4_spr_num(); + level_completed_bonuses_animation_draw_score(); + level_draw_objects(); + level_sync(); + if (g_sys.input.quit) { + return; + } + g_vars.objects_tbl[2].x_pos -= 3; + g_vars.objects_tbl[3].x_pos -= 3; + g_vars.objects_tbl[4].x_pos -= 3; + } while (g_vars.objects_tbl[2].x_pos > 155); + if (g_vars.level_items_total_count != 0) { + g_vars.objects_tbl[1].data.p.anim = object_anim_tbl[18]; + g_vars.objects_tbl[1].x_velocity = 64; + g_vars.objects_tbl[1].x_friction = 2; + while (1) { + level_update_object_anim(g_vars.objects_tbl[1].data.p.anim); + video_clear(); + level_completed_bonuses_animation_fixup_object4_spr_num(); + level_completed_bonuses_animation_draw_score(); + level_draw_objects(); + level_sync(); + if (g_sys.input.quit) { + return; + } + level_update_player_x_velocity(); + const int x_velocity = g_vars.objects_tbl[1].x_velocity >> 4; + g_vars.objects_tbl[1].x_pos += x_velocity; + if (x_velocity == 0) { + break; + } + } + g_vars.objects_tbl[1].data.p.anim = object_anim_tbl[17]; + uint16_t di = 0; + uint16_t bp = level_completed_bonuses_animation_helper(); + uint8_t al = 0; + while (!g_sys.input.quit) { + if (di >= 113) { + di = 0; + } + if (g_vars.level_items_count_tbl[di] != 0) { + for (int i = 0; i < 20; ++i) { + struct object_t *obj = &g_vars.objects_tbl[55 + i]; + if (obj->spr_num == 0xFFFF) { + obj->spr_num = 110 + di; + obj->x_pos = 155; + obj->y_pos = 0; + obj->data.p.y_velocity = 0; + --g_vars.level_items_count_tbl[di]; + ++di; + break; + } + } + bp = level_completed_bonuses_animation_helper(); + al = 0; + continue; + } + ++al; + if (al < 113) { + ++di; + } else { + if (bp == 0) { + break; + } + bp = level_completed_bonuses_animation_helper(); + al = 0; + } + } + } + video_wait_vbl(); + video_wait_vbl(); + g_vars.objects_tbl[1].data.p.anim = object_anim_tbl[1]; + do { + level_update_object_anim(g_vars.objects_tbl[1].data.p.anim); + video_clear(); + level_completed_bonuses_animation_fixup_object4_spr_num(); + level_completed_bonuses_animation_draw_score(); + level_draw_objects(); + level_sync(); + if (g_sys.input.quit) { + return; + } + g_vars.objects_tbl[2].x_pos -= 2; + g_vars.objects_tbl[3].x_pos -= 2; + g_vars.objects_tbl[4].x_pos -= 2; + if (g_vars.objects_tbl[4].x_pos < 0) { + g_vars.objects_tbl[1].x_pos += 3; + } else { + g_vars.objects_tbl[1].x_pos += 2; + } + } while (g_vars.objects_tbl[2].x_pos > -52); +} + +void do_level() { + static const uint8_t music_tbl[] = { 9, 9, 0, 0, 0, 13, 4, 4, 10, 13, 16, 16, 16, 9, 14, 4 }; + play_music(music_tbl[g_vars.level_num]); + load_level_data(g_vars.level_num); + set_level_palette(); + video_load_sprites(); + video_load_front_tiles(); + level_reset(); + level_reset_objects(); + level_init_tilemap(); + level_clear_panel(); + level_draw_panel(); + level_sync(); + random_reset(); + while (!g_sys.input.quit) { + level_update_objects_hit_animation(); + level_update_objects_axe(); + level_update_objects_monsters(); + level_update_objects_club_projectiles(); + level_update_objects_bonuses(); + level_update_objects_bonus_scores(); + level_update_objects_decors(); + level_update_player(); + level_update_player_collision(); + level_update_objects_items(); + level_update_gates(); + g_vars.tilemap_adjust_player_pos_flag = false; + level_update_scrolling(); + level_update_tilemap(); + level_draw_tilemap(); + level_draw_panel(); + level_draw_rotated_tiles(); + level_draw_objects(); + level_draw_front_tiles(); + level_draw_snow(); + if (!level_update_objects_anim()) { + break; + } + level_update_panel(); + level_sync(); + level_update_light_palette(); + level_update_player_bonuses(); + level_fixup_player_y_pos(); + } +} diff --git a/p2/resource.c b/p2/resource.c new file mode 100644 index 0000000..c9c1d98 --- /dev/null +++ b/p2/resource.c @@ -0,0 +1,155 @@ + +#include "resource.h" +#include "unpack.h" +#include "util.h" + +static const int BACKGROUND_SIZE = 320 * 200; + +static const char *_datapath; + +struct resource_t g_res; + +int g_uncompressed_size; + +uint8_t *load_file(const char *filename) { + FILE *fp = fopen_nocase(_datapath, filename); + if (fp) { + print_debug(DBG_RESOURCE, "Loading '%s'...", filename); + uint8_t *p = unpack(fp, &g_uncompressed_size); + print_debug(DBG_RESOURCE, "Uncompressed size %d", g_uncompressed_size); + fclose(fp); + return p; + } + print_error("Unable to open '%s'", filename); + return 0; +} + +static void detect_dos_demo() { + FILE *fp = fopen_nocase(_datapath, "JOYSTICK.SQZ"); + if (fp) { + g_res.dos_demo = true; + fclose(fp); + } +} + +void res_init(const char *path, int vga_size) { + _datapath = path; + + detect_dos_demo(); + + g_res.maps = load_file("MAP.SQZ"); + g_res.motif = load_file("MOTIF.SQZ"); + g_res.allfonts = load_file("ALLFONTS.SQZ"); + g_res.sprites = load_file("SPRITES.SQZ"); + g_res.frontdat = load_file("FRONT.SQZ"); + g_res.frontlen = g_uncompressed_size; + g_res.uniondat = load_file("UNION.SQZ"); + g_res.unionlen = g_uncompressed_size; + + g_res.vga = (uint8_t *)malloc(vga_size); + if (!g_res.vga) { + print_error("Failed to allocate vga buffer, %d bytes", vga_size); + } + + g_res.background = (uint8_t *)malloc(BACKGROUND_SIZE); + if (!g_res.background) { + print_error("Failed to allocate background buffer, %d bytes", BACKGROUND_SIZE); + } + + if (!g_res.dos_demo) { + g_res.samples = load_file("SAMPLE.SQZ"); + } +} + +void res_fini() { + free(g_res.maps); + g_res.maps = 0; + free(g_res.motif); + g_res.motif = 0; + free(g_res.allfonts); + g_res.allfonts = 0; + free(g_res.sprites); + g_res.sprites = 0; + free(g_res.frontdat); + g_res.frontdat = 0; + free(g_res.uniondat); + g_res.uniondat = 0; + free(g_res.vga); + g_res.vga = 0; + free(g_res.background); + g_res.background = 0; + free(g_res.samples); + g_res.samples = 0; +} + +void load_leveldat(const uint8_t *p, struct level_t *level) { + memcpy(g_res.level.tile_attributes0, p, 256); p += 256; + memcpy(g_res.level.tile_attributes1, p, 256); p += 256; + memcpy(g_res.level.tile_attributes2, p, 256); p += 256; + g_res.level.scrolling_top = READ_LE_UINT16(p); p += 2; + g_res.level.start_x_pos = READ_LE_UINT16(p); p += 2; + g_res.level.start_y_pos = READ_LE_UINT16(p); p += 2; + g_res.level.tilemap_w = READ_LE_UINT16(p); p += 2; + g_res.level.scrolling_mask = *p++; + for (int i = 0; i < 256; ++i) { + g_res.level.front_tiles_lut[i] = READ_LE_UINT16(p); p += 2; + } + for (int i = 0; i < MAX_LEVEL_GATES; ++i) { + struct level_gate_t *gate = &g_res.level.gates_tbl[i]; + gate->enter_pos = READ_LE_UINT16(p); p += 2; + gate->tilemap_pos = READ_LE_UINT16(p); p += 2; + gate->dst_pos = READ_LE_UINT16(p); p += 2; + gate->scroll_flag = *p++; + } + for (int i = 0; i < MAX_LEVEL_PLATFORMS; ++i) { + struct level_platform_t *platform = &g_res.level.platforms_tbl[i]; + platform->tilemap_pos = READ_LE_UINT16(p); p += 2; + platform->w = *p++; + platform->h = *p++; + platform->unk4 = READ_LE_UINT16(p); p += 2; + platform->unk6 = READ_LE_UINT16(p); p += 2; + platform->unk8 = *p++; + platform->unk9 = *p++; + } + memcpy(g_res.level.monsters_attributes, p, 0x800); p += 0x800; + g_res.level.items_spr_num_offset = READ_LE_UINT16(p); p += 2; + g_res.level.monsters_spr_num_offset = READ_LE_UINT16(p); p += 2; + for (int i = 0; i < MAX_LEVEL_BONUSES; ++i) { + struct level_bonus_t *bonus = &g_res.level.bonuses_tbl[i]; + bonus->tile_num0 = *p++; + bonus->tile_num1 = *p++; + bonus->count = *p++; + bonus->pos = READ_LE_UINT16(p); p += 2; + } + memcpy(g_res.level.tile_attributes3, p, 256); p += 256; + for (int i = 0; i < MAX_LEVEL_ITEMS; ++i) { + struct level_item_t *item = &g_res.level.items_tbl[i]; + item->x_pos = READ_LE_UINT16(p); p += 2; + item->y_pos = READ_LE_UINT16(p); p += 2; + item->spr_num = READ_LE_UINT16(p); p += 2; + item->y_delta = *p++; + } + for (int i = 0; i < MAX_LEVEL_TRIGGERS; ++i) { + struct level_trigger_t *trigger = &g_res.level.triggers_tbl[i]; + trigger->x_pos = READ_LE_UINT16(p); p += 2; + trigger->y_pos = READ_LE_UINT16(p); p += 2; + trigger->spr_num = READ_LE_UINT16(p); p += 2; + trigger->flags = *p++; + trigger->unk7 = *p++; + trigger->unk8 = *p++; + trigger->unk9 = *p++; + trigger->state = *p++; + trigger->unkB = READ_LE_UINT16(p); p += 2; + trigger->counter = *p++; + trigger->unkE = *p++; + } + g_res.level.monsters_xmin = READ_LE_UINT16(p); p += 2; + g_res.level.monsters_xmax = READ_LE_UINT16(p); p += 2; + g_res.level.monsters_unk0 = *p++; + g_res.level.monsters_unk1 = READ_LE_UINT16(p); p += 2; + g_res.level.monsters_state = *p++; + g_res.level.end_x_pos = READ_LE_UINT16(p); p += 2; + g_res.level.end_y_pos = READ_LE_UINT16(p); p += 2; + const int total = p - g_res.leveldat; + print_debug(DBG_RESOURCE, "level total offset %d", total); +} diff --git a/p2/resource.h b/p2/resource.h new file mode 100644 index 0000000..04bd026 --- /dev/null +++ b/p2/resource.h @@ -0,0 +1,111 @@ + +#ifndef RESOURCE_H__ +#define RESOURCE_H__ + +#include "intern.h" + +struct level_gate_t { + uint16_t enter_pos; // (y << 8) | x + uint16_t tilemap_pos; + uint16_t dst_pos; + uint8_t scroll_flag; +}; // sizeof == 7 + +struct level_platform_t { + uint16_t tilemap_pos; + uint8_t w; + uint8_t h; + uint16_t unk4; + uint16_t unk6; + uint8_t unk8; // y_offs + uint8_t unk9; +}; // sizeof == 10 + +struct level_bonus_t { + uint8_t tile_num0; /* new tile */ + uint8_t tile_num1; /* original tile */ + uint8_t count; + uint16_t pos; // (y << 8) | x +}; // sizeof == 5 + +struct level_item_t { + int16_t x_pos, y_pos; + uint16_t spr_num; + int8_t y_delta; +}; // sizeof == 7 + +struct level_trigger_t { + uint16_t x_pos; + uint16_t y_pos; + uint16_t spr_num; + uint8_t flags; + uint8_t unk7; + uint8_t unk8; + uint8_t unk9; + uint8_t state; + uint16_t unkB; + uint8_t counter; + uint8_t unkE; +}; // sizeof == 15 + +#define MAX_LEVEL_GATES 20 +#define MAX_LEVEL_PLATFORMS 15 +#define MAX_LEVEL_BONUSES 80 +#define MAX_LEVEL_ITEMS 70 +#define MAX_LEVEL_TRIGGERS 16 + +struct level_t { + uint8_t tile_attributes0[256]; + uint8_t tile_attributes1[256]; + uint8_t tile_attributes2[256]; /* 0x80: animated, 0x40: front, 0x20: animate if player is on top */ + uint8_t scrolling_top; + uint16_t start_x_pos; + uint16_t start_y_pos; + uint16_t tilemap_w; + uint16_t scrolling_mask; /* 4: screen scroll down 1 line, 2: no horizontal scrolling, 1: wider vertical scrolling */ + uint16_t front_tiles_lut[256]; + struct level_gate_t gates_tbl[MAX_LEVEL_GATES]; + struct level_platform_t platforms_tbl[MAX_LEVEL_PLATFORMS]; + uint8_t monsters_attributes[0x800]; + uint16_t items_spr_num_offset; + uint16_t monsters_spr_num_offset; + struct level_bonus_t bonuses_tbl[MAX_LEVEL_BONUSES]; + uint8_t tile_attributes3[256]; + struct level_item_t items_tbl[MAX_LEVEL_ITEMS]; + struct level_trigger_t triggers_tbl[MAX_LEVEL_TRIGGERS]; + uint16_t monsters_xmin; + uint16_t monsters_xmax; + uint8_t monsters_unk0; + uint16_t monsters_unk1; + uint8_t monsters_state; + uint16_t end_x_pos; + uint16_t end_y_pos; +}; + +struct resource_t { + bool dos_demo; + uint8_t *maps; + uint8_t *motif; + uint8_t *allfonts; + uint8_t *sprites; + uint8_t *frontdat; + int frontlen; + uint8_t *uniondat; + int unionlen; + uint8_t *leveldat; + int levellen; + uint8_t *vga; + uint8_t *background; + struct level_t level, restart; + uint8_t *samples; +}; + +extern struct resource_t g_res; +extern int g_uncompressed_size; + +extern void res_init(const char *datapath, int vga_size); +extern void res_fini(); +extern void load_leveldat(const uint8_t *data, struct level_t *level); +extern uint8_t *load_file(const char *filename); + +#endif diff --git a/p2/screen.c b/p2/screen.c new file mode 100644 index 0000000..bf68c56 --- /dev/null +++ b/p2/screen.c @@ -0,0 +1,246 @@ + +/* screen drawing */ + +#include "game.h" +#include "resource.h" +#include "sys.h" +#include "util.h" + +#define MAX_SPRITES 445 +#define MAX_SPRITESHEET_W 2048 +#define MAX_SPRITESHEET_H 1024 +#define MAX_FRONT_TILES 168 + +static void decode_planar(const uint8_t *src, uint8_t *dst, int dst_pitch, int w, int h, uint8_t transparent_color) { + const int plane_size = h * w / 8; + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w / 8; ++x) { + for (int i = 0; i < 8; ++i) { + const uint8_t mask = 1 << (7 - i); + uint8_t color = 0; + for (int b = 0; b < 4; ++b) { + if (src[b * plane_size] & mask) { + color |= (1 << b); + } + } + if (color != transparent_color) { + dst[x * 8 + i] = color; + } + } + ++src; + } + dst += dst_pitch; + } +} + +static void convert_planar_tile_4bpp(const uint8_t *src, uint8_t *dst, int dst_pitch) { + static const int tile_h = 16; + static const int tile_w = 16; + static const int plane_size = 16 * (16 / 8); + for (int y = 0; y < tile_h; ++y) { + for (int x = 0; x < tile_w / 8; ++x) { + for (int i = 0; i < 8; ++i) { + const uint8_t mask = 1 << (7 - i); + uint8_t color = 0; + for (int b = 0; b < 4; ++b) { + if (src[b * plane_size] & mask) { + color |= (1 << b); + } + } + if (i & 1) { + dst[x * 4 + (i >> 1)] |= color; + } else { + dst[x * 4 + (i >> 1)] = color << 4; + } + } + ++src; + } + dst += dst_pitch; + } +} + +void video_draw_string(int offset, int hspace, const char *s) { + offset += hspace; + while (*s) { + uint8_t code = *s++; + if (code != 0x20) { + code -= 0x30; + if (code > 9) { + code -= 2; + } + decode_planar(g_res.allfonts + code * 48, g_res.vga + offset * 8, 320, 8, 12, 0); + } + ++offset; + } +} + +void video_draw_panel_number(int offset, int num) { + const uint8_t *fnt = g_res.allfonts + 48 * 41 + 160 * 23; + decode_planar(fnt + num * 96, g_res.vga + offset * 8, 320, 16, 12, 0); +} + +void video_draw_number(int offset, int num) { + const uint8_t *fnt = g_res.allfonts + 0x1C70; + decode_planar(fnt + num * 88, g_res.vga + offset * 8, 320, 16, 11, 0); +} + +void video_clear() { + memset(g_res.vga, 0, GAME_SCREEN_W * GAME_SCREEN_H); +} + +void video_copy_img(const uint8_t *src) { + decode_planar(src, g_res.background, 320, 320, 200, 0xFF); +} + +void video_draw_panel(const uint8_t *src) { + decode_planar(src, g_res.vga + TILEMAP_SCREEN_H * 320, 320, 320, 23, 0xFF); +} + +void video_draw_tile(const uint8_t *src, int x_offset, int y_offset) { + int tile_w = 16; + int tile_h = 16; + if (y_offset < 0) { + tile_h += y_offset; + src -= y_offset * 8; + y_offset = 0; + } + if (y_offset + tile_h > TILEMAP_SCREEN_H) { + tile_h = TILEMAP_SCREEN_H - y_offset; + } + if (tile_h <= 0) { + return; + } + uint8_t *dst = g_res.vga + y_offset * TILEMAP_SCREEN_W + x_offset; + for (int y = 0; y < tile_h; ++y) { + for (int x = 0; x < tile_w / 2; ++x) { + const uint8_t color = *src++; + const uint8_t c1 = color >> 4; + if (c1 != 0) { + dst[x * 2] = c1; + } + const uint8_t c2 = color & 15; + if (c2 != 0) { + dst[x * 2 + 1] = c2; + } + } + dst += TILEMAP_SCREEN_W; + } +} + +void video_convert_tiles(uint8_t *data, int len) { + for (int offset = 0; offset < (len & ~127); offset += 128) { + uint8_t buffer[16 * 8]; + convert_planar_tile_4bpp(data + offset, buffer, 8); + memcpy(data + offset, buffer, 16 * 8); + } +} + +void video_load_front_tiles() { + g_sys.render_unload_sprites(RENDER_SPR_FG); + assert((g_res.frontlen & 127) == 0); + const int count = g_res.frontlen / (16 * 8); + assert(count <= MAX_FRONT_TILES); + struct sys_rect_t r[MAX_FRONT_TILES]; + const int w = 256; + const int h = 192; + memset(g_res.vga, 0, w * h); + int tile = 0; + for (int y = 0; y < h; y += 16) { + for (int x = 0; x < w; x += 16) { + r[tile].x = x; + r[tile].y = y; + r[tile].w = 16; + r[tile].h = 16; + decode_planar(g_res.frontdat + tile * 16 * 8, g_res.vga + y * w + x, w, 16, 16, 0); + ++tile; + if (tile == count) { + g_sys.render_load_sprites(RENDER_SPR_FG, count, r, g_res.vga, w, h, 0, 0x0); + return; + } + } + } +} + +void video_set_palette() { +} + +void fade_in_palette() { + if (!g_sys.input.quit) { + g_sys.fade_in_palette(); + } +} + +void fade_out_palette() { + if (!g_sys.input.quit) { + g_sys.fade_out_palette(); + } +} + +void video_wait_vbl() { +} + +void video_transition_close() { +} + +void video_transition_open() { +} + +void video_load_sprites() { + const uint16_t *sprite_offsets = (const uint16_t *)spr_size_tbl; + struct sys_rect_t r[MAX_SPRITES]; + uint8_t *data = (uint8_t *)calloc(MAX_SPRITESHEET_W * MAX_SPRITESHEET_H, 1); + if (data) { + int current_x = 0; + int max_w = 0; + int current_y = 0; + int max_h = 0; + + int offset = 0; + int count = 0; + uint8_t value; + for (int i = 0; (value = sprite_offsets[i] & 255) != 0; ++i, ++count) { + + value = (value >> 3) | ((value & 7) << 5); + if ((value & 0xE0) != 0) { + value &= ~0xE0; + ++value; + } + const int h = sprite_offsets[i] >> 8; + const int w = value * 8; + assert((sprite_offsets[i] & 255) == w); + const int size = (h * value) * 4; + + if (current_x + w > MAX_SPRITESHEET_W) { + current_y += max_h; + if (current_x > max_w) { + max_w = current_x; + } + current_x = 0; + max_h = h; + } else { + if (h > max_h) { + max_h = h; + } + } + decode_planar(g_res.sprites + offset, data + current_y * MAX_SPRITESHEET_W + current_x, MAX_SPRITESHEET_W, w, h, 0xFF); + offset += size; + + r[i].x = current_x; + r[i].y = current_y; + r[i].w = w; + r[i].h = h; + current_x += w; + } + assert(count <= MAX_SPRITES); + assert(max_w <= MAX_SPRITESHEET_W); + assert(current_y + max_h <= MAX_SPRITESHEET_H); + g_sys.render_unload_sprites(RENDER_SPR_GAME); + g_sys.render_load_sprites(RENDER_SPR_GAME, count, r, data, MAX_SPRITESHEET_W, current_y + max_h, 0, 0x0); + free(data); + print_debug(DBG_SCREEN, "sprites total_size %d count %d", offset, count); + } +} + +void video_draw_sprite(int num, int x, int y, int flag) { + g_sys.render_add_sprite(RENDER_SPR_GAME, num, x, y, flag != 0); +} diff --git a/p2/sound.c b/p2/sound.c new file mode 100644 index 0000000..d2e6829 --- /dev/null +++ b/p2/sound.c @@ -0,0 +1,151 @@ + +#include "game.h" +#include "resource.h" +#include "sys.h" +#include "util.h" + +#include + +#define MAX_SOUNDS 11 + +static const bool _volume = false; + +static const uint16_t sound_sizes_tbl[] = { + 0x188E, 0x1C80, 0x235E, 0x19E6, 0x0AB2, 0x0912, 0x0000, 0x35D2, + 0x06C4, 0x1C86, 0x0E2E +}; + +static const uint8_t sound_volume_tbl[] = { + 0x3F,0x37,0x32,0x2F,0x2C,0x2A,0x28,0x27,0x26,0x24,0x23,0x22,0x21,0x21,0x20,0x1F, + 0x1E,0x1E,0x1D,0x1C,0x1C,0x1B,0x1B,0x1A,0x1A,0x19,0x19,0x19,0x18,0x18,0x17,0x17, + 0x17,0x16,0x16,0x16,0x15,0x15,0x15,0x15,0x14,0x14,0x14,0x14,0x13,0x13,0x13,0x13, + 0x12,0x12,0x12,0x12,0x11,0x11,0x11,0x11,0x11,0x10,0x10,0x10,0x10,0x10,0x0F,0x0F, + 0x0F,0x0F,0x0F,0x0F,0x0E,0x0E,0x0E,0x0E,0x0E,0x0E,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D, + 0x0D,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0B,0x0B,0x0B,0x0B,0x0B,0x0B,0x0B, + 0x0B,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x09,0x09,0x09,0x09,0x09,0x09, + 0x09,0x09,0x09,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x07,0x07 +}; + +static const char *trk_names_tbl[] = { + "PRES.TRK", + "CODE.TRK", + "CARTE.TRK", + "PRESENTA.TRK", + "GLACE.TRK", + 0, + 0, + 0, + 0, + "MINES.TRK", + "MYSTERY.TRK", + 0, + 0, + "MONSTER.TRK", + "FINAL.TRK", + "BRAVO.TRK", + "KOOL.TRK", + "BOULA.TRK" +}; + +struct mixerchannel_t { + uint8_t *data; + uint32_t pos; + uint32_t step; + uint32_t size; +}; + +static const int _rate = SYS_AUDIO_FREQ; +static struct mixerchannel_t _channel; +static ModPlugFile *_mpf; + +static uint16_t sound_offsets_tbl[MAX_SOUNDS]; + +static void mix(void *param, uint8_t *buf, int len) { + memset(buf, 0, len); + if (_mpf) { + const int count = ModPlug_Read(_mpf, buf, len); + if (count == 0) { + ModPlug_SeekOrder(_mpf, 0); + } + } + if (_channel.data) { + for (int i = 0; i < len; i += sizeof(int16_t)) { + const int pos = _channel.pos >> 16; + if (pos >= _channel.size) { + _channel.data = 0; + break; + } + const int8_t pcm = _volume ? sound_volume_tbl[(_channel.data[pos] ^ 0x80) >> 1] : _channel.data[pos]; + const int sample = *(int16_t *)(buf + i) + pcm * 256; + *(int16_t *)(buf + i) = (sample < -32768 ? -32768 : (sample > 32767 ? 32767 : sample)); + _channel.pos += _channel.step; + } + } +} + +void sound_init() { + ModPlug_Settings mp_settings; + memset(&mp_settings, 0, sizeof(mp_settings)); + ModPlug_GetSettings(&mp_settings); + mp_settings.mFlags = MODPLUG_ENABLE_OVERSAMPLING | MODPLUG_ENABLE_NOISE_REDUCTION; + mp_settings.mChannels = 1; + mp_settings.mBits = 16; + mp_settings.mFrequency = _rate; + mp_settings.mResamplingMode = MODPLUG_RESAMPLE_FIR; + mp_settings.mLoopCount = -1; + ModPlug_SetSettings(&mp_settings); + uint16_t offset = 0; + for (int i = 0; i < MAX_SOUNDS; ++i) { + sound_offsets_tbl[i] = offset; + offset += sound_sizes_tbl[i]; + } + g_sys.start_audio(mix, 0); +} + +void sound_fini() { + g_sys.stop_audio(); +} + +void play_sound(int num) { + assert(num < MAX_SOUNDS); + print_debug(DBG_MIXER, "play_sound %d", num); + if (!g_res.samples) { /* no SAMPLE. file with demo */ + return; + } + const int sample_offset = sound_offsets_tbl[num]; + const int sample_size = sound_sizes_tbl[num]; + print_debug(DBG_MIXER, "sample num %d offset 0x%x size %d", num, sample_offset, sample_size); + if (sample_size == 0) { + return; + } + g_sys.lock_audio(); + _channel.data = g_res.samples + sample_offset; + _channel.pos = 0; + _channel.step = (8000 << 16) / _rate; + _channel.size = sample_size; + g_sys.unlock_audio(); +} + +void play_music(int num) { + if (g_res.dos_demo) { /* no .TRK files with demo */ + return; + } + const char *filename = trk_names_tbl[num]; + if (filename) { + print_debug(DBG_MIXER, "play_music '%s'", filename); + g_sys.lock_audio(); + if (_mpf) { + ModPlug_Unload(_mpf); + _mpf = 0; + } + uint8_t *data = load_file(filename); + if (data) { + _mpf = ModPlug_Load(data, g_uncompressed_size); + if (_mpf) { + print_debug(DBG_MIXER, "Loaded module '%s'", ModPlug_GetName(_mpf)); + } + free(data); + } + g_sys.unlock_audio(); + } +} diff --git a/p2/staticres.c b/p2/staticres.c new file mode 100644 index 0000000..5479db4 --- /dev/null +++ b/p2/staticres.c @@ -0,0 +1,371 @@ +#include "game.h" + +static const uint8_t palette_data_2df0[] = { + 0x00,0x00,0x00,0x38,0x28,0x20,0x28,0x18,0x10,0x20,0x10,0x08,0x24,0x28,0x3C,0x28, + 0x00,0x08,0x04,0x04,0x04,0x38,0x30,0x08,0x00,0x18,0x10,0x08,0x20,0x0C,0x08,0x30, + 0x18,0x14,0x10,0x0C,0x1C,0x18,0x14,0x24,0x20,0x1C,0x2C,0x28,0x24,0x3C,0x38,0x34 +}; +static const uint8_t palette_data_2e50[] = { + 0x00,0x00,0x00,0x38,0x28,0x20,0x28,0x18,0x10,0x20,0x10,0x08,0x10,0x28,0x38,0x28, + 0x00,0x08,0x00,0x00,0x00,0x38,0x30,0x08,0x00,0x18,0x10,0x08,0x20,0x0C,0x08,0x30, + 0x18,0x10,0x10,0x10,0x18,0x18,0x18,0x20,0x20,0x20,0x28,0x28,0x28,0x38,0x38,0x38 +}; +static const uint8_t palette_data_2dc0[] = { + 0x00,0x00,0x00,0x38,0x28,0x20,0x28,0x18,0x10,0x20,0x10,0x08,0x28,0x34,0x3C,0x28, + 0x00,0x08,0x04,0x04,0x04,0x38,0x30,0x08,0x00,0x14,0x18,0x00,0x20,0x1C,0x00,0x30, + 0x28,0x08,0x14,0x0C,0x14,0x1C,0x14,0x20,0x28,0x20,0x28,0x30,0x28,0x38,0x3C,0x38 +}; +static const uint8_t palette_data_2d90[] = { + 0x00,0x00,0x00,0x38,0x28,0x20,0x28,0x18,0x10,0x20,0x10,0x08,0x10,0x28,0x38,0x28, + 0x00,0x08,0x04,0x04,0x04,0x38,0x30,0x08,0x00,0x14,0x14,0x00,0x20,0x14,0x08,0x30, + 0x1C,0x34,0x20,0x14,0x04,0x08,0x1C,0x10,0x14,0x30,0x14,0x24,0x38,0x2C,0x34,0x3C +}; +static const uint8_t palette_data_2d00[] = { + 0x00,0x00,0x00,0x38,0x28,0x20,0x28,0x18,0x10,0x20,0x10,0x08,0x10,0x28,0x38,0x28, + 0x00,0x08,0x04,0x04,0x00,0x38,0x30,0x08,0x18,0x00,0x00,0x28,0x04,0x00,0x3C,0x14, + 0x00,0x34,0x20,0x14,0x20,0x14,0x24,0x28,0x1C,0x3C,0x2C,0x2C,0x3C,0x34,0x38,0x3C +}; +static const uint8_t palette_data_2d60[] = { + 0x00,0x00,0x00,0x3C,0x24,0x14,0x2C,0x0C,0x00,0x1C,0x08,0x04,0x3C,0x30,0x28,0x28, + 0x00,0x08,0x04,0x04,0x04,0x3C,0x2C,0x08,0x08,0x18,0x00,0x18,0x20,0x0C,0x28,0x2C, + 0x10,0x34,0x18,0x10,0x24,0x08,0x18,0x2C,0x18,0x24,0x34,0x24,0x30,0x3C,0x34,0x38 +}; +static const uint8_t palette_data_2ca0[] = { + 0x00,0x00,0x00,0x38,0x28,0x20,0x28,0x18,0x10,0x20,0x10,0x08,0x18,0x18,0x24,0x28, + 0x00,0x08,0x00,0x00,0x00,0x38,0x30,0x08,0x18,0x1C,0x2C,0x18,0x24,0x2C,0x28,0x30, + 0x38,0x34,0x20,0x14,0x00,0x18,0x14,0x00,0x24,0x1C,0x14,0x34,0x2C,0x3C,0x3C,0x38 +}; +static const uint8_t palette_data_2d30[] = { + 0x00,0x00,0x00,0x38,0x28,0x20,0x28,0x18,0x10,0x20,0x10,0x08,0x10,0x28,0x38,0x28, + 0x00,0x08,0x04,0x04,0x04,0x38,0x30,0x08,0x00,0x10,0x10,0x00,0x20,0x10,0x08,0x30, + 0x08,0x14,0x08,0x00,0x00,0x18,0x14,0x00,0x24,0x1C,0x14,0x34,0x2C,0x3C,0x3C,0x3C +}; +static const uint8_t palette_data_2cd0[] = { + 0x00,0x00,0x00,0x38,0x28,0x20,0x28,0x18,0x10,0x20,0x10,0x08,0x0C,0x08,0x14,0x28, + 0x00,0x08,0x00,0x00,0x00,0x38,0x30,0x08,0x00,0x0E,0x00,0x00,0x14,0x00,0x00,0x1A, + 0x00,0x0C,0x0A,0x02,0x14,0x18,0x14,0x1C,0x20,0x20,0x2C,0x28,0x2C,0x3C,0x3C,0x38 +}; +static const uint8_t palette_data_2e80[] = { + 0x00,0x00,0x00,0x38,0x28,0x20,0x28,0x18,0x10,0x20,0x10,0x08,0x10,0x28,0x38,0x28, + 0x00,0x08,0x00,0x00,0x00,0x38,0x30,0x08,0x00,0x14,0x00,0x00,0x20,0x00,0x08,0x30, + 0x08,0x34,0x20,0x14,0x1C,0x08,0x08,0x28,0x14,0x14,0x3C,0x28,0x28,0x38,0x38,0x38 +}; +static const uint8_t palette_data_2eb0[] = { + 0x00,0x00,0x00,0x38,0x28,0x20,0x28,0x18,0x10,0x20,0x10,0x08,0x10,0x28,0x38,0x28, + 0x00,0x08,0x00,0x00,0x00,0x38,0x30,0x08,0x00,0x14,0x00,0x00,0x20,0x00,0x08,0x30, + 0x08,0x34,0x20,0x14,0x14,0x18,0x1C,0x1C,0x20,0x24,0x28,0x28,0x2C,0x38,0x38,0x38 +}; +const uint8_t *palettes_tbl[] = { + palette_data_2df0, + palette_data_2e50, + palette_data_2dc0, + palette_data_2d90, + palette_data_2d00, + palette_data_2d60, + palette_data_2ca0, + palette_data_2d30, + palette_data_2cd0, + palette_data_2cd0, + palette_data_2e80, + palette_data_2e80, + palette_data_2e80, + palette_data_2eb0, + palette_data_2cd0, + palette_data_2ca0, +}; +const uint8_t credits_palette_data[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3C,0x00,0x00,0x3C,0x00,0x00,0x3C,0x00,0x3C, + 0x3C,0x1C,0x3C,0x3C,0x0C,0x3C,0x38,0x00,0x38,0x30,0x00,0x34,0x2C,0x00,0x34,0x24, + 0x00,0x2C,0x24,0x00,0x24,0x1C,0x00,0x20,0x18,0x00,0x18,0x14,0x00,0x14,0x10,0x00 +}; +const uint8_t light_palette_data[] = { + 0x00,0x00,0x00,0x0F,0x12,0x18,0x0C,0x0F,0x15,0x09,0x0C,0x12,0x00,0x0F,0x12,0x0C, + 0x0C,0x12,0x00,0x00,0x00,0x00,0x15,0x18,0x00,0x0C,0x0C,0x00,0x0F,0x0F,0x06,0x12, + 0x12,0x00,0x09,0x0C,0x00,0x0C,0x12,0x00,0x0F,0x18,0x00,0x12,0x1B,0x0F,0x15,0x1B +}; +const uint8_t spr_offs_tbl[] = { + 0x14,0x24,0x10,0x23,0x0C,0x24,0x10,0x24,0x10,0x25,0x10,0x25,0x0F,0x24,0x10,0x23, + 0x0F,0x23,0x0F,0x23,0x0C,0x23,0x10,0x26,0x14,0x1F,0x18,0x1F,0x10,0x1F,0x10,0x24, + 0x0C,0x20,0x14,0x1E,0x14,0x1F,0x14,0x1F,0x10,0x1E,0x0C,0x1E,0x10,0x1F,0x0C,0x1E, + 0x0C,0x20,0x14,0x22,0x10,0x21,0x10,0x23,0x10,0x22,0x14,0x1F,0x14,0x20,0x14,0x1E, + 0x14,0x1F,0x18,0x20,0x10,0x24,0x0C,0x20,0x10,0x20,0x10,0x21,0x0C,0x1D,0x0E,0x11, + 0x09,0x10,0x0E,0x11,0x0F,0x10,0x09,0x0F,0x0A,0x0F,0x0C,0x0E,0x10,0x0B,0x0C,0x10, + 0x10,0x19,0x10,0x1F,0x0C,0x21,0x10,0x1F,0x10,0x1F,0x04,0x06,0x04,0x08,0x08,0x09, + 0x08,0x0B,0x08,0x0B,0x08,0x13,0x0C,0x12,0x0C,0x0D,0x08,0x10,0x08,0x10,0x08,0x10, + 0x08,0x0F,0x0C,0x0F,0x0C,0x13,0x11,0x0F,0x0E,0x0D,0x07,0x0A,0x08,0x0F,0x08,0x0F, + 0x08,0x0C,0x08,0x0F,0x0B,0x09,0x0C,0x09,0x0C,0x09,0x0C,0x09,0x0C,0x09,0x0C,0x09, + 0x0B,0x09,0x0C,0x09,0x0C,0x09,0x0C,0x09,0x0C,0x09,0x0C,0x09,0x14,0x0D,0x17,0x0D, + 0x16,0x0D,0x17,0x0D,0x1B,0x11,0x0E,0x05,0x07,0x0D,0x07,0x0D,0x07,0x0D,0x07,0x0D, + 0x07,0x0D,0x06,0x13,0x0F,0x16,0x0B,0x1E,0x0B,0x1D,0x0B,0x21,0x06,0x21,0x0B,0x21, + 0x0B,0x07,0x0B,0x07,0x0B,0x07,0x0B,0x07,0x0B,0x07,0x0B,0x07,0x19,0x41,0x16,0x2C, + 0x16,0x20,0x13,0x2E,0x10,0x26,0x13,0x23,0x11,0x21,0x14,0x21,0x18,0x0E,0x18,0x1D, + 0x18,0x1F,0x0C,0x13,0x0C,0x14,0x09,0x17,0x09,0x1B,0x0F,0x17,0x0C,0x1B,0x0C,0x1F, + 0x0C,0x0E,0x08,0x0F,0x0C,0x13,0x08,0x12,0x08,0x12,0x0C,0x0C,0x0C,0x13,0x08,0x19, + 0x0C,0x15,0x0C,0x17,0x08,0x1B,0x08,0x14,0x08,0x17,0x08,0x15,0x0C,0x0B,0x0C,0x0B, + 0x08,0x16,0x08,0x16,0x08,0x13,0x08,0x12,0x08,0x12,0x08,0x13,0x08,0x1D,0x08,0x15, + 0x08,0x1C,0x0C,0x14,0x0C,0x0F,0x08,0x0F,0x08,0x13,0x08,0x15,0x08,0x15,0x0C,0x11, + 0x0C,0x0C,0x0C,0x0D,0x0C,0x0D,0x0C,0x11,0x0C,0x16,0x08,0x0D,0x08,0x10,0x08,0x18, + 0x08,0x11,0x0C,0x0B,0x08,0x17,0x0C,0x12,0x0C,0x0F,0x0C,0x0F,0x0C,0x0F,0x08,0x13, + 0x0C,0x14,0x08,0x12,0x04,0x17,0x08,0x15,0x08,0x12,0x08,0x0C,0x08,0x0C,0x08,0x0C, + 0x0C,0x17,0x08,0x12,0x08,0x10,0x08,0x0E,0x08,0x16,0x10,0x0D,0x0C,0x10,0x0C,0x12, + 0x08,0x0D,0x0C,0x0E,0x08,0x13,0x08,0x19,0x04,0x16,0x08,0x0D,0x08,0x13,0x0C,0x10, + 0x0C,0x12,0x0C,0x0D,0x08,0x0F,0x08,0x1C,0x08,0x0E,0x08,0x18,0x0C,0x12,0x10,0x14, + 0x10,0x11,0x08,0x0B,0x08,0x1A,0x0C,0x12,0x0C,0x0D,0x0C,0x0C,0x08,0x0F,0x08,0x13, + 0x08,0x13,0x08,0x13,0x08,0x13,0x08,0x13,0x0C,0x10,0x0C,0x16,0x08,0x12,0x0C,0x10, + 0x08,0x0C,0x08,0x0C,0x08,0x0B,0x10,0x10,0x14,0x0E,0x14,0x0E,0x14,0x0E,0x10,0x0E, + 0x10,0x0D,0x08,0x12,0x08,0x12,0x0C,0x12,0x10,0x29,0x10,0x1D,0x0C,0x14,0x14,0x21, + 0x18,0x1A,0x08,0x0B,0x08,0x0B,0x08,0x0B,0x08,0x0B,0x08,0x0B,0x08,0x0B,0x08,0x0B, + 0x08,0x0B,0x08,0x0B,0x08,0x0B,0x08,0x0B,0x08,0x0B,0x08,0x0B,0x08,0x0B,0x08,0x0B, + 0x08,0x0B,0x08,0x0B,0x08,0x0B,0x08,0x0B,0x08,0x0B,0x08,0x0B,0x08,0x0B,0x08,0x0B, + 0x08,0x0B,0x08,0x0B,0x08,0x0B,0x08,0x0B,0x04,0x08,0x08,0x0B,0x04,0x0B,0x04,0x05, + 0x04,0x05,0x08,0x0C,0x18,0x28,0x1C,0x1E,0x18,0x30,0x10,0x33,0x22,0x46,0x13,0x46, + 0x0D,0x1A,0x19,0x1D,0x1C,0x0E,0x08,0x0C,0x08,0x0C,0x08,0x0C,0x08,0x0C,0x08,0x0C, + 0x08,0x0C,0x08,0x0C,0x08,0x0C,0x08,0x0C,0x08,0x0C,0x08,0x0C,0x08,0x0C,0x08,0x0C, + 0x08,0x0C,0x08,0x0C,0x08,0x0C,0x10,0x15,0x04,0x0A,0x08,0x0C,0x08,0x0C,0x08,0x0C, + 0x20,0x1F,0x0C,0x18,0x10,0x1D,0x0C,0x1E,0x10,0x1D,0x18,0x28,0x10,0x13,0x10,0x19, + 0x0C,0x1C,0x10,0x0E,0x10,0x0F,0x10,0x0E,0x10,0x0F,0x08,0x1C,0x08,0x1C,0x08,0x1C, + 0x08,0x1C,0x0C,0x15,0x0C,0x19,0x10,0x17,0x11,0x17,0x0C,0x20,0x0C,0x1F,0x10,0x16, + 0x14,0x0E,0x14,0x09,0x0C,0x0F,0x0F,0x0B,0x0C,0x15,0x08,0x0A,0x08,0x09,0x04,0x0A, + 0x08,0x0A,0x08,0x0A,0x0C,0x07,0x08,0x0E,0x0C,0x0C,0x0C,0x0B,0x14,0x1D,0x10,0x1C, + 0x10,0x1C,0x14,0x18,0x14,0x24,0x14,0x1F,0x0C,0x20,0x0F,0x1F,0x0D,0x1F,0x10,0x1F, + 0x02,0x15,0x01,0x15,0xFF,0x18,0xFF,0x18,0x10,0x14,0x10,0x14,0x0C,0x1C,0x04,0x05, + 0x0C,0x07,0x10,0x0C,0x10,0x15,0x0C,0x06,0x0C,0x08,0x0C,0x16,0x10,0x1C,0x0C,0x1F, + 0x14,0x1E,0x10,0x1A,0x10,0x13,0x08,0x13,0x0C,0x12,0x0C,0x0B,0x0C,0x05,0x18,0x1B, + 0x18,0x1D,0x18,0x29,0x18,0x20,0x10,0x03,0x10,0x05,0x14,0x0B,0x18,0x13,0x1B,0x1A, + 0x20,0x20,0x1E,0x1F,0x20,0x1F,0x21,0x20,0x18,0x27,0x18,0x1D,0x15,0x1E,0x18,0x1B, + 0x20,0x22,0x1C,0x24,0x14,0x20,0x20,0x23,0x20,0x2B,0x1C,0x2D,0x10,0x25,0x10,0x25, + 0x10,0x22,0x10,0x25,0x18,0x2E,0x18,0x2B,0x1C,0x1E,0x14,0x1C,0x14,0x18,0x1C,0x10, + 0x10,0x31,0x10,0x3B,0x10,0x30,0x1C,0x22,0x20,0x1B,0x20,0x32,0x28,0x21,0x20,0x28, + 0x14,0x15,0x10,0x20,0x10,0x1F,0x10,0x1E,0x18,0x1C,0x28,0x20,0x14,0x1E,0x14,0x23, + 0x14,0x3E,0x1C,0x19,0x1C,0x19,0x1C,0x19,0x14,0x10,0x14,0x12,0x10,0x0E,0x34,0x29, + 0x08,0x16,0x0C,0x17,0x0C,0x17,0x0C,0x17,0x10,0x17,0x0C,0x18,0x10,0x18,0x10,0x18, + 0x10,0x17,0x10,0x1B,0x10,0x1B,0x10,0x18,0x0C,0x18,0x00,0x00 +}; +const uint8_t spr_size_tbl[] = { + 0x28,0x24,0x20,0x23,0x18,0x24,0x20,0x22,0x20,0x25,0x20,0x25,0x20,0x24,0x20,0x23, + 0x20,0x23,0x20,0x23,0x18,0x23,0x20,0x26,0x28,0x1F,0x30,0x1F,0x20,0x1F,0x20,0x24, + 0x18,0x20,0x28,0x1E,0x28,0x1F,0x28,0x1F,0x20,0x1E,0x18,0x1E,0x20,0x1F,0x18,0x1E, + 0x18,0x20,0x28,0x22,0x20,0x21,0x20,0x23,0x20,0x22,0x28,0x1F,0x28,0x20,0x28,0x1E, + 0x28,0x1F,0x30,0x20,0x20,0x24,0x18,0x20,0x20,0x20,0x20,0x21,0x18,0x1D,0x18,0x11, + 0x10,0x10,0x18,0x11,0x20,0x0E,0x18,0x0F,0x10,0x0F,0x18,0x0E,0x20,0x0B,0x18,0x10, + 0x20,0x19,0x20,0x1F,0x18,0x21,0x20,0x1F,0x20,0x1F,0x08,0x06,0x08,0x08,0x10,0x09, + 0x10,0x0B,0x10,0x0B,0x10,0x13,0x18,0x12,0x18,0x0D,0x10,0x10,0x10,0x10,0x10,0x10, + 0x10,0x0F,0x18,0x0F,0x18,0x13,0x18,0x18,0x20,0x13,0x18,0x19,0x10,0x0F,0x10,0x0F, + 0x10,0x09,0x10,0x0F,0x18,0x09,0x18,0x09,0x18,0x09,0x18,0x09,0x18,0x09,0x18,0x09, + 0x18,0x09,0x18,0x09,0x18,0x09,0x18,0x09,0x18,0x09,0x18,0x09,0x28,0x0D,0x30,0x0D, + 0x30,0x0D,0x30,0x0D,0x38,0x11,0x18,0x13,0x10,0x0D,0x10,0x0D,0x10,0x0D,0x10,0x0D, + 0x10,0x0D,0x18,0x13,0x20,0x16,0x18,0x1E,0x18,0x1D,0x18,0x21,0x10,0x21,0x18,0x21, + 0x18,0x07,0x18,0x07,0x18,0x07,0x18,0x07,0x18,0x07,0x18,0x07,0x30,0x41,0x28,0x2C, + 0x30,0x20,0x28,0x2E,0x20,0x26,0x28,0x23,0x30,0x21,0x28,0x21,0x30,0x0E,0x30,0x1D, + 0x30,0x1F,0x30,0x13,0x30,0x14,0x30,0x17,0x30,0x1B,0x30,0x17,0x28,0x1B,0x28,0x22, + 0x18,0x0E,0x10,0x0F,0x18,0x13,0x10,0x12,0x10,0x12,0x18,0x0C,0x18,0x13,0x10,0x19, + 0x18,0x15,0x18,0x17,0x10,0x1B,0x10,0x14,0x10,0x17,0x10,0x15,0x18,0x0B,0x18,0x0B, + 0x10,0x16,0x10,0x16,0x10,0x13,0x10,0x12,0x10,0x12,0x10,0x13,0x10,0x1D,0x10,0x15, + 0x10,0x1C,0x18,0x14,0x18,0x0F,0x10,0x0F,0x10,0x13,0x10,0x15,0x10,0x15,0x18,0x11, + 0x18,0x0C,0x18,0x0D,0x18,0x0D,0x18,0x11,0x18,0x16,0x10,0x0D,0x10,0x10,0x10,0x18, + 0x10,0x11,0x18,0x0B,0x10,0x17,0x18,0x12,0x18,0x0F,0x18,0x0F,0x18,0x0F,0x10,0x13, + 0x18,0x14,0x10,0x12,0x08,0x17,0x10,0x15,0x10,0x12,0x10,0x0C,0x10,0x0C,0x10,0x0C, + 0x18,0x17,0x10,0x12,0x10,0x10,0x10,0x0E,0x10,0x16,0x20,0x0D,0x18,0x10,0x18,0x12, + 0x10,0x0D,0x18,0x0E,0x10,0x13,0x10,0x19,0x08,0x16,0x10,0x0D,0x10,0x13,0x18,0x10, + 0x18,0x12,0x18,0x0D,0x10,0x0F,0x10,0x1C,0x10,0x0E,0x10,0x18,0x18,0x12,0x20,0x14, + 0x20,0x11,0x10,0x0B,0x10,0x1A,0x18,0x12,0x18,0x0D,0x18,0x0C,0x10,0x0F,0x10,0x13, + 0x10,0x13,0x10,0x13,0x10,0x13,0x10,0x13,0x18,0x10,0x18,0x16,0x10,0x12,0x18,0x10, + 0x10,0x0C,0x10,0x0C,0x10,0x0B,0x20,0x10,0x28,0x0E,0x28,0x0E,0x28,0x0E,0x20,0x0E, + 0x20,0x0D,0x10,0x12,0x10,0x12,0x18,0x12,0x20,0x29,0x20,0x1D,0x18,0x14,0x28,0x21, + 0x30,0x1A,0x10,0x0B,0x10,0x0B,0x10,0x0B,0x10,0x0B,0x10,0x0B,0x10,0x0B,0x10,0x0B, + 0x10,0x0B,0x10,0x0B,0x10,0x0B,0x10,0x0B,0x10,0x0B,0x10,0x0B,0x10,0x0B,0x10,0x0B, + 0x10,0x0B,0x10,0x0B,0x10,0x0B,0x10,0x0B,0x10,0x0B,0x10,0x0B,0x10,0x0B,0x10,0x0B, + 0x10,0x0B,0x10,0x0B,0x10,0x0B,0x10,0x0B,0x08,0x08,0x10,0x0B,0x08,0x0B,0x08,0x05, + 0x08,0x05,0x10,0x0C,0x30,0x28,0x38,0x1E,0x30,0x30,0x20,0x33,0x30,0x46,0x30,0x46, + 0x28,0x1B,0x30,0x1D,0x38,0x0E,0x10,0x0C,0x10,0x0C,0x10,0x0C,0x10,0x0C,0x10,0x0C, + 0x10,0x0C,0x10,0x0C,0x10,0x0C,0x10,0x0C,0x10,0x0C,0x10,0x0C,0x10,0x0C,0x10,0x0C, + 0x10,0x0C,0x10,0x0C,0x10,0x0C,0x20,0x15,0x08,0x0A,0x10,0x0C,0x10,0x0C,0x10,0x0C, + 0x40,0x1F,0x18,0x18,0x20,0x1D,0x18,0x1E,0x20,0x1D,0x30,0x28,0x20,0x13,0x20,0x19, + 0x18,0x1C,0x20,0x0E,0x20,0x0F,0x20,0x0E,0x20,0x0F,0x10,0x1C,0x10,0x1C,0x10,0x1C, + 0x10,0x1C,0x18,0x15,0x18,0x19,0x20,0x17,0x20,0x17,0x18,0x20,0x18,0x1E,0x20,0x16, + 0x28,0x0E,0x28,0x0C,0x18,0x0F,0x20,0x0B,0x18,0x15,0x10,0x0A,0x10,0x09,0x08,0x0A, + 0x10,0x0A,0x10,0x0A,0x18,0x07,0x10,0x0E,0x18,0x0C,0x18,0x0B,0x28,0x1D,0x20,0x1C, + 0x20,0x1C,0x28,0x18,0x28,0x24,0x28,0x1F,0x18,0x20,0x20,0x1F,0x20,0x1F,0x20,0x1F, + 0x10,0x15,0x10,0x15,0x10,0x18,0x18,0x18,0x28,0x14,0x28,0x12,0x20,0x1C,0x08,0x05, + 0x18,0x07,0x20,0x0C,0x20,0x15,0x18,0x06,0x18,0x08,0x18,0x16,0x20,0x1C,0x18,0x1F, + 0x28,0x1E,0x20,0x1A,0x20,0x13,0x10,0x13,0x18,0x12,0x18,0x0B,0x18,0x05,0x30,0x1B, + 0x30,0x1D,0x30,0x29,0x30,0x20,0x20,0x03,0x20,0x05,0x28,0x0B,0x30,0x13,0x38,0x1B, + 0x40,0x20,0x40,0x1F,0x40,0x1F,0x40,0x20,0x30,0x27,0x30,0x1D,0x30,0x1E,0x30,0x1B, + 0x40,0x22,0x38,0x24,0x28,0x20,0x40,0x23,0x40,0x2B,0x38,0x2D,0x20,0x25,0x20,0x25, + 0x20,0x22,0x20,0x25,0x30,0x2E,0x30,0x2B,0x38,0x1E,0x28,0x1C,0x28,0x18,0x38,0x10, + 0x20,0x31,0x20,0x3B,0x20,0x30,0x38,0x22,0x40,0x1B,0x20,0x1E,0x20,0x1C,0x20,0x20, + 0x28,0x15,0x20,0x20,0x20,0x1F,0x20,0x1E,0x30,0x1C,0x50,0x20,0x28,0x1E,0x28,0x23, + 0x28,0x3E,0x38,0x16,0x38,0x18,0x38,0x17,0x28,0x10,0x28,0x12,0x20,0x0E,0x68,0x29, + 0x10,0x16,0x18,0x17,0x18,0x17,0x18,0x17,0x20,0x17,0x18,0x18,0x20,0x18,0x20,0x18, + 0x20,0x17,0x20,0x1B,0x20,0x1B,0x20,0x18,0x18,0x18,0x00,0x00 +}; +const uint16_t score_tbl[] = { + 0x000A,0x0014,0x001E,0x0032,0x003C,0x0046,0x004B,0x0050,0x0064,0x00C8,0x01F4,0x0320,0x03E8,0x07D0,0x0BB8,0x1770, + 0x2710 +}; +const uint8_t score_spr_lut[] = { + 0x10,0x0F,0x0D,0x0E,0x0C,0x0C,0x0D,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x03,0x03,0x02,0x02,0x01,0x01,0x03,0x02,0x03,0x03,0x03,0x02,0x03,0x00, + 0x00,0x00,0x00,0x00,0x01,0x02,0x02,0x03,0x03,0x01,0x03,0x03,0x01,0x03,0x0B,0x01, + 0x00,0x03,0x01,0x03,0x02,0x02,0x02,0x00,0x03,0x02,0x01,0x00,0x02,0x03,0x01,0x01, + 0x01,0x02,0x03,0x01,0x03,0x03,0x00,0x09,0x0A,0x08,0x08,0x09,0x0A,0x08,0x07,0x08, + 0x0B,0x0A,0x04,0x06,0x04,0x07,0x05,0x04,0x04,0x05,0x07,0x04,0x0B,0x07,0x03,0x06, + 0x06,0x06,0x06,0x03,0x06,0x0A,0x07,0x05,0x05,0x0B,0x0A,0x08,0x09,0x00 +}; +static const uint8_t object_anim_5e71[] = { + 0x09,0x00,0xFE,0xFF +}; +static const uint8_t object_anim_5e79[] = { + 0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0x02,0x00,0x02,0x00,0x03,0x00,0x03,0x00, + 0x04,0x00,0x04,0x00,0x05,0x00,0x05,0x00,0xE8,0xFF +}; +static const uint8_t object_anim_5ead[] = { + 0x0B,0x00,0x0B,0x00,0x0B,0x00,0x0B,0x00,0x0C,0x00,0xFE,0xFF +}; +static const uint8_t object_anim_5eb9[] = { + 0x22,0x00,0x22,0x00,0x23,0x00,0x23,0x00,0x24,0x00,0x24,0x00,0x24,0x40,0xF2,0xFF +}; +static const uint8_t object_anim_5ec9[] = { + 0x15,0x00,0x15,0x00,0x15,0x00,0x15,0x00,0x16,0x00,0x16,0x00,0x16,0x00,0x16,0x00, + 0x17,0x00,0x17,0x00,0x17,0x00,0x17,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00, + 0x13,0x00,0x13,0x00,0x13,0x00,0x13,0x00,0x14,0x00,0x14,0x00,0x14,0x00,0x14,0x00, + 0xD0,0xFF +}; +static const uint8_t object_anim_5efb[] = { + 0x12,0x00,0x12,0x00,0x12,0x00,0x12,0x00,0x12,0x00,0x11,0x00,0x11,0x00,0x11,0x00, + 0x11,0x00,0x11,0x00,0xEC,0xFF +}; +static const uint8_t object_anim_5f11[] = { + 0x25,0x00,0x25,0x00,0x25,0x00,0x26,0x00,0x26,0x00,0x26,0x00,0x0E,0x00,0x0E,0x00, + 0x0E,0x40,0xEE,0xFF +}; +static const uint8_t object_anim_5f25[] = { + 0x0F,0x00,0x0F,0x00,0x0F,0x00,0x23,0x00,0x23,0x00,0x23,0x00,0x10,0x00,0x10,0x00, + 0x10,0x40,0xEE,0xFF +}; +static const uint8_t object_anim_5f39[] = { + 0x21,0x00,0xFE,0xFF +}; +static const uint8_t object_anim_5f4f[] = { + 0x06,0x00,0x06,0x00,0x06,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x06,0x00,0x06,0x00, + 0x06,0x00,0x06,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x06,0x00,0x06,0x00, + 0x06,0x00,0x06,0x00,0x06,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00, + 0xEC,0xFF +}; +static const uint8_t object_anim_5f81[] = { + 0x09,0x00,0x09,0x00,0x09,0x00,0x09,0x00,0x0A,0x00,0x0A,0x00,0x0A,0x00,0x0A,0x00, + 0x09,0x00,0x09,0x00,0x09,0x00,0x09,0x00,0x08,0x00,0x08,0x00,0x08,0x00,0x08,0x00, + 0xE0,0xFF +}; +static const uint8_t object_anim_5f3d[] = { + 0x19,0x00,0x19,0x00,0x1A,0x00,0x1A,0x00,0x1B,0x00,0x1B,0x00,0x1C,0x00,0x1C,0x00, + 0xF8,0xFF +}; +static const uint8_t object_anim_5fa3[] = { + 0x1D,0x00,0x1D,0x00,0x1D,0x00,0x1F,0x00,0x1F,0x00,0x1F,0x00,0xF4,0xFF +}; +static const uint8_t object_anim_5e75[] = { + 0x2F,0x00,0xFE,0xFF +}; +static const uint8_t object_anim_5e93[] = { + 0x27,0x00,0x27,0x00,0x28,0x00,0x28,0x00,0x29,0x00,0x29,0x00,0x2A,0x00,0x2A,0x00, + 0x2B,0x00,0x2B,0x00,0x2C,0x00,0x2C,0x00,0xE8,0xFF +}; +const uint8_t *object_anim_tbl[] = { + object_anim_5e71, object_anim_5e79, object_anim_5ead, object_anim_5eb9, + object_anim_5ec9, object_anim_5efb, object_anim_5f11, object_anim_5f25, + object_anim_5f39, 0, 0, 0, + 0, 0, 0, 0, + object_anim_5f4f, object_anim_5f81, object_anim_5f3d, object_anim_5fa3, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + object_anim_5e75, object_anim_5e93, 0, object_anim_5e75, + object_anim_5e75, object_anim_5e75, object_anim_5e75, object_anim_5e75, +}; +static const uint8_t club_anim_data[] = { + 0x22,0x00,0x3A,0x00,0x0B,0x00,0x10,0x00,0x23,0x00,0x3B,0x00,0x07,0x00,0x19,0x00, + 0x24,0x00,0x3C,0x00,0xE9,0xFF,0x02,0x00,0x25,0x00,0x3D,0x00,0x0B,0x00,0x03,0x00, + 0x26,0x00,0x3E,0x00,0x08,0x00,0x15,0x00,0x0E,0x00,0x3F,0x00,0xEE,0xFF,0x1B,0x00, + 0x0F,0x00,0x40,0x00,0x08,0x00,0x12,0x00,0x10,0x00,0x41,0x00,0xF7,0xFF,0xF6,0xFF, + 0xAA,0x55,0x22,0x00,0xEB,0x00,0x0F,0x00,0x1F,0x00,0x23,0x00,0xEC,0x00,0xFA,0xFF, + 0x0F,0x00,0x24,0x00,0xF0,0x00,0xE6,0xFF,0xF6,0xFF,0x25,0x00,0xEE,0x00,0x14,0x00, + 0x00,0x00,0x26,0x00,0xEC,0x00,0xFA,0xFF,0x0F,0x00,0x0E,0x00,0xEF,0x00,0xE7,0xFF, + 0x14,0x00,0x0F,0x00,0xEB,0x00,0x0E,0x00,0x1F,0x00,0x10,0x00,0xED,0x00,0xEE,0xFF, + 0xEB,0xFF,0xAA,0x55,0x22,0x00,0x43,0x00,0x08,0x00,0x27,0x00,0x23,0x00,0x43,0x00, + 0x02,0x00,0x1E,0x00,0x24,0x00,0x44,0x00,0xF4,0xFF,0x0D,0x00,0x25,0x00,0x44,0x00, + 0x02,0x00,0x1E,0x00,0x0E,0x00,0x44,0x00,0x00,0x00,0x20,0x00,0x0F,0x00,0x43,0x00, + 0x08,0x00,0x25,0x00,0x10,0x00,0x44,0x00,0xFA,0xFF,0x03,0x00,0xAA,0x55,0xD0,0x00, + 0xC0,0xFF,0x43,0x00,0x44,0x00,0x45,0x00,0x5B,0x00,0xF8,0xFF,0x22,0x00,0x12,0x01, + 0x08,0x00,0x13,0x00,0x23,0x00,0x13,0x01,0x02,0x00,0x0A,0x00,0x24,0x00,0x14,0x01, + 0xF4,0xFF,0xFD,0xFF,0x25,0x00,0x15,0x01,0x02,0x00,0x0A,0x00,0x0E,0x00,0x14,0x01, + 0x00,0x00,0x0C,0x00,0x0F,0x00,0x12,0x01,0x08,0x00,0x11,0x00,0x10,0x00,0x12,0x01, + 0xFA,0xFF,0xEF,0xFF,0xAA,0x55,0xD0,0x00,0xE0,0xFF,0x12,0x01,0x13,0x01,0x14,0x01, + 0x15,0x01,0xF8,0xFF +}; +const struct club_anim_t club_anim_tbl[] = { + { &club_anim_data[0x00], 2, 0x19, 0 }, + { &club_anim_data[0x42], 6, 0x1e, 0 }, + { &club_anim_data[0x84], 6, 0x14, 1 }, + { &club_anim_data[0xcc], 12, 0x1e, 3 }, +}; +const uint8_t player_anim_lut[] = { + 0x00,0x03,0x05,0x07,0x02,0x06,0x00,0x00,0x01,0x03,0x04,0x07,0x02,0x06,0x01,0x00, + 0x01,0x03,0x04,0x07,0x02,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 +}; +const uint8_t player_anim_data[] = { + 0x7C,0x00,0x7B,0x00,0x7A,0x00,0x79,0x00,0x7D,0x00,0x7E,0x00,0x7F,0x00,0x27,0x00, + 0x77,0x00,0xFE,0xEF,0x28,0x00,0x77,0x00,0x00,0xF0,0x29,0x00,0x77,0x00,0x00,0xEF, + 0x2A,0x00,0x77,0x00,0x01,0xF0,0x2B,0x00,0x77,0x00,0x00,0xF1,0x2C,0x00,0x77,0x00, + 0xFE,0xF1,0x2D,0x00,0x77,0x00,0x01,0xF2,0x2E,0x00,0x77,0x00,0xFF,0xF5,0x2F,0x00, + 0x77,0x00,0x02,0xF0,0x30,0x00,0x79,0x00,0xFA,0xE7,0x31,0x00,0x79,0x00,0xF7,0xE1, + 0x32,0x00,0x79,0x00,0xF5,0xDF,0x33,0x00,0x79,0x00,0xF3,0xE1,0x34,0x00,0x79,0x00, + 0xF3,0xE1,0x00,0x00 +}; +const uint8_t vscroll_offsets_data[] = { + 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x02,0x02,0x02,0x02,0x02,0x02, + 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02, + 0x02,0x02,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03, + 0x03,0x03,0x03,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, + 0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x06,0x06,0x06,0x06,0x06,0x06,0x06, + 0x07,0x07,0x07,0x07,0x07,0x07,0x08,0x08,0x08,0x09,0x09,0x09,0x0A,0x0A,0x0B,0x0B, + 0x0C,0x0D,0x0E,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10, + 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10, + 0x10,0x10,0x10,0x10 +}; +const uint8_t cos_tbl[] = { + 0x00,0x01,0x03,0x04,0x06,0x07,0x09,0x0A,0x0C,0x0E,0x0F,0x11,0x12,0x14,0x15,0x17, + 0x18,0x19,0x1B,0x1C,0x1E,0x1F,0x20,0x22,0x23,0x24,0x26,0x27,0x28,0x29,0x2A,0x2C, + 0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x36,0x37,0x38,0x39,0x39,0x3A, + 0x3B,0x3B,0x3C,0x3C,0x3D,0x3D,0x3E,0x3E,0x3E,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F, + 0x40,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3E,0x3E,0x3E,0x3D,0x3D,0x3C,0x3C,0x3B, + 0x3B,0x3A,0x39,0x39,0x38,0x37,0x36,0x36,0x35,0x34,0x33,0x32,0x31,0x30,0x2F,0x2E, + 0x2D,0x2C,0x2A,0x29,0x28,0x27,0x26,0x24,0x23,0x22,0x20,0x1F,0x1E,0x1C,0x1B,0x19, + 0x18,0x17,0x15,0x14,0x12,0x11,0x0F,0x0E,0x0C,0x0A,0x09,0x07,0x06,0x04,0x03,0x01, + 0xFF,0xFE,0xFC,0xFB,0xF9,0xF8,0xF6,0xF5,0xF3,0xF1,0xF0,0xEE,0xED,0xEB,0xEA,0xE8, + 0xE7,0xE6,0xE4,0xE3,0xE1,0xE0,0xDF,0xDD,0xDC,0xDB,0xD9,0xD8,0xD7,0xD6,0xD5,0xD3, + 0xD2,0xD1,0xD0,0xCF,0xCE,0xCD,0xCC,0xCB,0xCA,0xC9,0xC9,0xC8,0xC7,0xC6,0xC6,0xC5, + 0xC4,0xC4,0xC3,0xC3,0xC2,0xC2,0xC1,0xC1,0xC1,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0, + 0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC1,0xC1,0xC1,0xC2,0xC2,0xC3,0xC3,0xC4, + 0xC4,0xC5,0xC6,0xC6,0xC7,0xC8,0xC9,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1, + 0xD2,0xD3,0xD5,0xD6,0xD7,0xD8,0xD9,0xDB,0xDC,0xDD,0xDF,0xE0,0xE1,0xE3,0xE4,0xE6, + 0xE7,0xE8,0xEA,0xEB,0xED,0xEE,0xF0,0xF1,0xF3,0xF5,0xF6,0xF8,0xF9,0xFB,0xFC,0xFE +}; +const uint8_t sin_tbl[] = { + 0x40,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3E,0x3E,0x3E,0x3D,0x3D,0x3C,0x3C,0x3B, + 0x3B,0x3A,0x39,0x39,0x38,0x37,0x36,0x36,0x35,0x34,0x33,0x32,0x31,0x30,0x2F,0x2E, + 0x2D,0x2C,0x2A,0x29,0x28,0x27,0x26,0x24,0x23,0x22,0x20,0x1F,0x1E,0x1C,0x1B,0x19, + 0x18,0x17,0x15,0x14,0x12,0x11,0x0F,0x0E,0x0C,0x0A,0x09,0x07,0x06,0x04,0x03,0x01, + 0x00,0xFE,0xFC,0xFB,0xF9,0xF8,0xF6,0xF5,0xF3,0xF1,0xF0,0xEE,0xED,0xEB,0xEA,0xE8, + 0xE7,0xE6,0xE4,0xE3,0xE1,0xE0,0xDF,0xDD,0xDC,0xDB,0xD9,0xD8,0xD7,0xD6,0xD5,0xD3, + 0xD2,0xD1,0xD0,0xCF,0xCE,0xCD,0xCC,0xCB,0xCA,0xC9,0xC9,0xC8,0xC7,0xC6,0xC6,0xC5, + 0xC4,0xC4,0xC3,0xC3,0xC2,0xC2,0xC1,0xC1,0xC1,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0, + 0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC1,0xC1,0xC1,0xC2,0xC2,0xC3,0xC3,0xC4, + 0xC4,0xC5,0xC6,0xC6,0xC7,0xC8,0xC9,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1, + 0xD2,0xD3,0xD5,0xD6,0xD7,0xD8,0xD9,0xDB,0xDC,0xDD,0xDF,0xE0,0xE1,0xE3,0xE4,0xE6, + 0xE7,0xE8,0xEA,0xEB,0xED,0xEE,0xF0,0xF1,0xF3,0xF5,0xF6,0xF8,0xF9,0xFB,0xFC,0xFE, + 0x00,0x01,0x03,0x04,0x06,0x07,0x09,0x0A,0x0C,0x0E,0x0F,0x11,0x12,0x14,0x15,0x17, + 0x18,0x19,0x1B,0x1C,0x1E,0x1F,0x20,0x22,0x23,0x24,0x26,0x27,0x28,0x29,0x2A,0x2C, + 0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x36,0x37,0x38,0x39,0x39,0x3A, + 0x3B,0x3B,0x3C,0x3C,0x3D,0x3D,0x3E,0x3E,0x3E,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F +}; diff --git a/p2/unpack.c b/p2/unpack.c new file mode 100644 index 0000000..9b67b04 --- /dev/null +++ b/p2/unpack.c @@ -0,0 +1,306 @@ + +#include "util.h" + +struct unpack_eat_t { + FILE *fp; + uint8_t len; + uint16_t bits; + uint8_t *dst; +}; + +static struct unpack_eat_t uneat; + +static int next_bit(struct unpack_eat_t *u) { + const int bit = (u->bits & (1 << (16 - u->len))) != 0; + --u->len; + if (u->len == 0) { + u->bits = fread_le16(u->fp); + u->len = 16; + } + return bit; +} + +static int zero_bits(struct unpack_eat_t *u, int count) { + int i = 0; + for (; i < count; ++i) { + if (next_bit(u)) { + break; + } + } + return i; +} + +static uint8_t get_bits(struct unpack_eat_t *u, int count) { + assert(count < 8); + uint8_t val = 0; + for (int i = 0; i < count; ++i) { + val = (val << 1) | next_bit(u); + } + return val; +} + +static void copy_reference(struct unpack_eat_t *u, int count, int offset_hi, int offset_lo) { + const int16_t offset = offset_hi * 256 + offset_lo; + for (int i = 0; i < count; ++i) { + const uint8_t value = u->dst[offset]; + *u->dst++ = value; + } +} + +static int unpack_eat(FILE *in, struct unpack_eat_t *u) { + uint8_t buffer[17]; + const int header_size = fread(buffer, 1, sizeof(buffer), in); + if (header_size != 17 || READ_LE_UINT16(buffer + 4) != 0x899D || READ_LE_UINT16(buffer + 6) != 0x6C64) { + print_error("Unexpected signature for .eat file"); + return 0; + } + const uint16_t crc = READ_LE_UINT16(buffer + 12); + const int output_size = (buffer[14] << 14) + READ_LE_UINT16(buffer + 15); + print_debug(DBG_UNPACK, "uncompressed size %d crc 0x%04x", output_size, crc); + + uint8_t *output_buffer = (uint8_t *)malloc(output_size); + if (!output_buffer) { + print_error("Failed to allocate EAT unpack buffer, %d bytes", output_size); + return 0; + } + u->fp = in; + u->dst = output_buffer; + u->len = 16; + u->bits = fread_le16(u->fp); + while (1) { + while (next_bit(u)) { + *u->dst++ = fgetc(u->fp); + } + const int b = next_bit(u); + const int offset_lo = fgetc(u->fp); + if (b) { + int offset_hi = 0xFE | next_bit(u); + if (!next_bit(u)) { + int i = 1; + for (; i < 4 && !next_bit(u); ++i) { + offset_hi = (offset_hi << 1) | next_bit(u); + } + offset_hi -= (1 << i); + } + const int n = zero_bits(u, 4); + if (n != 4) { + copy_reference(u, n + 3, offset_hi, offset_lo); + } else if (next_bit(u)) { + copy_reference(u, next_bit(u) + 7, offset_hi, offset_lo); + } else if (!next_bit(u)) { + copy_reference(u, get_bits(u, 3) + 9, offset_hi, offset_lo); + } else { + copy_reference(u, fgetc(u->fp) + 17, offset_hi, offset_lo); + } + } else { + if (next_bit(u)) { + const int offset_hi = (0xF8 | get_bits(u, 3)) - 1; + copy_reference(u, 2, offset_hi, offset_lo); + } else if (offset_lo == 0xFF) { + break; + } else { + copy_reference(u, 2, 0xFF, offset_lo); + } + } + } + assert((u->dst - output_buffer) == output_size); + u->dst = output_buffer; + return output_size; +} + +struct unpack_sqz_t { + uint16_t top_code; + uint8_t code_size; + uint16_t new_codes; + uint8_t *dst; + int bits_left; + uint32_t current_bits; + uint8_t last_code; + uint16_t previous_code; + uint16_t prefix[0x1000]; + uint8_t str[0x1000]; + uint8_t stack[0x1000]; +}; + +#define SQZ_CODE_WIDTH 9 +#define SQZ_CODE_BASE (1 << (SQZ_CODE_WIDTH - 1)) + +static const int SQZ_CLEAR_CODE = SQZ_CODE_BASE; +static const int SQZ_END_CODE = SQZ_CODE_BASE + 1; +static const int SQZ_NEW_CODES = SQZ_CODE_BASE + 2; + +static struct unpack_sqz_t unsqz; + +static uint16_t unpack_sqz_get_bits(FILE *in, struct unpack_sqz_t *u, int count) { + u->current_bits <<= 8; + u->current_bits |= fgetc(in); + u->bits_left += 8; + if (u->bits_left < count) { + u->current_bits <<= 8; + u->current_bits |= fgetc(in); + u->bits_left += 8; + } + const uint32_t code = u->current_bits >> (u->bits_left - count); + u->bits_left -= count; + u->current_bits &= (1 << u->bits_left) - 1; + return code; +} + +static uint16_t unpack_sqz_get_code(FILE *in, struct unpack_sqz_t *u) { + if (u->top_code == u->new_codes && u->code_size != 12) { + ++u->code_size; + u->top_code <<= 1; + } + return unpack_sqz_get_bits(in, u, u->code_size); +} + +static uint16_t unpack_sqz_clear_code(FILE *in, struct unpack_sqz_t *u) { + u->top_code = 1 << SQZ_CODE_WIDTH; + u->code_size = SQZ_CODE_WIDTH; + u->new_codes = SQZ_NEW_CODES; + const uint16_t code = unpack_sqz_get_code(in, u); + if (code != SQZ_END_CODE) { + u->previous_code = code; + *u->dst++ = u->last_code = code & 255; + } + return code; +} + +static int unpack_sqz(FILE *in, struct unpack_sqz_t *u) { + uint8_t buf[4]; + fread(buf, 1, sizeof(buf), in); + assert((buf[1] & 0xF0) == 0x10); + const int output_size = ((buf[0] & 15) << 16) | READ_LE_UINT16(buf + 2); + print_debug(DBG_UNPACK, "SQZ uncompressed size %d", output_size); + uint8_t *output_buffer = (uint8_t *)malloc(output_size); + if (!output_buffer) { + print_error("Failed to allocate SQZ unpack buffer, %d bytes", output_size); + return 0; + } + u->dst = output_buffer; + uint16_t code = unpack_sqz_clear_code(in, u); + assert(code != SQZ_END_CODE); + while (1) { + code = unpack_sqz_get_code(in, u); + if (code == SQZ_END_CODE) { + print_debug(DBG_UNPACK, "lzw end code"); + break; + } else if (code == SQZ_CLEAR_CODE) { + print_debug(DBG_UNPACK, "lzw clear code"); + unpack_sqz_clear_code(in, u); + continue; + } + const uint16_t current_code = code; + uint8_t *sp = u->stack; + if (u->new_codes <= code) { + *sp++ = u->last_code; + code = u->previous_code; + } + while (code >= SQZ_CODE_BASE) { + *sp++ = u->str[code]; + code = u->prefix[code]; + } + *sp++ = u->last_code = code & 255; + do { + --sp; + *u->dst++ = *sp; + } while (sp != u->stack); + const uint16_t index = u->new_codes; + if (index < 0x1000) { + u->str[index] = u->last_code; + u->prefix[index] = u->previous_code; + ++u->new_codes; + } + u->previous_code = current_code; + } + const int count = (u->dst - output_buffer); + print_debug(DBG_UNPACK, "lzw output size %d (expected %d)", count, output_size); + assert(count == output_size); + u->dst = output_buffer; + return count; +} + +struct unpack_sqv_t { + uint8_t dict_buf[0x200 * 2]; + uint8_t rd[0x1000]; + int dict_len; + uint8_t *dst; +}; + +static struct unpack_sqv_t unsqv; + +static int unpack_sqv(FILE *in, struct unpack_sqv_t *u) { + + fread(u->rd, 1, 6, in); + const int uncompressed_size = (READ_LE_UINT16(u->rd) << 16) + READ_LE_UINT16(u->rd + 2); + const int dict_len = READ_LE_UINT16(u->rd + 4); + print_debug(DBG_UNPACK, "SQV uncompressed size %d dict_len %d", uncompressed_size, dict_len); + fread(u->dict_buf, 1, dict_len, in); + + uint8_t *output_buffer = (uint8_t *)malloc(uncompressed_size); + if (!output_buffer) { + print_error("Failed to allocate SQV unpack buffer, %d bytes", uncompressed_size); + return 0; + } + + u->dst = output_buffer; + uint8_t *dst = output_buffer; + + const uint8_t *src = u->rd; + int len = 1; + int bytes_count = 2; + uint16_t bits = 0; + uint16_t val = 0; + while ((dst - output_buffer) < uncompressed_size) { + --len; + if (len == 0) { + bytes_count -= 2; + if (bytes_count == 0) { + bytes_count = fread(u->rd, 1, 0x1000, in); + if (bytes_count == 0) { + break; + } + bytes_count += (bytes_count & 1); + src = u->rd; + } + bits = READ_BE_UINT16(src); src += 2; + len = 17; + continue; + } + const int carry = (bits & 0x8000) != 0; + bits <<= 1; + if (carry) { + val += 2; + } + assert(val < 0x400); + val = READ_LE_UINT16(u->dict_buf + val); + if ((val & 0x8000) == 0) { + continue; + } + *dst++ = val & 255; + val = 0; + } + assert((dst - output_buffer) == uncompressed_size); + return uncompressed_size; +} + +uint8_t *unpack(FILE *in, int *uncompressed_size) { + const uint16_t sig = fread_le16(in); + fseek(in, 0, SEEK_SET); + if (sig == 0x4CB4) { + memset(&uneat, 0, sizeof(unsqz)); + *uncompressed_size = unpack_eat(in, &uneat); + return uneat.dst; + } else if ((sig >> 8) == 0x10) { + memset(&unsqz, 0, sizeof(unsqz)); + *uncompressed_size = unpack_sqz(in, &unsqz); + return unsqz.dst; + } else { + memset(&unsqv, 0, sizeof(unsqv)); + *uncompressed_size = unpack_sqv(in, &unsqv); + return unsqv.dst; + } + *uncompressed_size = 0; + return 0; +} + diff --git a/p2/unpack.h b/p2/unpack.h new file mode 100644 index 0000000..d69d650 --- /dev/null +++ b/p2/unpack.h @@ -0,0 +1,9 @@ + +#ifndef UNPACK_H__ +#define UNPACK_H__ + +#include "intern.h" + +extern uint8_t *unpack(FILE *in, int *uncompressed_size); + +#endif /* UNPACK_H__ */