diff --git a/CMakeLists.txt b/CMakeLists.txt index ff93cb3..2f2ab02 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,6 +57,7 @@ target_sources(xsystem4 PRIVATE src/format.c src/hacks.c src/heap.c + src/icon.c src/id_pool.c src/input.c src/json.c diff --git a/include/icon.h b/include/icon.h new file mode 100644 index 0000000..43dda85 --- /dev/null +++ b/include/icon.h @@ -0,0 +1,26 @@ +/* Copyright (C) 2025 Nunuhara Cabbage + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef SYSTEM4_ICON_H +#define SYSTEM4_ICON_H + +#ifdef __ANDROID__ +#define icon_init() +#else +void icon_init(void); +#endif + +#endif /* SYSTEM4_ICON_H */ diff --git a/include/xsystem4.h b/include/xsystem4.h index f81f71f..7365b89 100644 --- a/include/xsystem4.h +++ b/include/xsystem4.h @@ -27,6 +27,7 @@ enum resume_save_format { struct config { char *game_name; + char *boot_name; char *ain_filename; char *vm_name; char *game_dir; diff --git a/src/icon.c b/src/icon.c new file mode 100644 index 0000000..cfd3299 --- /dev/null +++ b/src/icon.c @@ -0,0 +1,788 @@ +/* Copyright (C) 2023 Nunuhara Cabbage + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include + +#include "system4.h" +#include "system4/buffer.h" +#include "system4/cg.h" +#include "system4/file.h" +#include "system4/little_endian.h" + +#include "xsystem4.h" +#include "gfx/private.h" + +#pragma pack(1) + +#define NR_DIRECTORY_ENTRIES 16 +#define SECTION_NAME_SIZE 8 +#define DIRECTORY_ENTRY_RESOURCE 2 +#define IMAGE_SCN_CNT_UNINITIALIZED_DATA 0x80 + +struct dos_header { + uint16_t magic; + uint16_t cblp; + uint16_t cp; + uint16_t crlc; + uint16_t cparhdr; + uint16_t minalloc; + uint16_t maxalloc; + uint16_t ss; + uint16_t sp; + uint16_t csum; + uint16_t ip; + uint16_t cs; + uint16_t lfarlc; + uint16_t ovno; + uint16_t res[4]; + uint16_t oemid; + uint16_t oeminfo; + uint16_t res2[10]; + uint32_t lfanew; +}; + +// COFF header +struct coff_header { + uint16_t machine; + uint16_t number_of_sections; + uint32_t time_date_stamp; + uint32_t pointer_to_symbol_table; + uint32_t number_of_symbols; + uint16_t size_of_optional_header; + uint16_t characteristics; +}; + +struct data_directory { + uint32_t virtual_address; + uint32_t size; +}; + +// PE optional header +struct optional_header { + uint16_t magic; + uint8_t major_linker_version; + uint8_t minor_linker_version; + uint32_t size_of_code; + uint32_t size_of_initialized_data; + uint32_t size_of_uninitialized_data; + uint32_t address_of_entry_point; + uint32_t base_of_code; + uint32_t base_of_data; + uint32_t image_base; + uint32_t section_alignment; + uint32_t file_alignment; + uint16_t major_operating_system_version; + uint16_t minor_operating_system_version; + uint16_t major_image_version; + uint16_t minor_image_version; + uint16_t major_subsystem_version; + uint16_t minor_subsystem_version; + uint32_t win32_version_value; + uint32_t size_of_image; + uint32_t size_of_headers; + uint32_t checksum; + uint16_t subsystem; + uint16_t dll_characteristics; + uint32_t size_of_stack_reserve; + uint32_t size_of_stack_commit; + uint32_t size_of_heap_reserve; + uint32_t size_of_heap_commit; + uint32_t loader_flags; + uint32_t number_of_rva_and_sizes; + struct data_directory data_directory[NR_DIRECTORY_ENTRIES]; +}; + +struct pe_header { + uint32_t signature; + struct coff_header coff; + struct optional_header opt; +}; + +// header describing section in executable image +struct section_header { + uint8_t name[SECTION_NAME_SIZE]; + union { + uint32_t physical_address; + uint32_t virtual_size; + } misc; + uint32_t virtual_address; + uint32_t size_of_raw_data; + uint32_t pointer_to_raw_data; + uint32_t pointer_to_relocations; + uint32_t pointer_to_linenumbers; + uint16_t number_of_relocations; + uint16_t number_of_linenumbers; + uint32_t characteristics; +}; + +// resource directory table +struct res_dir_table { + uint32_t characteristics; + uint32_t time_date_stamp; + uint16_t major_version; + uint16_t minor_version; + uint16_t number_of_named_entries; + uint16_t number_of_id_entries; +}; + +// entry in resource directory table +struct res_dir_entry { + uint32_t id; + uint32_t off; +}; + +// leaf data of resource directory table +struct res_data_entry { + uint32_t offset_to_data; + uint32_t size; + uint32_t code_page; + uint32_t resource_handle; +}; + +// group_cursor resource data (table header) +struct cursor_dir { + uint16_t reserved; + uint16_t type; + uint16_t count; +}; + +// entry in group_cursor table +struct cursor_dir_entry { + uint16_t width; + uint16_t height; + uint16_t plane_count; + uint16_t bit_count; + uint32_t bytes_in_res; + uint16_t res_id; +}; + +// group_icon resource data (table header) +struct icon_dir { + uint16_t reserved; + uint16_t type; + uint16_t count; +}; + +// entry in group_icon table +struct icon_dir_entry { + uint8_t width; + uint8_t height; + uint8_t color_count; + uint8_t reserved; + uint16_t plane_count; + uint16_t bit_count; + uint32_t bytes_in_res; + uint16_t res_id; +}; + +// BITMAPINFOHEADER +struct bitmap_info { + uint32_t size; + uint32_t width; + uint32_t height; + uint16_t planes; + uint16_t bpp; + uint32_t compression; + uint32_t size_image; + uint32_t ppm_x; + uint32_t ppm_y; + uint32_t colors_used; + uint32_t colors_important; +}; + +struct color_map { + struct { uint8_t b, g, r, u; } colors[256]; +}; + +// cursor resource data +struct cursor_data { + uint16_t hotspot_x; + uint16_t hotspot_y; + struct bitmap_info bm_info; + struct color_map colors; +}; + +// icon resource data +struct icon_data { + struct bitmap_info bm_info; + struct color_map colors; +}; + +struct resource { + uint32_t id; + struct resource *children; + unsigned nr_children; + struct { + uint32_t addr; + uint32_t size; + } leaf; +}; + +struct vma { + uint32_t virt_addr; + uint32_t size; + uint32_t raw_addr; + bool initialized; +}; + +// TODO: use this abstraction for memory reads on executable image +struct vmm { + struct vma *vmas; + unsigned nr_vmas; + struct buffer exe; +}; + +uint8_t *vmm_lookup(uint32_t ptr); + +#pragma pack() + +static bool buffer_expect_string(struct buffer *buf, const char *magic, size_t magic_size) +{ + if (buffer_remaining(buf) < magic_size) + return false; + return !strncmp(buffer_strdata(buf), magic, magic_size); +} + +static uint16_t buffer_read_u16_at(struct buffer *buf, size_t off) +{ + size_t tmp = buf->index; + buf->index = off; + uint32_t v = buffer_read_u16(buf); + buf->index = tmp; + return v; +} + +static uint32_t buffer_read_u32_at(struct buffer *buf, size_t off) +{ + size_t tmp = buf->index; + buf->index = off; + uint32_t v = buffer_read_int32(buf); + buf->index = tmp; + return v; +} + +#define read_member16(b, s, m) \ + buffer_read_u16_at(b, b->index + offsetof(s, m)); +#define read_member32(b, s, m) \ + buffer_read_u32_at(b, b->index + offsetof(s, m)); + +static bool read_pe_header(struct buffer *buf, uint32_t *addr_out, uint32_t *size_out, + struct vma **vma_out, uint16_t *nr_vmas_out) +{ + possibly_unused size_t pe_loc = buf->index; + if (buffer_remaining(buf) < sizeof(struct pe_header)) + return false; + + if (buffer_read_int32(buf) != 0x4550) + return false; + + uint16_t nr_sections = read_member16(buf, struct coff_header, number_of_sections); + uint16_t opt_size = read_member16(buf, struct coff_header, size_of_optional_header); + + buffer_skip(buf, sizeof(struct coff_header)); + assert(buf->index == pe_loc + offsetof(struct pe_header, opt)); + size_t opt_pos = buf->index; + buffer_skip(buf, offsetof(struct optional_header, data_directory)); + buffer_skip(buf, sizeof(struct data_directory) * DIRECTORY_ENTRY_RESOURCE); + + *addr_out = read_member32(buf, struct data_directory, virtual_address); + *size_out = read_member32(buf, struct data_directory, size); + + buffer_seek(buf, opt_pos + opt_size); + if (buffer_remaining(buf) < nr_sections * sizeof(struct section_header)) + return false; + + struct vma *vma = xmalloc(nr_sections * sizeof(struct vma)); + for (int i = 0; i < nr_sections; i++) { + vma[i].virt_addr = read_member32(buf, struct section_header, virtual_address); + vma[i].size = read_member32(buf, struct section_header, size_of_raw_data); + vma[i].raw_addr = read_member32(buf, struct section_header, pointer_to_raw_data); + uint32_t characteristics = read_member32(buf, struct section_header, characteristics); + vma[i].initialized = !(characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA); + buffer_skip(buf, sizeof(struct section_header)); + } + + *vma_out = vma; + *nr_vmas_out = nr_sections; + return true; +} + +static bool read_res_dir_table(struct buffer *b, struct res_dir_entry **entry_out, + uint16_t *nr_named_out, uint16_t *nr_id_out) +{ + if (buffer_remaining(b) < sizeof(struct res_dir_table)) + return false; + + uint16_t nr_named = read_member16(b, struct res_dir_table, number_of_named_entries); + uint16_t nr_id = read_member16(b, struct res_dir_table, number_of_id_entries); + unsigned nr_entries = nr_named + nr_id; + buffer_skip(b, sizeof(struct res_dir_table)); + if (buffer_remaining(b) < nr_entries * sizeof(struct res_dir_entry)) + return false; + + struct res_dir_entry *entry = xmalloc(nr_entries * sizeof(struct res_dir_entry)); + for (unsigned i = 0; i < nr_entries; i++) { + entry[i].id = buffer_read_int32(b); + entry[i].off = buffer_read_int32(b); + } + + *entry_out = entry; + *nr_named_out = nr_named; + *nr_id_out = nr_id; + return true; +} + +bool read_resource_data(struct buffer *b, struct resource *node) +{ + if (buffer_remaining(b) < sizeof(struct res_data_entry)) + return false; + node->leaf.addr = buffer_read_int32(b); + node->leaf.size = buffer_read_int32(b); + return true; +} + +bool read_resource_table(struct buffer *b, struct resource *node, uint32_t res_base) +{ + uint16_t nr_named, nr_id; + struct res_dir_entry *entry = NULL; + if (!read_res_dir_table(b, &entry, &nr_named, &nr_id)) + goto error; + + node->nr_children = nr_named + nr_id; + node->children = xcalloc(node->nr_children, sizeof(struct resource)); + + for (unsigned i = 0; i < node->nr_children; i++) { + node->children[i].id = entry[i].id; + buffer_seek(b, res_base + (entry[i].off & 0x7fffffff)); + if (entry[i].off & 0x80000000) { + if (!read_resource_table(b, &node->children[i], res_base)) + goto error; + } else { + if (!read_resource_data(b, &node->children[i])) + goto error; + } + } + + free(entry); + return true; +error: + free(entry); + free(node->children); + return false; +} + +struct resource *read_resources(struct buffer *b) +{ + struct resource *root = xcalloc(1, sizeof(struct resource)); + if (read_resource_table(b, root, b->index)) + return root; + + free(root); + return NULL; +} + +void _free_resources(struct resource *res) +{ + for (unsigned i = 0; i < res->nr_children; i++) { + _free_resources(&res->children[i]); + } + free(res->children); +} + +void free_resources(struct resource *root) +{ + _free_resources(root); + free(root); +} + +void read_bitmap_info(struct buffer *b, struct bitmap_info *bm_info) +{ + bm_info->size = buffer_read_int32(b); + bm_info->width = buffer_read_int32(b); + bm_info->height = buffer_read_int32(b); + bm_info->planes = buffer_read_u16(b); + bm_info->bpp = buffer_read_u16(b); + bm_info->compression = buffer_read_int32(b); + bm_info->size_image = buffer_read_int32(b); + bm_info->ppm_x = buffer_read_int32(b); + bm_info->ppm_y = buffer_read_int32(b); + bm_info->colors_used = buffer_read_int32(b); + bm_info->colors_important = buffer_read_int32(b); +} + +static struct cg *cg_alloc_direct(unsigned w, unsigned h) +{ + struct cg *cg = xcalloc(1, sizeof(struct cg)); + cg->metrics.w = w; + cg->metrics.h = h; + cg->metrics.bpp = 32; + cg->metrics.has_pixel = true; + cg->metrics.has_alpha = true; + cg->metrics.pixel_pitch = w * 4; + cg->metrics.alpha_pitch = 1; + cg->pixels = xcalloc(w * 4, h); + return cg; +} + +static struct cg *load_bmp_8bpp(struct bitmap_info *bm_info, struct color_map *colors, + uint8_t *pixels, uint8_t *bitmask) +{ + unsigned w = bm_info->width; + unsigned h = bm_info->height / 2; + + // expand mask + uint8_t *mask = xmalloc(w * h); + for (int row = 0; row < h; row++) { + uint8_t *dst = mask + row * w; + uint8_t *src = bitmask + row * (w/8); + for (int bit = 0x80, col = 0; col < w; bit >>= 1, col++, dst++) { + if (bit == 0) { + bit = 0x80; + src++; + } + *dst = (*src & bit) ? 0 : 255; + } + } + + // convert pixels/mask to RGBA CG + struct cg *cg = cg_alloc_direct(w, h); + for (unsigned row = 0; row < h; row++) { + uint8_t *dst = cg->pixels + row * w * 4; + uint8_t *p_src = pixels + (h - (row + 1)) * w; + uint8_t *m_src = mask + (h - (row + 1)) * w; + for (unsigned col = 0; col < w; col++, p_src++, m_src++, dst += 4) { + uint8_t c = *p_src; + dst[0] = colors->colors[c].r; + dst[1] = colors->colors[c].g; + dst[2] = colors->colors[c].b; + dst[3] = *m_src; + } + } + + free(mask); + return cg; +} + +static struct cg *load_bmp_4bpp(struct bitmap_info *bm_info, struct color_map *colors, + uint8_t *pixels, uint8_t *bitmask) +{ + unsigned w = bm_info->width; + unsigned h = bm_info->height / 2; + + // expand mask + uint8_t *mask = xmalloc(w * h); + for (int row = 0; row < h; row++) { + uint8_t *dst = mask + row * w; + uint8_t *src = bitmask + row * (((w/8) + 3) & ~3); + for (int bit = 0x80, col = 0; col < w; bit >>= 1, col++, dst++) { + if (bit == 0) { + bit = 0x80; + src++; + } + *dst = (*src & bit) ? 0 : 255; + } + } + + // convert pixels/mask to RGBA CG + struct cg *cg = cg_alloc_direct(w, h); + for (unsigned row = 0; row < h; row++) { + uint8_t *dst = cg->pixels + row * w * 4; + uint8_t *p_src = pixels + (h - (row + 1)) * w / 2; + uint8_t *m_src = mask + (h - (row + 1)) * w; + for (unsigned col = 0; col < w; col += 2, p_src++, m_src += 2, dst += 8) { + uint8_t c = *p_src >> 4; + dst[0] = colors->colors[c].r; + dst[1] = colors->colors[c].g; + dst[2] = colors->colors[c].b; + dst[3] = m_src[0]; + c = *p_src & 0xf; + dst[4] = colors->colors[c].r; + dst[5] = colors->colors[c].g; + dst[6] = colors->colors[c].b; + dst[7] = m_src[1]; + } + } + + free(mask); + return cg; +} + +static bool read_icon_data(struct buffer *b, struct icon_data *icon, uint8_t **image, uint8_t **bitmask) +{ + if (buffer_remaining(b) < sizeof(struct bitmap_info)) + return false; + read_bitmap_info(b, &icon->bm_info); + if (icon->bm_info.planes != 1) + return false; + if (icon->bm_info.bpp != 8 && icon->bm_info.bpp != 4) + return false; + if (icon->bm_info.compression != 0) + return false; + + int nr_colors = icon->bm_info.colors_used; + if (!nr_colors) { + if (icon->bm_info.bpp == 4) + nr_colors = 16; + else + nr_colors = 256; + } + if (buffer_remaining(b) < 4 * nr_colors) + return false; + + int image_size = icon->bm_info.width * (icon->bm_info.height / 2); + if (icon->bm_info.bpp == 4) + image_size /= 2; + + for (unsigned i = 0; i < nr_colors; i++) { + icon->colors.colors[i].b = buffer_read_u8(b); + icon->colors.colors[i].g = buffer_read_u8(b); + icon->colors.colors[i].r = buffer_read_u8(b); + icon->colors.colors[i].u = buffer_read_u8(b); + } + *image = (uint8_t*)buffer_strdata(b); + buffer_skip(b, image_size); + *bitmask = (uint8_t*)buffer_strdata(b); + return true; +} + +static SDL_Surface *read_icon(struct buffer *b, struct resource *res, struct cg **cg_out) +{ + struct icon_data icon; + uint8_t *image; + uint8_t *bitmask; + buffer_seek(b, res->leaf.addr); + if (!read_icon_data(b, &icon, &image, &bitmask)) { + //WARNING("failed to read icon data"); + return NULL; + } + + struct cg *cg; + if (icon.bm_info.bpp == 8) { + cg = load_bmp_8bpp(&icon.bm_info, &icon.colors, image, bitmask); + } else if (icon.bm_info.bpp == 4) { + cg = load_bmp_4bpp(&icon.bm_info, &icon.colors, image, bitmask); + } else { + WARNING("invalid bpp: %d", icon.bm_info.bpp); + return NULL; + } + SDL_Surface *s = SDL_CreateRGBSurfaceWithFormatFrom(cg->pixels, cg->metrics.w, + cg->metrics.h, 32, cg->metrics.w * 4, SDL_PIXELFORMAT_RGBA32); + if (!s) + ERROR("SDL_CreateRGBSurfaceWithFormatFrom: %s", SDL_GetError()); + *cg_out = cg; + return s; +} + +static int read_group_icon(struct buffer *b, struct resource *res) +{ + buffer_seek(b, res->leaf.addr); + if (buffer_remaining(b) < sizeof(struct icon_dir)) + return -1; + + uint16_t count = read_member16(b, struct icon_dir, count); + if (count < 1) { + WARNING("group_icon directory contains no entries"); + return -1; + } + //if (count > 1) + // WARNING("Ignoring additional entries in group_icon directory"); + + buffer_skip(b, sizeof(struct icon_dir)); + buffer_skip(b, offsetof(struct icon_dir_entry, res_id)); + return buffer_read_u16(b); +} + +static int nr_icons = 0; +static SDL_Surface **icons = NULL; +static struct cg **icon_cgs = NULL; + +void read_icons(struct buffer *b, struct resource *root) +{ + struct resource *icon = NULL; + struct resource *group_icon = NULL; + for (unsigned i = 0; i < root->nr_children; i++) { + if (root->children[i].id == 3) { + icon = &root->children[i]; + } else if (root->children[i].id == 14) { + group_icon = &root->children[i]; + } + } + + nr_icons = group_icon->nr_children; + icons = xcalloc(nr_icons, sizeof(struct SDL_Surface*)); + icon_cgs = xcalloc(nr_icons, sizeof(struct cg*)); + for (unsigned i = 0; i < group_icon->nr_children; i++) { + struct resource *child = &group_icon->children[i]; + if (child->nr_children != 1 || child->children[0].nr_children != 0) { + WARNING("Unexpected resource layout (icon %d)", i); + continue; + } + int res_id = read_group_icon(b, &child->children[0]); + if (res_id < 0) + continue; + + // get the corresponding icon resource + struct resource *icon_res = NULL; + for (unsigned j = 0; j < icon->nr_children; j++) { + struct resource *child = &icon->children[j]; + if (child->id == res_id) { + if (child->nr_children != 1 || child->children[0].nr_children != 0) { + WARNING("Unexpected resource layout (icon %d)", i); + continue; + } + icon_res = &child->children[0]; + } + } + if (!icon_res) { + WARNING("Couldn't find icon for group_icon %d", i); + continue; + } + icons[i] = read_icon(b, icon_res, &icon_cgs[i]); + } +} + +SDL_Surface *icon_get(unsigned no) +{ + return no < nr_icons ? icons[no] : NULL; +} + +bool _icon_init(const char *exe_path) +{ + struct buffer buf; + struct vma *vma = NULL; + struct res_dir_entry *entry = NULL; + + buf.buf = file_read(exe_path, &buf.size); + buf.index = 0; + if (!buf.buf) { + WARNING("Failed to read executable: %s", exe_path); + return false; + } + + // expect DOS header + if (!buffer_expect_string(&buf, "MZ", 2)) + goto error; + if (buffer_remaining(&buf) < sizeof(struct dos_header)) + goto error; + + // seek to PE header + buffer_skip(&buf, offsetof(struct dos_header, lfanew)); + buffer_seek(&buf, buffer_read_int32(&buf)); + if (!buffer_expect_string(&buf, "PE\0\0", 4)) + goto error; + + // read relevant data from PE header + uint32_t res_virt_addr; + uint32_t res_virt_size; + uint16_t nr_vmas; + if (!read_pe_header(&buf, &res_virt_addr, &res_virt_size, &vma, &nr_vmas)) + goto error; + + // calculate size of expanded process image + size_t vma_size = 0; + for (int i = 0; i < nr_vmas; i++) { + vma_size = max(vma_size, vma[i].virt_addr + vma[i].size); + } + + // load sections into memory at correct offsets + // TODO: use an abstraction for virtual memory lookups that doesn't involve + // copying memory + buf.buf = realloc(buf.buf, vma_size); + buf.size = vma_size; + for (int i = nr_vmas - 1; i >= 0; i--) { + if (!vma[i].initialized) + continue; + if (vma[i].virt_addr + vma[i].size > buf.size) + goto error; + if (vma[i].raw_addr + vma[i].size > buf.size) + goto error; + if (vma[i].virt_addr != vma[i].raw_addr) { + memmove(buf.buf + vma[i].virt_addr, buf.buf + vma[i].raw_addr, vma[i].size); + } + } + + if (res_virt_addr + res_virt_size > buf.size) + goto error; + + buffer_seek(&buf, res_virt_addr); + struct resource *root = read_resources(&buf); + if (!root) + goto error; + + read_icons(&buf, root); + + free_resources(root); + free(buf.buf); + free(vma); + free(entry); + + SDL_Surface *icon = icon_get(0); + if (icon) + SDL_SetWindowIcon(sdl.window, icon); + + return true; +error: + WARNING("Invalid/unexpected executable format: %s", exe_path); + free(buf.buf); + free(vma); + free(entry); + return false; +} + +static char *get_exe_path(const char *name) +{ + char filename[256]; + snprintf(filename, 256, "%s.exe", name); + + char *path = gamedir_path(filename); + if (file_exists(path)) + return path; + char *i_path = path_get_icase(path); + free(path); + if (i_path) + return i_path; + return NULL; +} + +static const char *exe_names[] = { + "System40", + "Rance01", // XXX: MG deleted BootName from AliceStart.ini... + "daibanchou", + "galzoo", + "Hoken", + "ojou", + "paschacpp", + "toushin3", + "vanish", + "yokubori", +}; + +void icon_init(void) +{ + char *path = NULL; + if (config.boot_name) + path = get_exe_path(config.boot_name); + for (int i = 0; !path && i < sizeof(exe_names)/sizeof(*exe_names); i++) { + path = get_exe_path(exe_names[i]); + } + if (path) { + _icon_init(path); + free(path); + } +} diff --git a/src/meson.build b/src/meson.build index 9f1cec1..ba0dbe2 100644 --- a/src/meson.build +++ b/src/meson.build @@ -19,6 +19,7 @@ xsystem4 = [version_h, 'format.c', 'hacks.c', 'heap.c', + 'icon.c', 'id_pool.c', 'input.c', 'json.c', diff --git a/src/system4.c b/src/system4.c index 912a1be..40da671 100644 --- a/src/system4.c +++ b/src/system4.c @@ -48,6 +48,7 @@ void apply_game_specific_hacks(struct ain *ain); struct config config = { .game_name = NULL, + .boot_name = NULL, .ain_filename = NULL, .vm_name = NULL, .game_dir = NULL, @@ -132,6 +133,8 @@ static bool read_config(const char *path) for (int i = 0; i < ini_size; i++) { if (!strcmp(ini[i].name->text, "GameName")) { config.game_name = strdup(ini_string(&ini[i])->text); + } else if (!strcmp(ini[i].name->text, "BootName")) { + config.boot_name = strdup(ini_string(&ini[i])->text); } else if (!strcmp(ini[i].name->text, "CodeName")) { config.ain_filename = strdup(ini_string(&ini[i])->text); } else if (!strcmp(ini[i].name->text, "MainVM")) { diff --git a/src/video.c b/src/video.c index 3ab9caa..fbcc9e4 100644 --- a/src/video.c +++ b/src/video.c @@ -29,6 +29,7 @@ #include "gfx/gfx.h" #include "gfx/private.h" +#include "icon.h" #include "xsystem4.h" struct sdl_private sdl; @@ -278,6 +279,7 @@ int gfx_init(void) init_window_size(); atexit(gfx_fini); gfx_clear(); + icon_init(); gfx_initialized = true; return 0; }