diff --git a/CMakeLists.txt b/CMakeLists.txt index a7dede226..667df5221 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -184,7 +184,6 @@ cmake_dependent_option(PCL "Generic PCL5e Printer" cmake_dependent_option(SIO_DETECT "Super I/O Detection Helper" ON "DEV_BRANCH" OFF) cmake_dependent_option(WACOM "Wacom Input Devices" ON "DEV_BRANCH" OFF) cmake_dependent_option(XL24 "ATI VGA Wonder XL24 (ATI-28800-6)" ON "DEV_BRANCH" OFF) -cmake_dependent_option(NETSWITCH "Network Switch Support" ON "DEV_BRANCH" OFF) cmake_dependent_option(VFIO "Virtual Function I/O" ON "DEV_BRANCH" OFF) cmake_dependent_option(SOFTMODEM "AC'97 Softmodem" ON "DEV_BRANCH" OFF) @@ -224,7 +223,7 @@ if(NOT EMU_BUILD_NUM) set(EMU_BUILD_NUM 0) endif() if(NOT EMU_COPYRIGHT_YEAR) - set(EMU_COPYRIGHT_YEAR 2025) + set(EMU_COPYRIGHT_YEAR 2026) endif() # Libasan diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..e0aaf1410 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,30 @@ +# Code of Conduct +In order for everyone to enjoy their time contributing to 86Box or otherwise being a part of the community, we kindly ask you to review and follow the below rules. + +## 1. No illegal activity or GitHub ToS violations +- 1.1. Do not distribute malware for non-research purposes. Post samples in a clearly named encrypted archive. +- 1.2. Posting old software is allowed if at least 10 years old and out of support. +- 1.3. Do not post NSFW content (defined at the staff's discretion). +- 1.4. Do not do anything forbidden by the law or the Discord or GitHub Terms of Service. + +## 2. No offensive or disruptive behavior or harassment +- 2.1. No gate-keeping. We aim to accommodate and welcome people of all different opinions and knowledge levels to this community. +- 2.2. You may speak any language but provide a summary in English so that we can understand what you are saying and use English when requested. +- 2.3. Do not retroactively delete messages or do major edits unless you posted something by mistake. +- 2.4. Do not antagonize, defame, demean, blackmail, impersonate, dox others, bring outside drama, use intentionally offensive profile aspects, or otherwise post messages to start a fight (eg. platform wars). Discuss or debate the idea, not the person. +- 2.5. Do not backseat moderate, spam, flood, unsolicitedly ping people, advertise without permission, or evade blocks. +- 2.6. Do not speak on behalf of the project unless you are a team member. This includes all messages which could reasonably be understood as being an official position. Ask a team member if you're unsure about your message. +- 2.7. Decisions by higher-ranked users supersede those by lower-ranked users. This applies to moderation **and** emulator development. Rank and seniority must be earned. +- 2.8. Do not engage in political discussions. +- 2.9. Ignoring essential communication from team members does not exempt you from possible actions against you. Seniority must be earned. + +## 3. Moderation and appeal protocol +- 3.1. Rule violations are punished at the team's discretion, taking all circumstances into account. +- 3.2. Rules enforcement must be equal, impartial, and not retroactive or politically motivated. +- 3.3. Everyone is innocent until proven guilty. +- 3.4. Unless there is an emergency, urgent action is otherwise warranted, or a user is participating with the express purpose of violating our rules, the team shall deliberate before taking action in order to reach consensus and avoid team conflicts. + +## 4. Do not insist on requests or suggestions +- 4.1. You may politely request something from us; if rejected, you are going to be told why and what to do to have it reconsidered. +- 4.2. Follow the contribution requirements listed on the GitHub readme. +- 4.3. Follow proper procedure (eg. for pull requests or bug reports). diff --git a/README.md b/README.md index 506eba794..736e66779 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,10 @@ ===== [![Build Status](https://ci.86box.net/job/86Box/badge/icon)](https://ci.86box.net/job/86Box/) -[![License](https://img.shields.io/github/license/86Box/86Box)](COPYING) [![Latest release](https://img.shields.io/github/release/86Box/86Box.svg)](https://github.com/86Box/86Box/releases) [![Downloads](https://img.shields.io/github/downloads/86Box/86Box/total.svg)](https://github.com/86Box/86Box/releases) +[![License](https://img.shields.io/github/license/86Box/86Box)](COPYING) +[![Latest release](https://img.shields.io/github/release/86Box/86Box.svg)](https://github.com/86Box/86Box/releases) +[![Downloads](https://img.shields.io/github/downloads/86Box/86Box/total.svg)](https://github.com/86Box/86Box/releases) +[![Translation status](https://weblate.86box.net/widget/86box/86box/language-badge.svg)](https://weblate.86box.net/engage/86box/) **86Box** is a low level x86 emulator that runs older operating systems and software designed for IBM PC systems and compatibles from 1981 through fairly recent system designs based on the PCI bus. diff --git a/src/86box.c b/src/86box.c index 67a81fba1..16f4851ad 100644 --- a/src/86box.c +++ b/src/86box.c @@ -83,6 +83,7 @@ #include <86box/fdc.h> #include <86box/fdc_ext.h> #include <86box/hdd.h> +#include <86box/hdd_audio.h> #include <86box/hdc.h> #include <86box/hdc_ide.h> #include <86box/scsi.h> @@ -1354,7 +1355,7 @@ pc_init_roms(void) while (machine_get_internal_name_ex(c) != NULL) { m = machine_available(c); if (!m) - pclog("Missing machine: %s\n", machine_getname_ex(c)); + pclog("Missing machine: %s\n", machine_getname(c)); c++; } @@ -1395,7 +1396,7 @@ pc_init_modules(void) /* Load the ROMs for the selected machine. */ if (!machine_available(machine)) { - swprintf(temp, sizeof_w(temp), plat_get_string(STRING_HW_NOT_AVAILABLE_MACHINE), machine_getname()); + swprintf(temp, sizeof_w(temp), plat_get_string(STRING_HW_NOT_AVAILABLE_MACHINE), machine_getname(machine)); c = 0; machine = -1; while (machine_get_internal_name_ex(c) != NULL) { @@ -1477,6 +1478,8 @@ pc_init_modules(void) fdd_audio_load_profiles(); fdd_audio_init(); } + + hdd_audio_init(); sound_init(); @@ -1720,6 +1723,9 @@ pc_reset_hard_init(void) fdd_reset(); + /* Reset HDD audio to pick up any profile changes */ + hdd_audio_reset(); + /* Reset and reconfigure the SCSI layer. */ scsi_card_init(); @@ -1817,7 +1823,7 @@ update_mouse_msg(void) wchar_t wmachine[2048]; wchar_t *wcp; - mbstowcs(wmachine, machine_getname(), strlen(machine_getname()) + 1); + mbstowcs(wmachine, machine_getname(machine), strlen(machine_getname(machine)) + 1); if (!cpu_override) mbstowcs(wcpufamily, cpu_f->name, strlen(cpu_f->name) + 1); diff --git a/src/cdrom/cdrom.c b/src/cdrom/cdrom.c index 52ea97972..e5f6ba259 100644 --- a/src/cdrom/cdrom.c +++ b/src/cdrom/cdrom.c @@ -3224,6 +3224,22 @@ cdrom_is_empty(const uint8_t id) return ret; } +int +cdrom_is_playing(const uint8_t id) +{ + const cdrom_t *dev = &cdrom[id]; + + return (dev->cd_status == CD_STATUS_PLAYING); +} + +int +cdrom_is_paused(const uint8_t id) +{ + const cdrom_t *dev = &cdrom[id]; + + return (dev->cd_status == CD_STATUS_PAUSED); +} + /* The mechanics of ejecting a CD-ROM from a drive. */ void cdrom_eject(const uint8_t id) diff --git a/src/chipset/acc2036.c b/src/chipset/acc2036.c index 3984f82e0..9f039c4b9 100644 --- a/src/chipset/acc2036.c +++ b/src/chipset/acc2036.c @@ -123,7 +123,8 @@ acc2036_recalc(acc2036_t *dev) ram_page_t *ep = &dev->ems_pages[i - start_i]; mem_mapping_disable(&rp->mapping); - mem_mapping_set_addr(&ep->mapping, ep->virt, 0x000040000); + ep->virt = ((i << 14) + 0x000a0000); + mem_mapping_set_addr(&ep->mapping, ep->virt, 0x00004000); mem_mapping_set_exec(&ep->mapping, ram + ep->phys); mem_set_mem_state_both(ep->virt, 0x00004000, MEM_READ_INTERNAL | MEM_WRITE_INTERNAL); } else { @@ -135,7 +136,7 @@ acc2036_recalc(acc2036_t *dev) int flags; uint8_t val; - mem_mapping_set_addr(&rp->mapping, rp->virt, 0x000040000); + mem_mapping_set_addr(&rp->mapping, rp->virt, 0x00004000); mem_mapping_set_exec(&rp->mapping, ram + rp->phys); if ((i >= 8) && (i <= 15)) { @@ -203,6 +204,8 @@ acc2036_recalc(acc2036_t *dev) mem_remap_top(384); } + mem_mapping_disable(&ram_mid_mapping); + flushmmucache_nopc(); } diff --git a/src/config.c b/src/config.c index d5deded9d..52eb05dae 100644 --- a/src/config.c +++ b/src/config.c @@ -51,6 +51,7 @@ #include <86box/lpt.h> #include <86box/serial.h> #include <86box/hdd.h> +#include <86box/hdd_audio.h> #include <86box/hdc.h> #include <86box/hdc_ide.h> #include <86box/fdd.h> @@ -349,16 +350,21 @@ load_machine(void) for (i = 0; machine_migrations[i].old; i++) { if (!strcmp(p, machine_migrations[i].old)) { machine = machine_get_machine_from_internal_name(machine_migrations[i].new); - migrate_from = p; - if (machine_migrations[i].new_bios) { - migration_cat = ini_find_or_create_section(config, machine_get_device(machine)->name); - ini_section_set_string(migration_cat, "bios", machine_migrations[i].new_bios); + if (machine != -1) { + migrate_from = p; + if (machine_migrations[i].new_bios) { + migration_cat = ini_find_or_create_section(config, machine_get_device(machine)->name); + ini_section_set_string(migration_cat, "bios", machine_migrations[i].new_bios); + } } break; } } - if (!migrate_from) + if (!migrate_from) { machine = machine_get_machine_from_internal_name(p); + if (machine == -1) + machine = 0; + } } else { machine = 0; } @@ -803,8 +809,8 @@ load_network(void) nc->net_type = NET_TYPE_VDE; else if (!strcmp(p, "tap") || !strcmp(p, "4")) nc->net_type = NET_TYPE_TAP; - else if (!strcmp(p, "nmswitch") || !strcmp(p, "5")) - nc->net_type = NET_TYPE_NMSWITCH; + else if (!strcmp(p, "nlswitch") || !strcmp(p, "nmswitch") || !strcmp(p, "5")) + nc->net_type = NET_TYPE_NLSWITCH; else if (!strcmp(p, "nrswitch") || !strcmp(p, "6")) nc->net_type = NET_TYPE_NRSWITCH; else @@ -855,8 +861,8 @@ load_network(void) nc->net_type = NET_TYPE_VDE; else if (!strcmp(p, "tap") || !strcmp(p, "4")) nc->net_type = NET_TYPE_TAP; - else if (!strcmp(p, "nmswitch") || !strcmp(p, "5")) - nc->net_type = NET_TYPE_NMSWITCH; + else if (!strcmp(p, "nlswitch") || !strcmp(p, "nmswitch") || !strcmp(p, "5")) + nc->net_type = NET_TYPE_NLSWITCH; else if (!strcmp(p, "nrswitch") || !strcmp(p, "6")) nc->net_type = NET_TYPE_NRSWITCH; else @@ -880,18 +886,17 @@ load_network(void) } else strcpy(nc->host_dev_name, "none"); - sprintf(temp, "net_%02i_switch_group", c + 1); - net_cards_conf[c].switch_group = ini_section_get_int(cat, temp, 0); + sprintf(temp, "net_%02i_switch_group", c + 1); + nc->switch_group = ini_section_get_int(cat, temp, NET_SWITCH_GRP_MIN); + if (nc->switch_group < NET_SWITCH_GRP_MIN) + nc->switch_group = NET_SWITCH_GRP_MIN; - sprintf(temp, "net_%02i_promisc", c + 1); - net_cards_conf[c].promisc_mode = ini_section_get_int(cat, temp, 0); + sprintf(temp, "net_%02i_promisc", c + 1); + nc->promisc_mode = ini_section_get_int(cat, temp, 0); - sprintf(temp, "net_%02i_nrs_host", c + 1); - p = ini_section_get_string(cat, temp, NULL); - if (p != NULL) - strncpy(net_cards_conf[c].nrs_hostname, p, sizeof(net_cards_conf[c].nrs_hostname) - 1); - else - strncpy(net_cards_conf[c].nrs_hostname, "", sizeof(net_cards_conf[c].nrs_hostname) - 1); + sprintf(temp, "net_%02i_nrs_host", c + 1); + p = ini_section_get_string(cat, temp, NULL); + strncpy(nc->nrs_hostname, p ? p : "", sizeof(nc->nrs_hostname) - 1); sprintf(temp, "net_%02i_link", c + 1); nc->link_state = ini_section_get_int(cat, temp, @@ -1247,6 +1252,8 @@ load_hard_disks(void) uint32_t board = 0; uint32_t dev = 0; + hdd_audio_load_profiles(); + memset(temp, '\0', sizeof(temp)); for (uint8_t c = 0; c < HDD_NUM; c++) { sprintf(temp, "hdd_%02i_parameters", c + 1); @@ -1315,6 +1322,11 @@ load_hard_disks(void) p = ini_section_get_string(cat, temp, tmp2); hdd[c].speed_preset = hdd_preset_get_from_internal_name(p); + /* Audio Profile */ + sprintf(temp, "hdd_%02i_audio", c + 1); + p = ini_section_get_string(cat, temp, "none"); + hdd[c].audio_profile = hdd_audio_get_profile_by_internal_name(p); + /* MFM/RLL */ sprintf(temp, "hdd_%02i_mfm_channel", c + 1); if (hdd[c].bus_type == HDD_BUS_MFM) @@ -2948,8 +2960,8 @@ save_network(void) case NET_TYPE_TAP: ini_section_set_string(cat, temp, "tap"); break; - case NET_TYPE_NMSWITCH: - ini_section_set_string(cat, temp, "nmswitch"); + case NET_TYPE_NLSWITCH: + ini_section_set_string(cat, temp, "nlswitch"); break; case NET_TYPE_NRSWITCH: ini_section_set_string(cat, temp, "nrswitch"); @@ -2976,26 +2988,22 @@ save_network(void) ini_section_set_int(cat, temp, nc->link_state); sprintf(temp, "net_%02i_switch_group", c + 1); - if (nc->device_num == 0) + if (nc->switch_group == NET_SWITCH_GRP_MIN) ini_section_delete_var(cat, temp); else - ini_section_set_int(cat, temp, net_cards_conf[c].switch_group); + ini_section_set_int(cat, temp, nc->switch_group); sprintf(temp, "net_%02i_promisc", c + 1); - if (nc->device_num == 0) + if (nc->promisc_mode == 0) ini_section_delete_var(cat, temp); else - ini_section_set_int(cat, temp, net_cards_conf[c].promisc_mode); + ini_section_set_int(cat, temp, nc->promisc_mode); sprintf(temp, "net_%02i_nrs_host", c + 1); - if (nc->device_num == 0) + if (nc->nrs_hostname[0] == '\0') ini_section_delete_var(cat, temp); - else { - if (nc->nrs_hostname[0] != '\0') - ini_section_set_string(cat, temp, net_cards_conf[c].nrs_hostname); - else - ini_section_delete_var(cat, temp); - } + else + ini_section_set_string(cat, temp, net_cards_conf[c].nrs_hostname); } ini_delete_section_if_empty(config, cat); @@ -3458,6 +3466,17 @@ save_hard_disks(void) ini_section_delete_var(cat, temp); else ini_section_set_string(cat, temp, hdd_preset_get_internal_name(hdd[c].speed_preset)); + + sprintf(temp, "hdd_%02i_audio", c + 1); + if (!hdd_is_valid(c) || hdd[c].audio_profile == 0) { + ini_section_delete_var(cat, temp); + } else { + const char *internal_name = hdd_audio_get_profile_internal_name(hdd[c].audio_profile); + if (internal_name && strcmp(internal_name, "none") != 0) + ini_section_set_string(cat, temp, internal_name); + else + ini_section_delete_var(cat, temp); + } } ini_delete_section_if_empty(config, cat); diff --git a/src/device/cassette.c b/src/device/cassette.c index b2d8f7a1c..c64813999 100644 --- a/src/device/cassette.c +++ b/src/device/cassette.c @@ -500,7 +500,10 @@ pc_cas_set_motor(pc_cassette_t *cas, unsigned char val) else timer_disable(&cas->timer); - ui_sb_update_icon(SB_CASSETTE, !!val); + if (!cas->save) + ui_sb_update_icon(SB_CASSETTE, !!val); + else + ui_sb_update_icon_write(SB_CASSETTE, !!val); } unsigned char @@ -665,8 +668,12 @@ cassette_callback(void *priv) pc_cas_clock(cas, 8); - if (cas->motor) - ui_sb_update_icon(SB_CASSETTE, 1); + if (cas->motor) { + if (cas->pcm && cas->save) + ui_sb_update_icon_write(SB_CASSETTE, 1); + else + ui_sb_update_icon(SB_CASSETTE, 1); + } timer_advance_u64(&cas->timer, 8ULL * PITCONST); } diff --git a/src/device/kbc_at.c b/src/device/kbc_at.c index 771b95c2a..4cb23428e 100644 --- a/src/device/kbc_at.c +++ b/src/device/kbc_at.c @@ -470,16 +470,8 @@ kbc_scan_kbd_at(atkbc_t *dev) kbc_ibf_process(dev); /* AT mode. */ } else { -#if 0 - dev->t = dev->mem[0x28]; -#endif - if (dev->mem[0x2e] != 0x00) { -#if 0 - if (!(dev->t & 0x02)) - return; -#endif + if (dev->mem[0x2e] != 0x00) dev->mem[0x2e] = 0x00; - } dev->p2 &= 0xbf; if ((dev->ports[0] != NULL) && (dev->ports[0]->out_new != -1)) { /* In our case, we never have noise on the line, so we can simplify this. */ @@ -539,9 +531,6 @@ at_main_ibf: /* Keyboard controller command want to output a single byte. */ kbc_at_log("ATkbc: %02X coming from channel %i with high status %02X\n", dev->val, dev->channel, dev->stat_hi); kbc_send_to_ob(dev, dev->val, dev->channel, dev->stat_hi); -#if 0 - dev->state = (dev->pending == 2) ? STATE_KBC_AMI_OUT : STATE_MAIN_IBF; -#endif dev->state = STATE_MAIN_IBF; dev->pending = 0; goto at_main_ibf; @@ -683,12 +672,8 @@ kbc_at_poll_ps2(atkbc_t *dev) /* Keyboard controller command want to output a single byte. */ kbc_at_log("ATkbc: %02X coming from channel %i with high status %02X\n", dev->val, dev->channel, dev->stat_hi); kbc_send_to_ob(dev, dev->val, dev->channel, dev->stat_hi); -#if 0 - dev->state = (dev->pending == 2) ? STATE_KBC_AMI_OUT : STATE_MAIN_IBF; -#endif dev->state = STATE_MAIN_IBF; dev->pending = 0; - // goto ps2_main_ibf; break; case STATE_KBC_OUT: /* Keyboard controller command want to output multiple bytes. */ @@ -768,19 +753,6 @@ write_p2(atkbc_t *dev, uint8_t val) uint8_t kbc_ven = dev->flags & KBC_VEN_MASK; -#if 0 - /* PS/2: Handle IRQ's. */ - if (dev->misc_flags & FLAG_PS2) { - /* IRQ 12 */ - if (dev->irq[1] != 0xffff) - picint_common(1 << dev->irq[1], 0, val & 0x20, NULL); - - /* IRQ 1 */ - if (dev->irq[0] != 0xffff) - picint_common(1 << dev->irq[0], 0, val & 0x10, NULL); - } -#endif - /* AT, PS/2: Handle A20. */ if ((mem_a20_key ^ val) & 0x02) { /* A20 enable change */ mem_a20_key = val & 0x02; @@ -2199,16 +2171,6 @@ write_cmd_toshiba(void *priv, uint8_t val) t3100e_notify_set(0x00); ret = 0; break; - - case 0xc0: /* Read P1 */ - kbc_at_log("ATkbc: read P1\n"); - - /* The T3100e returns all bits set except bit 6 which - * is set by t3100e_mono_set() */ - dev->p1 = (t3100e_mono_get() & 1) ? 0xff : 0xbf; - kbc_delay_to_ob(dev, dev->p1, 0, 0x00); - ret = 0; - break; } return ret; @@ -2282,7 +2244,13 @@ read_p1(atkbc_t *dev) Compaq: Reserved; NCR: DMA mode. */ - uint8_t ret = machine_get_p1(dev->p1) | (dev->p1 & 0x03); + uint8_t kbc_ven = dev->flags & KBC_VEN_MASK; + uint8_t ret = 0x00; + + if ((dev != NULL) && (kbc_ven == KBC_VEN_TOSHIBA)) + ret = machine_get_p1(0xff); + else + ret = machine_get_p1(dev->p1) | (dev->p1 & 0x03); dev->p1 = ((dev->p1 + 1) & 0x03) | (dev->p1 & 0xfc); diff --git a/src/device/kbc_xt.c b/src/device/kbc_xt.c index a9fa8bf6c..c1a3aee5c 100644 --- a/src/device/kbc_xt.c +++ b/src/device/kbc_xt.c @@ -85,6 +85,7 @@ typedef struct xtkbd_t { uint8_t type; uint8_t pravetz_flags; uint8_t cpu_speed; + uint8_t ignore; pc_timer_t send_delay_timer; } xtkbd_t; @@ -96,6 +97,8 @@ static int is_tandy = 0; static int is_t1x00 = 0; static int is_amstrad = 0; +#define kbd_adddata kbd_adddata_xt_common + #ifdef ENABLE_KEYBOARD_XT_LOG int keyboard_xt_do_log = ENABLE_KEYBOARD_XT_LOG; @@ -117,7 +120,6 @@ kbd_log(const char *fmt, ...) static uint8_t get_fdd_switch_settings(void) { - uint8_t fdd_count = 0; for (uint8_t i = 0; i < FDD_NUM; i++) { @@ -134,7 +136,6 @@ get_fdd_switch_settings(void) static uint8_t get_videomode_switch_settings(void) { - if (video_is_mda()) return 0x30; else if (video_is_cga()) @@ -172,8 +173,8 @@ kbd_poll(void *priv) } } -static void -kbd_adddata(uint16_t val) +void +kbd_adddata_xt_common(uint16_t val) { /* Test for T1000 'Fn' key (Right Alt / Right Ctrl) */ if (is_t1x00) { @@ -257,6 +258,98 @@ kbd_adddata_process(uint16_t val, void (*adddata)(uint16_t val)) } } +void +kbd_adddata_process_10x(uint16_t val, void (*adddata)(uint16_t val)) +{ + uint8_t fake_shift[4] = { 0 }; + uint8_t num_lock = 0; + uint8_t shift_states = 0; + + if (!adddata) + return; + + keyboard_get_states(NULL, &num_lock, NULL, NULL); + shift_states = keyboard_get_shift() & STATE_SHIFT_MASK; + + switch (val) { + case FAKE_LSHIFT_ON: + kbd_log("%s: Fake left shift on, scan code: ", dev->name); + if (num_lock) { + if (shift_states) { + kbd_log("N/A (one or both shifts on)\n"); + break; + } else { + /* Num lock on and no shifts are pressed, send non-inverted fake shift. */ + kbd_log("E0 2A\n"); + fake_shift[0] = 0xe0; + fake_shift[1] = 0x2a; + for (int i = 0; i < 2; i++) + adddata(fake_shift[0]); + } + } else { + if (shift_states & STATE_LSHIFT) { + /* Num lock off and left shift pressed. */ + kbd_log("E0 AA\n"); + fake_shift[0] = 0xe0; + fake_shift[1] = 0xaa; + for (int i = 0; i < 2; i++) + adddata(fake_shift[0]); + } + if (shift_states & STATE_RSHIFT) { + /* Num lock off and right shift pressed. */ + kbd_log("E0 B6\n"); + fake_shift[0] = 0xe0; + fake_shift[1] = 0xb6; + for (int i = 0; i < 2; i++) + adddata(fake_shift[0]); + } + kbd_log(shift_states ? "" : "N/A (both shifts off)\n"); + } + break; + + case FAKE_LSHIFT_OFF: + kbd_log("%s: Fake left shift on, scan code: ", dev->name); + if (num_lock) { + if (shift_states) { + kbd_log("N/A (one or both shifts on)\n"); + break; + } else { + /* Num lock on and no shifts are pressed, send non-inverted fake shift. */ + kbd_log("E0 AA\n"); + fake_shift[0] = 0xe0; + fake_shift[1] = 0xaa; + for (int i = 0; i < 2; i++) + adddata(fake_shift[0]); + } + } else { + if (shift_states & STATE_LSHIFT) { + /* Num lock off and left shift pressed. */ + kbd_log("E0 2A\n"); + fake_shift[0] = 0xe0; + fake_shift[1] = 0x2a; + for (int i = 0; i < 2; i++) + adddata(fake_shift[0]); + break; + } + if (shift_states & STATE_RSHIFT) { + /* Num lock off and right shift pressed. */ + kbd_log("E0 36\n"); + fake_shift[0] = 0xe0; + fake_shift[1] = 0x36; + for (int i = 0; i < 2; i++) + adddata(fake_shift[0]); + break; + } + kbd_log(shift_states ? "" : "N/A (both shifts off)\n"); + } + break; + + default: + adddata(val); + break; + } +} + static void kbd_adddata_ex(uint16_t val) { diff --git a/src/device/keyboard_at.c b/src/device/keyboard_at.c index d6fdaf4b7..fd8d11503 100644 --- a/src/device/keyboard_at.c +++ b/src/device/keyboard_at.c @@ -5342,6 +5342,46 @@ add_data_vals(atkbc_dev_t *dev, uint8_t *val, uint8_t len) kbc_at_dev_queue_add(dev, val[i], 1); } +static void +add_data_kbd_84(uint16_t val) +{ + atkbc_dev_t *dev = SavedKbd; + uint8_t fake_shift = 0; + uint8_t num_lock = 0; + uint8_t shift_states = 0; + + keyboard_get_states(NULL, &num_lock, NULL, NULL); + shift_states = keyboard_get_shift() & STATE_LSHIFT; + + /* If NumLock is on, invert the left shift state so we can always check for + the the same way flag being set (and with NumLock on that then means it + is actually *NOT* set). */ + if (num_lock) + shift_states ^= STATE_LSHIFT; + + switch (val) { + case FAKE_LSHIFT_ON: + /* If NumLock is on, fake shifts are sent when shift is *NOT* presed, + if NumLock is off, fake shifts are sent when shift is pressed. */ + if (shift_states) { + /* Send fake shift. */ + fake_shift = num_lock ? 0x2a : 0xaa; + add_data_vals(dev, &fake_shift, 1); + } + break; + case FAKE_LSHIFT_OFF: + if (shift_states) { + /* Send fake shift. */ + fake_shift = num_lock ? 0xaa : 0x2a; + add_data_vals(dev, &fake_shift, 1); + } + break; + default: + kbc_at_dev_queue_add(dev, val, 1); + break; + } +} + static void add_data_kbd(uint16_t val) { @@ -5975,7 +6015,10 @@ keyboard_at_init(const device_t *info) bat_counter = 0x0000; } - keyboard_send = add_data_kbd; + if ((dev->type & FLAG_TYPE_MASK) > KBD_84_KEY) + keyboard_send = add_data_kbd; + else + keyboard_send = add_data_kbd_84; SavedKbd = dev; keyboard_update_states(0, 0, 0, 0); diff --git a/src/device/keyboard_xt.c b/src/device/keyboard_xt.c index 41f8937e5..0ac25b061 100644 --- a/src/device/keyboard_xt.c +++ b/src/device/keyboard_xt.c @@ -557,6 +557,18 @@ typedef struct { int type; } kbd_t; +static void +kbd_adddata_xt(uint16_t val) +{ + kbd_adddata_process(val, kbd_adddata_xt_common); +} + +static void +kbd_adddata_xt_10x(uint16_t val) +{ + kbd_adddata_process_10x(val, kbd_adddata_xt_common); +} + static void * kbd_init(const device_t *info) { @@ -564,10 +576,13 @@ kbd_init(const device_t *info) dev->type = device_get_config_int("keys"); - if (dev->type == KBD_83_KEY) + if (dev->type == KBD_83_KEY) { keyboard_set_table(scancode_xt); - else + keyboard_send = kbd_adddata_xt; + } else { keyboard_set_table(scancode_set1); + keyboard_send = kbd_adddata_xt_10x; + } return dev; } diff --git a/src/device/postcard.c b/src/device/postcard.c index d94bc8ddf..f33574452 100644 --- a/src/device/postcard.c +++ b/src/device/postcard.c @@ -191,7 +191,7 @@ postcard_init(UNUSED(const device_t *info)) if (machine_has_bus(machine, MACHINE_BUS_MCA)) postcard_port = 0x680; /* MCA machines */ else if (strstr(machines[machine].name, " PS/2 ") || - strstr(machine_getname_ex(machine), " PS/1 ")) + strstr(machine_getname(machine), " PS/1 ")) postcard_port = 0x190; /* ISA PS/2 machines */ else if (strstr(machines[machine].name, " IBM XT ")) postcard_port = 0x60; /* IBM XT */ diff --git a/src/discord.c b/src/discord.c index 091d4c95f..3eb5424e9 100644 --- a/src/discord.c +++ b/src/discord.c @@ -91,9 +91,9 @@ discord_update_activity(int paused) #endif if (strlen(vm_name) < 100) { snprintf(activity.details, sizeof(activity.details), "Running \"%s\"", vm_name); - snprintf(activity.state, sizeof(activity.state), "%s (%s/%s)", strchr(machine_getname(), ']') + 2, cpufamily, cpu_s->name); + snprintf(activity.state, sizeof(activity.state), "%s (%s/%s)", strchr(machine_getname(machine), ']') + 2, cpufamily, cpu_s->name); } else { - strncpy(activity.details, strchr(machine_getname(), ']') + 2, sizeof(activity.details) - 1); + strncpy(activity.details, strchr(machine_getname(machine), ']') + 2, sizeof(activity.details) - 1); snprintf(activity.state, sizeof(activity.state), "%s/%s", cpufamily, cpu_s->name); } #pragma GCC diagnostic pop diff --git a/src/disk/CMakeLists.txt b/src/disk/CMakeLists.txt index 9b8d72f54..fff17a5e4 100644 --- a/src/disk/CMakeLists.txt +++ b/src/disk/CMakeLists.txt @@ -36,6 +36,7 @@ add_library(hdd OBJECT hdc_ide_sff8038i.c hdc_ide_um8673f.c hdc_ide_w83769f.c + hdd_audio.c ) add_library(rdisk OBJECT rdisk.c) diff --git a/src/disk/hdd.c b/src/disk/hdd.c index 357c787b8..bbbec0865 100644 --- a/src/disk/hdd.c +++ b/src/disk/hdd.c @@ -27,6 +27,7 @@ #include <86box/hdd.h> #include <86box/cdrom.h> #include <86box/video.h> +#include <86box/hdd_audio.h> #include "cpu.h" #define HDD_OVERHEAD_TIME 50.0 @@ -38,7 +39,6 @@ hdd_init(void) { /* Clear all global data. */ memset(hdd, 0x00, sizeof(hdd)); - return 0; } @@ -196,6 +196,9 @@ hdd_seek_get_time(hard_disk_t *hdd, uint32_t dst_addr, uint8_t operation, uint8_ } if (!max_seek_time || seek_time <= max_seek_time) { + if (new_cylinder != hdd->cur_cylinder) + hdd_audio_seek(hdd, new_cylinder); + hdd->cur_addr = dst_addr; hdd->cur_track = new_track; hdd->cur_cylinder = new_cylinder; @@ -586,6 +589,14 @@ hdd_preset_get_internal_name(int preset) return hdd_speed_presets[preset].internal_name; } +uint32_t +hdd_preset_get_rpm(int preset) +{ + if (preset < 0 || preset >= hdd_preset_get_num()) + return 0; + return hdd_speed_presets[preset].rpm; +} + int hdd_preset_get_from_internal_name(char *s) { diff --git a/src/disk/hdd_audio.c b/src/disk/hdd_audio.c new file mode 100644 index 000000000..902bd55b0 --- /dev/null +++ b/src/disk/hdd_audio.c @@ -0,0 +1,999 @@ +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * This file is part of the 86Box distribution. + * + * Hard disk audio emulation. + * + * Authors: Toni Riikonen, + * + * Copyright 2026 Toni Riikonen. + */ + + #include +#include +#include +#include +#include <86box/86box.h> +#include <86box/hdd.h> +#include <86box/hdd_audio.h> +#include <86box/sound.h> +#include <86box/sound_util.h> +#include <86box/thread.h> +#include <86box/plat.h> +#include <86box/path.h> +#include <86box/ini.h> +#include <86box/mem.h> +#include <86box/rom.h> + +/* Maximum number of simultaneous seek sounds per HDD */ +#define HDD_MAX_SEEK_VOICES_PER_HDD 8 + +/* Maximum number of HDDs with audio emulation */ +#define HDD_AUDIO_MAX_DRIVES 8 + +typedef struct { + int active; + int position; + float volume; + int profile_id; /* Which profile's seek sound to use */ +} hdd_seek_voice_t; + +/* Per-HDD audio state */ +typedef struct { + int hdd_index; /* Index into hdd[] array */ + int profile_id; /* Audio profile ID */ + hdd_spindle_state_t spindle_state; + int spindle_pos; + int spindle_transition_pos; + hdd_seek_voice_t seek_voices[HDD_MAX_SEEK_VOICES_PER_HDD]; +} hdd_audio_drive_state_t; + +/* Audio samples structure for a profile */ +typedef struct { + int16_t *spindle_start_buffer; + int spindle_start_samples; + float spindle_start_volume; + int16_t *spindle_loop_buffer; + int spindle_loop_samples; + float spindle_loop_volume; + int16_t *spindle_stop_buffer; + int spindle_stop_samples; + float spindle_stop_volume; + int16_t *seek_buffer; + int seek_samples; + float seek_volume; + int loaded; +} hdd_audio_samples_t; + +/* Global audio profile configurations */ +static hdd_audio_profile_config_t audio_profiles[HDD_AUDIO_PROFILE_MAX]; +static int audio_profile_count = 0; + +/* Per-profile loaded samples */ +static hdd_audio_samples_t profile_samples[HDD_AUDIO_PROFILE_MAX]; + +/* Per-HDD audio states */ +static hdd_audio_drive_state_t drive_states[HDD_AUDIO_MAX_DRIVES]; +static int active_drive_count = 0; + +static mutex_t *hdd_audio_mutex = NULL; + +#ifdef ENABLE_HDD_AUDIO_LOG +int hdd_audio_do_log = ENABLE_HDD_AUDIO_LOG; + +static void +hdd_audio_log(const char *fmt, ...) +{ + va_list ap; + + if (hdd_audio_do_log) { + va_start(ap, fmt); + pclog_ex(fmt, ap); + va_end(ap); + } +} +#else +# define hdd_audio_log(fmt, ...) +#endif + +/* Load audio profiles from configuration file */ +void +hdd_audio_load_profiles(void) +{ + ini_t profiles_ini; + char cfg_fn[1024] = { 0 }; + + /* + * asset_getfile returns a path from the trusted asset search paths. + * The filename is hardcoded and validated against existing files. + */ + int ret = asset_getfile("assets/sounds/hdd/hdd_audio_profiles.cfg", cfg_fn, 1024); + if (!ret) { + hdd_audio_log("HDD Audio: Could not find hdd_audio_profiles.cfg\n"); + return; + } + + /* Validate that the path does not contain path traversal sequences */ + if (strstr(cfg_fn, "..") != NULL) { + hdd_audio_log("HDD Audio: Invalid path detected\n"); + return; + } + + /* Validate the path ends with our expected filename */ + const char *expected_suffix = "hdd_audio_profiles.cfg"; + size_t cfg_len = strlen(cfg_fn); + size_t suffix_len = strlen(expected_suffix); + if (cfg_len < suffix_len || strcmp(cfg_fn + cfg_len - suffix_len, expected_suffix) != 0) { + pclog("HDD Audio: Unexpected config path\n"); + return; + } + + profiles_ini = ini_read_ex(cfg_fn, 1); /* lgtm[cpp/path-injection] */ + if (profiles_ini == NULL) { + hdd_audio_log("HDD Audio: Failed to load hdd_audio_profiles.cfg\n"); + return; + } + + audio_profile_count = 0; + + /* Load profiles by trying known profile section names */ + for (int i = 0; i < HDD_AUDIO_PROFILE_MAX && audio_profile_count < HDD_AUDIO_PROFILE_MAX; i++) { + char section_name[64]; + sprintf(section_name, "Profile \"%d\"", i); + + ini_section_t cat = ini_find_section(profiles_ini, section_name); + if (cat == NULL) + continue; + + hdd_audio_profile_config_t *config = &audio_profiles[audio_profile_count]; + memset(config, 0, sizeof(hdd_audio_profile_config_t)); + + config->id = ini_section_get_int(cat, "id", i); + + const char *name = ini_section_get_string(cat, "name", "Unknown"); + strncpy(config->name, name, sizeof(config->name) - 1); + + const char *internal_name = ini_section_get_string(cat, "internal_name", "unknown"); + strncpy(config->internal_name, internal_name, sizeof(config->internal_name) - 1); + + config->rpm = ini_section_get_int(cat, "rpm", 0); + + /* Load spindle motor sample files */ + const char *file = ini_section_get_string(cat, "spindlemotor_start_file", ""); + strncpy(config->spindlemotor_start.filename, file, sizeof(config->spindlemotor_start.filename) - 1); + config->spindlemotor_start.volume = (float) ini_section_get_double(cat, "spindlemotor_start_volume", 1.0); + + file = ini_section_get_string(cat, "spindlemotor_loop_file", ""); + strncpy(config->spindlemotor_loop.filename, file, sizeof(config->spindlemotor_loop.filename) - 1); + config->spindlemotor_loop.volume = (float) ini_section_get_double(cat, "spindlemotor_loop_volume", 1.0); + + file = ini_section_get_string(cat, "spindlemotor_stop_file", ""); + strncpy(config->spindlemotor_stop.filename, file, sizeof(config->spindlemotor_stop.filename) - 1); + config->spindlemotor_stop.volume = (float) ini_section_get_double(cat, "spindlemotor_stop_volume", 1.0); + + /* Load seek sample file */ + file = ini_section_get_string(cat, "seek_track_file", ""); + strncpy(config->seek_track.filename, file, sizeof(config->seek_track.filename) - 1); + config->seek_track.volume = (float) ini_section_get_double(cat, "seek_track_volume", 1.0); + + hdd_audio_log("HDD Audio: Loaded profile %d: %s (%s)\n", + audio_profile_count, config->name, config->internal_name); + + audio_profile_count++; + } + + ini_close(profiles_ini); + + hdd_audio_log("HDD Audio: Loaded %d audio profiles\n", audio_profile_count); +} + +/* Public API functions */ +int +hdd_audio_get_profile_count(void) +{ + return audio_profile_count; +} + +const hdd_audio_profile_config_t * +hdd_audio_get_profile(int id) +{ + if (id < 0 || id >= audio_profile_count) + return NULL; + return &audio_profiles[id]; +} + +const char * +hdd_audio_get_profile_name(int id) +{ + if (id < 0 || id >= audio_profile_count) + return NULL; + return audio_profiles[id].name; +} + +const char * +hdd_audio_get_profile_internal_name(int id) +{ + if (id < 0 || id >= audio_profile_count) + return NULL; + return audio_profiles[id].internal_name; +} + +uint32_t +hdd_audio_get_profile_rpm(int id) +{ + if (id < 0 || id >= audio_profile_count) + return 0; + return audio_profiles[id].rpm; +} + +int +hdd_audio_get_profile_by_internal_name(const char *internal_name) +{ + if (!internal_name) + return 0; + + for (int i = 0; i < audio_profile_count; i++) { + if (strcmp(audio_profiles[i].internal_name, internal_name) == 0) + return i; + } + return 0; +} + +void +hdd_audio_close(void) +{ + /* Free all loaded profile samples */ + for (int i = 0; i < HDD_AUDIO_PROFILE_MAX; i++) { + if (profile_samples[i].spindle_start_buffer) { + free(profile_samples[i].spindle_start_buffer); + profile_samples[i].spindle_start_buffer = NULL; + } + if (profile_samples[i].spindle_loop_buffer) { + free(profile_samples[i].spindle_loop_buffer); + profile_samples[i].spindle_loop_buffer = NULL; + } + if (profile_samples[i].spindle_stop_buffer) { + free(profile_samples[i].spindle_stop_buffer); + profile_samples[i].spindle_stop_buffer = NULL; + } + if (profile_samples[i].seek_buffer) { + free(profile_samples[i].seek_buffer); + profile_samples[i].seek_buffer = NULL; + } + profile_samples[i].loaded = 0; + } + + if (hdd_audio_mutex) { + thread_close_mutex(hdd_audio_mutex); + hdd_audio_mutex = NULL; + } +} + +/* Load samples for a specific profile */ +static void +hdd_audio_load_profile_samples(int profile_id) +{ + if (profile_id < 0 || profile_id >= audio_profile_count) + return; + + hdd_audio_profile_config_t *config = &audio_profiles[profile_id]; + hdd_audio_samples_t *samples = &profile_samples[profile_id]; + + /* Already loaded? */ + if (samples->loaded) + return; + + /* Profile 0 is "None" - no audio */ + if (profile_id == 0 || strcmp(config->internal_name, "none") == 0) { + samples->loaded = 1; + return; + } + + hdd_audio_log("HDD Audio: Loading samples for profile %d (%s)\n", profile_id, config->name); + + /* Load spindle loop (main running sound) */ + if (config->spindlemotor_loop.filename[0]) { + samples->spindle_loop_buffer = sound_load_wav( + config->spindlemotor_loop.filename, + &samples->spindle_loop_samples); + if (samples->spindle_loop_buffer) { + samples->spindle_loop_volume = config->spindlemotor_loop.volume; + hdd_audio_log("HDD Audio: Loaded spindle loop, %d frames\n", samples->spindle_loop_samples); + } else { + hdd_audio_log("HDD Audio: Failed to load spindle loop: %s\n", config->spindlemotor_loop.filename); + } + } + + /* Load spindle start */ + if (config->spindlemotor_start.filename[0]) { + samples->spindle_start_buffer = sound_load_wav( + config->spindlemotor_start.filename, + &samples->spindle_start_samples); + if (samples->spindle_start_buffer) { + samples->spindle_start_volume = config->spindlemotor_start.volume; + hdd_audio_log("HDD Audio: Loaded spindle start, %d frames\n", samples->spindle_start_samples); + } + } + + /* Load spindle stop */ + if (config->spindlemotor_stop.filename[0]) { + samples->spindle_stop_buffer = sound_load_wav( + config->spindlemotor_stop.filename, + &samples->spindle_stop_samples); + if (samples->spindle_stop_buffer) { + samples->spindle_stop_volume = config->spindlemotor_stop.volume; + hdd_audio_log("HDD Audio: Loaded spindle stop, %d frames\n", samples->spindle_stop_samples); + } + } + + /* Load seek sound */ + if (config->seek_track.filename[0]) { + samples->seek_buffer = sound_load_wav( + config->seek_track.filename, + &samples->seek_samples); + if (samples->seek_buffer) { + samples->seek_volume = config->seek_track.volume; + hdd_audio_log("HDD Audio: Loaded seek sound, %d frames (%.1f ms)\n", + samples->seek_samples, (float)samples->seek_samples / 48.0f); + } else { + hdd_audio_log("HDD Audio: Failed to load seek sound: %s\n", config->seek_track.filename); + } + } + + samples->loaded = 1; +} + +/* Find drive state for a given HDD index, or NULL if not tracked */ +static hdd_audio_drive_state_t * +hdd_audio_find_drive_state(int hdd_index) +{ + for (int i = 0; i < active_drive_count; i++) { + if (drive_states[i].hdd_index == hdd_index) + return &drive_states[i]; + } + return NULL; +} + +void +hdd_audio_init(void) +{ + /* Initialize profile samples */ + memset(profile_samples, 0, sizeof(profile_samples)); + memset(drive_states, 0, sizeof(drive_states)); + active_drive_count = 0; + + hdd_audio_log("HDD Audio Init: audio_profile_count=%d\n", audio_profile_count); + + /* Create mutex BEFORE loading samples or calling spinup */ + if (!hdd_audio_mutex) + hdd_audio_mutex = thread_create_mutex(); + + /* Find all HDDs with valid audio profiles and initialize their states */ + for (int i = 0; i < HDD_NUM && active_drive_count < HDD_AUDIO_MAX_DRIVES; i++) { + if (hdd[i].bus_type != HDD_BUS_DISABLED && hdd[i].audio_profile > 0) { + hdd_audio_log("HDD Audio Init: HDD %d bus_type=%d audio_profile=%d\n", + i, hdd[i].bus_type, hdd[i].audio_profile); + + hdd_audio_drive_state_t *state = &drive_states[active_drive_count]; + state->hdd_index = i; + state->profile_id = hdd[i].audio_profile; + state->spindle_state = HDD_SPINDLE_STOPPED; + state->spindle_pos = 0; + state->spindle_transition_pos = 0; + + /* Initialize seek voices for this drive */ + for (int v = 0; v < HDD_MAX_SEEK_VOICES_PER_HDD; v++) { + state->seek_voices[v].active = 0; + state->seek_voices[v].position = 0; + state->seek_voices[v].volume = 1.0f; + state->seek_voices[v].profile_id = state->profile_id; + } + + /* Load samples for this profile if not already loaded */ + hdd_audio_load_profile_samples(state->profile_id); + + hdd_audio_log("HDD Audio: Initialized drive %d with profile %d (%s)\n", + i, state->profile_id, + hdd_audio_get_profile_name(state->profile_id)); + + active_drive_count++; + } + } + + hdd_audio_log("HDD Audio Init: %d active drives with audio\n", active_drive_count); + + /* Start spindle motors for all active drives */ + for (int i = 0; i < active_drive_count; i++) { + hdd_audio_spinup_drive(drive_states[i].hdd_index); + } + + sound_hdd_thread_init(); +} + +void +hdd_audio_reset(void) +{ + hdd_audio_log("HDD Audio: Reset\n"); + + /* Lock mutex to prevent audio callback from accessing buffers during reset */ + if (hdd_audio_mutex) + thread_wait_mutex(hdd_audio_mutex); + + /* Reset all drive states */ + for (int i = 0; i < active_drive_count; i++) { + drive_states[i].spindle_state = HDD_SPINDLE_STOPPED; + drive_states[i].spindle_pos = 0; + drive_states[i].spindle_transition_pos = 0; + for (int v = 0; v < HDD_MAX_SEEK_VOICES_PER_HDD; v++) { + drive_states[i].seek_voices[v].active = 0; + drive_states[i].seek_voices[v].position = 0; + drive_states[i].seek_voices[v].volume = 1.0f; + } + } + active_drive_count = 0; + + /* Free previously loaded samples (but keep profiles) */ + for (int i = 0; i < HDD_AUDIO_PROFILE_MAX; i++) { + if (profile_samples[i].spindle_start_buffer) { + free(profile_samples[i].spindle_start_buffer); + profile_samples[i].spindle_start_buffer = NULL; + } + if (profile_samples[i].spindle_loop_buffer) { + free(profile_samples[i].spindle_loop_buffer); + profile_samples[i].spindle_loop_buffer = NULL; + } + if (profile_samples[i].spindle_stop_buffer) { + free(profile_samples[i].spindle_stop_buffer); + profile_samples[i].spindle_stop_buffer = NULL; + } + if (profile_samples[i].seek_buffer) { + free(profile_samples[i].seek_buffer); + profile_samples[i].seek_buffer = NULL; + } + profile_samples[i].loaded = 0; + } + + if (hdd_audio_mutex) + thread_release_mutex(hdd_audio_mutex); + + /* Find all HDDs with valid audio profiles and initialize their states */ + for (int i = 0; i < HDD_NUM && active_drive_count < HDD_AUDIO_MAX_DRIVES; i++) { + if (hdd[i].bus_type != HDD_BUS_DISABLED && hdd[i].audio_profile > 0) { + hdd_audio_log("HDD Audio Reset: HDD %d audio_profile=%d\n", i, hdd[i].audio_profile); + + hdd_audio_drive_state_t *state = &drive_states[active_drive_count]; + state->hdd_index = i; + state->profile_id = hdd[i].audio_profile; + state->spindle_state = HDD_SPINDLE_STOPPED; + state->spindle_pos = 0; + state->spindle_transition_pos = 0; + + /* Initialize seek voices for this drive */ + for (int v = 0; v < HDD_MAX_SEEK_VOICES_PER_HDD; v++) { + state->seek_voices[v].active = 0; + state->seek_voices[v].position = 0; + state->seek_voices[v].volume = 1.0f; + state->seek_voices[v].profile_id = state->profile_id; + } + + /* Load samples for this profile if not already loaded */ + hdd_audio_load_profile_samples(state->profile_id); + + hdd_audio_log("HDD Audio: Reset drive %d with profile %d (%s)\n", + i, state->profile_id, + hdd_audio_get_profile_name(state->profile_id)); + + active_drive_count++; + } + } + + hdd_audio_log("HDD Audio Reset: %d active drives with audio\n", active_drive_count); + + /* Start spindle motors for all active drives */ + for (int i = 0; i < active_drive_count; i++) { + hdd_audio_spinup_drive(drive_states[i].hdd_index); + } +} + +void +hdd_audio_seek(hard_disk_t *hdd_drive, uint32_t new_cylinder) +{ + uint32_t cylinder_diff = abs((int) hdd_drive->cur_cylinder - (int) new_cylinder); + + if (cylinder_diff == 0) + return; + + /* Find the drive state for this HDD */ + hdd_audio_drive_state_t *drive_state = NULL; + for (int i = 0; i < active_drive_count; i++) { + if (&hdd[drive_states[i].hdd_index] == hdd_drive) { + drive_state = &drive_states[i]; + break; + } + } + + /* If no drive state found, drive has no audio profile */ + if (!drive_state) + return; + + int profile_id = drive_state->profile_id; + + /* No audio profile selected */ + if (profile_id == 0 || profile_id >= audio_profile_count) + return; + + /* Load samples if not already loaded */ + if (!profile_samples[profile_id].loaded) + hdd_audio_load_profile_samples(profile_id); + + hdd_audio_samples_t *samples = &profile_samples[profile_id]; + + if (!samples->seek_buffer || samples->seek_samples == 0) { + return; + } + + /* Mutex must exist */ + if (!hdd_audio_mutex) + return; + + int min_seek_spacing = 0; + if (hdd_drive->cyl_switch_usec > 0) + min_seek_spacing = (int)(hdd_drive->cyl_switch_usec * 48000.0 / 1000000.0); + + thread_wait_mutex(hdd_audio_mutex); + + /* Check if we should skip due to minimum spacing (per-drive) */ + for (int v = 0; v < HDD_MAX_SEEK_VOICES_PER_HDD; v++) { + if (drive_state->seek_voices[v].active) { + int pos = drive_state->seek_voices[v].position; + if (pos >= 0 && pos < min_seek_spacing) { + thread_release_mutex(hdd_audio_mutex); + return; + } + } + } + + /* Find a free seek voice for this drive */ + for (int v = 0; v < HDD_MAX_SEEK_VOICES_PER_HDD; v++) { + if (!drive_state->seek_voices[v].active) { + drive_state->seek_voices[v].active = 1; + drive_state->seek_voices[v].position = 0; + drive_state->seek_voices[v].volume = samples->seek_volume; + drive_state->seek_voices[v].profile_id = profile_id; + thread_release_mutex(hdd_audio_mutex); + return; + } + } + + thread_release_mutex(hdd_audio_mutex); +} + +/* Spinup a specific drive by HDD index */ +void +hdd_audio_spinup_drive(int hdd_index) +{ + hdd_audio_drive_state_t *state = hdd_audio_find_drive_state(hdd_index); + if (!state) + return; + + if (state->spindle_state == HDD_SPINDLE_RUNNING || state->spindle_state == HDD_SPINDLE_STARTING) + return; + + hdd_audio_log("HDD Audio: Spinup requested for drive %d (current state: %d)\n", hdd_index, state->spindle_state); + + if (hdd_audio_mutex) + thread_wait_mutex(hdd_audio_mutex); + state->spindle_state = HDD_SPINDLE_STARTING; + state->spindle_transition_pos = 0; + if (hdd_audio_mutex) + thread_release_mutex(hdd_audio_mutex); +} + +/* Spindown a specific drive by HDD index */ +void +hdd_audio_spindown_drive(int hdd_index) +{ + hdd_audio_drive_state_t *state = hdd_audio_find_drive_state(hdd_index); + if (!state) + return; + + if (state->spindle_state == HDD_SPINDLE_STOPPED || state->spindle_state == HDD_SPINDLE_STOPPING) + return; + + hdd_audio_log("HDD Audio: Spindown requested for drive %d (current state: %d)\n", hdd_index, state->spindle_state); + + if (hdd_audio_mutex) + thread_wait_mutex(hdd_audio_mutex); + state->spindle_state = HDD_SPINDLE_STOPPING; + state->spindle_transition_pos = 0; + if (hdd_audio_mutex) + thread_release_mutex(hdd_audio_mutex); +} + +/* Legacy functions for backward compatibility - operate on all drives */ +void +hdd_audio_spinup(void) +{ + for (int i = 0; i < active_drive_count; i++) { + hdd_audio_spinup_drive(drive_states[i].hdd_index); + } +} + +void +hdd_audio_spindown(void) +{ + for (int i = 0; i < active_drive_count; i++) { + hdd_audio_spindown_drive(drive_states[i].hdd_index); + } +} + +hdd_spindle_state_t +hdd_audio_get_spindle_state(void) +{ + /* Return running if any drive is running */ + for (int i = 0; i < active_drive_count; i++) { + if (drive_states[i].spindle_state == HDD_SPINDLE_RUNNING) + return HDD_SPINDLE_RUNNING; + } + for (int i = 0; i < active_drive_count; i++) { + if (drive_states[i].spindle_state == HDD_SPINDLE_STARTING) + return HDD_SPINDLE_STARTING; + } + for (int i = 0; i < active_drive_count; i++) { + if (drive_states[i].spindle_state == HDD_SPINDLE_STOPPING) + return HDD_SPINDLE_STOPPING; + } + return HDD_SPINDLE_STOPPED; +} + +hdd_spindle_state_t +hdd_audio_get_drive_spindle_state(int hdd_index) +{ + hdd_audio_drive_state_t *state = hdd_audio_find_drive_state(hdd_index); + if (!state) + return HDD_SPINDLE_STOPPED; + return state->spindle_state; +} + +/* Helper: Mix spindle start sound into float buffer */ +static void +hdd_audio_mix_spindle_start_float(hdd_audio_drive_state_t *state, hdd_audio_samples_t *samples, + float *float_buffer, int frames_in_buffer) +{ + if (!samples->spindle_start_buffer || samples->spindle_start_samples <= 0) { + state->spindle_state = HDD_SPINDLE_RUNNING; + state->spindle_pos = 0; + return; + } + + float start_volume = samples->spindle_start_volume; + for (int i = 0; i < frames_in_buffer && state->spindle_transition_pos < samples->spindle_start_samples; i++) { + float left_sample = (float) samples->spindle_start_buffer[state->spindle_transition_pos * 2] / 131072.0f * start_volume; + float right_sample = (float) samples->spindle_start_buffer[state->spindle_transition_pos * 2 + 1] / 131072.0f * start_volume; + float_buffer[i * 2] += left_sample; + float_buffer[i * 2 + 1] += right_sample; + state->spindle_transition_pos++; + } + + if (state->spindle_transition_pos >= samples->spindle_start_samples) { + state->spindle_state = HDD_SPINDLE_RUNNING; + state->spindle_pos = 0; + hdd_audio_log("HDD Audio: Drive %d spinup complete, now running\n", state->hdd_index); + } +} + +/* Helper: Mix spindle loop sound into float buffer */ +static void +hdd_audio_mix_spindle_loop_float(hdd_audio_drive_state_t *state, hdd_audio_samples_t *samples, + float *float_buffer, int frames_in_buffer) +{ + if (!samples->spindle_loop_buffer || samples->spindle_loop_samples <= 0) + return; + + float spindle_volume = samples->spindle_loop_volume; + for (int i = 0; i < frames_in_buffer; i++) { + float left_sample = (float) samples->spindle_loop_buffer[state->spindle_pos * 2] / 131072.0f * spindle_volume; + float right_sample = (float) samples->spindle_loop_buffer[state->spindle_pos * 2 + 1] / 131072.0f * spindle_volume; + float_buffer[i * 2] += left_sample; + float_buffer[i * 2 + 1] += right_sample; + + state->spindle_pos++; + if (state->spindle_pos >= samples->spindle_loop_samples) { + state->spindle_pos = 0; + } + } +} + +/* Helper: Mix spindle stop sound into float buffer */ +static void +hdd_audio_mix_spindle_stop_float(hdd_audio_drive_state_t *state, hdd_audio_samples_t *samples, + float *float_buffer, int frames_in_buffer) +{ + if (!samples->spindle_stop_buffer || samples->spindle_stop_samples <= 0) { + state->spindle_state = HDD_SPINDLE_STOPPED; + return; + } + + float stop_volume = samples->spindle_stop_volume; + for (int i = 0; i < frames_in_buffer && state->spindle_transition_pos < samples->spindle_stop_samples; i++) { + float left_sample = (float) samples->spindle_stop_buffer[state->spindle_transition_pos * 2] / 131072.0f * stop_volume; + float right_sample = (float) samples->spindle_stop_buffer[state->spindle_transition_pos * 2 + 1] / 131072.0f * stop_volume; + float_buffer[i * 2] += left_sample; + float_buffer[i * 2 + 1] += right_sample; + state->spindle_transition_pos++; + } + + if (state->spindle_transition_pos >= samples->spindle_stop_samples) { + state->spindle_state = HDD_SPINDLE_STOPPED; + hdd_audio_log("HDD Audio: Drive %d spindown complete, now stopped\n", state->hdd_index); + } +} + +/* Helper: Mix seek sounds into float buffer */ +static void +hdd_audio_mix_seek_float(hdd_audio_drive_state_t *state, float *float_buffer, int frames_in_buffer) +{ + for (int v = 0; v < HDD_MAX_SEEK_VOICES_PER_HDD; v++) { + if (!state->seek_voices[v].active) + continue; + + int seek_profile_id = state->seek_voices[v].profile_id; + hdd_audio_samples_t *seek_samples = &profile_samples[seek_profile_id]; + if (!seek_samples->seek_buffer || seek_samples->seek_samples == 0) + continue; + + float voice_vol = state->seek_voices[v].volume; + int pos = state->seek_voices[v].position; + if (pos < 0) pos = 0; + + for (int i = 0; i < frames_in_buffer && pos < seek_samples->seek_samples; i++, pos++) { + float seek_left = (float) seek_samples->seek_buffer[pos * 2] / 131072.0f * voice_vol; + float seek_right = (float) seek_samples->seek_buffer[pos * 2 + 1] / 131072.0f * voice_vol; + + float_buffer[i * 2] += seek_left; + float_buffer[i * 2 + 1] += seek_right; + } + + if (pos >= seek_samples->seek_samples) { + state->seek_voices[v].active = 0; + state->seek_voices[v].position = 0; + } else { + state->seek_voices[v].position = pos; + } + } +} + +/* Helper: Mix spindle start sound into int16 buffer */ +static void +hdd_audio_mix_spindle_start_int16(hdd_audio_drive_state_t *state, hdd_audio_samples_t *samples, + int16_t *buffer, int frames_in_buffer) +{ + if (!samples->spindle_start_buffer || samples->spindle_start_samples <= 0) { + state->spindle_state = HDD_SPINDLE_RUNNING; + state->spindle_pos = 0; + return; + } + + float start_volume = samples->spindle_start_volume; + for (int i = 0; i < frames_in_buffer && state->spindle_transition_pos < samples->spindle_start_samples; i++) { + int32_t left = buffer[i * 2] + (int32_t)(samples->spindle_start_buffer[state->spindle_transition_pos * 2] * start_volume); + int32_t right = buffer[i * 2 + 1] + (int32_t)(samples->spindle_start_buffer[state->spindle_transition_pos * 2 + 1] * start_volume); + if (left > 32767) left = 32767; + if (left < -32768) left = -32768; + if (right > 32767) right = 32767; + if (right < -32768) right = -32768; + buffer[i * 2] = (int16_t) left; + buffer[i * 2 + 1] = (int16_t) right; + state->spindle_transition_pos++; + } + + if (state->spindle_transition_pos >= samples->spindle_start_samples) { + state->spindle_state = HDD_SPINDLE_RUNNING; + state->spindle_pos = 0; + hdd_audio_log("HDD Audio: Drive %d spinup complete, now running\n", state->hdd_index); + } +} + +/* Helper: Mix spindle loop sound into int16 buffer */ +static void +hdd_audio_mix_spindle_loop_int16(hdd_audio_drive_state_t *state, hdd_audio_samples_t *samples, + int16_t *buffer, int frames_in_buffer) +{ + if (!samples->spindle_loop_buffer || samples->spindle_loop_samples <= 0) + return; + + float spindle_volume = samples->spindle_loop_volume; + for (int i = 0; i < frames_in_buffer; i++) { + int32_t left = buffer[i * 2] + (int32_t)(samples->spindle_loop_buffer[state->spindle_pos * 2] * spindle_volume); + int32_t right = buffer[i * 2 + 1] + (int32_t)(samples->spindle_loop_buffer[state->spindle_pos * 2 + 1] * spindle_volume); + if (left > 32767) left = 32767; + if (left < -32768) left = -32768; + if (right > 32767) right = 32767; + if (right < -32768) right = -32768; + buffer[i * 2] = (int16_t) left; + buffer[i * 2 + 1] = (int16_t) right; + + state->spindle_pos++; + if (state->spindle_pos >= samples->spindle_loop_samples) { + state->spindle_pos = 0; + } + } +} + +/* Helper: Mix spindle stop sound into int16 buffer */ +static void +hdd_audio_mix_spindle_stop_int16(hdd_audio_drive_state_t *state, hdd_audio_samples_t *samples, + int16_t *buffer, int frames_in_buffer) +{ + if (!samples->spindle_stop_buffer || samples->spindle_stop_samples <= 0) { + state->spindle_state = HDD_SPINDLE_STOPPED; + return; + } + + float stop_volume = samples->spindle_stop_volume; + for (int i = 0; i < frames_in_buffer && state->spindle_transition_pos < samples->spindle_stop_samples; i++) { + int32_t left = buffer[i * 2] + (int32_t)(samples->spindle_stop_buffer[state->spindle_transition_pos * 2] * stop_volume); + int32_t right = buffer[i * 2 + 1] + (int32_t)(samples->spindle_stop_buffer[state->spindle_transition_pos * 2 + 1] * stop_volume); + if (left > 32767) left = 32767; + if (left < -32768) left = -32768; + if (right > 32767) right = 32767; + if (right < -32768) right = -32768; + buffer[i * 2] = (int16_t) left; + buffer[i * 2 + 1] = (int16_t) right; + state->spindle_transition_pos++; + } + + if (state->spindle_transition_pos >= samples->spindle_stop_samples) { + state->spindle_state = HDD_SPINDLE_STOPPED; + hdd_audio_log("HDD Audio: Drive %d spindown complete, now stopped\n", state->hdd_index); + } +} + +/* Helper: Mix seek sounds into int16 buffer */ +static void +hdd_audio_mix_seek_int16(hdd_audio_drive_state_t *state, int16_t *buffer, int frames_in_buffer) +{ + for (int v = 0; v < HDD_MAX_SEEK_VOICES_PER_HDD; v++) { + if (!state->seek_voices[v].active) + continue; + + int seek_profile_id = state->seek_voices[v].profile_id; + hdd_audio_samples_t *seek_samples = &profile_samples[seek_profile_id]; + if (!seek_samples->seek_buffer || seek_samples->seek_samples == 0) + continue; + + float voice_vol = state->seek_voices[v].volume; + int pos = state->seek_voices[v].position; + if (pos < 0) pos = 0; + + for (int i = 0; i < frames_in_buffer && pos < seek_samples->seek_samples; i++, pos++) { + int32_t left = buffer[i * 2] + (int32_t)(seek_samples->seek_buffer[pos * 2] * voice_vol); + int32_t right = buffer[i * 2 + 1] + (int32_t)(seek_samples->seek_buffer[pos * 2 + 1] * voice_vol); + + if (left > 32767) left = 32767; + if (left < -32768) left = -32768; + if (right > 32767) right = 32767; + if (right < -32768) right = -32768; + + buffer[i * 2] = (int16_t) left; + buffer[i * 2 + 1] = (int16_t) right; + } + + if (pos >= seek_samples->seek_samples) { + state->seek_voices[v].active = 0; + state->seek_voices[v].position = 0; + } else { + state->seek_voices[v].position = pos; + } + } +} + +/* Process a single drive's audio in float mode */ +static void +hdd_audio_process_drive_float(hdd_audio_drive_state_t *state, float *float_buffer, int frames_in_buffer) +{ + int profile_id = state->profile_id; + + if (profile_id <= 0 || profile_id >= HDD_AUDIO_PROFILE_MAX) + return; + + hdd_audio_samples_t *samples = &profile_samples[profile_id]; + if (!samples->loaded) + return; + + /* Handle spindle states for this drive */ + switch (state->spindle_state) { + case HDD_SPINDLE_STARTING: + hdd_audio_mix_spindle_start_float(state, samples, float_buffer, frames_in_buffer); + break; + case HDD_SPINDLE_RUNNING: + hdd_audio_mix_spindle_loop_float(state, samples, float_buffer, frames_in_buffer); + break; + case HDD_SPINDLE_STOPPING: + hdd_audio_mix_spindle_stop_float(state, samples, float_buffer, frames_in_buffer); + break; + case HDD_SPINDLE_STOPPED: + default: + break; + } + + /* Seek sounds - only play when spindle is running */ + if (samples->seek_buffer && samples->seek_samples > 0 && + hdd_audio_mutex && state->spindle_state == HDD_SPINDLE_RUNNING) { + thread_wait_mutex(hdd_audio_mutex); + hdd_audio_mix_seek_float(state, float_buffer, frames_in_buffer); + thread_release_mutex(hdd_audio_mutex); + } +} + +/* Process a single drive's audio in int16 mode */ +static void +hdd_audio_process_drive_int16(hdd_audio_drive_state_t *state, int16_t *buffer, int frames_in_buffer) +{ + int profile_id = state->profile_id; + + if (profile_id <= 0 || profile_id >= HDD_AUDIO_PROFILE_MAX) + return; + + hdd_audio_samples_t *samples = &profile_samples[profile_id]; + if (!samples->loaded) + return; + + /* Handle spindle states for this drive */ + switch (state->spindle_state) { + case HDD_SPINDLE_STARTING: + hdd_audio_mix_spindle_start_int16(state, samples, buffer, frames_in_buffer); + break; + case HDD_SPINDLE_RUNNING: + hdd_audio_mix_spindle_loop_int16(state, samples, buffer, frames_in_buffer); + break; + case HDD_SPINDLE_STOPPING: + hdd_audio_mix_spindle_stop_int16(state, samples, buffer, frames_in_buffer); + break; + case HDD_SPINDLE_STOPPED: + default: + break; + } + + /* Seek sounds - only play when spindle is running */ + if (samples->seek_buffer && samples->seek_samples > 0 && + hdd_audio_mutex && state->spindle_state == HDD_SPINDLE_RUNNING) { + thread_wait_mutex(hdd_audio_mutex); + hdd_audio_mix_seek_int16(state, buffer, frames_in_buffer); + thread_release_mutex(hdd_audio_mutex); + } +} + +void +hdd_audio_callback(int16_t *buffer, int length) +{ + int frames_in_buffer = length / 2; + + if (sound_is_float) { + float *float_buffer = (float *) buffer; + + /* Initialize buffer to silence */ + for (int i = 0; i < length; i++) { + float_buffer[i] = 0.0f; + } + + /* Process each active drive */ + for (int d = 0; d < active_drive_count; d++) { + hdd_audio_process_drive_float(&drive_states[d], float_buffer, frames_in_buffer); + } + } else { + /* Initialize buffer to silence */ + for (int i = 0; i < length; i++) { + buffer[i] = 0; + } + + /* Process each active drive */ + for (int d = 0; d < active_drive_count; d++) { + hdd_audio_process_drive_int16(&drive_states[d], buffer, frames_in_buffer); + } + } +} \ No newline at end of file diff --git a/src/disk/mo.c b/src/disk/mo.c index 5ffe74f78..079e82c6e 100644 --- a/src/disk/mo.c +++ b/src/disk/mo.c @@ -254,7 +254,7 @@ mo_disk_close(const mo_t *dev) mo_disk_unload(dev); memcpy(dev->drv->prev_image_path, dev->drv->image_path, - sizeof(dev->drv->prev_image_path)); + sizeof(dev->drv->image_path)); memset(dev->drv->image_path, 0, sizeof(dev->drv->image_path)); dev->drv->medium_size = 0; diff --git a/src/disk/rdisk.c b/src/disk/rdisk.c index 683b1e963..87e9eb5e4 100644 --- a/src/disk/rdisk.c +++ b/src/disk/rdisk.c @@ -307,7 +307,7 @@ rdisk_disk_close(const rdisk_t *dev) rdisk_disk_unload(dev); memcpy(dev->drv->prev_image_path, dev->drv->image_path, - sizeof(dev->drv->prev_image_path)); + sizeof(dev->drv->image_path)); memset(dev->drv->image_path, 0, sizeof(dev->drv->image_path)); dev->drv->medium_size = 0; diff --git a/src/floppy/fdc.c b/src/floppy/fdc.c index de0549ca4..fb1b0c31e 100644 --- a/src/floppy/fdc.c +++ b/src/floppy/fdc.c @@ -1142,7 +1142,7 @@ fdc_write(uint16_t addr, uint8_t val, void *priv) timer_set_delay_u64(&fdc->timer, 1000 * TIMER_USEC); else timer_set_delay_u64(&fdc->timer, 256 * TIMER_USEC); - break; + break; default: timer_set_delay_u64(&fdc->timer, 256 * TIMER_USEC); break; @@ -1882,13 +1882,17 @@ fdc_callback(void *priv) fdc->st0 = 0x20 | (fdc->params[0] & 3); if (!fdd_track0(drive_num)) fdc->st0 |= 0x50; - if (fdc->flags & FDC_FLAG_PCJR) { - fdc->fintr = 1; - fdc->interrupt = -4; - } else - fdc->interrupt = -3; - timer_set_delay_u64(&fdc->timer, 2048 * TIMER_USEC); fdc->stat = 0x10 | (1 << fdc->rw_drive); + if (fdd_get_turbo(drive_num)) { + if (fdc->flags & FDC_FLAG_PCJR) { + fdc->fintr = 1; + fdc->interrupt = -4; + } else { + fdc->interrupt = -3; + } + timer_set_delay_u64(&fdc->timer, 2048 * TIMER_USEC); + } + /* Interrupts and callbacks in the fdd callback function (fdc_seek_complete_interrupt) */ return; case 0x0d: /*Format track*/ if (fdc->format_state == 1) { diff --git a/src/floppy/fdd.c b/src/floppy/fdd.c index 14627a3f9..89ec2e9f6 100644 --- a/src/floppy/fdd.c +++ b/src/floppy/fdd.c @@ -419,9 +419,9 @@ fdd_seek(int drive, int track_diff) fdd_changed[drive] = 0; - if (fdd[drive].turbo) + if (fdd[drive].turbo) { fdd_do_seek(drive, fdd[drive].track); - else { + } else { /* Trigger appropriate audio for track movements */ int actual_track_diff = abs(old_track - fdd[drive].track); if (actual_track_diff > 0) { @@ -429,11 +429,6 @@ fdd_seek(int drive, int track_diff) fdd_audio_play_multi_track_seek(drive, old_track, fdd[drive].track); } - if (old_track + track_diff < 0) { - fdd_do_seek(drive, fdd[drive].track); - return; - } - fdd_seek_in_progress[drive] = 1; if (!fdd_seek_timer[drive].callback) { @@ -489,7 +484,7 @@ fdd_type_invert_densel(int type) int ret; if (drive_types[type].flags & FLAG_PS2) - ret = (!!strstr(machine_getname(), "PS/1")) || (!!strstr(machine_getname(), "PS/2")) || (!!strstr(machine_getname(), "PS/55")); + ret = (!!strstr(machine_getname(machine), "PS/1")) || (!!strstr(machine_getname(machine), "PS/2")) || (!!strstr(machine_getname(machine), "PS/55")); else ret = drive_types[type].flags & FLAG_INVERT_DENSEL; diff --git a/src/floppy/fdd_audio.c b/src/floppy/fdd_audio.c index 8cbebf209..62e1881ef 100644 --- a/src/floppy/fdd_audio.c +++ b/src/floppy/fdd_audio.c @@ -31,6 +31,7 @@ #include <86box/plat.h> #include <86box/path.h> #include <86box/ini.h> +#include <86box/sound_util.h> #ifndef DISABLE_FDD_AUDIO @@ -53,9 +54,6 @@ static multi_seek_state_t seek_state[FDD_NUM][MAX_CONCURRENT_SEEKS] = {}; extern uint64_t motoron[FDD_NUM]; extern char exe_path[2048]; -/* Forward declaration */ -static int16_t *load_wav(const char *filename, int *sample_count); - extern uint8_t *rom; extern uint32_t biosmask; extern uint32_t biosaddr; @@ -412,7 +410,7 @@ load_profile_samples(int profile_id) if (samples->spindlemotor_start.buffer == NULL && config->spindlemotor_start.filename[0]) { strcpy(samples->spindlemotor_start.filename, config->spindlemotor_start.filename); samples->spindlemotor_start.volume = config->spindlemotor_start.volume; - samples->spindlemotor_start.buffer = load_wav(config->spindlemotor_start.filename, + samples->spindlemotor_start.buffer = sound_load_wav(config->spindlemotor_start.filename, &samples->spindlemotor_start.samples); if (samples->spindlemotor_start.buffer) { fdd_log(" Loaded spindlemotor_start: %s (%d samples, volume %.2f)\n", @@ -428,7 +426,7 @@ load_profile_samples(int profile_id) if (samples->spindlemotor_loop.buffer == NULL && config->spindlemotor_loop.filename[0]) { strcpy(samples->spindlemotor_loop.filename, config->spindlemotor_loop.filename); samples->spindlemotor_loop.volume = config->spindlemotor_loop.volume; - samples->spindlemotor_loop.buffer = load_wav(config->spindlemotor_loop.filename, + samples->spindlemotor_loop.buffer = sound_load_wav(config->spindlemotor_loop.filename, &samples->spindlemotor_loop.samples); if (samples->spindlemotor_loop.buffer) { fdd_log(" Loaded spindlemotor_loop: %s (%d samples, volume %.2f)\n", @@ -444,7 +442,7 @@ load_profile_samples(int profile_id) if (samples->spindlemotor_stop.buffer == NULL && config->spindlemotor_stop.filename[0]) { strcpy(samples->spindlemotor_stop.filename, config->spindlemotor_stop.filename); samples->spindlemotor_stop.volume = config->spindlemotor_stop.volume; - samples->spindlemotor_stop.buffer = load_wav(config->spindlemotor_stop.filename, + samples->spindlemotor_stop.buffer = sound_load_wav(config->spindlemotor_stop.filename, &samples->spindlemotor_stop.samples); if (samples->spindlemotor_stop.buffer) { fdd_log(" Loaded spindlemotor_stop: %s (%d samples, volume %.2f)\n", @@ -466,7 +464,7 @@ load_profile_samples(int profile_id) if (samples->seek_up[idx].buffer == NULL && config->seek_up[idx].filename[0]) { strcpy(samples->seek_up[idx].filename, config->seek_up[idx].filename); samples->seek_up[idx].volume = config->seek_up[idx].volume; - samples->seek_up[idx].buffer = load_wav(config->seek_up[idx].filename, + samples->seek_up[idx].buffer = sound_load_wav(config->seek_up[idx].filename, &samples->seek_up[idx].samples); if (samples->seek_up[idx].buffer) { fdd_log(" Loaded seek_up[%d]: %s (%d samples, volume %.2f)\n", @@ -479,7 +477,7 @@ load_profile_samples(int profile_id) if (samples->seek_down[idx].buffer == NULL && config->seek_down[idx].filename[0]) { strcpy(samples->seek_down[idx].filename, config->seek_down[idx].filename); samples->seek_down[idx].volume = config->seek_down[idx].volume; - samples->seek_down[idx].buffer = load_wav(config->seek_down[idx].filename, + samples->seek_down[idx].buffer = sound_load_wav(config->seek_down[idx].filename, &samples->seek_down[idx].samples); if (samples->seek_down[idx].buffer) { fdd_log(" Loaded seek_down[%d]: %s (%d samples, volume %.2f)\n", @@ -493,7 +491,7 @@ load_profile_samples(int profile_id) if (samples->post_seek_up[idx].buffer == NULL) { strcpy(samples->post_seek_up[idx].filename, config->post_seek_up[idx].filename); samples->post_seek_up[idx].volume = config->post_seek_up[idx].volume; - samples->post_seek_up[idx].buffer = load_wav(config->post_seek_up[idx].filename, + samples->post_seek_up[idx].buffer = sound_load_wav(config->post_seek_up[idx].filename, &samples->post_seek_up[idx].samples); if (samples->post_seek_up[idx].buffer) { fdd_log(" Loaded POST seek_up[%d] (%d-track): %s (%d samples, volume %.2f)\n", @@ -507,7 +505,7 @@ load_profile_samples(int profile_id) if (samples->post_seek_down[idx].buffer == NULL) { strcpy(samples->post_seek_down[idx].filename, config->post_seek_down[idx].filename); samples->post_seek_down[idx].volume = config->post_seek_down[idx].volume; - samples->post_seek_down[idx].buffer = load_wav(config->post_seek_down[idx].filename, + samples->post_seek_down[idx].buffer = sound_load_wav(config->post_seek_down[idx].filename, &samples->post_seek_down[idx].samples); if (samples->post_seek_down[idx].buffer) { fdd_log(" Loaded POST seek_down[%d] (%d-track): %s (%d samples, volume %.2f)\n", @@ -529,7 +527,7 @@ load_profile_samples(int profile_id) if (samples->bios_post_seek_up[vendor][idx].buffer == NULL) { strcpy(samples->bios_post_seek_up[vendor][idx].filename, config->bios_post_seek_up[vendor][idx].filename); samples->bios_post_seek_up[vendor][idx].volume = config->bios_post_seek_up[vendor][idx].volume; - samples->bios_post_seek_up[vendor][idx].buffer = load_wav(config->bios_post_seek_up[vendor][idx].filename, + samples->bios_post_seek_up[vendor][idx].buffer = sound_load_wav(config->bios_post_seek_up[vendor][idx].filename, &samples->bios_post_seek_up[vendor][idx].samples); if (samples->bios_post_seek_up[vendor][idx].buffer) { fdd_log(" Loaded %s POST seek_up[%d] (%d-track): %s (%d samples, volume %.2f)\n", @@ -543,7 +541,7 @@ load_profile_samples(int profile_id) if (samples->bios_post_seek_down[vendor][idx].buffer == NULL) { strcpy(samples->bios_post_seek_down[vendor][idx].filename, config->bios_post_seek_down[vendor][idx].filename); samples->bios_post_seek_down[vendor][idx].volume = config->bios_post_seek_down[vendor][idx].volume; - samples->bios_post_seek_down[vendor][idx].buffer = load_wav(config->bios_post_seek_down[vendor][idx].filename, + samples->bios_post_seek_down[vendor][idx].buffer = sound_load_wav(config->bios_post_seek_down[vendor][idx].filename, &samples->bios_post_seek_down[vendor][idx].samples); if (samples->bios_post_seek_down[vendor][idx].buffer) { fdd_log(" Loaded %s POST seek_down[%d] (%d-track): %s (%d samples, volume %.2f)\n", @@ -947,92 +945,6 @@ fdd_audio_play_multi_track_seek(int drive, int from_track, int to_track) drive, slot, sample_to_use->samples); } -static int16_t * -load_wav(const char *filename, int *sample_count) -{ - if ((filename == NULL) || (strlen(filename) == 0)) - return NULL; - - if (strstr(filename, "..") != NULL) - return NULL; - - FILE *f = asset_fopen(filename, "rb"); - if (f == NULL) { - fdd_log("FDD Audio: Failed to open WAV file: %s\n", filename); - return NULL; - } - - wav_header_t hdr; - if (fread(&hdr, sizeof(hdr), 1, f) != 1) { - fdd_log("FDD Audio: Failed to read WAV header from: %s\n", filename); - fclose(f); - return NULL; - } - - if (memcmp(hdr.riff, "RIFF", 4) || memcmp(hdr.wave, "WAVE", 4) || memcmp(hdr.fmt, "fmt ", 4) || memcmp(hdr.data, "data", 4)) { - fdd_log("FDD Audio: Invalid WAV format in file: %s\n", filename); - fclose(f); - return NULL; - } - - /* Accept both mono and stereo, 16-bit PCM */ - if (hdr.audio_format != 1 || hdr.bits_per_sample != 16 || (hdr.num_channels != 1 && hdr.num_channels != 2)) { - fdd_log("FDD Audio: Unsupported WAV format in %s (format: %d, bits: %d, channels: %d)\n", - filename, hdr.audio_format, hdr.bits_per_sample, hdr.num_channels); - fclose(f); - return NULL; - } - - int input_samples = hdr.data_size / 2; /* 2 bytes per sample */ - int16_t *input_data = malloc(hdr.data_size); - if (!input_data) { - fdd_log("FDD Audio: Failed to allocate memory for WAV data: %s\n", filename); - fclose(f); - return NULL; - } - - if (fread(input_data, 1, hdr.data_size, f) != hdr.data_size) { - fdd_log("FDD Audio: Failed to read WAV data from: %s\n", filename); - free(input_data); - fclose(f); - return NULL; - } - fclose(f); - - int16_t *output_data; - int output_samples; - - if (hdr.num_channels == 1) { - /* Convert mono to stereo */ - output_samples = input_samples; /* Number of stereo sample pairs */ - output_data = malloc(input_samples * 2 * sizeof(int16_t)); /* Allocate for stereo */ - if (!output_data) { - fdd_log("FDD Audio: Failed to allocate stereo conversion buffer for: %s\n", filename); - free(input_data); - return NULL; - } - - /* Convert mono to stereo by duplicating each sample */ - for (int i = 0; i < input_samples; i++) { - output_data[i * 2] = input_data[i]; /* Left channel */ - output_data[i * 2 + 1] = input_data[i]; /* Right channel */ - } - - free(input_data); - fdd_log("FDD Audio: Loaded %s (mono->stereo, %d samples)\n", filename, output_samples); - } else { - /* Already stereo */ - output_data = input_data; - output_samples = input_samples / 2; /* Number of stereo sample pairs */ - fdd_log("FDD Audio: Loaded %s (stereo, %d samples)\n", filename, output_samples); - } - - if (sample_count) - *sample_count = output_samples; - - return output_data; -} - void fdd_audio_callback(int16_t *buffer, int length) { diff --git a/src/include/86box/cdrom.h b/src/include/86box/cdrom.h index 487676023..fb157ba66 100644 --- a/src/include/86box/cdrom.h +++ b/src/include/86box/cdrom.h @@ -481,6 +481,8 @@ extern void cdrom_close(void); extern void cdrom_insert(const uint8_t id); extern void cdrom_exit(const uint8_t id); extern int cdrom_is_empty(const uint8_t id); +extern int cdrom_is_playing(const uint8_t id); +extern int cdrom_is_paused(const uint8_t id); extern void cdrom_eject(const uint8_t id); extern void cdrom_reload(const uint8_t id); diff --git a/src/include/86box/fdd_audio.h b/src/include/86box/fdd_audio.h index 433aeaff8..ac6f225be 100644 --- a/src/include/86box/fdd_audio.h +++ b/src/include/86box/fdd_audio.h @@ -71,23 +71,6 @@ typedef enum { MOTOR_STATE_STOPPING } motor_state_t; -/* WAV header structure */ -typedef struct { - char riff[4]; - uint32_t size; - char wave[4]; - char fmt[4]; - uint32_t fmt_size; - uint16_t audio_format; - uint16_t num_channels; - uint32_t sample_rate; - uint32_t byte_rate; - uint16_t block_align; - uint16_t bits_per_sample; - char data[4]; - uint32_t data_size; -} wav_header_t; - /* Audio sample structure */ typedef struct { char filename[512]; diff --git a/src/include/86box/hdd.h b/src/include/86box/hdd.h index 88855552b..d8f275de3 100644 --- a/src/include/86box/hdd.h +++ b/src/include/86box/hdd.h @@ -172,6 +172,7 @@ typedef struct hard_disk_t { uint32_t hpc; uint32_t tracks; uint32_t speed_preset; + uint32_t audio_profile; uint32_t num_zones; uint32_t phy_cyl; @@ -233,6 +234,7 @@ extern double hdd_seek_get_time(hard_disk_t *hdd, uint32_t dst_addr, uint8_ int hdd_preset_get_num(void); const char *hdd_preset_getname(int preset); extern const char *hdd_preset_get_internal_name(int preset); +extern uint32_t hdd_preset_get_rpm(int preset); extern int hdd_preset_get_from_internal_name(char *s); extern void hdd_preset_apply(int hdd_id); diff --git a/src/include/86box/hdd_audio.h b/src/include/86box/hdd_audio.h new file mode 100644 index 000000000..b404efc38 --- /dev/null +++ b/src/include/86box/hdd_audio.h @@ -0,0 +1,83 @@ +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * This file is part of the 86Box distribution. + * + * Definitions for the hard disk audio emulation. + * + * Authors: Toni Riikonen, + * + * Copyright 2026 Toni Riikonen. + */ +#ifndef EMU_HDD_AUDIO_H +#define EMU_HDD_AUDIO_H + +#include +#include <86box/hdd.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define HDD_AUDIO_PROFILE_MAX 64 + +/* Spindle motor states */ +typedef enum { + HDD_SPINDLE_STOPPED = 0, + HDD_SPINDLE_STARTING, + HDD_SPINDLE_RUNNING, + HDD_SPINDLE_STOPPING +} hdd_spindle_state_t; + +/* Audio sample configuration structure */ +typedef struct { + char filename[512]; + float volume; +} hdd_audio_sample_config_t; + +/* HDD audio profile configuration */ +typedef struct { + int id; + char name[128]; + char internal_name[64]; + uint32_t rpm; + hdd_audio_sample_config_t spindlemotor_start; + hdd_audio_sample_config_t spindlemotor_loop; + hdd_audio_sample_config_t spindlemotor_stop; + hdd_audio_sample_config_t seek_track; +} hdd_audio_profile_config_t; + +/* Functions for profile management */ +extern void hdd_audio_load_profiles(void); +extern int hdd_audio_get_profile_count(void); +extern const hdd_audio_profile_config_t *hdd_audio_get_profile(int id); +extern const char *hdd_audio_get_profile_name(int id); +extern const char *hdd_audio_get_profile_internal_name(int id); +extern uint32_t hdd_audio_get_profile_rpm(int id); +extern int hdd_audio_get_profile_by_internal_name(const char *internal_name); + +/* HDD audio initialization and cleanup */ +extern void hdd_audio_init(void); +extern void hdd_audio_reset(void); +extern void hdd_audio_close(void); +extern void hdd_audio_callback(int16_t *buffer, int length); +extern void hdd_audio_seek(hard_disk_t *hdd, uint32_t new_cylinder); + +/* Per-drive spindle control */ +extern void hdd_audio_spinup_drive(int hdd_index); +extern void hdd_audio_spindown_drive(int hdd_index); +extern hdd_spindle_state_t hdd_audio_get_drive_spindle_state(int hdd_index); + +/* Legacy functions for backward compatibility - operate on all drives */ +extern void hdd_audio_spinup(void); +extern void hdd_audio_spindown(void); +extern hdd_spindle_state_t hdd_audio_get_spindle_state(void); + +#ifdef __cplusplus +} +#endif + +#endif /* EMU_HDD_AUDIO_H */ \ No newline at end of file diff --git a/src/include/86box/keyboard.h b/src/include/86box/keyboard.h index 78714986c..8b32851bb 100644 --- a/src/include/86box/keyboard.h +++ b/src/include/86box/keyboard.h @@ -165,7 +165,9 @@ extern uint16_t scancode_map[768]; extern uint16_t scancode_config_map[768]; extern void (*keyboard_send)(uint16_t val); +extern void kbd_adddata_xt_common(uint16_t val); extern void kbd_adddata_process(uint16_t val, void (*adddata)(uint16_t val)); +extern void kbd_adddata_process_10x(uint16_t val, void (*adddata)(uint16_t val)); extern const scancode scancode_xt[512]; @@ -211,6 +213,7 @@ extern void keyboard_close(void); extern void keyboard_set_table(const scancode *ptr); extern void keyboard_poll_host(void); extern void keyboard_process(void); +extern void keyboard_process_10x(void); extern uint16_t keyboard_convert(int ch); extern void keyboard_input(int down, uint16_t scan); extern void keyboard_all_up(void); diff --git a/src/include/86box/machine.h b/src/include/86box/machine.h index b39ecdad6..c648ea4f4 100644 --- a/src/include/86box/machine.h +++ b/src/include/86box/machine.h @@ -387,8 +387,7 @@ extern void * machine_snd; /* Core functions. */ extern int machine_count(void); extern int machine_available(int m); -extern const char * machine_getname(void); -extern const char * machine_getname_ex(int m); +extern const char * machine_getname(int m); extern const char * machine_get_internal_name(void); extern const char * machine_get_nvr_name(void); extern int machine_get_machine_from_internal_name(const char *s); @@ -1204,6 +1203,7 @@ extern int machine_at_spitfire_init(const machine_t *); extern int machine_at_ma30d_init(const machine_t *); /* i440EX */ +extern int machine_at_brio83xx_init(const machine_t *); extern int machine_at_p6i440e2_init(const machine_t *); /* i440BX */ diff --git a/src/include/86box/network.h b/src/include/86box/network.h index 9c906ef1a..0642c3f53 100644 --- a/src/include/86box/network.h +++ b/src/include/86box/network.h @@ -50,8 +50,8 @@ #define NET_TYPE_PCAP 2 /* use the (Win)Pcap API */ #define NET_TYPE_VDE 3 /* use the VDE plug API */ #define NET_TYPE_TAP 4 /* use a linux TAP device */ -#define NET_TYPE_NMSWITCH 5 /* use the network multicast switch provider */ -#define NET_TYPE_NRSWITCH 6 /* use the network remote switch provider */ +#define NET_TYPE_NLSWITCH 5 /* use the local switch provider */ +#define NET_TYPE_NRSWITCH 6 /* use the remote switch provider */ #define NET_MAX_FRAME 1518 /* Queue size must be a power of 2 */ @@ -60,6 +60,8 @@ #define NET_QUEUE_COUNT 4 #define NET_CARD_MAX 4 #define NET_HOST_INTF_MAX 64 +#define NET_SWITCH_GRP_MIN 1 +#define NET_SWITCH_GRP_MAX 10 #define NET_PERIOD_10M 0.8 #define NET_PERIOD_100M 0.08 @@ -132,7 +134,7 @@ extern const netdrv_t net_slirp_drv; extern const netdrv_t net_vde_drv; extern const netdrv_t net_tap_drv; extern const netdrv_t net_null_drv; -extern const netdrv_t net_netswitch_drv; +extern const netdrv_t net_switch_drv; struct _netcard_t { const device_t *device; diff --git a/src/include/86box/plat.h b/src/include/86box/plat.h index 2d28ec9bc..316825f10 100644 --- a/src/include/86box/plat.h +++ b/src/include/86box/plat.h @@ -153,6 +153,7 @@ extern void plat_language_code_r(int id, char *outbuf, int len); extern void plat_get_cpu_string(char *outbuf, uint8_t len); extern void plat_set_thread_name(void *thread, const char *name); extern void plat_break(void); +extern void plat_send_to_clipboard(unsigned char *rgb, int width, int height); /* Resource management. */ extern wchar_t *plat_get_string(int id); diff --git a/src/include/86box/snd_azt2316a.h b/src/include/86box/snd_azt2316a.h index 8aae3f1ff..266193e55 100644 --- a/src/include/86box/snd_azt2316a.h +++ b/src/include/86box/snd_azt2316a.h @@ -2,5 +2,7 @@ #define SOUND_AZT2316A_H extern void azt2316a_enable_wss(uint8_t enable, void *priv); +extern void aztpr16_update_mixer(void *priv); +extern void aztpr16_wss_mode(uint8_t mode, void *priv); #endif /*SOUND_AZT2316A*/ diff --git a/src/include/86box/snd_sb_dsp.h b/src/include/86box/snd_sb_dsp.h index 0bc719d98..200912c42 100644 --- a/src/include/86box/snd_sb_dsp.h +++ b/src/include/86box/snd_sb_dsp.h @@ -8,16 +8,17 @@ #define SB_SUBTYPE_CLONE_AZT2316A_0X11 1 /* Aztech Sound Galaxy Pro 16 AB, DSP 3.1 - SBPRO2 clone */ #define SB_SUBTYPE_CLONE_AZT1605_0X0C 2 /* Aztech Sound Galaxy Nova 16 Extra / Packard Bell Forte 16, DSP 2.1 - SBPRO2 clone */ -#define SB_SUBTYPE_ESS_ES688 3 /* ESS Technology ES688 */ -#define SB_SUBTYPE_ESS_ES1688 4 /* ESS Technology ES1688 */ +#define SB_SUBTYPE_CLONE_AZTPR16_0X09 3 /* Aztech Sound Galaxy Pro 16 Extra */ +#define SB_SUBTYPE_ESS_ES688 4 /* ESS Technology ES688 */ +#define SB_SUBTYPE_ESS_ES1688 5 /* ESS Technology ES1688 */ /* ESS-related */ #define IS_ESS(dsp) ((dsp)->sb_subtype >= SB_SUBTYPE_ESS_ES688) /* Check for future ESS cards here */ #define IS_NOT_ESS(dsp) ((dsp)->sb_subtype < SB_SUBTYPE_ESS_ES688) /* Check for future ESS cards here */ /* aztech-related */ -#define IS_AZTECH(dsp) ((dsp)->sb_subtype == SB_SUBTYPE_CLONE_AZT2316A_0X11 || (dsp)->sb_subtype == SB_SUBTYPE_CLONE_AZT1605_0X0C) /* check for future AZT cards here */ -#define AZTECH_EEPROM_SIZE 16 +#define IS_AZTECH(dsp) ((dsp)->sb_subtype == SB_SUBTYPE_CLONE_AZT2316A_0X11 || (dsp)->sb_subtype == SB_SUBTYPE_CLONE_AZT1605_0X0C || (dsp)->sb_subtype == SB_SUBTYPE_CLONE_AZTPR16_0X09) /* check for future AZT cards here */ +#define AZTECH_EEPROM_SIZE 36 typedef struct sb_dsp_t { int sb_type; diff --git a/src/include/86box/sound.h b/src/include/86box/sound.h index e35969ead..9dec03231 100644 --- a/src/include/86box/sound.h +++ b/src/include/86box/sound.h @@ -103,6 +103,9 @@ extern void sound_cd_thread_reset(void); extern void sound_fdd_thread_init(void); extern void sound_fdd_thread_end(void); +extern void sound_hdd_thread_init(void); +extern void sound_hdd_thread_end(void); + extern void closeal(void); extern void inital(void); extern void givealbuffer(const void *buf); @@ -110,6 +113,7 @@ extern void givealbuffer_music(const void *buf); extern void givealbuffer_wt(const void *buf); extern void givealbuffer_cd(const void *buf); extern void givealbuffer_fdd(const void *buf, const uint32_t size); +extern void givealbuffer_hdd(const void *buf, const uint32_t size); #define sb_vibra16c_onboard_relocate_base sb_vibra16s_onboard_relocate_base #define sb_vibra16cl_onboard_relocate_base sb_vibra16s_onboard_relocate_base @@ -130,6 +134,7 @@ extern const device_t azt2316a_device; extern const device_t acermagic_s20_device; extern const device_t mirosound_pcm10_device; extern const device_t azt1605_device; +extern const device_t aztpr16_device; /* C-Media CMI8x38 */ extern const device_t cmi8338_device; diff --git a/src/include/86box/sound_util.h b/src/include/86box/sound_util.h new file mode 100644 index 000000000..95e95a360 --- /dev/null +++ b/src/include/86box/sound_util.h @@ -0,0 +1,28 @@ +#ifndef SOUND_UTIL_H +#define SOUND_UTIL_H + +#include + +/* WAV file header structure */ +typedef struct wav_header_t { + char riff[4]; + uint32_t file_size; + char wave[4]; + char fmt[4]; + uint32_t fmt_size; + uint16_t audio_format; + uint16_t num_channels; + uint32_t sample_rate; + uint32_t byte_rate; + uint16_t block_align; + uint16_t bits_per_sample; + char data[4]; + uint32_t data_size; +} wav_header_t; + +/* Load a WAV file and return stereo 16-bit samples + * Returns allocated buffer (caller must free) or NULL on error + * sample_count receives the number of stereo sample pairs */ +int16_t *sound_load_wav(const char *filename, int *sample_count); + +#endif /* SOUND_UTIL_H */ \ No newline at end of file diff --git a/src/include/86box/video.h b/src/include/86box/video.h index b94bdf415..b7f86ac18 100644 --- a/src/include/86box/video.h +++ b/src/include/86box/video.h @@ -145,6 +145,9 @@ typedef struct monitor_t { int mon_renderedframes; atomic_int mon_actualrenderedframes; atomic_int mon_screenshots; + atomic_int mon_screenshots_clipboard; + atomic_int mon_screenshots_raw; + atomic_int mon_screenshots_raw_clipboard; uint32_t *mon_pal_lookup; int *mon_cga_palette; int mon_pal_lookup_static; /* Whether it should not be freed by the API. */ diff --git a/src/machine/m_at_slot1.c b/src/machine/m_at_slot1.c index 870837713..578814a8f 100644 --- a/src/machine/m_at_slot1.c +++ b/src/machine/m_at_slot1.c @@ -344,6 +344,45 @@ machine_at_ma30d_init(const machine_t *model) } /* i440EX */ +int +machine_at_brio83xx_init(const machine_t *model) +{ + int ret; + + ret = bios_load_linear("roms/machines/brio83xx/QHL0700.rom", + 0x000c0000, 262144, 0); + + if (bios_only || !ret) + return ret; + + machine_at_common_init_ex(model, 2); + + pci_init(PCI_CONFIG_TYPE_1); + // Actual settings! + pci_register_slot(0x00, PCI_CARD_NORTHBRIDGE, 1, 2, 3, 4); /* Onboard */ + pci_register_slot(0x01, PCI_CARD_AGPBRIDGE, 1, 2, 3, 4); /* Onboard */ + pci_register_slot(0x07, PCI_CARD_SOUTHBRIDGE, 1, 2, 3, 4); /* Onboard */ + pci_register_slot(0x0F, PCI_CARD_NORMAL, 1, 2, 3, 4); /* Slot 01 */ + pci_register_slot(0x10, PCI_CARD_NORMAL, 2, 3, 4, 1); /* Slot 02 */ + pci_register_slot(0x12, PCI_CARD_NORMAL, 3, 4, 1, 2); /* Slot 03 */ + pci_register_slot(0x14, PCI_CARD_VIDEO, 1, 2, 3, 4); /* Onboard */ + + if (gfxcard[0] == VID_INTERNAL) + device_add(&s3_trio64v2_dx_onboard_pci_device); + + device_add(&i440ex_device); + device_add(&piix4_device); + + device_add_params(&fdc37c67x_device, (void *) (FDC37XXX5)); + + /* Chip not quite confirmed, but this does operate fine. */ + device_add(&sst_flash_29ee020_device); + + spd_register(SPD_TYPE_SDRAM, 0x3, 256); + + return ret; +} + int machine_at_p6i440e2_init(const machine_t *model) { diff --git a/src/machine/m_at_t3100e.c b/src/machine/m_at_t3100e.c index 4919f4975..e13ec9530 100644 --- a/src/machine/m_at_t3100e.c +++ b/src/machine/m_at_t3100e.c @@ -158,6 +158,7 @@ #include <86box/device.h> #include <86box/keyboard.h> #include <86box/rom.h> +#include <86box/video.h> #include "cpu.h" #include <86box/fdd.h> #include <86box/fdc.h> @@ -817,6 +818,7 @@ machine_at_t3100e_init(const machine_t *model) machine_at_common_ide_init(model); + video_reset(gfxcard[0]); device_add_params(machine_get_kbc_device(machine), (void *) model->kbc_params); if (fdc_current[0] == FDC_INTERNAL) diff --git a/src/machine/machine.c b/src/machine/machine.c index 30f979fd4..2db543cf2 100644 --- a/src/machine/machine.c +++ b/src/machine/machine.c @@ -69,7 +69,7 @@ machine_init_ex(int m) int ret = 0; if (!bios_only) { - machine_log("Initializing as \"%s\"\n", machine_getname()); + machine_log("Initializing as \"%s\"\n", machine_getname(machine)); machine_init_p1(); diff --git a/src/machine/machine_table.c b/src/machine/machine_table.c index e404a3f4a..86729cd66 100644 --- a/src/machine/machine_table.c +++ b/src/machine/machine_table.c @@ -14788,7 +14788,7 @@ const machine_t machines[] = { .cpu = { .package = CPU_PKG_SOCKET5_7, .block = CPU_BLOCK(CPU_K5, CPU_5K86, CPU_K6, CPU_K6_2, CPU_K6_2C, CPU_K6_3, CPU_K6_2P, - CPU_K6_3P, CPU_Cx6x86, CPU_Cx6x86MX, CPU_Cx6x86L), + CPU_K6_3P, CPU_Cx6x86, CPU_Cx6x86MX, CPU_Cx6x86L, CPU_WINCHIP, CPU_WINCHIP2), .min_bus = 50000000, .max_bus = 66666667, .min_voltage = 2800, @@ -18265,6 +18265,55 @@ const machine_t machines[] = { }, /* 440EX */ + /* Has a SM(S)C FDC37C675 Super I/O chip with on-chip KBC with Phoenix + MultiKey/42 (version 1.38) KBC firmware. */ + { + .name = "[i440EX] HP Brio 83xx", + .internal_name = "brio83xx", + .type = MACHINE_TYPE_SLOT1, + .chipset = MACHINE_CHIPSET_INTEL_440EX, + .init = machine_at_brio83xx_init, + .p1_handler = machine_generic_p1_handler, + .gpio_handler = NULL, + .available_flag = MACHINE_AVAILABLE, + .gpio_acpi_handler = NULL, + .cpu = { + .package = CPU_PKG_SLOT1, + .block = CPU_BLOCK_NONE, + .min_bus = 66666667, + .max_bus = 66666667, + /* NOTE: Range not confirmed. */ + .min_voltage = 1800, + .max_voltage = 3500, + .min_multi = 1.5, + .max_multi = 8.0 + }, + .bus_flags = MACHINE_PS2_AGP | MACHINE_BUS_USB, + .flags = MACHINE_IDE_DUAL | MACHINE_VIDEO | MACHINE_APM | MACHINE_ACPI | MACHINE_USB, + .ram = { + /* PC manual says 128 MB max, but 256 MB confirmed to work + and 512 MB confirmed to not work. */ + .min = 8192, + .max = 262144, + .step = 8192 + }, + .nvrmask = 255, + .jumpered_ecp_dma = 0, + .default_jumpered_ecp_dma = -1, + .kbc_device = NULL, + .kbc_params = 0x00000000, + .kbc_p1 = 0x00000cf0, + .gpio = 0xffffffff, + .gpio_acpi = 0xffffffff, + .device = NULL, + .kbd_device = NULL, + .fdc_device = NULL, + .sio_device = NULL, + .vid_device = &s3_trio64v2_dx_onboard_pci_device, + .snd_device = NULL, + .net_device = NULL + }, + /* Has a Winbond W83977TF Super I/O chip with on-chip KBC with AMIKey-2 KBC firmware. */ { @@ -20472,13 +20521,7 @@ machine_count(void) } const char * -machine_getname(void) -{ - return (machines[machine].name); -} - -const char * -machine_getname_ex(int m) +machine_getname(int m) { return (machines[m].name); } @@ -20706,7 +20749,7 @@ machine_get_machine_from_internal_name(const char *s) c++; } - return 0; + return -1; } int diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 0943a9258..627535d53 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -19,6 +19,7 @@ list(APPEND net_sources network.c net_pcap.c net_slirp.c + net_switch.c net_dp8390.c net_3c501.c net_3c503.c @@ -57,18 +58,6 @@ if(WIN32) target_link_libraries(86Box ws2_32) endif() -if(NETSWITCH) - add_compile_definitions(USE_NETSWITCH) - list(APPEND net_sources - net_netswitch.c - netswitch.c - pb_common.c - pb_encode.c - pb_decode.c - networkmessage.pb.c - ) -endif() - if (UNIX) if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") set_source_files_properties(net_slirp.c PROPERTIES COMPILE_FLAGS "-I/usr/local/include") diff --git a/src/network/net_l80225.c b/src/network/net_l80225.c index 341799e57..6493edec6 100644 --- a/src/network/net_l80225.c +++ b/src/network/net_l80225.c @@ -34,17 +34,8 @@ l80225_mii_readw(uint16_t *regs, uint16_t addr) return 0; } -/* Readonly mask for MDI (PHY) registers */ -static const uint16_t tulip_mdi_mask[] = { - 0x0000, 0xffff, 0xffff, 0xffff, 0xc01f, 0xffff, 0xffff, 0x0000, - 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, - 0x0fff, 0x0000, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, - 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, - -}; - void l80225_mii_writew(uint16_t *regs, uint16_t addr, uint16_t val) { - regs[addr] = val & tulip_mdi_mask[addr]; + regs[addr] = val; } diff --git a/src/network/net_netswitch.c b/src/network/net_netswitch.c deleted file mode 100644 index 1cd0a81a7..000000000 --- a/src/network/net_netswitch.c +++ /dev/null @@ -1,497 +0,0 @@ -/* - * 86Box A hypervisor and IBM PC system emulator that specializes in - * running old operating systems and software designed for IBM - * PC systems and compatibles from 1981 through fairly recent - * system designs based on the PCI bus. - * - * This file is part of the 86Box distribution. - * - * Network Switch network driver - * - * Authors: cold-brewed - * - * Copyright 2024 cold-brewed - */ -#include -#include -#include -#include -#include -#include -#include -#ifdef _WIN32 -# define WIN32_LEAN_AND_MEAN -# include -# include -#else -# include -#endif - -#define HAVE_STDARG_H -#include <86box/86box.h> -#include <86box/device.h> -#include <86box/thread.h> -#include <86box/timer.h> -#include <86box/network.h> -#include <86box/net_event.h> -#include "netswitch.h" -#include "networkmessage.pb.h" - -enum { - NET_EVENT_STOP = 0, - NET_EVENT_TX, - NET_EVENT_RX, - NET_EVENT_SWITCH, - NET_EVENT_MAX -}; - -/* Special define for the windows portion. We only need to poll up to - * NET_EVENT_SWITCH. NET_EVENT_SWITCH gives us a different NET_EVENT_MAX - * excluding the others, and windows does not like polling events that - * do not exist. */ -#define NET_EVENT_WIN_MAX NET_EVENT_SWITCH - -#define SWITCH_PKT_BATCH NET_QUEUE_LEN -/* In µs, how often to send a keepalive and perform connection maintenance */ -#define SWITCH_KEEPALIVE_INTERVAL 5000000 -/* In ms, how long until we consider a connection gone? */ -#define SWITCH_MAX_INTERVAL 10000 - -typedef struct { - void *nsconn; - uint8_t mac_addr[6]; - netcard_t *card; - thread_t *poll_tid; - net_evt_t tx_event; - net_evt_t stop_event; - netpkt_t pktv[SWITCH_PKT_BATCH]; - pc_timer_t stats_timer; - pc_timer_t maintenance_timer; - ns_rx_packet_t rx_packet; - char switch_type[16]; -#ifdef _WIN32 - HANDLE sock_event; -#endif -} net_netswitch_t; - -// Used for debugging, needs to be moved to an official location -void print_packet(const netpkt_t netpkt) { -#ifdef NET_SWITCH_LOG - if(netpkt.len == 0) { - net_switch_log("Something is wrong, len is %d\n", netpkt.len); - return; - } - /* Temporarily disable log suppression for packet dumping to allow specific formatting */ - pclog_toggle_suppr(); - uint8_t linebuff[17] = "\0"; - char src_mac_buf[32] = ""; - char dst_mac_buf[32] = ""; - for(int m_i=0; m_i < 6; m_i++) { - char src_octet[4]; - char dst_octet[4]; - snprintf(src_octet, sizeof(src_octet), "%02X%s", netpkt.data[m_i+6], m_i < 5 ? ":" : ""); - strncat(src_mac_buf, src_octet, sizeof (src_mac_buf) - 1); - - snprintf(dst_octet, sizeof(dst_octet), "%02X%s", netpkt.data[m_i], m_i < 5 ? ":" : ""); - strncat(dst_mac_buf, dst_octet, sizeof (dst_mac_buf) - 1); - } - net_switch_log("%s -> %s\n\n", src_mac_buf, dst_mac_buf); - - // Payload length (bytes 12-13 with zero index) - uint16_t payload_length = (netpkt.data[12] & 0xFF) << 8; - payload_length |= (netpkt.data[13] & 0xFF); - const uint16_t actual_length = netpkt.len - 14; - if(payload_length <= 1500) { - // 802.3 / 802.2 - net_switch_log("Payload length according to frame: %i\n", payload_length); - // remaining length of packet (len - 14) to calculate padding - net_switch_log("Actual payload length: %i\n", actual_length); - if(payload_length <=46 ) { - net_switch_log("Likely has %d bytes padding\n", actual_length - payload_length); - } - } else { - // Type II - net_switch_log("EtherType: 0x%04X\n", payload_length); - } - // actual packet size - net_switch_log("Full frame size: %i\n", netpkt.len); - net_switch_log("\n"); - - for(int i=0; i< netpkt.len; i++) { - - net_switch_log("%02x ", netpkt.data[i]); - if ((netpkt.data[i] < 0x20) || (netpkt.data[i] > 0x7e)) { - linebuff[i % 16] = '.'; - } else { - linebuff[i % 16] = netpkt.data[i]; - } - - if( (i+1) % 8 == 0) { - net_switch_log(" "); - } - - if( (i+1) % 16 == 0) { - net_switch_log("| %s |\n", (char *)linebuff); - linebuff[0] = '\0'; - } - - // last char? - if(i+1 == netpkt.len) { - const int togo = 16 - (i % 16); - for(int remaining = 0; remaining < togo-1; remaining++) { - // This would represent the byte display and the space - net_switch_log(" "); - } - // spacing between byte groupings - if(togo > 8) { - net_switch_log(" "); - } - linebuff[(i % 16) +1] = '\0'; - net_switch_log(" | %s", (char *)linebuff); - - for(int remaining = 0; remaining < togo-1; remaining++) { - // This would represent the remaining bytes on the right - net_switch_log(" "); - } - net_switch_log(" |\n"); - } - } - net_switch_log("\n"); - pclog_toggle_suppr(); -#endif /* NET_SWITCH_LOG*/ -} - -#ifdef ENABLE_NET_SWITCH_STATS -static void -stats_timer(void *priv) -{ - /* Get the device state structure. */ - net_netswitch_t *netswitch = priv; - const NSCONN *nsconn = netswitch->nsconn; - net_switch_log("Max (frame / packet) TX (%zu/%zu) RX (%zu/%zu)\n", - nsconn->stats.max_tx_frame, nsconn->stats.max_tx_packet, - nsconn->stats.max_rx_frame, nsconn->stats.max_rx_packet); - net_switch_log("Last ethertype (TX/RX) (%02x%02x/%02x%02x)\n", nsconn->stats.last_tx_ethertype[0], nsconn->stats.last_tx_ethertype[1], - nsconn->stats.last_rx_ethertype[0], nsconn->stats.last_rx_ethertype[1]); - net_switch_log("Packet totals (all/tx/rx/would fragment/max vec) (%zu/%zu/%zu/%zu/%i)\n", nsconn->stats.total_tx_packets + nsconn->stats.total_rx_packets, - nsconn->stats.total_tx_packets, nsconn->stats.total_rx_packets, nsconn->stats.total_fragments, nsconn->stats.max_vec); - net_switch_log("---\n"); - /* Restart the timer */ - timer_on_auto(&netswitch->stats_timer, 60000000); -} -#endif - -static void -maintenance_timer(void *priv) -{ - /* Get the device state structure. */ - net_netswitch_t *netswitch = (net_netswitch_t *) priv; - NSCONN *nsconn = (NSCONN *) netswitch->nsconn; - if (!ns_send_control(nsconn, MessageType_MESSAGE_TYPE_KEEPALIVE)) { - net_switch_log("Failed to send keepalive packet\n"); - } - const int64_t interval = ns_get_current_millis() - nsconn->last_packet_stamp; -// net_switch_log("Last packet time: %lld ago\n", interval); -// net_switch_log("Last packet time: %lld ago\n", interval); - - /* A timeout has likely occurred, try to fix the connection if type is REMOTE */ - if((interval > SWITCH_MAX_INTERVAL) && nsconn->switch_type == SWITCH_TYPE_REMOTE) { - /* FIXME: This is really rough, needs moar logic */ - nsconn->client_state = CONNECTING; - net_switch_log("We appear to be disconnected, attempting to reconnect\n"); - /* TODO: Proper connect function! This is duplicated code */ - if(!ns_send_control(nsconn, MessageType_MESSAGE_TYPE_CONNECT_REQUEST)) { - /* TODO: Failure */ - } - } - /* Restart the timer */ - timer_on_auto(&netswitch->maintenance_timer, SWITCH_KEEPALIVE_INTERVAL); -} - -/* Lots of #ifdef madness here thanks to the polling differences on windows */ -static void -net_netswitch_thread(void *priv) -{ - net_netswitch_t *net_netswitch = (net_netswitch_t *) priv; - NSCONN *nsconn = (NSCONN *) net_netswitch->nsconn; - bool status; - char switch_type[32]; - snprintf(switch_type, sizeof(switch_type), "%s", nsconn->switch_type == SWITCH_TYPE_REMOTE ? "Remote" : "Local"); - - net_switch_log("%s Net Switch: polling started.\n", switch_type); - -#ifdef _WIN32 - WSAEventSelect(ns_pollfd(net_netswitch->nsconn), net_netswitch->sock_event, FD_READ); - - HANDLE events[NET_EVENT_MAX]; - events[NET_EVENT_STOP] = net_event_get_handle(&net_netswitch->stop_event); - events[NET_EVENT_TX] = net_event_get_handle(&net_netswitch->tx_event); - events[NET_EVENT_RX] = net_netswitch->sock_event; - - bool run = true; -#else - struct pollfd pfd[NET_EVENT_MAX]; - pfd[NET_EVENT_STOP].fd = net_event_get_fd(&net_netswitch->stop_event); - pfd[NET_EVENT_STOP].events = POLLIN | POLLPRI; - - pfd[NET_EVENT_TX].fd = net_event_get_fd(&net_netswitch->tx_event); - pfd[NET_EVENT_TX].events = POLLIN | POLLPRI; - - pfd[NET_EVENT_RX].fd = ns_pollfd(net_netswitch->nsconn); - pfd[NET_EVENT_RX].events = POLLIN | POLLPRI; -#endif - -#ifdef _WIN32 - while (run) { - int ret = WaitForMultipleObjects(NET_EVENT_WIN_MAX, events, FALSE, INFINITE); - - switch (ret - WAIT_OBJECT_0) { -#else - while (1) { - poll(pfd, NET_EVENT_MAX, -1); -#endif - -#ifdef _WIN32 - case NET_EVENT_STOP: - net_event_clear(&net_netswitch->stop_event); - run = false; - break; - case NET_EVENT_TX: -#else - if (pfd[NET_EVENT_STOP].revents & POLLIN) { - net_event_clear(&net_netswitch->stop_event); - break; - } - if (pfd[NET_EVENT_TX].revents & POLLIN) { -#endif - net_event_clear(&net_netswitch->tx_event); - - const int packets = network_tx_popv(net_netswitch->card, net_netswitch->pktv, SWITCH_PKT_BATCH); - if (packets > nsconn->stats.max_vec) { - nsconn->stats.max_vec = packets; - } - for (int i = 0; i < packets; i++) { - // net_switch_log("%d packet(s) to send\n", packets); -#if defined(NET_PRINT_PACKET_TX) || defined(NET_PRINT_PACKET_ALL) - data_packet_info_t packet_info = get_data_packet_info(&net_netswitch->pktv[i], net_netswitch->mac_addr); - /* Temporarily disable log suppression for packet logging */ - pclog_toggle_suppr(); - net_switch_log("%s Net Switch: TX: %s\n", switch_type, packet_info.printable); - pclog_toggle_suppr(); - print_packet(net_netswitch->pktv[i]); -#endif - /* Only send if we're in a connected state (always true for local) */ - if(ns_connected(net_netswitch->nsconn)) { - const ssize_t nc = ns_send_pb(net_netswitch->nsconn, &net_netswitch->pktv[i], 0); - if (nc < 1) { - perror("Got"); - net_switch_log("%s Net Switch: Problem, no bytes sent. Got back %i\n", switch_type, nc); - } - } - } -#ifdef _WIN32 - break; - case NET_EVENT_RX: -#else - } - if (pfd[NET_EVENT_RX].revents & POLLIN) { -#endif - - /* Packets are available for reading */ - status = ns_recv_pb(net_netswitch->nsconn, &net_netswitch->rx_packet, NET_MAX_FRAME, 0); - if (!status) { - net_switch_log("Receive packet failed. Skipping.\n"); - continue; - } - - /* These types are handled in the backend and don't need to be considered */ - if (is_control_packet(&net_netswitch->rx_packet) || is_fragment_packet(&net_netswitch->rx_packet)) { - continue; - } - data_packet_info_t packet_info = get_data_packet_info(&net_netswitch->rx_packet.pkt, net_netswitch->mac_addr); -#if defined(NET_PRINT_PACKET_RX) || defined(NET_PRINT_PACKET_ALL) - print_packet(net_netswitch->rx_packet.pkt); -#endif - /* - * Accept packets that are - * Unicast for us - * Broadcasts that are not from us - * All other packets *if* promiscuous mode is enabled (excluding our own) - */ - if (packet_info.is_packet_for_me || (packet_info.is_broadcast && !packet_info.is_packet_from_me)) { - /* Temporarily disable log suppression for packet logging */ - pclog_toggle_suppr(); - net_switch_log("%s Net Switch: RX: %s\n", switch_type, packet_info.printable); - pclog_toggle_suppr(); - network_rx_put_pkt(net_netswitch->card, &net_netswitch->rx_packet.pkt); - } else if (packet_info.is_packet_from_me) { - net_switch_log("%s Net Switch: Got my own packet... ignoring\n", switch_type); - } else { - /* Not our packet. Pass it along if promiscuous mode is enabled. */ - if (ns_flags(net_netswitch->nsconn) & FLAGS_PROMISC) { - net_switch_log("%s Net Switch: Got packet from %s (not mine, promiscuous is set, getting)\n", switch_type, packet_info.src_mac_h); - network_rx_put_pkt(net_netswitch->card, &net_netswitch->rx_packet.pkt); - } else { - net_switch_log("%s Net Switch: RX: %s (not mine, dest %s != %s, promiscuous not set, ignoring)\n", switch_type, packet_info.printable, packet_info.dest_mac_h, packet_info.my_mac_h); - } - } -#ifdef _WIN32 - break; - } -#else - } -#endif - } - - net_switch_log("%s Net Switch: polling stopped.\n", switch_type); -} - -void -net_netswitch_error(char *errbuf, const char *message) { - strncpy(errbuf, message, NET_DRV_ERRBUF_SIZE); - net_switch_log("Net Switch: %s\n", message); -} - -void * -net_netswitch_init(const netcard_t *card, const uint8_t *mac_addr, void *priv, char *netdrv_errbuf) -{ - net_switch_log("Net Switch: Init\n"); - - netcard_conf_t *netcard = (netcard_conf_t *) priv; - - ns_flags_t flags = FLAGS_NONE; - ns_type_t switch_type; - - const int net_type = netcard->net_type; - if(net_type == NET_TYPE_NRSWITCH) { - net_switch_log("Switch type: Remote\n"); - switch_type = SWITCH_TYPE_REMOTE; - } else if (net_type == NET_TYPE_NMSWITCH) { - net_switch_log("Switch type: Local Multicast\n"); - switch_type = SWITCH_TYPE_LOCAL; - if(netcard->promisc_mode) { - flags |= FLAGS_PROMISC; - } - } else { - net_switch_log("Failed: Unknown net switch type %d\n", net_type); - return NULL; - } - - // FIXME: Only here during dev. This would be an error otherwise (hostname not specified) - if(strlen(netcard->nrs_hostname) == 0) { - strncpy(netcard->nrs_hostname, "127.0.0.1", 128 - 1); - } - - net_netswitch_t *net_netswitch = calloc(1, sizeof(net_netswitch_t)); - net_netswitch->card = (netcard_t *) card; - memcpy(net_netswitch->mac_addr, mac_addr, sizeof(net_netswitch->mac_addr)); - snprintf(net_netswitch->switch_type, sizeof(net_netswitch->switch_type), "%s", net_type == NET_TYPE_NRSWITCH ? "Remote" : "Local"); - -// net_switch_log("%s Net Switch: mode: %d, group %d, hostname %s len %lu\n", net_netswitch->switch_type, netcard->promisc_mode, netcard->switch_group, netcard->nrs_hostname, strlen(netcard->nrs_hostname)); - - struct ns_open_args ns_args; - ns_args.type = switch_type; - /* Setting FLAGS_PROMISC here lets all packets through except the ones from us */ - ns_args.flags = flags; - /* This option sets which switch group you want to be a part of. - * Functionally equivalent to being plugged into a different switch */ - ns_args.group = netcard->switch_group; - /* You could also set the client_id here. If 0, it will be generated. */ - ns_args.client_id = 0; - memcpy(ns_args.mac_addr, net_netswitch->mac_addr, 6); - /* The remote switch hostname */ - strncpy(ns_args.nrs_hostname, netcard->nrs_hostname, sizeof(ns_args.nrs_hostname) - 1); - ns_args.nrs_hostname[127] = 0x00; - - net_switch_log("%s Net Switch: Starting up virtual switch with group %d, flags %d\n", net_netswitch->switch_type, ns_args.group, ns_args.flags); - - if ((net_netswitch->nsconn = ns_open(&ns_args)) == NULL) { - char buf[NET_DRV_ERRBUF_SIZE]; - /* We're using some errnos for our own purposes */ - switch (errno) { - case EFAULT: - snprintf(buf, NET_DRV_ERRBUF_SIZE, "Unable to open switch group %d: Cannot resolve remote switch hostname %s", ns_args.group, ns_args.nrs_hostname); - break; - default: - snprintf(buf, NET_DRV_ERRBUF_SIZE, "Unable to open switch group %d (%s)", ns_args.group, strerror(errno)); - break; - - } - net_netswitch_error(netdrv_errbuf, buf); - free(net_netswitch); - return NULL; - } - - for (int i = 0; i < SWITCH_PKT_BATCH; i++) { - net_netswitch->pktv[i].data = calloc(1, NET_MAX_FRAME); - } - net_netswitch->rx_packet.pkt.data = calloc(1, NET_MAX_FRAME); - - net_event_init(&net_netswitch->tx_event); - net_event_init(&net_netswitch->stop_event); -#ifdef _WIN32 - net_netswitch->sock_event = CreateEvent(NULL, FALSE, FALSE, NULL); -#endif - net_netswitch->poll_tid = thread_create(net_netswitch_thread, net_netswitch); - - /* Add the timers */ -#ifdef ENABLE_NET_SWITCH_STATS - timer_add(&net_netswitch->stats_timer, stats_timer, net_netswitch, 0); - timer_on_auto(&net_netswitch->stats_timer, 5000000); -#endif - timer_add(&net_netswitch->maintenance_timer, maintenance_timer, net_netswitch, 0); - timer_on_auto(&net_netswitch->maintenance_timer, SWITCH_KEEPALIVE_INTERVAL); - - /* Send join message. Return status not checked here. */ - ns_send_control(net_netswitch->nsconn, MessageType_MESSAGE_TYPE_JOIN); - - return net_netswitch; -} - -void -net_netswitch_in_available(void *priv) -{ - net_netswitch_t *net_netswitch = (net_netswitch_t *) priv; - net_event_set(&net_netswitch->tx_event); -} - -void -net_netswitch_close(void *priv) -{ - if (priv == NULL) - return; - - net_netswitch_t *net_netswitch = (net_netswitch_t *) priv; - - net_switch_log("%s Net Switch: closing.\n", net_netswitch->switch_type); - - /* Tell the thread to terminate. */ - net_event_set(&net_netswitch->stop_event); - - /* Wait for the thread to finish. */ - net_switch_log("%s Net Switch: waiting for thread to end...\n", net_netswitch->switch_type); - thread_wait(net_netswitch->poll_tid); - net_switch_log("%s Net Switch: thread ended\n", net_netswitch->switch_type); - - for (int i = 0; i < SWITCH_PKT_BATCH; i++) { - free(net_netswitch->pktv[i].data); - } - free(net_netswitch->rx_packet.pkt.data); - - net_event_close(&net_netswitch->tx_event); - net_event_close(&net_netswitch->stop_event); - -#ifdef _WIN32 - WSACleanup(); -#endif - - ns_close(net_netswitch->nsconn); - free(net_netswitch); -} - -const netdrv_t net_netswitch_drv = { - .notify_in = &net_netswitch_in_available, - .init = &net_netswitch_init, - .close = &net_netswitch_close, - .priv = NULL, -}; diff --git a/src/network/net_switch.c b/src/network/net_switch.c new file mode 100644 index 000000000..5fc3a2091 --- /dev/null +++ b/src/network/net_switch.c @@ -0,0 +1,551 @@ +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * This file is part of the 86Box distribution. + * + * Network Switch network driver. + * + * Authors: RichardG, + * + * Copyright 2026 RichardG. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN +# include +# include +# include +# include +# define IFF_POINTOPOINT IFF_POINTTOPOINT +#else +# include +# include +# include +# include +# include +# include +# include +# include +#endif +#define HAVE_STDARG_H +#include <86box/86box.h> +#include <86box/device.h> +#include <86box/plat.h> +#include <86box/thread.h> +#include <86box/timer.h> +#include <86box/network.h> +#include <86box/machine.h> +#include <86box/ini.h> +#include <86box/config.h> +#include <86box/net_event.h> +#include <86box/bswap.h> + +#define SWITCH_PKT_BATCH NET_QUEUE_LEN + +#define SWITCH_MULTICAST_GROUP 0xefff5656 /* 239.255.86.86 */ +#define SWITCH_MULTICAST_PORT 8086 + +enum { + NET_EVENT_STOP = 0, + NET_EVENT_TX, + NET_EVENT_RX, + NET_EVENT_MAX +}; + +typedef union { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; +} net_switch_sockaddr_t; + +typedef struct net_switch_hostaddr_t { + struct net_switch_hostaddr_t *next; + net_switch_sockaddr_t addr; + net_switch_sockaddr_t addr_tx; + int socket_tx; +} net_switch_hostaddr_t; + +typedef struct net_switch_t { + int socket_rx; + net_switch_hostaddr_t *hostaddrs; + uint16_t port_out; + + uint8_t promisc; + union { + uint8_t mac_addr[6]; + uint64_t mac_addr_u64; + }; + netcard_t * card; /* netcard attached to us */ + thread_t * poll_tid; + net_evt_t tx_event; + net_evt_t stop_event; + netpkt_t pkt; + netpkt_t pkt_tx_v[SWITCH_PKT_BATCH]; + int during_tx; + int recv_on_tx; +#ifdef _WIN32 + HANDLE sock_event; +#endif +} net_switch_t; + +#ifdef ENABLE_SWITCH_LOG +int switch_do_log = ENABLE_SWITCH_LOG; + +static void +netswitch_log(const char *fmt, ...) +{ + va_list ap; + + if (switch_do_log) { + va_start(ap, fmt); + pclog_ex(fmt, ap); + va_end(ap); + } +} +#else +# define netswitch_log(fmt, ...) +#endif + +static void +net_switch_in_available(void *priv) +{ + net_switch_t *netswitch = (net_switch_t *) priv; + net_event_set(&netswitch->tx_event); +} + +static unsigned int +net_switch_add_hostaddr(net_switch_t *netswitch, net_switch_sockaddr_t *addr, net_switch_sockaddr_t *broadcast, net_switch_sockaddr_t *netmask, unsigned int flags) +{ + if (!addr || !(flags & IFF_UP)) + return 0; + + /* Iterate to the end of the list. */ + net_switch_hostaddr_t **p = &netswitch->hostaddrs; + for (; *p; p = &(*p)->next) { + /* Check for duplicates. */ + switch (addr->sa.sa_family) { + case AF_INET: + if ((*p)->addr.sin.sin_addr.s_addr == addr->sin.sin_addr.s_addr) + return 0; + break; +#ifdef UNUSED + case AF_INET6: + if (AS_U64((*p)->addr.sin6.sin6_addr.s6_addr[0]) == AS_U64(addr->sin6.sin6_addr.s6_addr[0]) && + AS_U64((*p)->addr.sin6.sin6_addr.s6_addr[8]) == AS_U64(addr->sin6.sin6_addr.s6_addr[8])) + return 0; + break; +#endif + default: + return 0; + } + } + + /* Handle address. */ + net_switch_hostaddr_t *hostaddr = (net_switch_hostaddr_t *) calloc(1, sizeof(net_switch_hostaddr_t)); + hostaddr->socket_tx = -1; + unsigned int ret = 1; + if (addr->sa.sa_family == AF_INET) { +#ifdef ENABLE_SWITCH_LOG + char buf[INET_ADDRSTRLEN]; + buf[0] = '\0'; + inet_ntop(addr->sin.sin_family, &addr->sin.sin_addr.s_addr, buf, sizeof(buf)); +#endif + + /* Initialize transmit socket for this interface. */ + hostaddr->socket_tx = socket(addr->sin.sin_family, SOCK_DGRAM, 0); + if (hostaddr->socket_tx < 0) { + netswitch_log("Network Switch: could not initialize transmit socket for interface %s\n", buf); + goto fail; + } + + /* Initialize addresses. */ + memcpy(&hostaddr->addr.sin, &addr->sin, sizeof(struct sockaddr_in)); + hostaddr->addr_tx.sin.sin_family = addr->sin.sin_family; + hostaddr->addr_tx.sin.sin_port = netswitch->port_out; + + /* The problem with multicasting through multiple interfaces is loopback, in which + all copies of the datagram get reflected back to us and to other instances on the + same host. Disabling IP_MULTICAST_LOOP on all but one transmit socket can mitigate + that, but not on Windows where that option applies to the receive socket, so we + instead disable loopback on all multicast sockets and use a broadcast for loopback. */ + if ((flags & (IFF_MULTICAST | IFF_LOOPBACK)) == IFF_MULTICAST) { + /* Set multicast interface for the transmit socket. */ + if (setsockopt(hostaddr->socket_tx, IPPROTO_IP, IP_MULTICAST_IF, (char *) &hostaddr->addr.sin.sin_addr.s_addr, sizeof(hostaddr->addr.sin.sin_addr.s_addr)) < 0) { + netswitch_log("Network Switch: could not configure multicast on interface %s\n", buf); + goto broadcast; + } + + /* Join IPv4 multicast group. */ + struct ip_mreq mreq = { + .imr_multiaddr = { .s_addr = htonl(SWITCH_MULTICAST_GROUP) }, + .imr_interface = { .s_addr = hostaddr->addr.sin.sin_addr.s_addr } + }; + if (setsockopt(netswitch->socket_rx, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) &mreq, sizeof(mreq)) < 0) { + netswitch_log("Network Switch: could not join multicast group on interface %s\n", buf); + goto broadcast; + } + + /* Destination address is multicast to our group. */ + hostaddr->addr_tx.sin.sin_addr.s_addr = mreq.imr_multiaddr.s_addr; + + /* Disable multicast loopback on non-Windows platforms. (no harm on Windows) */ + int val = 0; + setsockopt(hostaddr->socket_tx, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &val, sizeof(val)); + + netswitch_log("Network Switch: added multicast interface %s", buf); + } else { +broadcast: + /* Determine destination address. */ + if (flags & IFF_LOOPBACK) { + /* Loopback interfaces don't advertise broadcast support and therefore + the broadcast address is invalid, so we build one from the netmask. */ + hostaddr->addr_tx.sin.sin_addr.s_addr = hostaddr->addr.sin.sin_addr.s_addr | (netmask ? ~netmask->sin.sin_addr.s_addr : htonl(0x00ffffff)); + ret = 0; + } else if (!(flags & (IFF_BROADCAST | IFF_POINTOPOINT)) || + !broadcast || !broadcast->sin.sin_addr.s_addr || + (broadcast->sin.sin_addr.s_addr == hostaddr->addr.sin.sin_addr.s_addr)) { + /* This interface is unicast-only or P2P with a bad peer address, nothing we can do. */ + netswitch_log("Network Switch: ignored %s interface %s\n", (flags & (IFF_LOOPBACK | IFF_BROADCAST)) ? "broadcast" : "unicast", buf); + goto fail; + } else { + /* Valid broadcast/peer address. */ + hostaddr->addr_tx.sin.sin_addr.s_addr = broadcast->sin.sin_addr.s_addr; + } + + /* Enable broadcast on the transmit socket if required. */ + if (flags & (IFF_LOOPBACK | IFF_BROADCAST)) { + int val = 1; + if (setsockopt(hostaddr->socket_tx, SOL_SOCKET, SO_BROADCAST, (char *) &val, sizeof(val)) < 0) { + netswitch_log("Network Switch: could not configure broadcast on interface %s\n", buf); + goto fail; + } + } + + netswitch_log("Network Switch: added %s interface %s", (flags & (IFF_LOOPBACK | IFF_BROADCAST)) ? "broadcast" : "unicast", buf); + } +#ifdef ENABLE_SWITCH_LOG + buf[0] = '\0'; + inet_ntop(hostaddr->addr_tx.sin.sin_family, &hostaddr->addr_tx.sin.sin_addr.s_addr, buf, sizeof(buf)); + netswitch_log(" -> %s:%d\n", buf, ntohs(netswitch->port_out)); +#endif + } else { + goto fail; + } + + /* Add address to list. */ + *p = hostaddr; + return ret; + +fail: + if (hostaddr->socket_tx >= 0) + close(hostaddr->socket_tx); + free(hostaddr); + return 0; +} + +static void +net_switch_update_hostaddrs(net_switch_t *netswitch) +{ + unsigned int added = 0; +#ifdef _WIN32 + DWORD buf_size = 16 * sizeof(INTERFACE_INFO); + INTERFACE_INFO *buf; +retry: + buf = (INTERFACE_INFO *) malloc(buf_size); + DWORD returned; + if (WSAIoctl(netswitch->socket_rx, SIO_GET_INTERFACE_LIST, NULL, 0, buf, buf_size, &returned, NULL, NULL) == SOCKET_ERROR) { + free(buf); + if (WSAGetLastError() == WSAEFAULT) { + buf_size *= 2; + goto retry; + } + } else { + returned /= sizeof(INTERFACE_INFO); + for (int i = 0; i < returned; i++) { + added += net_switch_add_hostaddr(netswitch, + (net_switch_sockaddr_t *) &buf[i].iiAddress.Address, + (net_switch_sockaddr_t *) &buf[i].iiBroadcastAddress.Address, + (net_switch_sockaddr_t *) &buf[i].iiNetmask.Address, + buf[i].iiFlags); + } + free(buf); + } +#else + struct ifaddrs *buf; + if (getifaddrs(&buf) >= 0) { + for (struct ifaddrs *addr = buf; addr; addr = addr->ifa_next) { + added += net_switch_add_hostaddr(netswitch, + (net_switch_sockaddr_t *) addr->ifa_addr, + (net_switch_sockaddr_t *) ((addr->ifa_flags & IFF_POINTOPOINT) ? addr->ifa_dstaddr : addr->ifa_broadaddr), + (net_switch_sockaddr_t *) addr->ifa_netmask, + addr->ifa_flags); + } + freeifaddrs(buf); + } +#endif + + /* Add loopback if it's not present. */ + struct sockaddr_in fallback = { + .sin_family = AF_INET, + .sin_addr = { .s_addr = htonl(INADDR_LOOPBACK) } + }; + struct sockaddr_in fallback_broadcast = { + .sin_family = AF_INET, + .sin_addr = { .s_addr = htonl(INADDR_BROADCAST) } + }; + added += net_switch_add_hostaddr(netswitch, + (net_switch_sockaddr_t *) (struct sockaddr *) &fallback, + (net_switch_sockaddr_t *) (struct sockaddr *) &fallback_broadcast, + NULL, IFF_UP | IFF_LOOPBACK); + + /* If no non-loopback interfaces have been successfully added, + fall back to IPv4 multicast on a single OS-selected interface. */ + if (!added) { + fallback.sin_addr.s_addr = htonl(INADDR_ANY); + net_switch_add_hostaddr(netswitch, + (net_switch_sockaddr_t *) (struct sockaddr *) &fallback, + (net_switch_sockaddr_t *) (struct sockaddr *) &fallback_broadcast, + NULL, IFF_UP | IFF_MULTICAST); + } +} + +static void +net_switch_thread(void *priv) +{ + net_switch_t *netswitch = (net_switch_t *) priv; + + /* Start polling. */ + netswitch_log("Network Switch: polling started\n"); + +#ifdef _WIN32 + WSAEventSelect(netswitch->socket_rx, netswitch->sock_event, FD_READ); + + HANDLE events[NET_EVENT_MAX]; + events[NET_EVENT_STOP] = net_event_get_handle(&netswitch->stop_event); + events[NET_EVENT_TX] = net_event_get_handle(&netswitch->tx_event); + events[NET_EVENT_RX] = netswitch->sock_event; +#else + struct pollfd pfd[NET_EVENT_MAX]; + pfd[NET_EVENT_STOP].fd = net_event_get_fd(&netswitch->stop_event); + pfd[NET_EVENT_STOP].events = POLLIN | POLLPRI; + + pfd[NET_EVENT_TX].fd = net_event_get_fd(&netswitch->tx_event); + pfd[NET_EVENT_TX].events = POLLIN | POLLPRI; + + pfd[NET_EVENT_RX].fd = netswitch->socket_rx; + pfd[NET_EVENT_RX].events = POLLIN | POLLPRI; +#endif + + int packets; + ssize_t len; +#ifdef _WIN32 + uint8_t run = 1; + while (run) { + int ret = WaitForMultipleObjects(NET_EVENT_MAX, events, FALSE, INFINITE); + switch (ret - WAIT_OBJECT_0) { + case NET_EVENT_STOP: + run = 0; +#else + while (1) { + poll(pfd, NET_EVENT_MAX, -1); + if (pfd[NET_EVENT_STOP].revents & POLLIN) { +#endif + net_event_clear(&netswitch->stop_event); + break; +#ifdef _WIN32 + + case NET_EVENT_TX: +#else + } + if (pfd[NET_EVENT_TX].revents & POLLIN) { +#endif + net_event_clear(&netswitch->tx_event); + netswitch->during_tx = 1; + packets = network_tx_popv(netswitch->card, netswitch->pkt_tx_v, SWITCH_PKT_BATCH); + for (int i = 0; i < packets; i++) { +#define MAC_FORMAT "(%02X:%02X:%02X:%02X:%02X:%02X -> %02X:%02X:%02X:%02X:%02X:%02X)" +#define MAC_FORMAT_ARGS(p) (p)[6], (p)[7], (p)[8], (p)[9], (p)[10], (p)[11], (p)[0], (p)[1], (p)[2], (p)[3], (p)[4], (p)[5] + netswitch_log("Network Switch: sending %d-byte packet " MAC_FORMAT "\n", + netswitch->pkt_tx_v[i].len, MAC_FORMAT_ARGS(netswitch->pkt_tx_v[i].data)); + + /* Send through all known host interfaces. */ + for (net_switch_hostaddr_t *hostaddr = netswitch->hostaddrs; hostaddr; hostaddr = hostaddr->next) + sendto(hostaddr->socket_tx, + (char *) netswitch->pkt_tx_v[i].data, netswitch->pkt_tx_v[i].len, 0, + &hostaddr->addr_tx.sa, sizeof(hostaddr->addr_tx.sa)); + } + netswitch->during_tx = 0; + + if (netswitch->recv_on_tx) { + do { + packets = network_rx_on_tx_popv(netswitch->card, netswitch->pkt_tx_v, SWITCH_PKT_BATCH); + for (int i = 0; i < packets; i++) + network_rx_put_pkt(netswitch->card, &(netswitch->pkt_tx_v[i])); + } while (packets > 0); + netswitch->recv_on_tx = 0; + } +#ifdef _WIN32 + break; + + case NET_EVENT_RX: +#else + } + if (pfd[NET_EVENT_RX].revents & POLLIN) { +#endif + len = recv(netswitch->socket_rx, (char *) netswitch->pkt.data, NET_MAX_FRAME, 0); + if (len < 12) { + netswitch_log("Network Switch: recv error (%d)\n", len); + } else if ((AS_U64(netswitch->pkt.data[6]) & le64_to_cpu(0xffffffffffffULL)) == netswitch->mac_addr_u64) { + /* A packet we've sent has looped back, drop it. */ + } else if (netswitch->promisc || /* promiscuous mode? */ + (netswitch->pkt.data[0] & 1) || /* broadcast packet? */ + ((AS_U64(netswitch->pkt.data[0]) & le64_to_cpu(0xffffffffffffULL)) == netswitch->mac_addr_u64)) { /* packet for me? */ + netswitch_log("Network Switch: receiving %d-byte packet " MAC_FORMAT "\n", + len, MAC_FORMAT_ARGS(netswitch->pkt.data)); + netswitch->pkt.len = len; + if (netswitch->during_tx) { + network_rx_on_tx_put_pkt(netswitch->card, &netswitch->pkt); + netswitch->recv_on_tx = 1; + } else { + network_rx_put_pkt(netswitch->card, &netswitch->pkt); + } + } else { + netswitch_log("Network Switch: dropping %d-byte packet " MAC_FORMAT "\n", + len, MAC_FORMAT_ARGS(netswitch->pkt.data)); + } +#ifdef _WIN32 + break; +#endif + } + } + + netswitch_log("Network Switch: polling stopped\n"); +} + +static void net_switch_close(void *priv); + +void * +net_switch_init(const netcard_t *card, const uint8_t *mac_addr, void *priv, char *netdrv_errbuf) +{ + netcard_conf_t *netcard = (netcard_conf_t *) priv; + + netswitch_log("Network Switch: initializing with group %d...\n", netcard->switch_group); + + net_switch_t *netswitch = calloc(1, sizeof(net_switch_t)); + memcpy(netswitch->mac_addr, mac_addr, sizeof(netswitch->mac_addr)); + netswitch->card = (netcard_t *) card; + netswitch->promisc = !!netcard->promisc_mode; + + /* Initialize receive socket. */ + netswitch->socket_rx = socket(AF_INET, SOCK_DGRAM, 0); + if (netswitch->socket_rx < 0) { + strncpy(netdrv_errbuf, "Could not initialize receive socket\n", NET_DRV_ERRBUF_SIZE); + goto fail; + } + + int val = 1; + if (setsockopt(netswitch->socket_rx, SOL_SOCKET, SO_REUSEADDR, (char *) &val, sizeof(val)) < 0) { + strncpy(netdrv_errbuf, "Could not set SO_REUSEADDR\n", NET_DRV_ERRBUF_SIZE); + goto fail; + } +#ifndef _WIN32 + if (setsockopt(netswitch->socket_rx, SOL_SOCKET, SO_REUSEPORT, (char *) &val, sizeof(val)) < 0) { + strncpy(netdrv_errbuf, "Could not set SO_REUSEPORT\n", NET_DRV_ERRBUF_SIZE); + goto fail; + } +#endif + + /* Disable multicast loopback on Windows. (no harm on other platforms) */ + val = 0; + setsockopt(netswitch->socket_rx, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &val, sizeof(val)); + + netswitch->port_out = htons(SWITCH_MULTICAST_PORT - NET_SWITCH_GRP_MIN + netcard->switch_group); + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_addr = { .s_addr = htonl(INADDR_ANY) }, + .sin_port = netswitch->port_out + }; + if (bind(netswitch->socket_rx, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + snprintf(netdrv_errbuf, NET_DRV_ERRBUF_SIZE, "Could not bind to port %d\n", (int) addr.sin_port); + goto fail; + } + + /* Add host interfaces. */ + net_switch_update_hostaddrs(netswitch); + if (!netswitch->hostaddrs) { + strncpy(netdrv_errbuf, "Could not add any interfaces\n", NET_DRV_ERRBUF_SIZE); + goto fail; + } + + for (int i = 0; i < SWITCH_PKT_BATCH; i++) + netswitch->pkt_tx_v[i].data = calloc(1, NET_MAX_FRAME); + netswitch->pkt.data = calloc(1, NET_MAX_FRAME); + net_event_init(&netswitch->tx_event); + net_event_init(&netswitch->stop_event); +#ifdef _WIN32 + netswitch->sock_event = CreateEvent(NULL, FALSE, FALSE, NULL); +#endif + + netswitch_log("Network Switch: creating thread...\n"); + netswitch->poll_tid = thread_create(net_switch_thread, netswitch); + + return netswitch; + +fail: + net_switch_close(netswitch); + return NULL; +} + +void +net_switch_close(void *priv) +{ + if (!priv) + return; + + net_switch_t *netswitch = (net_switch_t *) priv; + + netswitch_log("Network Switch: closing\n"); + + if (netswitch->poll_tid) { + /* Tell the polling thread to shut down. */ + net_event_set(&netswitch->stop_event); + + /* Wait for the thread to finish. */ + netswitch_log("Network Switch: waiting for thread to end...\n"); + thread_wait(netswitch->poll_tid); + } + + net_switch_hostaddr_t *hostaddr = netswitch->hostaddrs; + while (hostaddr) { + if (hostaddr->socket_tx >= 0) + close(hostaddr->socket_tx); + net_switch_hostaddr_t *next = hostaddr->next; + free(hostaddr); + hostaddr = next; + } + if (netswitch->socket_rx >= 0) + close(netswitch->socket_rx); + net_event_close(&netswitch->stop_event); + net_event_close(&netswitch->tx_event); + for (int i = 0; i < SWITCH_PKT_BATCH; i++) + free(netswitch->pkt_tx_v[i].data); + free(netswitch->pkt.data); + free(netswitch); +} + +const netdrv_t net_switch_drv = { + .notify_in = &net_switch_in_available, + .init = &net_switch_init, + .close = &net_switch_close, + .priv = NULL +}; diff --git a/src/network/net_tulip.c b/src/network/net_tulip.c index f93ac8767..298cda232 100644 --- a/src/network/net_tulip.c +++ b/src/network/net_tulip.c @@ -291,8 +291,6 @@ #define ETH_ALEN 6 -static bar_t tulip_pci_bar[3]; - struct tulip_descriptor { uint32_t status; uint32_t control; @@ -338,6 +336,8 @@ struct TULIPState { uint32_t bios_addr; uint8_t filter[16][6]; int has_bios; + + bar_t tulip_pci_bar[3]; }; typedef struct TULIPState TULIPState; @@ -346,10 +346,10 @@ static void tulip_desc_read(TULIPState *s, uint32_t p, struct tulip_descriptor *desc) { - dma_bm_read(p , (uint8_t *) &(desc->status) , 4, 4); - dma_bm_read(p + 4, (uint8_t *) &(desc->control) , 4, 4); - dma_bm_read(p + 8, (uint8_t *) &(desc->buf_addr1), 4, 4); - dma_bm_read(p + 12, (uint8_t *) &(desc->buf_addr2), 4, 4); + desc->status = mem_readl_phys(p); + desc->control = mem_readl_phys(p + 4); + desc->buf_addr1 = mem_readl_phys(p + 8); + desc->buf_addr2 = mem_readl_phys(p + 12); if (s->csr[0] & CSR0_DBO) { bswap32s(&desc->status); @@ -364,20 +364,15 @@ tulip_desc_write(TULIPState *s, uint32_t p, struct tulip_descriptor *desc) { if (s->csr[0] & CSR0_DBO) { - uint32_t status = bswap32(desc->status); - uint32_t control = bswap32(desc->control); - uint32_t buf_addr1 = bswap32(desc->buf_addr1); - uint32_t buf_addr2 = bswap32(desc->buf_addr2); - - dma_bm_write(p , (uint8_t *) &status , 4, 4); - dma_bm_write(p + 4, (uint8_t *) &control , 4, 4); - dma_bm_write(p + 8, (uint8_t *) &buf_addr1, 4, 4); - dma_bm_write(p + 12, (uint8_t *) &buf_addr2, 4, 4); + mem_writel_phys(p, bswap32(desc->status)); + mem_writel_phys(p + 4, bswap32(desc->control)); + mem_writel_phys(p + 8, bswap32(desc->buf_addr1)); + mem_writel_phys(p + 12, bswap32(desc->buf_addr2)); } else { - dma_bm_write(p , (uint8_t *) &(desc->status) , 4, 4); - dma_bm_write(p + 4, (uint8_t *) &(desc->control) , 4, 4); - dma_bm_write(p + 8, (uint8_t *) &(desc->buf_addr1), 4, 4); - dma_bm_write(p + 12, (uint8_t *) &(desc->buf_addr2), 4, 4); + mem_writel_phys(p, desc->status); + mem_writel_phys(p + 4, desc->control); + mem_writel_phys(p + 8, desc->buf_addr1); + mem_writel_phys(p + 12, desc->buf_addr2); } } @@ -438,10 +433,6 @@ tulip_copy_rx_bytes(TULIPState *s, struct tulip_descriptor *desc) len = s->rx_frame_len; } - if (s->rx_frame_len + len > sizeof(s->rx_frame)) { - return; - } - dma_bm_write(desc->buf_addr1, s->rx_frame + (s->rx_frame_size - s->rx_frame_len), len, 4); s->rx_frame_len -= len; } @@ -453,10 +444,6 @@ tulip_copy_rx_bytes(TULIPState *s, struct tulip_descriptor *desc) len = s->rx_frame_len; } - if (s->rx_frame_len + len > sizeof(s->rx_frame)) { - return; - } - dma_bm_write(desc->buf_addr2, s->rx_frame + (s->rx_frame_size - s->rx_frame_len), len, 4); s->rx_frame_len -= len; } @@ -581,7 +568,7 @@ static const uint16_t tulip_mdi_default[] = { 0x0600, 0x0001, 0x0000, - 0x3b40, + 0x0000, 0x0000, 0x0000, 0x0000, @@ -1241,34 +1228,34 @@ tulip_pci_read(UNUSED(int func), int addr, void *priv) ret = 0x02; break; case 0x10: - ret = (tulip_pci_bar[0].addr_regs[0] & 0x80) | 0x01; + ret = (s->tulip_pci_bar[0].addr_regs[0] & 0x80) | 0x01; break; case 0x11: - ret = tulip_pci_bar[0].addr_regs[1]; + ret = s->tulip_pci_bar[0].addr_regs[1]; break; case 0x12: - ret = tulip_pci_bar[0].addr_regs[2]; + ret = s->tulip_pci_bar[0].addr_regs[2]; break; case 0x13: - ret = tulip_pci_bar[0].addr_regs[3]; + ret = s->tulip_pci_bar[0].addr_regs[3]; break; #ifdef USE_128_BYTE_BAR case 0x14: - ret = (tulip_pci_bar[1].addr_regs[0] & 0x80); + ret = (s->tulip_pci_bar[1].addr_regs[0] & 0x80); break; #endif case 0x15: #ifdef USE_128_BYTE_BAR - ret = tulip_pci_bar[1].addr_regs[1]; + ret = s->tulip_pci_bar[1].addr_regs[1]; #else - ret = tulip_pci_bar[1].addr_regs[1] & 0xf0; + ret = s->tulip_pci_bar[1].addr_regs[1] & 0xf0; #endif break; case 0x16: - ret = tulip_pci_bar[1].addr_regs[2]; + ret = s->tulip_pci_bar[1].addr_regs[2]; break; case 0x17: - ret = tulip_pci_bar[1].addr_regs[3]; + ret = s->tulip_pci_bar[1].addr_regs[3]; break; case 0x2C: ret = s->subsys_ven_id & 0xFF; @@ -1283,16 +1270,16 @@ tulip_pci_read(UNUSED(int func), int addr, void *priv) ret = s->subsys_id >> 8; break; case 0x30: - ret = (tulip_pci_bar[2].addr_regs[0] & 0x01); + ret = (s->tulip_pci_bar[2].addr_regs[0] & 0x01); break; case 0x31: - ret = tulip_pci_bar[2].addr_regs[1]; + ret = s->tulip_pci_bar[2].addr_regs[1]; break; case 0x32: - ret = tulip_pci_bar[2].addr_regs[2]; + ret = s->tulip_pci_bar[2].addr_regs[2]; break; case 0x33: - ret = tulip_pci_bar[2].addr_regs[3]; + ret = s->tulip_pci_bar[2].addr_regs[3]; break; case 0x3C: ret = s->pci_conf[0x3C]; @@ -1346,9 +1333,9 @@ tulip_pci_write(UNUSED(int func), int addr, uint8_t val, void *priv) tulip_readb_io, tulip_readw_io, tulip_readl_io, tulip_writeb_io, tulip_writew_io, tulip_writel_io, priv); - tulip_pci_bar[0].addr_regs[addr & 3] = val; - tulip_pci_bar[0].addr &= 0xffffff80; - s->PCIBase = tulip_pci_bar[0].addr; + s->tulip_pci_bar[0].addr_regs[addr & 3] = val; + s->tulip_pci_bar[0].addr &= 0xffffff80; + s->PCIBase = s->tulip_pci_bar[0].addr; if (s->pci_conf[0x4] & PCI_COMMAND_IO) { //pclog("PCI write=%02x, base=%04x, io?=%x.\n", addr, s->PCIBase, s->pci_conf[0x4] & PCI_COMMAND_IO); if (s->PCIBase != 0) @@ -1365,13 +1352,13 @@ tulip_pci_write(UNUSED(int func), int addr, uint8_t val, void *priv) case 0x16: case 0x17: mem_mapping_disable(&s->memory); - tulip_pci_bar[1].addr_regs[addr & 3] = val; + s->tulip_pci_bar[1].addr_regs[addr & 3] = val; #ifdef USE_128_BYTE_BAR - tulip_pci_bar[1].addr &= 0xffffff80; + s->tulip_pci_bar[1].addr &= 0xffffff80; #else - tulip_pci_bar[1].addr &= 0xfffff000; + s->tulip_pci_bar[1].addr &= 0xfffff000; #endif - s->MMIOBase = tulip_pci_bar[1].addr; + s->MMIOBase = s->tulip_pci_bar[1].addr; if (s->pci_conf[0x4] & PCI_COMMAND_MEM) { //pclog("PCI write=%02x, mmiobase=%08x, mmio?=%x.\n", addr, s->PCIBase, s->pci_conf[0x4] & PCI_COMMAND_MEM); if (s->MMIOBase != 0) @@ -1390,10 +1377,10 @@ tulip_pci_write(UNUSED(int func), int addr, uint8_t val, void *priv) return; mem_mapping_disable(&s->bios_rom.mapping); - tulip_pci_bar[2].addr_regs[addr & 3] = val; - tulip_pci_bar[2].addr &= 0xffff0001; - s->bios_addr = tulip_pci_bar[2].addr & 0xffff0000; - if (tulip_pci_bar[2].addr_regs[0] & 0x01) { + s->tulip_pci_bar[2].addr_regs[addr & 3] = val; + s->tulip_pci_bar[2].addr &= 0xffff0001; + s->bios_addr = s->tulip_pci_bar[2].addr & 0xffff0000; + if (s->tulip_pci_bar[2].addr_regs[0] & 0x01) { if (s->bios_addr != 0) mem_mapping_set_addr(&s->bios_rom.mapping, s->bios_addr, 0x10000); } @@ -1665,16 +1652,16 @@ nic_init(const device_t *info) } } - tulip_pci_bar[0].addr_regs[0] = 1; - tulip_pci_bar[1].addr_regs[0] = 0; - s->pci_conf[0x04] = 7; + s->tulip_pci_bar[0].addr_regs[0] = 1; + s->tulip_pci_bar[1].addr_regs[0] = 0; + s->pci_conf[0x04] = 7; /* Enable our BIOS space in PCI, if needed. */ if (s->has_bios) { rom_init(&s->bios_rom, ROM_PATH_DEC21140, s->bios_addr, 0x10000, 0xffff, 0, MEM_MAPPING_EXTERNAL); - tulip_pci_bar[2].addr = 0xffff0000; + s->tulip_pci_bar[2].addr = 0xffff0000; } else - tulip_pci_bar[2].addr = 0; + s->tulip_pci_bar[2].addr = 0; mem_mapping_disable(&s->bios_rom.mapping); eeprom_data = (info->local == 3) ? s->eeprom_data : (uint8_t *) &nmc93cxx_eeprom_data(s->eeprom)[0]; diff --git a/src/network/netswitch.c b/src/network/netswitch.c deleted file mode 100644 index 1ec2c5e6f..000000000 --- a/src/network/netswitch.c +++ /dev/null @@ -1,970 +0,0 @@ -/* - * 86Box A hypervisor and IBM PC system emulator that specializes in - * running old operating systems and software designed for IBM - * PC systems and compatibles from 1981 through fairly recent - * system designs based on the PCI bus. - * - * This file is part of the 86Box distribution. - * - * Network Switch backend - * - * Authors: cold-brewed - * - * Copyright 2024 cold-brewed - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef _WIN32 -# define WIN32_LEAN_AND_MEAN -# include -# include -#else -# include -# include -#endif - -#define HAVE_STDARG_H -#include <86box/86box.h> -#include <86box/device.h> -#include <86box/thread.h> -#include <86box/timer.h> -#include <86box/network.h> -#include <86box/net_event.h> -#include <86box/random.h> -#include -#include "netswitch.h" -#include "pb_encode.h" -#include "pb_decode.h" - -#include "networkmessage.pb.h" - -bool ns_socket_setup(NSCONN *conn) { - - if(conn == NULL) { - errno=EINVAL; - return false; - } - -#ifdef _WIN32 - // Initialize Windows Socket API with the given version. - WSADATA wsaData; - if (WSAStartup(MAKEWORD(2, 0), &wsaData)) { - perror("WSAStartup"); - return false; - } -#endif - - /* Create the "main" socket - * Local mode: the listener socket for multicast packets - * Remote mode: the "main" socket for send and receive */ - conn->fddata = socket(AF_INET, SOCK_DGRAM, 0); - if (conn->fddata < 0) { - perror("socket"); - return false; - } - - /* Here things diverge depending on local or remote type */ - if(conn->switch_type == SWITCH_TYPE_LOCAL) { - - /* Set socket options - allow multiple sockets to use the same address */ - u_int on = 1; - if (setsockopt(conn->fddata, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)) < 0) { - perror("Reusing ADDR failed"); - return false; - } -#ifndef _WIN32 - /* ... and same port number - * Not needed on windows because SO_REUSEPORT doesn't exist there. However, the same - * functionality comes along with SO_REUSEADDR. */ - if (setsockopt(conn->fddata, SOL_SOCKET, SO_REUSEPORT, (char *) &on, sizeof(on)) < 0) { - perror("Reusing PORT failed"); - return false; - } -#endif - - memset(&conn->addr, 0, sizeof(conn->addr)); - conn->addr.sin_family = AF_INET; - conn->addr.sin_addr.s_addr = htonl(INADDR_ANY); - conn->addr.sin_port = htons(conn->local_multicast_port); - - /* Bind to receive address */ - if (bind(conn->fddata, (struct sockaddr *) &conn->addr, sizeof(conn->addr)) < 0) { - perror("bind"); - return false; - } - - /* Request to join multicast group */ - /*** NOTE: intermittent airplane (non-connected wifi) failures with 239.255.86.86 - needs more investigation */ - struct ip_mreq mreq; - mreq.imr_multiaddr.s_addr = inet_addr(conn->mcast_group); - mreq.imr_interface.s_addr = htonl(INADDR_ANY); - if (setsockopt(conn->fddata, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) &mreq, sizeof(mreq)) < 0) { - perror("setsockopt"); - return false; - } - - /* Now create the outgoing data socket */ - conn->fdout = socket(AF_INET, SOCK_DGRAM, 0); - if (conn->fdout < 0) { - perror("out socket"); - return false; - } - - /* Set up destination address */ - memset(&conn->outaddr, 0, sizeof(conn->outaddr)); - conn->outaddr.sin_family = AF_INET; - conn->outaddr.sin_addr.s_addr = inet_addr(conn->mcast_group); - conn->outaddr.sin_port = htons(conn->local_multicast_port); - } else if (conn->switch_type == SWITCH_TYPE_REMOTE) { - /* Remote switch path */ - int status; - struct addrinfo hints; - struct addrinfo *servinfo; - char connect_ip[128] = "\0"; - - memset(&hints, 0, sizeof hints); - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_DGRAM; - hints.ai_flags = AI_PASSIVE; // not sure? - - if((status = getaddrinfo(conn->nrs_hostname, NULL, &hints, &servinfo)) != 0) { - net_switch_log("getaddrinfo error: %s\n", gai_strerror(status)); - errno=EFAULT; - return false; - } - - for(const struct addrinfo *p = servinfo; p != NULL; p = p->ai_next) { // NOLINT (only want the first result) - /* Take the first result, ipv4 since AF_INET was set in the hints */ - const struct sockaddr_in *ipv4 = (struct sockaddr_in *) p->ai_addr; - const void *addr = &(ipv4->sin_addr); - inet_ntop(p->ai_family, addr, connect_ip, sizeof connect_ip); - break; - } - freeaddrinfo(servinfo); - - if(strlen(connect_ip) == 0) { - /* Couldn't look up the hostname */ - net_switch_log("Hostname lookup failure?\n"); - errno=EFAULT; - return false; - } - - /* Set up local socket address and port */ - memset(&conn->addr, 0, sizeof(conn->addr)); - conn->addr.sin_family = AF_INET; - conn->addr.sin_addr.s_addr = htonl(INADDR_ANY); - conn->addr.sin_port = htons(conn->remote_source_port); - - /* Bind to receive address. Try the first 100 ports to allow the use of multiple systems simultaneously */ - for(int i=0; i<100; i++) { - if(i==99) { - net_switch_log("Unable to find an available port to bind\n"); - return false; - } - if (bind(conn->fddata, (struct sockaddr *) &conn->addr, sizeof(conn->addr)) < 0) { - net_switch_log("local port %d unavailable, trying next..\n", conn->remote_source_port); - conn->remote_source_port += 1; - conn->addr.sin_port = htons(conn->remote_source_port); - continue ; - } else { - net_switch_log("** Local port for net remote switch is %d\n", conn->remote_source_port); - break; - } - - } - - - /* Set up remote address and port */ - memset(&conn->outaddr, 0, sizeof(conn->outaddr)); - conn->outaddr.sin_family = AF_INET; - conn->outaddr.sin_addr.s_addr = inet_addr(connect_ip); - conn->outaddr.sin_port = htons(conn->remote_network_port); - - /* In remote mode the file descriptor for send (fdout) is the same as receive */ - conn->fdout = conn->fddata; - - } else { - errno=EINVAL; - return false; - } - - return true; -} - -NSCONN * -ns_open(struct ns_open_args *open_args) { - struct nsconn *conn=NULL; - - /* Each "group" is really just the base port + group number - * A different group effectively gets you a different switch - * Clamp the group at MAX_SWITCH_GROUP */ - if(open_args->group > MAX_SWITCH_GROUP) { - open_args->group = MAX_SWITCH_GROUP; - } - // FIXME: hardcoded for testing - char *mcast_group = "239.255.86.86"; // Admin scope - // char *mcast_group = "224.0.0.86"; // Local scope - - if ( (conn=calloc(1,sizeof(struct nsconn)))==NULL) { - errno=ENOMEM; - return NULL; - } - - /* Type */ - conn->switch_type = open_args->type; - - /* Allocate the fragment buffer */ - for (int i = 0; i < FRAGMENT_BUFFER_LENGTH; i++) { - conn->fragment_buffer[i] = calloc(1, sizeof(ns_fragment_t)); - /* Set the default size to 0 and null data buffer to indicate it is unused. - * The data buffer will be allocated as needed. */ - conn->fragment_buffer[i]->size = 0; - conn->fragment_buffer[i]->data = NULL; - } -// net_switch_log("Fragment buffers: %d total, %d each\n", FRAGMENT_BUFFER_LENGTH, MAX_FRAME_SEND_SIZE); - - snprintf(conn->mcast_group, MAX_MCAST_GROUP_LEN, "%s", mcast_group); - conn->flags = open_args->flags; - - /* Increment the multicast port by the switch group number. Each group is - * just a different port. */ - conn->local_multicast_port = open_args->group + NET_SWITCH_MULTICAST_PORT; - conn->remote_network_port = NET_SWITCH_REMOTE_PORT; - /* Source ports for remote switch will start here and be incremented until an available port is found */ - conn->remote_source_port = NET_SWITCH_REMOTE_PORT + NET_SWITCH_RECV_PORT_OFFSET; - - /* Remote switch hostname */ - strncpy(conn->nrs_hostname, open_args->nrs_hostname, sizeof(conn->nrs_hostname) - 1); - conn->nrs_hostname[127] = 0x00; - - /* Switch type */ - if(conn->switch_type == SWITCH_TYPE_REMOTE) { - net_switch_log("Connecting to remote %s:%d, initial local port %d, group %d\n", conn->nrs_hostname, conn->remote_network_port, conn->remote_source_port, open_args->group); - } else { - net_switch_log("Opening IP %s, port %d, group %d\n", mcast_group, conn->local_multicast_port, open_args->group); - } - - /* Client state, disconnected by default. - * Primarily used in remote mode */ - conn->client_state = DISCONNECTED; - - /* Client ID. Generate the ID if set to zero. */ - if(open_args->client_id == 0) { - conn->client_id = ns_gen_client_id(); - } - - /* MAC address is set from the emulated card */ - memcpy(conn->mac_addr, open_args->mac_addr, PB_MAC_ADDR_SIZE); - - /* Protocol version */ - conn->version = NS_PROTOCOL_VERSION; - - if(!ns_socket_setup(conn)) { - goto fail; - } - - if (conn->switch_type == SWITCH_TYPE_REMOTE) { - /* Perhaps one day do the entire handshake process here */ - if(!ns_send_control(conn, MessageType_MESSAGE_TYPE_CONNECT_REQUEST)) { - goto fail; - } - conn->client_state = CONNECTING; - net_switch_log("Client state is now CONNECTING\n"); - } else { - conn->client_state = LOCAL; - } - - /* Initialize sequence numbers */ - conn->sequence = 1; - conn->remote_sequence = 1; - - /* Initialize stats */ - conn->stats.max_tx_frame = 0; - conn->stats.max_tx_packet = 0; - conn->stats.max_rx_frame = 0; - conn->stats.max_rx_packet = 0; - conn->stats.total_rx_packets = 0; - conn->stats.total_tx_packets = 0; - conn->stats.total_fragments = 0; - conn->stats.max_vec = 0; - memcpy(conn->stats.last_tx_ethertype, (uint8_t []) { 0, 0}, sizeof(conn->stats.last_tx_ethertype)); - memcpy(conn->stats.last_rx_ethertype, (uint8_t []) { 0, 0}, sizeof(conn->stats.last_rx_ethertype)); - - /* Assuming all went well we have our sockets */ - return conn; - - /* Cleanup */ -fail: - for (int i = 0; i < FRAGMENT_BUFFER_LENGTH; i++) { - free(conn->fragment_buffer[i]); - } - return NULL; -} - -int -ns_pollfd(const NSCONN *conn) { - if (conn->fddata != 0) - return conn->fddata; - else { - errno=EBADF; - return -1; - } -} - -ssize_t -ns_sock_recv(const NSCONN *conn,void *buf, const size_t len, const int flags) { - if (fd_valid(conn->fddata)) - return recv(conn->fddata,buf,len,0); - else { - errno=EBADF; - return -1; - } -} - -ssize_t -ns_sock_send(NSCONN *conn,const void *buf, const size_t len, const int flags) { - if (fd_valid(conn->fddata)) { - /* Use the outgoing socket for sending, set elsewhere: - * Remote mode: same as sending - * Local mode: different from sending */ - return sendto(conn->fdout, buf, len, 0, (struct sockaddr *) &conn->outaddr, sizeof(conn->outaddr)); - } else { - errno=EBADF; - return -1; - } -} - -ssize_t -ns_send_pb(NSCONN *conn, const netpkt_t *packet,int flags) { - - NetworkMessage network_message = NetworkMessage_init_zero; - uint8_t fragment_count; - - /* Do we need to fragment? First, determine how many packets we will be sending */ - if(packet->len <= MAX_FRAME_SEND_SIZE) { - fragment_count = 1; -// net_switch_log("No Fragmentation. Frame size %d is less than max size %d\n", packet->len, MAX_FRAME_SEND_SIZE); - } else { - /* Since we're using integer math and the remainder is - * discarded we'll add one to the result *unless* the result can be evenly divided. */ - const uint8_t extra = (packet->len % MAX_FRAME_SEND_SIZE) == 0 ? 0 : 1; - fragment_count = (packet->len / MAX_FRAME_SEND_SIZE) + extra; -// net_switch_log("Fragmentation required, frame size %d exceeds max size %d\n", packet->len, MAX_FRAME_SEND_SIZE); - } - - /* Loop here for each fragment. Send each fragment. In the even that the packet is *not* a fragment (regular data packet) - * this will only execute once. */ - const uint32_t fragment_sequence = conn->sequence; - const int64_t packet_timestamp = ns_get_current_millis(); - for (uint8_t fragment_index = 0; fragment_index < fragment_count; fragment_index++) { - uint8_t buffer[NET_SWITCH_BUFFER_LENGTH]; - pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer)); -#ifdef ENABLE_NET_SWITCH_PB_FILE_DEBUG - uint8_t file_buffer[NET_SWITCH_BUFFER_LENGTH]; - /* file_stream used for debugging and writing the message to a file */ - pb_ostream_t file_stream = pb_ostream_from_buffer(file_buffer, sizeof(file_buffer)); -#endif - /* Single frame is type DATA, fragments are FRAGMENT */ - network_message.message_type = fragment_count > 1 ? MessageType_MESSAGE_TYPE_FRAGMENT : MessageType_MESSAGE_TYPE_DATA; - network_message.client_id = conn->client_id; - network_message.timestamp = packet_timestamp; - network_message.version = conn->version; - - /* Need some additional data if we're a fragment */ - if(fragment_count > 1) { - network_message.fragment.total = fragment_count; - network_message.fragment.id = fragment_sequence; - network_message.fragment.sequence = fragment_index + 1; - network_message.has_fragment = true; - } - - /* TODO: Better / real ack logic. Needs its own function. Currently just putting in dummy data. */ - network_message.ack.id = 1; - network_message.ack.history = 1; - network_message.has_ack = true; - network_message.sequence = conn->sequence; - - /* Frame data must be allocated */ - network_message.frame = calloc(1, PB_BYTES_ARRAY_T_ALLOCSIZE(packet->len)); - - /* Calculate offsets based on our position in the fragment. - * For anything other than the *last* packet, we'll have a max frame size */ - uint16_t copy_length; - const uint16_t copy_offset = fragment_index * MAX_FRAME_SEND_SIZE; - if(fragment_index == (fragment_count - 1)) { - copy_length = packet->len % MAX_FRAME_SEND_SIZE == 0 ? MAX_FRAME_SEND_SIZE : packet->len % MAX_FRAME_SEND_SIZE; - } else { - copy_length = MAX_FRAME_SEND_SIZE; - } - if(fragment_count > 1) { -// net_switch_log("Fragment %d/%d, %d bytes\n", fragment_index + 1, fragment_count, copy_length); - } - network_message.frame->size = copy_length; - memcpy(network_message.frame->bytes, packet->data + copy_offset, copy_length); - - /* mac address must be allocated */ - network_message.mac = calloc(1, PB_BYTES_ARRAY_T_ALLOCSIZE(PB_MAC_ADDR_SIZE)); - network_message.mac->size = PB_MAC_ADDR_SIZE; - memcpy(network_message.mac->bytes, conn->mac_addr, PB_MAC_ADDR_SIZE); - - /* Encode the protobuf message */ - if (!pb_encode_ex(&stream, NetworkMessage_fields, &network_message,PB_ENCODE_DELIMITED)) { - net_switch_log("Encoding failed: %s\n", PB_GET_ERROR(&stream)); - errno = EBADF; - return -1; - } - - /* Send on the socket */ - const ssize_t nc = ns_sock_send(conn, buffer, stream.bytes_written, 0); - if(!nc) { - net_switch_log("Error sending data on the socket\n"); - errno=EBADF; - pb_release(NetworkMessage_fields, &network_message); - return -1; - } -#ifdef ENABLE_NET_SWITCH_PB_FILE_DEBUG - /* File writing for troubleshooting when needed */ - FILE *f = fopen("/var/tmp/pbuf", "wb"); - if (f) { - if (!pb_encode(&file_stream, NetworkMessage_fields, &network_message)) { - net_switch_log("File encoding failed: %s\n", PB_GET_ERROR(&file_stream)); - } - fwrite(file_buffer, file_stream.bytes_written, 1, f); - fclose(f); - } else { - net_switch_log("file open failed\n"); - } -#endif - - /* Stats */ - if(network_message.frame->size > conn->stats.max_tx_frame) { - conn->stats.max_tx_frame = network_message.frame->size; - } - if(nc > conn->stats.max_tx_packet) { - conn->stats.max_tx_packet = nc; - } - if(nc > MAX_FRAME_SEND_SIZE) { - conn->stats.total_fragments = fragment_count > 1 ? conn->stats.total_fragments + fragment_count : conn->stats.total_fragments; - } - conn->stats.total_tx_packets++; - memcpy(conn->stats.last_tx_ethertype, &packet->data[12], 2); - - /* Increment the sequence number */ - seq_increment(conn); - - /* nanopb will free all the allocated entries for us */ - pb_release(NetworkMessage_fields, &network_message); - - } - - return packet->len; -} - -bool store_fragment(const NSCONN *conn, const NetworkMessage *network_message) { - - if(conn == NULL || network_message == NULL) { - return false; - } - - /* The fragment sequence indicates which fragment this is in the overall fragment - * collection. This is used to index the fragments while being stored for reassembly - * (zero indexed locally) */ - const uint32_t fragment_index = network_message->fragment.sequence - 1; - const uint32_t fragment_size = network_message->frame->size; - - /* Make sure the fragments aren't too small - * (see header notes about size requirements for MIN_FRAG_RECV_SIZE and FRAGMENT_BUFFER_LENGTH) - * NOTE: The last packet is exempt from this rule because it can have a smaller amount. - * This is primarily to ensure there's enough space to fit all the fragments. */ - if(network_message->fragment.sequence != network_message->fragment.total) { - if (network_message->frame->size < MIN_FRAG_RECV_SIZE) { - net_switch_log("size: %d < %d\n", network_message->frame->size, MIN_FRAG_RECV_SIZE); - return false; - } - } - - /* Make sure we can handle the amount of incoming fragments */ - if (network_message->fragment.total > FRAGMENT_BUFFER_LENGTH) { - net_switch_log("buflen: %d > %d\n", network_message->fragment.total, FRAGMENT_BUFFER_LENGTH); - return false; - } - - /* Allocate or reallocate as needed. - * size > 0 indicates this buffer has already been allocated. */ - if(conn->fragment_buffer[fragment_index]->size > 0) { - conn->fragment_buffer[fragment_index]->data = realloc(conn->fragment_buffer[fragment_index]->data, sizeof(char) * fragment_size); - } else { - conn->fragment_buffer[fragment_index]->data = calloc(1, sizeof(char) * fragment_size); - } - - if (conn->fragment_buffer[fragment_index]->data == NULL) { - net_switch_log("Failed to allocate / reallocate fragment buffer space\n"); - return false; - } - - /* Each fragment will belong to a particular ID. All members will have the same ID, - * which is generally set to the sequence number of the first fragment */ - conn->fragment_buffer[fragment_index]->id = network_message->fragment.id; - /* The sequence here is set to the index of the packet in the total fragment collection - * (network_message->fragment.sequence) */ - conn->fragment_buffer[fragment_index]->sequence = fragment_index; - /* Total number of fragments in this set */ - conn->fragment_buffer[fragment_index]->total = network_message->fragment.total; - /* The sequence number from the packet that contained the fragment */ - conn->fragment_buffer[fragment_index]->packet_sequence = network_message->sequence; - /* Copy the fragment data and size */ - /* The size of fragment_buffer[fragment_index]->data is checked against MAX_FRAME_SEND_SIZE above */ - memcpy(conn->fragment_buffer[fragment_index]->data, network_message->frame->bytes, fragment_size); - conn->fragment_buffer[fragment_index]->size = fragment_size; - /* 10 seconds for a TTL */ - conn->fragment_buffer[fragment_index]->ttl = ns_get_current_millis() + 10000; - - return true; -} - -bool -reassemble_fragment(const NSCONN *conn, netpkt_t *pkt, const uint32_t packet_count) -{ - uint32_t total = 0; - - /* Make sure the reassembled packet doesn't exceed NET_MAX_FRAME */ -// if (packet_count * MAX_FRAME_SEND_SIZE > NET_MAX_FRAME) { -// return false; -// } - - /* Too many packets! */ - if (packet_count > FRAGMENT_BUFFER_LENGTH) { - return false; - } - - // TODO: Check fragment ID - // TODO: Check TTL - - /* Get the fragment size from the first entry. All fragments in a particular - * set must be of the same size except the last fragment, which may be smaller. - * The fragment size will be used to determine the offset. */ - const uint16_t fragment_size = conn->fragment_buffer[0]->size; - -// net_switch_log("Reassembling %d fragments\n", packet_count); - - for(int i = 0; i < packet_count; i++) { - /* Size of zero means we're trying to assemble from a bad fragment */ - if(conn->fragment_buffer[i]->size == 0) { - net_switch_log("Fragment size 0 when trying to reassemble (id %i/index %i/seq %i/ total %i)\n",conn->fragment_buffer[i]->id, i, conn->fragment_buffer[i]->sequence, conn->fragment_buffer[i]->total); - return false; - } - if(conn->fragment_buffer[i]->data == NULL) { - net_switch_log("Missing fragment data when trying to reassemble\n"); - return false; - } - - memcpy(pkt->data + (fragment_size * i), conn->fragment_buffer[i]->data, conn->fragment_buffer[i]->size); - total += conn->fragment_buffer[i]->size; - - /* Zero out the size to indicate the slot is unused */ - conn->fragment_buffer[i]->size = 0; - free(conn->fragment_buffer[i]->data); - conn->fragment_buffer[i]->data = NULL; - } - - /* Set the size, must cast due to netpkt_t (len is int) */ - pkt->len = (int) total; -// net_switch_log("%d bytes reassembled and converted to data packet.\n", pkt->len); - - return true; -} - -bool -ns_recv_pb(NSCONN *conn, ns_rx_packet_t *packet,size_t len,int flags) { - NetworkMessage network_message = NetworkMessage_init_zero; - ns_rx_packet_t *ns_packet = packet; - - uint8_t buffer[NET_SWITCH_BUFFER_LENGTH]; - - /* TODO: Use the passed len? Most likely not needed */ - const ssize_t nc = ns_sock_recv(conn, buffer, NET_SWITCH_BUFFER_LENGTH, 0); - if(!nc) { - net_switch_log("Error receiving data on the socket\n"); - errno=EBADF; - return false; - } - pb_istream_t stream = pb_istream_from_buffer(buffer, sizeof(buffer)); - - if (!pb_decode_delimited(&stream, NetworkMessage_fields, &network_message)) { - /* Decode failed */ - net_switch_log("PB decoding failed: %s\n", PB_GET_ERROR(&stream)); - /* Allocated fields are automatically released upon failure */ - return false; - } - - /* Basic checks for validity */ - if(network_message.mac == NULL || network_message.message_type == MessageType_MESSAGE_TYPE_UNSPECIFIED || - network_message.client_id == 0) { - net_switch_log("Invalid packet received! Skipping..\n"); - goto fail; - } - - /* These fields should always be set. Start copying into our packet structure. */ - ns_packet->client_id = network_message.client_id; - ns_packet->type = network_message.message_type; - memcpy(ns_packet->mac, network_message.mac->bytes, PB_MAC_ADDR_SIZE); - ns_packet->timestamp = network_message.timestamp; - ns_packet->version = network_message.version; - conn->remote_sequence = network_message.sequence; - conn->last_packet_stamp = network_message.timestamp; - - /* Control messages take a different path */ - if(network_message.message_type != MessageType_MESSAGE_TYPE_DATA && - network_message.message_type != MessageType_MESSAGE_TYPE_FRAGMENT) { - process_control_packet(conn, ns_packet); - pb_release(NetworkMessage_fields, &network_message); - return true; - } - - /* All packets should be DATA or FRAGMENT at this point and have a frame */ - if(network_message.frame == NULL) { - net_switch_log("Invalid data packet received! Frame is null. Skipping..\n"); - goto fail; - } - - /* Fragment path first */ - if(network_message.message_type == MessageType_MESSAGE_TYPE_FRAGMENT) { - - /* Store fragment */ - if(!store_fragment(conn, &network_message)) { - net_switch_log("Failed to store fragment\n"); - goto fail; - } - - /* Is this the last fragment? If not, return */ - if(network_message.fragment.sequence != network_message.fragment.total) { - // FIXME: Really dumb, needs to be smarter - pb_release(NetworkMessage_fields, &network_message); - return true; - } - - /* This is the last fragment. Attempt to reassemble */ - if(!reassemble_fragment(conn, &ns_packet->pkt, network_message.fragment.total)) { - net_switch_log("Failed to reassemble fragment\n"); - goto fail; - } - /* Change the type to DATA */ - ns_packet->type = MessageType_MESSAGE_TYPE_DATA; - - } else { - /* Standard DATA packet path. Copy frame from the message */ - memcpy(ns_packet->pkt.data, network_message.frame->bytes, network_message.frame->size); - ns_packet->pkt.len = network_message.frame->size; - } - - /* Stats */ - if(network_message.frame->size > conn->stats.max_rx_frame) { - conn->stats.max_rx_frame = network_message.frame->size; - } - if(nc > conn->stats.max_rx_packet) { - conn->stats.max_rx_packet = nc; - } - memcpy(conn->stats.last_rx_ethertype, &packet->pkt.data[12], 2); - conn->stats.total_rx_packets++; - /* End Stats */ - - /* nanopb allocates the necessary fields while serializing. - They need to be manually released once you are done with the message */ - pb_release(NetworkMessage_fields, &network_message); - return true; -fail: - pb_release(NetworkMessage_fields, &network_message); - return false; -} - -bool process_control_packet(NSCONN *conn, const ns_rx_packet_t *packet) { - - control_packet_info_t packet_info = get_control_packet_info(*packet, conn->mac_addr); -// net_switch_log("Last timestamp: %lld\n", ns_get_current_millis()); -// net_switch_log("(%lld ms) [%03d] ", ns_get_current_millis() - packet_info.timestamp, conn->sequence); - - /* I probably want to eventually differentiate between local and remote here, kind of basic now */ - if(!packet_info.is_packet_from_me) { /* in case of local mode */ - switch (packet_info.type) { - case MessageType_MESSAGE_TYPE_JOIN: - net_switch_log("Client ID 0x%08llx (MAC %s) has joined the chat\n", packet_info.client_id, packet_info.src_mac_h); - break; - case MessageType_MESSAGE_TYPE_LEAVE: - net_switch_log("Client ID 0x%08llx (MAC %s) has left us\n", packet_info.client_id, packet_info.src_mac_h); - break; - case MessageType_MESSAGE_TYPE_KEEPALIVE: -// net_switch_log("Client ID 0x%08llx (MAC %s) is still alive\n", packet_info.client_id, packet_info.src_mac_h); - break; - case MessageType_MESSAGE_TYPE_ACK: -// net_switch_log("Client ID 0x%08llx (MAC %s) has sent an ACK\n", packet_info.client_id, packet_info.src_mac_h); - break; - case MessageType_MESSAGE_TYPE_CONNECT_REPLY: - conn->client_state = CONNECTED; - net_switch_log("Client ID 0x%08llx (MAC %s) has sent a connection reply\n", packet_info.client_id, packet_info.src_mac_h); - net_switch_log("Client state is now CONNECTED\n"); - break; - case MessageType_MESSAGE_TYPE_FRAGMENT: - net_switch_log("Client ID 0x%08llx (MAC %s) has sent a fragment\n", packet_info.client_id, packet_info.src_mac_h); - break; - default: - net_switch_log("Client ID 0x%08llx (MAC %s) has sent a message that we don't understand (type %d)\n", packet_info.client_id, packet_info.src_mac_h, packet_info.type); - break; - } - } - return true; - -} - -bool -ns_send_control(NSCONN *conn, const MessageType type) { - - NetworkMessage network_message = NetworkMessage_init_zero; - uint8_t buffer[NET_SWITCH_BUFFER_LENGTH]; - - pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer)); - network_message.message_type = type; - network_message.client_id = conn->client_id; - - /* No frame data so we only need to allocate mac address */ - network_message.mac = calloc(1, PB_BYTES_ARRAY_T_ALLOCSIZE(PB_MAC_ADDR_SIZE)); - network_message.mac->size = PB_MAC_ADDR_SIZE; - memcpy(network_message.mac->bytes, conn->mac_addr, PB_MAC_ADDR_SIZE); - - network_message.timestamp = ns_get_current_millis(); - network_message.version = conn->version; - network_message.sequence = conn->sequence; - - if (!pb_encode_ex(&stream, NetworkMessage_fields, &network_message, PB_ENCODE_DELIMITED)) { - net_switch_log("Encoding failed: %s\n", PB_GET_ERROR(&stream)); - errno = EBADF; - return false; - } - - const ssize_t nc = ns_sock_send(conn, buffer, stream.bytes_written, 0); - if(!nc) { - net_switch_log("Error sending control message on the socket\n"); - errno=EBADF; - pb_release(NetworkMessage_fields, &network_message); - return -1; - } - /* Increment the sequence number */ - seq_increment(conn); - - /* Stats */ - conn->stats.total_tx_packets++; - - /* Must release allocated data */ - pb_release(NetworkMessage_fields, &network_message); - - return true; -} - -uint32_t -ns_gen_client_id(void) { - uint32_t msb; - do { - msb = random_generate(); - } while (msb < 0x10); - return ( random_generate() | (random_generate() << 8) | (random_generate() << 16) | (msb << 24)); -} - -const char * -ns_printable_message_type(const MessageType type) -{ - switch (type) { - case MessageType_MESSAGE_TYPE_DATA: - return "Data"; - case MessageType_MESSAGE_TYPE_JOIN: - return "Join"; - case MessageType_MESSAGE_TYPE_LEAVE: - return "Leave"; - case MessageType_MESSAGE_TYPE_KEEPALIVE: - return "Keepalive"; - case MessageType_MESSAGE_TYPE_FRAGMENT: - return "Fragment"; - case MessageType_MESSAGE_TYPE_ACK: - return "Ack"; - case MessageType_MESSAGE_TYPE_UNSPECIFIED: - return "Unspecified (shouldn't get this)"; - default: - return "Unknown message type - probably hasn't been added yet!"; - } -} - -int64_t -ns_get_current_millis(void) { - struct timeval time; - gettimeofday(&time, NULL); - /* Windows won't properly promote integers so this is necessary */ - const int64_t seconds = (int64_t) time.tv_sec * 1000; - - return seconds + (time.tv_usec / 1000); -} - -int -ns_flags(const NSCONN *conn) { - return conn->flags; -} - -int -ns_close(NSCONN *conn) { - if(conn->switch_type == SWITCH_TYPE_REMOTE) { - /* TBD */ - } - /* No need to check the return here as we're closing out */ - ns_send_control(conn, MessageType_MESSAGE_TYPE_LEAVE); - for (int i = 0; i < FRAGMENT_BUFFER_LENGTH; i++) { - if (conn->fragment_buffer[i]->size > 0) { - free(conn->fragment_buffer[i]->data); - conn->fragment_buffer[i]->data = NULL; - } - free(conn->fragment_buffer[i]); - } - close(conn->fddata); - close(conn->fdout); - return 0; -} - -bool is_control_packet(const ns_rx_packet_t *packet) { - return packet->type != MessageType_MESSAGE_TYPE_DATA; -} - -bool is_fragment_packet(const ns_rx_packet_t *packet) { - return packet->type == MessageType_MESSAGE_TYPE_FRAGMENT; -} - -bool ns_connected(const NSCONN *conn) { - if(conn->switch_type == SWITCH_TYPE_LOCAL) { - return true; - } - - if(conn->switch_type == SWITCH_TYPE_REMOTE) { - if(conn->client_state == CONNECTED) { - return true; - } - } - - return false; -} - -char* formatted_mac(uint8_t mac_addr[6]) -{ - char *mac_h = calloc(1, sizeof(char)* 32); - for(int i=0; i < 6; i++) { - char octet[4]; - snprintf(octet, sizeof(octet), "%02X%s", mac_addr[i], i < 5 ? ":" : ""); - strncat(mac_h, octet, sizeof(mac_h) - 1); - } - return mac_h; -} - -control_packet_info_t get_control_packet_info(const ns_rx_packet_t packet, const uint8_t *my_mac) -{ - control_packet_info_t packet_info; - - packet_info.src_mac_h[0] = '\0'; - packet_info.printable[0] = '\0'; - packet_info.client_id = packet.client_id; - memcpy(packet_info.src_mac, &packet.mac, 6); - packet_info.type = packet.type; - packet_info.is_packet_from_me = (memcmp(my_mac, packet_info.src_mac, sizeof(uint8_t) * 6) == 0); - char *formatted_mac_h = formatted_mac(packet_info.src_mac); - strncpy(packet_info.src_mac_h, formatted_mac_h, MAX_PRINTABLE_MAC); - free(formatted_mac_h); - snprintf(packet_info.printable, sizeof(packet_info.printable), "%s", ns_printable_message_type(packet_info.type)); - packet_info.timestamp = packet.timestamp; - - return packet_info; -} - -data_packet_info_t -get_data_packet_info(const netpkt_t *packet, const uint8_t *my_mac) { - data_packet_info_t packet_info; - - packet_info.src_mac_h[0] = '\0'; - packet_info.dest_mac_h[0] = '\0'; - packet_info.my_mac_h[0] = '\0'; - packet_info.printable[0] = '\0'; - - memcpy(packet_info.dest_mac,&packet->data[0], 6); - memcpy(packet_info.src_mac, &packet->data[6], 6); - - /* Broadcast and multicast are treated the same at L2 and both will have the - * least significant bit of 1 in the first transmitted byte. - * The below test matches 0xFF for standard broadcast along with 0x01 and 0x33 for multicast */ - packet_info.is_broadcast = ((packet->data[0] & 1) == 1); - packet_info.is_packet_for_me = (memcmp(my_mac, packet_info.dest_mac, sizeof(uint8_t) * 6) == 0); - packet_info.is_packet_from_me = (memcmp(my_mac, packet_info.src_mac, sizeof(uint8_t) * 6) == 0); - packet_info.is_data_packet = packet->len > 0; - packet_info.size = packet->len; - - /* Since this function is applied to every packet, only enable the pretty formatting below - * if logging is specifically enabled. */ -#ifdef ENABLE_NET_SWITCH_LOG - /* Pretty formatting for hardware addresses */ - for(int i=0; i < 6; i++) { - char octet[4]; - snprintf(octet, sizeof(octet), "%02X%s", packet_info.src_mac[i], i < 5 ? ":" : ""); - strncat(packet_info.src_mac_h, octet, sizeof (packet_info.src_mac_h) - 1); - - snprintf(octet, sizeof(octet), "%02X%s", packet_info.dest_mac[i], i < 5 ? ":" : ""); - strncat(packet_info.dest_mac_h, octet, sizeof (packet_info.dest_mac_h) - 1); - - snprintf(octet, sizeof(octet), "%02X%s", my_mac[i], i < 5 ? ":" : ""); - strncat(packet_info.my_mac_h, octet, sizeof (packet_info.my_mac_h) - 1); - } - - /* Printable output formatting */ - if(packet_info.is_broadcast) { - if(packet_info.is_packet_from_me) { - snprintf(packet_info.printable, sizeof(packet_info.printable), "(broadcast)"); - } else { - snprintf(packet_info.printable, sizeof(packet_info.printable), "%s (broadcast)", packet_info.src_mac_h); - } - } else { - snprintf(packet_info.printable, sizeof(packet_info.printable), "%s%s -> %s%s", packet_info.src_mac_h, packet_info.is_packet_from_me ? " (me)" : " ", - packet_info.dest_mac_h, packet_info.is_packet_for_me ? " (me)" : ""); - } -#endif - return packet_info; -} - -bool -fd_valid(const int fd) -{ -#ifdef _WIN32 - int error_code; - int error_code_size = sizeof(error_code); - getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *) &error_code, &error_code_size); - if (error_code == WSAENOTSOCK) { - return false; - } - return true; -#else - if (fcntl(fd, F_GETFD) == -1) { - return false; - } - /* All other values will be a valid fd */ - return true; -#endif -} - -bool -seq_increment(NSCONN *conn) -{ - if(conn == NULL) { - return false; - } - conn->sequence++; - if (conn->sequence == 0) { - conn->sequence = 1; - } - return true; -} diff --git a/src/network/netswitch.h b/src/network/netswitch.h deleted file mode 100644 index 69062b66c..000000000 --- a/src/network/netswitch.h +++ /dev/null @@ -1,295 +0,0 @@ -/* - * 86Box A hypervisor and IBM PC system emulator that specializes in - * running old operating systems and software designed for IBM - * PC systems and compatibles from 1981 through fairly recent - * system designs based on the PCI bus. - * - * This file is part of the 86Box distribution. - * - * Network Switch backend - * - * Authors: cold-brewed - * - * Copyright 2024 cold-brewed - */ -#ifndef NET_SWITCH_H -#define NET_SWITCH_H - -#ifdef _WIN32 -#include // before Windows.h, else Winsock 1 conflict -#include // needed for ip_mreq definition for multicast -#include -#else -#include -#include -#include -#include -#include -#endif -#include -#include "pb.h" -#include "networkmessage.pb.h" - -/* Local switch multicast port */ -#define NET_SWITCH_MULTICAST_PORT 8086 -/* Remote switch connect port */ -#define NET_SWITCH_REMOTE_PORT 8088 -/* Remove switch. This offset is where the local source ports will begin. */ -#define NET_SWITCH_RECV_PORT_OFFSET 198 -/* Multicast group (IP Address) maximum length. String representation. */ -#define MAX_MCAST_GROUP_LEN 32 -/* The buffer length used for both receiving on sockets and protobuf serialize / deserialize */ -#define NET_SWITCH_BUFFER_LENGTH 2048 - -/* Any frame above this size gets fragmented */ -#define MAX_FRAME_SEND_SIZE 1200 -/* Minimum fragment size we'll accept */ -#define MIN_FRAG_RECV_SIZE 12 -/* - Size of the fragment buffer - how many can we hold? - Note: FRAGMENT_BUFFER_LENGTH * MIN_FRAG_RECV_SIZE *must* be greater - than NET_MAX_FRAME or bad things will happen with large packets! -*/ -#define FRAGMENT_BUFFER_LENGTH 128 -/* Maximum number of switch groups */ -#define MAX_SWITCH_GROUP 31 -/* Size of a mac address in bytes. Used for the protobuf serializing / deserializing */ -#define PB_MAC_ADDR_SIZE 6 -/* This will define the version in use and the minimum required for communication */ -#define NS_PROTOCOL_VERSION 1 -/* Maximum string size for a printable (formatted) mac address */ -#define MAX_PRINTABLE_MAC 32 -/* Maximum hostname length for a remote switch host */ -#define MAX_HOSTNAME 128 - -typedef enum { - FLAGS_NONE = 0, - FLAGS_PROMISC = 1 << 0, -} ns_flags_t; - -typedef enum { - SWITCH_TYPE_LOCAL = 0, - SWITCH_TYPE_REMOTE, -} ns_type_t; - -typedef enum { - DISCONNECTED, - CONNECTING, - CONNECTED, - LOCAL, -} ns_client_state_t; - -struct ns_open_args { - uint8_t group; - ns_flags_t flags; - ns_type_t type; - char *client_id; - uint8_t mac_addr[6]; - char nrs_hostname[MAX_HOSTNAME]; -}; - -struct nsconn; - -typedef struct nsconn NSCONN; - -struct ns_stats { - size_t max_tx_frame; - size_t max_tx_packet; - size_t max_rx_frame; - size_t max_rx_packet; - uint8_t last_tx_ethertype[2]; - uint8_t last_rx_ethertype[2]; - u_long total_rx_packets; - u_long total_tx_packets; - u_long total_fragments; - uint8_t max_vec; -}; - -typedef struct { - /* The ID of the fragment. All fragments in a set should have the same ID. */ - uint32_t id; - /* The fragment index in the sequence of fragments. NOTE: one indexed, not zero! - * Example: the first fragment of three would be 1 in the sequence */ - uint32_t sequence; - /* Total number of fragments for the collection */ - uint32_t total; - /* The sequence number of the packet that delivered the fragment. Not the same as fragment sequence above! */ - uint32_t packet_sequence; - /* Frame data */ - char *data; - /* Frame size. A size of zero indicates an unused fragment slot and unallocated data field. */ - uint32_t size; - /* Epoch time (in ms) that the fragment is valid until */ - uint64_t ttl; -} ns_fragment_t; - -struct nsconn { - uint16_t flags; - int fdctl; - int fddata; - int fdout; - char mcast_group[MAX_MCAST_GROUP_LEN]; - struct sockaddr_in addr; - struct sockaddr_in outaddr; - size_t outlen; - struct sockaddr *sock; - struct sockaddr *outsock; - struct ns_stats stats; - uint32_t client_id; - uint8_t mac_addr[6]; - uint16_t sequence; - uint16_t remote_sequence; - uint8_t version; - uint8_t switch_type; - ns_client_state_t client_state; - int64_t last_packet_stamp; - /* Remote switch hostname */ - char nrs_hostname[MAX_HOSTNAME]; - /* Remote connect port for remote network switch */ - uint16_t remote_network_port; - /* Local multicast port for the local network switch */ - uint16_t local_multicast_port; - /* - * The source port to receive packets. Only applies to remote mode. - * This will also be the source port for sent packets in order to aid - * NAT - */ - uint16_t remote_source_port; - ns_fragment_t *fragment_buffer[FRAGMENT_BUFFER_LENGTH]; -}; - -typedef struct { - uint32_t id; - uint32_t history; -} ns_ack_t; - -typedef struct { - uint32_t id; - uint32_t sequence; - uint32_t total; -} ns_fragment_info_t; - -typedef struct { - netpkt_t pkt; - MessageType type; - uint32_t client_id; - uint8_t mac[6]; - uint32_t flags; - int64_t timestamp; - ns_ack_t ack; - ns_fragment_info_t fragment; - uint32_t version; -} ns_rx_packet_t; - -typedef struct { - size_t size; - char src_mac_h[MAX_PRINTABLE_MAC]; - char dest_mac_h[MAX_PRINTABLE_MAC]; - char my_mac_h[MAX_PRINTABLE_MAC]; - uint8_t src_mac[6]; - uint8_t dest_mac[6]; - bool is_packet_from_me; - bool is_broadcast; - bool is_packet_for_me; - bool is_data_packet; - char printable[128]; -} data_packet_info_t; - -typedef struct { - uint8_t src_mac[6]; - char src_mac_h[MAX_PRINTABLE_MAC]; - bool is_packet_from_me; - MessageType type; - char printable[128]; - uint64_t client_id; - int64_t timestamp; -} control_packet_info_t; - -/* Initializes and opens the Net Multicast Switch */ -NSCONN *ns_open(struct ns_open_args *open_args); - -/* Returns the flags */ -int ns_flags(const NSCONN *conn); - -/* Returns the file descriptor for polling */ -int ns_pollfd(const NSCONN *conn); - -/* This should be used to receive serialized protobuf packets - * and have the output placed in the packet struct */ -bool ns_recv_pb(NSCONN *conn, ns_rx_packet_t *packet,size_t len,int flags); - -/* Do not call directly! Used internally */ -ssize_t ns_sock_recv(const NSCONN *conn,void *buf,size_t len,int flags); - -/* This should be used to send serialized protobuf packets -* and have the output placed in the packet struct */ -ssize_t ns_send_pb(NSCONN *conn, const netpkt_t *packet,int flags); - -/* Send control messages */ -bool ns_send_control(NSCONN *conn, MessageType type); - -const char* ns_printable_message_type(MessageType type); - -/* Do not call directly! Used internally */ -ssize_t ns_sock_send(NSCONN *conn,const void *buf,size_t len,int flags); - -uint32_t ns_gen_client_id(void); - -/* Closes and cleans up */ -int ns_close(NSCONN *conn); - -/* Return current time in milliseconds */ -int64_t ns_get_current_millis(void); - -/* Is the packet a control packet? - * Any type other than DATA is a control packet, including fragments */ -bool is_control_packet(const ns_rx_packet_t *packet); - -/* Logic for handling control packets */ -bool process_control_packet(NSCONN *conn, const ns_rx_packet_t *packet); - -/* Is the packet a fragment packet? */ -bool is_fragment_packet(const ns_rx_packet_t *packet); - -/* Store a fragment in the fragment buffer */ -bool store_fragment(const NSCONN *conn, const NetworkMessage *network_message); - -/* Reassemble a fragment from the fragment buffer */ -bool reassemble_fragment(const NSCONN *conn, netpkt_t *pkt, uint32_t packet_count); - -/* Set up the socket. Accounts for the differences between local and remote modes */ -bool ns_socket_setup(NSCONN *conn); - -/* Is the switch in a connected state? Always returns true in local mode */ -bool ns_connected(const NSCONN *conn); - -/* Return a string with a properly formatted mac address. - * Note: Caller must free! */ -char* formatted_mac(uint8_t mac_addr[6]); - -/* Used for control packet info and logic */ -control_packet_info_t get_control_packet_info(ns_rx_packet_t packet, const uint8_t *my_mac); - -/* Used for data packet info and logic */ -data_packet_info_t get_data_packet_info(const netpkt_t *packet, const uint8_t *my_mac); - -/* Checks for a valid file descriptor */ -bool fd_valid(int fd); - -/* Wrapping increment for the sequence number */ -bool seq_increment(NSCONN *conn); - -#ifdef ENABLE_NET_SWITCH_LOG -static void -net_switch_log(const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - pclog_ex(fmt, ap); - va_end(ap); -} -#else -# define net_switch_log(fmt, ...) -#endif - -#endif \ No newline at end of file diff --git a/src/network/network.c b/src/network/network.c index 8680d6c64..1a23c4280 100644 --- a/src/network/network.c +++ b/src/network/network.c @@ -499,13 +499,11 @@ network_attach(void *card_drv, uint8_t *mac, NETRXCB rx, NETSETLINKSTATE set_lin card->host_drv.priv = card->host_drv.init(card, mac, net_cards_conf[net_card_current].host_dev_name, net_drv_error); break; #endif -#ifdef USE_NETSWITCH - case NET_TYPE_NMSWITCH: + case NET_TYPE_NLSWITCH: case NET_TYPE_NRSWITCH: - card->host_drv = net_netswitch_drv; + card->host_drv = net_switch_drv; card->host_drv.priv = card->host_drv.init(card, mac, &net_cards_conf[net_card_current], net_drv_error); break; -#endif /* USE_NETSWITCH */ default: card->host_drv.priv = NULL; break; @@ -517,14 +515,6 @@ network_attach(void *card_drv, uint8_t *mac, NETRXCB rx, NETSETLINKSTATE set_lin if (!card->host_drv.priv) { if(net_cards_conf[net_card_current].net_type != NET_TYPE_NONE) { -#ifdef USE_NETSWITCH - // FIXME: Hardcoded during dev - // FIXME: Remove when done! - if((net_cards_conf[net_card_current].net_type == NET_TYPE_NMSWITCH) || - (net_cards_conf[net_card_current].net_type == NET_TYPE_NRSWITCH)) - fatal("%s", net_drv_error); -#endif /* USE_NETSWITCH */ - // We're here because of a failure swprintf(tempmsg, sizeof_w(tempmsg), L"%ls:\n\n%s\n\n%ls", plat_get_string(STRING_NET_ERROR), net_drv_error, plat_get_string(STRING_NET_ERROR_DESC)); ui_msgbox(MBX_ERROR, tempmsg); diff --git a/src/network/networkmessage.pb.c b/src/network/networkmessage.pb.c deleted file mode 100644 index c15f03e01..000000000 --- a/src/network/networkmessage.pb.c +++ /dev/null @@ -1,19 +0,0 @@ -/* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8-dev */ - -#include "networkmessage.pb.h" -#if PB_PROTO_HEADER_VERSION != 40 -#error Regenerate this file with the current version of nanopb generator. -#endif - -PB_BIND(Fragment, Fragment, AUTO) - - -PB_BIND(Ack, Ack, AUTO) - - -PB_BIND(NetworkMessage, NetworkMessage, AUTO) - - - - diff --git a/src/network/networkmessage.pb.h b/src/network/networkmessage.pb.h deleted file mode 100644 index 0a74e55b9..000000000 --- a/src/network/networkmessage.pb.h +++ /dev/null @@ -1,140 +0,0 @@ -/* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8-dev */ - -#ifndef PB_NETWORKMESSAGE_PB_H_INCLUDED -#define PB_NETWORKMESSAGE_PB_H_INCLUDED -#include "pb.h" - -#if PB_PROTO_HEADER_VERSION != 40 -#error Regenerate this file with the current version of nanopb generator. -#endif - -/* Enum definitions */ -typedef enum _MessageType { - MessageType_MESSAGE_TYPE_UNSPECIFIED = 0, - MessageType_MESSAGE_TYPE_DATA = 1, - MessageType_MESSAGE_TYPE_JOIN = 2, - MessageType_MESSAGE_TYPE_LEAVE = 3, - MessageType_MESSAGE_TYPE_KEEPALIVE = 4, - MessageType_MESSAGE_TYPE_FRAGMENT = 5, - MessageType_MESSAGE_TYPE_ACK = 6, - MessageType_MESSAGE_TYPE_CONNECT_REQUEST = 7, - MessageType_MESSAGE_TYPE_CONNECT_REPLY = 8 -} MessageType; - -/* Struct definitions */ -typedef struct _Fragment { - uint32_t id; - uint32_t sequence; - uint32_t total; -} Fragment; - -typedef struct _Ack { - uint32_t id; - uint32_t history; -} Ack; - -typedef struct _NetworkMessage { - MessageType message_type; - uint32_t client_id; - pb_bytes_array_t *mac; - pb_bytes_array_t *frame; - uint32_t flags; - uint32_t version; - bool has_ack; - Ack ack; - bool has_fragment; - Fragment fragment; - int64_t timestamp; - uint32_t sequence; -} NetworkMessage; - - -#ifdef __cplusplus -extern "C" { -#endif - -/* Helper constants for enums */ -#define _MessageType_MIN MessageType_MESSAGE_TYPE_UNSPECIFIED -#define _MessageType_MAX MessageType_MESSAGE_TYPE_CONNECT_REPLY -#define _MessageType_ARRAYSIZE ((MessageType)(MessageType_MESSAGE_TYPE_CONNECT_REPLY+1)) - - - -#define NetworkMessage_message_type_ENUMTYPE MessageType - - -/* Initializer values for message structs */ -#define Fragment_init_default {0, 0, 0} -#define Ack_init_default {0, 0} -#define NetworkMessage_init_default {_MessageType_MIN, 0, NULL, NULL, 0, 0, false, Ack_init_default, false, Fragment_init_default, 0, 0} -#define Fragment_init_zero {0, 0, 0} -#define Ack_init_zero {0, 0} -#define NetworkMessage_init_zero {_MessageType_MIN, 0, NULL, NULL, 0, 0, false, Ack_init_zero, false, Fragment_init_zero, 0, 0} - -/* Field tags (for use in manual encoding/decoding) */ -#define Fragment_id_tag 1 -#define Fragment_sequence_tag 2 -#define Fragment_total_tag 3 -#define Ack_id_tag 1 -#define Ack_history_tag 2 -#define NetworkMessage_message_type_tag 1 -#define NetworkMessage_client_id_tag 2 -#define NetworkMessage_mac_tag 3 -#define NetworkMessage_frame_tag 4 -#define NetworkMessage_flags_tag 5 -#define NetworkMessage_version_tag 6 -#define NetworkMessage_ack_tag 7 -#define NetworkMessage_fragment_tag 8 -#define NetworkMessage_timestamp_tag 9 -#define NetworkMessage_sequence_tag 10 - -/* Struct field encoding specification for nanopb */ -#define Fragment_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UINT32, id, 1) \ -X(a, STATIC, SINGULAR, UINT32, sequence, 2) \ -X(a, STATIC, SINGULAR, UINT32, total, 3) -#define Fragment_CALLBACK NULL -#define Fragment_DEFAULT NULL - -#define Ack_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UINT32, id, 1) \ -X(a, STATIC, SINGULAR, UINT32, history, 2) -#define Ack_CALLBACK NULL -#define Ack_DEFAULT NULL - -#define NetworkMessage_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UENUM, message_type, 1) \ -X(a, STATIC, SINGULAR, UINT32, client_id, 2) \ -X(a, POINTER, SINGULAR, BYTES, mac, 3) \ -X(a, POINTER, SINGULAR, BYTES, frame, 4) \ -X(a, STATIC, SINGULAR, UINT32, flags, 5) \ -X(a, STATIC, SINGULAR, UINT32, version, 6) \ -X(a, STATIC, OPTIONAL, MESSAGE, ack, 7) \ -X(a, STATIC, OPTIONAL, MESSAGE, fragment, 8) \ -X(a, STATIC, SINGULAR, INT64, timestamp, 9) \ -X(a, STATIC, SINGULAR, UINT32, sequence, 10) -#define NetworkMessage_CALLBACK NULL -#define NetworkMessage_DEFAULT NULL -#define NetworkMessage_ack_MSGTYPE Ack -#define NetworkMessage_fragment_MSGTYPE Fragment - -extern const pb_msgdesc_t Fragment_msg; -extern const pb_msgdesc_t Ack_msg; -extern const pb_msgdesc_t NetworkMessage_msg; - -/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ -#define Fragment_fields &Fragment_msg -#define Ack_fields &Ack_msg -#define NetworkMessage_fields &NetworkMessage_msg - -/* Maximum encoded size of messages (where known) */ -/* NetworkMessage_size depends on runtime parameters */ -#define Ack_size 12 -#define Fragment_size 18 - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif diff --git a/src/network/networkmessage.proto.txt b/src/network/networkmessage.proto.txt deleted file mode 100644 index 9511f17c8..000000000 --- a/src/network/networkmessage.proto.txt +++ /dev/null @@ -1,38 +0,0 @@ -syntax = "proto3"; -import "nanopb.proto"; - -enum MessageType { - MESSAGE_TYPE_UNSPECIFIED = 0; - MESSAGE_TYPE_DATA = 1; - MESSAGE_TYPE_JOIN = 2; - MESSAGE_TYPE_LEAVE = 3; - MESSAGE_TYPE_KEEPALIVE = 4; - MESSAGE_TYPE_FRAGMENT = 5; - MESSAGE_TYPE_ACK = 6; - MESSAGE_TYPE_CONNECT_REQUEST = 7; - MESSAGE_TYPE_CONNECT_REPLY = 8; -} - -message Fragment { - uint32 id = 1; - uint32 sequence = 2; - uint32 total = 3; -} - -message Ack { - uint32 id = 1; - uint32 history = 2; -} - -message NetworkMessage { - MessageType message_type = 1; - uint32 client_id = 2; - bytes mac = 3 [(nanopb).type = FT_POINTER]; - bytes frame = 4 [(nanopb).type = FT_POINTER]; - uint32 flags = 5; - uint32 version = 6; - Ack ack = 7; - Fragment fragment = 8; - int64 timestamp = 9; - uint32 sequence = 10; -} \ No newline at end of file diff --git a/src/network/pb.h b/src/network/pb.h deleted file mode 100644 index 7433ac24b..000000000 --- a/src/network/pb.h +++ /dev/null @@ -1,917 +0,0 @@ -/* Common parts of the nanopb library. Most of these are quite low-level - * stuff. For the high-level interface, see pb_encode.h and pb_decode.h. - */ - -#ifndef PB_H_INCLUDED -#define PB_H_INCLUDED - -/***************************************************************** - * Nanopb compilation time options. You can change these here by * - * uncommenting the lines, or on the compiler command line. * - *****************************************************************/ - -/* Enable support for dynamically allocated fields */ -#define PB_ENABLE_MALLOC 1 - -/* Define this if your CPU / compiler combination does not support - * unaligned memory access to packed structures. Note that packed - * structures are only used when requested in .proto options. */ -/* #define PB_NO_PACKED_STRUCTS 1 */ - -/* Increase the number of required fields that are tracked. - * A compiler warning will tell if you need this. */ -/* #define PB_MAX_REQUIRED_FIELDS 256 */ - -/* Add support for tag numbers > 65536 and fields larger than 65536 bytes. */ -/* #define PB_FIELD_32BIT 1 */ - -/* Disable support for error messages in order to save some code space. */ -/* #define PB_NO_ERRMSG 1 */ - -/* Disable support for custom streams (support only memory buffers). */ -#define PB_BUFFER_ONLY 1 - -/* Disable support for 64-bit datatypes, for compilers without int64_t - or to save some code space. */ -/* #define PB_WITHOUT_64BIT 1 */ - -/* Don't encode scalar arrays as packed. This is only to be used when - * the decoder on the receiving side cannot process packed scalar arrays. - * Such example is older protobuf.js. */ -/* #define PB_ENCODE_ARRAYS_UNPACKED 1 */ - -/* Enable conversion of doubles to floats for platforms that do not - * support 64-bit doubles. Most commonly AVR. */ -/* #define PB_CONVERT_DOUBLE_FLOAT 1 */ - -/* Check whether incoming strings are valid UTF-8 sequences. Slows down - * the string processing slightly and slightly increases code size. */ -/* #define PB_VALIDATE_UTF8 1 */ - -/* This can be defined if the platform is little-endian and has 8-bit bytes. - * Normally it is automatically detected based on __BYTE_ORDER__ macro. */ -/* #define PB_LITTLE_ENDIAN_8BIT 1 */ - -/* Configure static assert mechanism. Instead of changing these, set your - * compiler to C11 standard mode if possible. */ -/* #define PB_C99_STATIC_ASSERT 1 */ -/* #define PB_NO_STATIC_ASSERT 1 */ - -/****************************************************************** - * You usually don't need to change anything below this line. * - * Feel free to look around and use the defined macros, though. * - ******************************************************************/ - - -/* Version of the nanopb library. Just in case you want to check it in - * your own program. */ -#define NANOPB_VERSION "nanopb-0.4.8-dev" - -/* Include all the system headers needed by nanopb. You will need the - * definitions of the following: - * - strlen, memcpy, memset functions - * - [u]int_least8_t, uint_fast8_t, [u]int_least16_t, [u]int32_t, [u]int64_t - * - size_t - * - bool - * - * If you don't have the standard header files, you can instead provide - * a custom header that defines or includes all this. In that case, - * define PB_SYSTEM_HEADER to the path of this file. - */ -#ifdef PB_SYSTEM_HEADER -#include PB_SYSTEM_HEADER -#else -#include -#include -#include -#include -#include - -#ifdef PB_ENABLE_MALLOC -#include -#endif -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/* Macro for defining packed structures (compiler dependent). - * This just reduces memory requirements, but is not required. - */ -#if defined(PB_NO_PACKED_STRUCTS) - /* Disable struct packing */ -# define PB_PACKED_STRUCT_START -# define PB_PACKED_STRUCT_END -# define pb_packed -#elif defined(__GNUC__) || defined(__clang__) - /* For GCC and clang */ -# define PB_PACKED_STRUCT_START -# define PB_PACKED_STRUCT_END -# define pb_packed __attribute__((packed)) -#elif defined(__ICCARM__) || defined(__CC_ARM) - /* For IAR ARM and Keil MDK-ARM compilers */ -# define PB_PACKED_STRUCT_START _Pragma("pack(push, 1)") -# define PB_PACKED_STRUCT_END _Pragma("pack(pop)") -# define pb_packed -#elif defined(_MSC_VER) && (_MSC_VER >= 1500) - /* For Microsoft Visual C++ */ -# define PB_PACKED_STRUCT_START __pragma(pack(push, 1)) -# define PB_PACKED_STRUCT_END __pragma(pack(pop)) -# define pb_packed -#else - /* Unknown compiler */ -# define PB_PACKED_STRUCT_START -# define PB_PACKED_STRUCT_END -# define pb_packed -#endif - -/* Detect endianness */ -#ifndef PB_LITTLE_ENDIAN_8BIT -#if ((defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN) || \ - (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || \ - defined(__LITTLE_ENDIAN__) || defined(__ARMEL__) || \ - defined(__THUMBEL__) || defined(__AARCH64EL__) || defined(_MIPSEL) || \ - defined(_M_IX86) || defined(_M_X64) || defined(_M_ARM)) \ - && CHAR_BIT == 8 -#define PB_LITTLE_ENDIAN_8BIT 1 -#endif -#endif - -/* Handly macro for suppressing unreferenced-parameter compiler warnings. */ -#ifndef PB_UNUSED -#define PB_UNUSED(x) (void)(x) -#endif - -/* Harvard-architecture processors may need special attributes for storing - * field information in program memory. */ -#ifndef PB_PROGMEM -#ifdef __AVR__ -#include -#define PB_PROGMEM PROGMEM -#define PB_PROGMEM_READU32(x) pgm_read_dword(&x) -#else -#define PB_PROGMEM -#define PB_PROGMEM_READU32(x) (x) -#endif -#endif - -/* Compile-time assertion, used for checking compatible compilation options. - * If this does not work properly on your compiler, use - * #define PB_NO_STATIC_ASSERT to disable it. - * - * But before doing that, check carefully the error message / place where it - * comes from to see if the error has a real cause. Unfortunately the error - * message is not always very clear to read, but you can see the reason better - * in the place where the PB_STATIC_ASSERT macro was called. - */ -#ifndef PB_NO_STATIC_ASSERT -# ifndef PB_STATIC_ASSERT -# if defined(__ICCARM__) - /* IAR has static_assert keyword but no _Static_assert */ -# define PB_STATIC_ASSERT(COND,MSG) static_assert(COND,#MSG); -# elif defined(_MSC_VER) && (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 201112) - /* MSVC in C89 mode supports static_assert() keyword anyway */ -# define PB_STATIC_ASSERT(COND,MSG) static_assert(COND,#MSG); -# elif defined(PB_C99_STATIC_ASSERT) - /* Classic negative-size-array static assert mechanism */ -# define PB_STATIC_ASSERT(COND,MSG) typedef char PB_STATIC_ASSERT_MSG(MSG, __LINE__, __COUNTER__)[(COND)?1:-1]; -# define PB_STATIC_ASSERT_MSG(MSG, LINE, COUNTER) PB_STATIC_ASSERT_MSG_(MSG, LINE, COUNTER) -# define PB_STATIC_ASSERT_MSG_(MSG, LINE, COUNTER) pb_static_assertion_##MSG##_##LINE##_##COUNTER -# elif defined(__cplusplus) - /* C++11 standard static_assert mechanism */ -# define PB_STATIC_ASSERT(COND,MSG) static_assert(COND,#MSG); -# else - /* C11 standard _Static_assert mechanism */ -# define PB_STATIC_ASSERT(COND,MSG) _Static_assert(COND,#MSG); -# endif -# endif -#else - /* Static asserts disabled by PB_NO_STATIC_ASSERT */ -# define PB_STATIC_ASSERT(COND,MSG) -#endif - -/* Test that PB_STATIC_ASSERT works - * If you get errors here, you may need to do one of these: - * - Enable C11 standard support in your compiler - * - Define PB_C99_STATIC_ASSERT to enable C99 standard support - * - Define PB_NO_STATIC_ASSERT to disable static asserts altogether - */ -PB_STATIC_ASSERT(1, STATIC_ASSERT_IS_NOT_WORKING) - -/* Number of required fields to keep track of. */ -#ifndef PB_MAX_REQUIRED_FIELDS -#define PB_MAX_REQUIRED_FIELDS 64 -#endif - -#if PB_MAX_REQUIRED_FIELDS < 64 -#error You should not lower PB_MAX_REQUIRED_FIELDS from the default value (64). -#endif - -#ifdef PB_WITHOUT_64BIT -#ifdef PB_CONVERT_DOUBLE_FLOAT -/* Cannot use doubles without 64-bit types */ -#undef PB_CONVERT_DOUBLE_FLOAT -#endif -#endif - -/* List of possible field types. These are used in the autogenerated code. - * Least-significant 4 bits tell the scalar type - * Most-significant 4 bits specify repeated/required/packed etc. - */ - -typedef uint_least8_t pb_type_t; - -/**** Field data types ****/ - -/* Numeric types */ -#define PB_LTYPE_BOOL 0x00U /* bool */ -#define PB_LTYPE_VARINT 0x01U /* int32, int64, enum, bool */ -#define PB_LTYPE_UVARINT 0x02U /* uint32, uint64 */ -#define PB_LTYPE_SVARINT 0x03U /* sint32, sint64 */ -#define PB_LTYPE_FIXED32 0x04U /* fixed32, sfixed32, float */ -#define PB_LTYPE_FIXED64 0x05U /* fixed64, sfixed64, double */ - -/* Marker for last packable field type. */ -#define PB_LTYPE_LAST_PACKABLE 0x05U - -/* Byte array with pre-allocated buffer. - * data_size is the length of the allocated PB_BYTES_ARRAY structure. */ -#define PB_LTYPE_BYTES 0x06U - -/* String with pre-allocated buffer. - * data_size is the maximum length. */ -#define PB_LTYPE_STRING 0x07U - -/* Submessage - * submsg_fields is pointer to field descriptions */ -#define PB_LTYPE_SUBMESSAGE 0x08U - -/* Submessage with pre-decoding callback - * The pre-decoding callback is stored as pb_callback_t right before pSize. - * submsg_fields is pointer to field descriptions */ -#define PB_LTYPE_SUBMSG_W_CB 0x09U - -/* Extension pseudo-field - * The field contains a pointer to pb_extension_t */ -#define PB_LTYPE_EXTENSION 0x0AU - -/* Byte array with inline, pre-allocated byffer. - * data_size is the length of the inline, allocated buffer. - * This differs from PB_LTYPE_BYTES by defining the element as - * pb_byte_t[data_size] rather than pb_bytes_array_t. */ -#define PB_LTYPE_FIXED_LENGTH_BYTES 0x0BU - -/* Number of declared LTYPES */ -#define PB_LTYPES_COUNT 0x0CU -#define PB_LTYPE_MASK 0x0FU - -/**** Field repetition rules ****/ - -#define PB_HTYPE_REQUIRED 0x00U -#define PB_HTYPE_OPTIONAL 0x10U -#define PB_HTYPE_SINGULAR 0x10U -#define PB_HTYPE_REPEATED 0x20U -#define PB_HTYPE_FIXARRAY 0x20U -#define PB_HTYPE_ONEOF 0x30U -#define PB_HTYPE_MASK 0x30U - -/**** Field allocation types ****/ - -#define PB_ATYPE_STATIC 0x00U -#define PB_ATYPE_POINTER 0x80U -#define PB_ATYPE_CALLBACK 0x40U -#define PB_ATYPE_MASK 0xC0U - -#define PB_ATYPE(x) ((x) & PB_ATYPE_MASK) -#define PB_HTYPE(x) ((x) & PB_HTYPE_MASK) -#define PB_LTYPE(x) ((x) & PB_LTYPE_MASK) -#define PB_LTYPE_IS_SUBMSG(x) (PB_LTYPE(x) == PB_LTYPE_SUBMESSAGE || \ - PB_LTYPE(x) == PB_LTYPE_SUBMSG_W_CB) - -/* Data type used for storing sizes of struct fields - * and array counts. - */ -#if defined(PB_FIELD_32BIT) - typedef uint32_t pb_size_t; - typedef int32_t pb_ssize_t; -#else - typedef uint_least16_t pb_size_t; - typedef int_least16_t pb_ssize_t; -#endif -#define PB_SIZE_MAX ((pb_size_t)-1) - -/* Data type for storing encoded data and other byte streams. - * This typedef exists to support platforms where uint8_t does not exist. - * You can regard it as equivalent on uint8_t on other platforms. - */ -typedef uint_least8_t pb_byte_t; - -/* Forward declaration of struct types */ -typedef struct pb_istream_s pb_istream_t; -typedef struct pb_ostream_s pb_ostream_t; -typedef struct pb_field_iter_s pb_field_iter_t; - -/* This structure is used in auto-generated constants - * to specify struct fields. - */ -typedef struct pb_msgdesc_s pb_msgdesc_t; -struct pb_msgdesc_s { - const uint32_t *field_info; - const pb_msgdesc_t * const * submsg_info; - const pb_byte_t *default_value; - - bool (*field_callback)(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_iter_t *field); - - pb_size_t field_count; - pb_size_t required_field_count; - pb_size_t largest_tag; -}; - -/* Iterator for message descriptor */ -struct pb_field_iter_s { - const pb_msgdesc_t *descriptor; /* Pointer to message descriptor constant */ - void *message; /* Pointer to start of the structure */ - - pb_size_t index; /* Index of the field */ - pb_size_t field_info_index; /* Index to descriptor->field_info array */ - pb_size_t required_field_index; /* Index that counts only the required fields */ - pb_size_t submessage_index; /* Index that counts only submessages */ - - pb_size_t tag; /* Tag of current field */ - pb_size_t data_size; /* sizeof() of a single item */ - pb_size_t array_size; /* Number of array entries */ - pb_type_t type; /* Type of current field */ - - void *pField; /* Pointer to current field in struct */ - void *pData; /* Pointer to current data contents. Different than pField for arrays and pointers. */ - void *pSize; /* Pointer to count/has field */ - - const pb_msgdesc_t *submsg_desc; /* For submessage fields, pointer to field descriptor for the submessage. */ -}; - -/* For compatibility with legacy code */ -typedef pb_field_iter_t pb_field_t; - -/* Make sure that the standard integer types are of the expected sizes. - * Otherwise fixed32/fixed64 fields can break. - * - * If you get errors here, it probably means that your stdint.h is not - * correct for your platform. - */ -#ifndef PB_WITHOUT_64BIT -PB_STATIC_ASSERT(sizeof(int64_t) == 2 * sizeof(int32_t), INT64_T_WRONG_SIZE) -PB_STATIC_ASSERT(sizeof(uint64_t) == 2 * sizeof(uint32_t), UINT64_T_WRONG_SIZE) -#endif - -/* This structure is used for 'bytes' arrays. - * It has the number of bytes in the beginning, and after that an array. - * Note that actual structs used will have a different length of bytes array. - */ -#define PB_BYTES_ARRAY_T(n) struct { pb_size_t size; pb_byte_t bytes[n]; } -#define PB_BYTES_ARRAY_T_ALLOCSIZE(n) ((size_t)n + offsetof(pb_bytes_array_t, bytes)) - -struct pb_bytes_array_s { - pb_size_t size; - pb_byte_t bytes[1]; -}; -typedef struct pb_bytes_array_s pb_bytes_array_t; - -/* This structure is used for giving the callback function. - * It is stored in the message structure and filled in by the method that - * calls pb_decode. - * - * The decoding callback will be given a limited-length stream - * If the wire type was string, the length is the length of the string. - * If the wire type was a varint/fixed32/fixed64, the length is the length - * of the actual value. - * The function may be called multiple times (especially for repeated types, - * but also otherwise if the message happens to contain the field multiple - * times.) - * - * The encoding callback will receive the actual output stream. - * It should write all the data in one call, including the field tag and - * wire type. It can write multiple fields. - * - * The callback can be null if you want to skip a field. - */ -typedef struct pb_callback_s pb_callback_t; -struct pb_callback_s { - /* Callback functions receive a pointer to the arg field. - * You can access the value of the field as *arg, and modify it if needed. - */ - union { - bool (*decode)(pb_istream_t *stream, const pb_field_t *field, void **arg); - bool (*encode)(pb_ostream_t *stream, const pb_field_t *field, void * const *arg); - } funcs; - - /* Free arg for use by callback */ - void *arg; -}; - -extern bool pb_default_field_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_t *field); - -/* Wire types. Library user needs these only in encoder callbacks. */ -typedef enum { - PB_WT_VARINT = 0, - PB_WT_64BIT = 1, - PB_WT_STRING = 2, - PB_WT_32BIT = 5, - PB_WT_PACKED = 255 /* PB_WT_PACKED is internal marker for packed arrays. */ -} pb_wire_type_t; - -/* Structure for defining the handling of unknown/extension fields. - * Usually the pb_extension_type_t structure is automatically generated, - * while the pb_extension_t structure is created by the user. However, - * if you want to catch all unknown fields, you can also create a custom - * pb_extension_type_t with your own callback. - */ -typedef struct pb_extension_type_s pb_extension_type_t; -typedef struct pb_extension_s pb_extension_t; -struct pb_extension_type_s { - /* Called for each unknown field in the message. - * If you handle the field, read off all of its data and return true. - * If you do not handle the field, do not read anything and return true. - * If you run into an error, return false. - * Set to NULL for default handler. - */ - bool (*decode)(pb_istream_t *stream, pb_extension_t *extension, - uint32_t tag, pb_wire_type_t wire_type); - - /* Called once after all regular fields have been encoded. - * If you have something to write, do so and return true. - * If you do not have anything to write, just return true. - * If you run into an error, return false. - * Set to NULL for default handler. - */ - bool (*encode)(pb_ostream_t *stream, const pb_extension_t *extension); - - /* Free field for use by the callback. */ - const void *arg; -}; - -struct pb_extension_s { - /* Type describing the extension field. Usually you'll initialize - * this to a pointer to the automatically generated structure. */ - const pb_extension_type_t *type; - - /* Destination for the decoded data. This must match the datatype - * of the extension field. */ - void *dest; - - /* Pointer to the next extension handler, or NULL. - * If this extension does not match a field, the next handler is - * automatically called. */ - pb_extension_t *next; - - /* The decoder sets this to true if the extension was found. - * Ignored for encoding. */ - bool found; -}; - -#define pb_extension_init_zero {NULL,NULL,NULL,false} - -/* Memory allocation functions to use. You can define pb_realloc and - * pb_free to custom functions if you want. */ -#ifdef PB_ENABLE_MALLOC -# ifndef pb_realloc -# define pb_realloc(ptr, size) realloc(ptr, size) -# endif -# ifndef pb_free -# define pb_free(ptr) free(ptr) -# endif -#endif - -/* This is used to inform about need to regenerate .pb.h/.pb.c files. */ -#define PB_PROTO_HEADER_VERSION 40 - -/* These macros are used to declare pb_field_t's in the constant array. */ -/* Size of a structure member, in bytes. */ -#define pb_membersize(st, m) (sizeof ((st*)0)->m) -/* Number of entries in an array. */ -#define pb_arraysize(st, m) (pb_membersize(st, m) / pb_membersize(st, m[0])) -/* Delta from start of one member to the start of another member. */ -#define pb_delta(st, m1, m2) ((int)offsetof(st, m1) - (int)offsetof(st, m2)) - -/* Force expansion of macro value */ -#define PB_EXPAND(x) x - -/* Binding of a message field set into a specific structure */ -#define PB_BIND(msgname, structname, width) \ - const uint32_t structname ## _field_info[] PB_PROGMEM = \ - { \ - msgname ## _FIELDLIST(PB_GEN_FIELD_INFO_ ## width, structname) \ - 0 \ - }; \ - const pb_msgdesc_t* const structname ## _submsg_info[] = \ - { \ - msgname ## _FIELDLIST(PB_GEN_SUBMSG_INFO, structname) \ - NULL \ - }; \ - const pb_msgdesc_t structname ## _msg = \ - { \ - structname ## _field_info, \ - structname ## _submsg_info, \ - msgname ## _DEFAULT, \ - msgname ## _CALLBACK, \ - 0 msgname ## _FIELDLIST(PB_GEN_FIELD_COUNT, structname), \ - 0 msgname ## _FIELDLIST(PB_GEN_REQ_FIELD_COUNT, structname), \ - 0 msgname ## _FIELDLIST(PB_GEN_LARGEST_TAG, structname), \ - }; \ - msgname ## _FIELDLIST(PB_GEN_FIELD_INFO_ASSERT_ ## width, structname) - -#define PB_GEN_FIELD_COUNT(structname, atype, htype, ltype, fieldname, tag) +1 -#define PB_GEN_REQ_FIELD_COUNT(structname, atype, htype, ltype, fieldname, tag) \ - + (PB_HTYPE_ ## htype == PB_HTYPE_REQUIRED) -#define PB_GEN_LARGEST_TAG(structname, atype, htype, ltype, fieldname, tag) \ - * 0 + tag - -/* X-macro for generating the entries in struct_field_info[] array. */ -#define PB_GEN_FIELD_INFO_1(structname, atype, htype, ltype, fieldname, tag) \ - PB_FIELDINFO_1(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ - PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) - -#define PB_GEN_FIELD_INFO_2(structname, atype, htype, ltype, fieldname, tag) \ - PB_FIELDINFO_2(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ - PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) - -#define PB_GEN_FIELD_INFO_4(structname, atype, htype, ltype, fieldname, tag) \ - PB_FIELDINFO_4(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ - PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) - -#define PB_GEN_FIELD_INFO_8(structname, atype, htype, ltype, fieldname, tag) \ - PB_FIELDINFO_8(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ - PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) - -#define PB_GEN_FIELD_INFO_AUTO(structname, atype, htype, ltype, fieldname, tag) \ - PB_FIELDINFO_AUTO2(PB_FIELDINFO_WIDTH_AUTO(_PB_ATYPE_ ## atype, _PB_HTYPE_ ## htype, _PB_LTYPE_ ## ltype), \ - tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ - PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) - -#define PB_FIELDINFO_AUTO2(width, tag, type, data_offset, data_size, size_offset, array_size) \ - PB_FIELDINFO_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size) - -#define PB_FIELDINFO_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size) \ - PB_FIELDINFO_ ## width(tag, type, data_offset, data_size, size_offset, array_size) - -/* X-macro for generating asserts that entries fit in struct_field_info[] array. - * The structure of macros here must match the structure above in PB_GEN_FIELD_INFO_x(), - * but it is not easily reused because of how macro substitutions work. */ -#define PB_GEN_FIELD_INFO_ASSERT_1(structname, atype, htype, ltype, fieldname, tag) \ - PB_FIELDINFO_ASSERT_1(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ - PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) - -#define PB_GEN_FIELD_INFO_ASSERT_2(structname, atype, htype, ltype, fieldname, tag) \ - PB_FIELDINFO_ASSERT_2(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ - PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) - -#define PB_GEN_FIELD_INFO_ASSERT_4(structname, atype, htype, ltype, fieldname, tag) \ - PB_FIELDINFO_ASSERT_4(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ - PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) - -#define PB_GEN_FIELD_INFO_ASSERT_8(structname, atype, htype, ltype, fieldname, tag) \ - PB_FIELDINFO_ASSERT_8(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ - PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) - -#define PB_GEN_FIELD_INFO_ASSERT_AUTO(structname, atype, htype, ltype, fieldname, tag) \ - PB_FIELDINFO_ASSERT_AUTO2(PB_FIELDINFO_WIDTH_AUTO(_PB_ATYPE_ ## atype, _PB_HTYPE_ ## htype, _PB_LTYPE_ ## ltype), \ - tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ - PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ - PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) - -#define PB_FIELDINFO_ASSERT_AUTO2(width, tag, type, data_offset, data_size, size_offset, array_size) \ - PB_FIELDINFO_ASSERT_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size) - -#define PB_FIELDINFO_ASSERT_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size) \ - PB_FIELDINFO_ASSERT_ ## width(tag, type, data_offset, data_size, size_offset, array_size) - -#define PB_DATA_OFFSET_STATIC(htype, structname, fieldname) PB_DO ## htype(structname, fieldname) -#define PB_DATA_OFFSET_POINTER(htype, structname, fieldname) PB_DO ## htype(structname, fieldname) -#define PB_DATA_OFFSET_CALLBACK(htype, structname, fieldname) PB_DO ## htype(structname, fieldname) -#define PB_DO_PB_HTYPE_REQUIRED(structname, fieldname) offsetof(structname, fieldname) -#define PB_DO_PB_HTYPE_SINGULAR(structname, fieldname) offsetof(structname, fieldname) -#define PB_DO_PB_HTYPE_ONEOF(structname, fieldname) offsetof(structname, PB_ONEOF_NAME(FULL, fieldname)) -#define PB_DO_PB_HTYPE_OPTIONAL(structname, fieldname) offsetof(structname, fieldname) -#define PB_DO_PB_HTYPE_REPEATED(structname, fieldname) offsetof(structname, fieldname) -#define PB_DO_PB_HTYPE_FIXARRAY(structname, fieldname) offsetof(structname, fieldname) - -#define PB_SIZE_OFFSET_STATIC(htype, structname, fieldname) PB_SO ## htype(structname, fieldname) -#define PB_SIZE_OFFSET_POINTER(htype, structname, fieldname) PB_SO_PTR ## htype(structname, fieldname) -#define PB_SIZE_OFFSET_CALLBACK(htype, structname, fieldname) PB_SO_CB ## htype(structname, fieldname) -#define PB_SO_PB_HTYPE_REQUIRED(structname, fieldname) 0 -#define PB_SO_PB_HTYPE_SINGULAR(structname, fieldname) 0 -#define PB_SO_PB_HTYPE_ONEOF(structname, fieldname) PB_SO_PB_HTYPE_ONEOF2(structname, PB_ONEOF_NAME(FULL, fieldname), PB_ONEOF_NAME(UNION, fieldname)) -#define PB_SO_PB_HTYPE_ONEOF2(structname, fullname, unionname) PB_SO_PB_HTYPE_ONEOF3(structname, fullname, unionname) -#define PB_SO_PB_HTYPE_ONEOF3(structname, fullname, unionname) pb_delta(structname, fullname, which_ ## unionname) -#define PB_SO_PB_HTYPE_OPTIONAL(structname, fieldname) pb_delta(structname, fieldname, has_ ## fieldname) -#define PB_SO_PB_HTYPE_REPEATED(structname, fieldname) pb_delta(structname, fieldname, fieldname ## _count) -#define PB_SO_PB_HTYPE_FIXARRAY(structname, fieldname) 0 -#define PB_SO_PTR_PB_HTYPE_REQUIRED(structname, fieldname) 0 -#define PB_SO_PTR_PB_HTYPE_SINGULAR(structname, fieldname) 0 -#define PB_SO_PTR_PB_HTYPE_ONEOF(structname, fieldname) PB_SO_PB_HTYPE_ONEOF(structname, fieldname) -#define PB_SO_PTR_PB_HTYPE_OPTIONAL(structname, fieldname) 0 -#define PB_SO_PTR_PB_HTYPE_REPEATED(structname, fieldname) PB_SO_PB_HTYPE_REPEATED(structname, fieldname) -#define PB_SO_PTR_PB_HTYPE_FIXARRAY(structname, fieldname) 0 -#define PB_SO_CB_PB_HTYPE_REQUIRED(structname, fieldname) 0 -#define PB_SO_CB_PB_HTYPE_SINGULAR(structname, fieldname) 0 -#define PB_SO_CB_PB_HTYPE_ONEOF(structname, fieldname) PB_SO_PB_HTYPE_ONEOF(structname, fieldname) -#define PB_SO_CB_PB_HTYPE_OPTIONAL(structname, fieldname) 0 -#define PB_SO_CB_PB_HTYPE_REPEATED(structname, fieldname) 0 -#define PB_SO_CB_PB_HTYPE_FIXARRAY(structname, fieldname) 0 - -#define PB_ARRAY_SIZE_STATIC(htype, structname, fieldname) PB_AS ## htype(structname, fieldname) -#define PB_ARRAY_SIZE_POINTER(htype, structname, fieldname) PB_AS_PTR ## htype(structname, fieldname) -#define PB_ARRAY_SIZE_CALLBACK(htype, structname, fieldname) 1 -#define PB_AS_PB_HTYPE_REQUIRED(structname, fieldname) 1 -#define PB_AS_PB_HTYPE_SINGULAR(structname, fieldname) 1 -#define PB_AS_PB_HTYPE_OPTIONAL(structname, fieldname) 1 -#define PB_AS_PB_HTYPE_ONEOF(structname, fieldname) 1 -#define PB_AS_PB_HTYPE_REPEATED(structname, fieldname) pb_arraysize(structname, fieldname) -#define PB_AS_PB_HTYPE_FIXARRAY(structname, fieldname) pb_arraysize(structname, fieldname) -#define PB_AS_PTR_PB_HTYPE_REQUIRED(structname, fieldname) 1 -#define PB_AS_PTR_PB_HTYPE_SINGULAR(structname, fieldname) 1 -#define PB_AS_PTR_PB_HTYPE_OPTIONAL(structname, fieldname) 1 -#define PB_AS_PTR_PB_HTYPE_ONEOF(structname, fieldname) 1 -#define PB_AS_PTR_PB_HTYPE_REPEATED(structname, fieldname) 1 -#define PB_AS_PTR_PB_HTYPE_FIXARRAY(structname, fieldname) pb_arraysize(structname, fieldname[0]) - -#define PB_DATA_SIZE_STATIC(htype, structname, fieldname) PB_DS ## htype(structname, fieldname) -#define PB_DATA_SIZE_POINTER(htype, structname, fieldname) PB_DS_PTR ## htype(structname, fieldname) -#define PB_DATA_SIZE_CALLBACK(htype, structname, fieldname) PB_DS_CB ## htype(structname, fieldname) -#define PB_DS_PB_HTYPE_REQUIRED(structname, fieldname) pb_membersize(structname, fieldname) -#define PB_DS_PB_HTYPE_SINGULAR(structname, fieldname) pb_membersize(structname, fieldname) -#define PB_DS_PB_HTYPE_OPTIONAL(structname, fieldname) pb_membersize(structname, fieldname) -#define PB_DS_PB_HTYPE_ONEOF(structname, fieldname) pb_membersize(structname, PB_ONEOF_NAME(FULL, fieldname)) -#define PB_DS_PB_HTYPE_REPEATED(structname, fieldname) pb_membersize(structname, fieldname[0]) -#define PB_DS_PB_HTYPE_FIXARRAY(structname, fieldname) pb_membersize(structname, fieldname[0]) -#define PB_DS_PTR_PB_HTYPE_REQUIRED(structname, fieldname) pb_membersize(structname, fieldname[0]) -#define PB_DS_PTR_PB_HTYPE_SINGULAR(structname, fieldname) pb_membersize(structname, fieldname[0]) -#define PB_DS_PTR_PB_HTYPE_OPTIONAL(structname, fieldname) pb_membersize(structname, fieldname[0]) -#define PB_DS_PTR_PB_HTYPE_ONEOF(structname, fieldname) pb_membersize(structname, PB_ONEOF_NAME(FULL, fieldname)[0]) -#define PB_DS_PTR_PB_HTYPE_REPEATED(structname, fieldname) pb_membersize(structname, fieldname[0]) -#define PB_DS_PTR_PB_HTYPE_FIXARRAY(structname, fieldname) pb_membersize(structname, fieldname[0][0]) -#define PB_DS_CB_PB_HTYPE_REQUIRED(structname, fieldname) pb_membersize(structname, fieldname) -#define PB_DS_CB_PB_HTYPE_SINGULAR(structname, fieldname) pb_membersize(structname, fieldname) -#define PB_DS_CB_PB_HTYPE_OPTIONAL(structname, fieldname) pb_membersize(structname, fieldname) -#define PB_DS_CB_PB_HTYPE_ONEOF(structname, fieldname) pb_membersize(structname, PB_ONEOF_NAME(FULL, fieldname)) -#define PB_DS_CB_PB_HTYPE_REPEATED(structname, fieldname) pb_membersize(structname, fieldname) -#define PB_DS_CB_PB_HTYPE_FIXARRAY(structname, fieldname) pb_membersize(structname, fieldname) - -#define PB_ONEOF_NAME(type, tuple) PB_EXPAND(PB_ONEOF_NAME_ ## type tuple) -#define PB_ONEOF_NAME_UNION(unionname,membername,fullname) unionname -#define PB_ONEOF_NAME_MEMBER(unionname,membername,fullname) membername -#define PB_ONEOF_NAME_FULL(unionname,membername,fullname) fullname - -#define PB_GEN_SUBMSG_INFO(structname, atype, htype, ltype, fieldname, tag) \ - PB_SUBMSG_INFO_ ## htype(_PB_LTYPE_ ## ltype, structname, fieldname) - -#define PB_SUBMSG_INFO_REQUIRED(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE) -#define PB_SUBMSG_INFO_SINGULAR(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE) -#define PB_SUBMSG_INFO_OPTIONAL(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE) -#define PB_SUBMSG_INFO_ONEOF(ltype, structname, fieldname) PB_SUBMSG_INFO_ONEOF2(ltype, structname, PB_ONEOF_NAME(UNION, fieldname), PB_ONEOF_NAME(MEMBER, fieldname)) -#define PB_SUBMSG_INFO_ONEOF2(ltype, structname, unionname, membername) PB_SUBMSG_INFO_ONEOF3(ltype, structname, unionname, membername) -#define PB_SUBMSG_INFO_ONEOF3(ltype, structname, unionname, membername) PB_SI ## ltype(structname ## _ ## unionname ## _ ## membername ## _MSGTYPE) -#define PB_SUBMSG_INFO_REPEATED(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE) -#define PB_SUBMSG_INFO_FIXARRAY(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE) -#define PB_SI_PB_LTYPE_BOOL(t) -#define PB_SI_PB_LTYPE_BYTES(t) -#define PB_SI_PB_LTYPE_DOUBLE(t) -#define PB_SI_PB_LTYPE_ENUM(t) -#define PB_SI_PB_LTYPE_UENUM(t) -#define PB_SI_PB_LTYPE_FIXED32(t) -#define PB_SI_PB_LTYPE_FIXED64(t) -#define PB_SI_PB_LTYPE_FLOAT(t) -#define PB_SI_PB_LTYPE_INT32(t) -#define PB_SI_PB_LTYPE_INT64(t) -#define PB_SI_PB_LTYPE_MESSAGE(t) PB_SUBMSG_DESCRIPTOR(t) -#define PB_SI_PB_LTYPE_MSG_W_CB(t) PB_SUBMSG_DESCRIPTOR(t) -#define PB_SI_PB_LTYPE_SFIXED32(t) -#define PB_SI_PB_LTYPE_SFIXED64(t) -#define PB_SI_PB_LTYPE_SINT32(t) -#define PB_SI_PB_LTYPE_SINT64(t) -#define PB_SI_PB_LTYPE_STRING(t) -#define PB_SI_PB_LTYPE_UINT32(t) -#define PB_SI_PB_LTYPE_UINT64(t) -#define PB_SI_PB_LTYPE_EXTENSION(t) -#define PB_SI_PB_LTYPE_FIXED_LENGTH_BYTES(t) -#define PB_SUBMSG_DESCRIPTOR(t) &(t ## _msg), - -/* The field descriptors use a variable width format, with width of either - * 1, 2, 4 or 8 of 32-bit words. The two lowest bytes of the first byte always - * encode the descriptor size, 6 lowest bits of field tag number, and 8 bits - * of the field type. - * - * Descriptor size is encoded as 0 = 1 word, 1 = 2 words, 2 = 4 words, 3 = 8 words. - * - * Formats, listed starting with the least significant bit of the first word. - * 1 word: [2-bit len] [6-bit tag] [8-bit type] [8-bit data_offset] [4-bit size_offset] [4-bit data_size] - * - * 2 words: [2-bit len] [6-bit tag] [8-bit type] [12-bit array_size] [4-bit size_offset] - * [16-bit data_offset] [12-bit data_size] [4-bit tag>>6] - * - * 4 words: [2-bit len] [6-bit tag] [8-bit type] [16-bit array_size] - * [8-bit size_offset] [24-bit tag>>6] - * [32-bit data_offset] - * [32-bit data_size] - * - * 8 words: [2-bit len] [6-bit tag] [8-bit type] [16-bit reserved] - * [8-bit size_offset] [24-bit tag>>6] - * [32-bit data_offset] - * [32-bit data_size] - * [32-bit array_size] - * [32-bit reserved] - * [32-bit reserved] - * [32-bit reserved] - */ - -#define PB_FIELDINFO_1(tag, type, data_offset, data_size, size_offset, array_size) \ - (0 | (((tag) << 2) & 0xFF) | ((type) << 8) | (((uint32_t)(data_offset) & 0xFF) << 16) | \ - (((uint32_t)(size_offset) & 0x0F) << 24) | (((uint32_t)(data_size) & 0x0F) << 28)), - -#define PB_FIELDINFO_2(tag, type, data_offset, data_size, size_offset, array_size) \ - (1 | (((tag) << 2) & 0xFF) | ((type) << 8) | (((uint32_t)(array_size) & 0xFFF) << 16) | (((uint32_t)(size_offset) & 0x0F) << 28)), \ - (((uint32_t)(data_offset) & 0xFFFF) | (((uint32_t)(data_size) & 0xFFF) << 16) | (((uint32_t)(tag) & 0x3c0) << 22)), - -#define PB_FIELDINFO_4(tag, type, data_offset, data_size, size_offset, array_size) \ - (2 | (((tag) << 2) & 0xFF) | ((type) << 8) | (((uint32_t)(array_size) & 0xFFFF) << 16)), \ - ((uint32_t)(int_least8_t)(size_offset) | (((uint32_t)(tag) << 2) & 0xFFFFFF00)), \ - (data_offset), (data_size), - -#define PB_FIELDINFO_8(tag, type, data_offset, data_size, size_offset, array_size) \ - (3 | (((tag) << 2) & 0xFF) | ((type) << 8)), \ - ((uint32_t)(int_least8_t)(size_offset) | (((uint32_t)(tag) << 2) & 0xFFFFFF00)), \ - (data_offset), (data_size), (array_size), 0, 0, 0, - -/* These assertions verify that the field information fits in the allocated space. - * The generator tries to automatically determine the correct width that can fit all - * data associated with a message. These asserts will fail only if there has been a - * problem in the automatic logic - this may be worth reporting as a bug. As a workaround, - * you can increase the descriptor width by defining PB_FIELDINFO_WIDTH or by setting - * descriptorsize option in .options file. - */ -#define PB_FITS(value,bits) ((uint32_t)(value) < ((uint32_t)1<2GB messages with nanopb anyway. - */ -#define PB_FIELDINFO_ASSERT_4(tag, type, data_offset, data_size, size_offset, array_size) \ - PB_STATIC_ASSERT(PB_FITS(tag,30) && PB_FITS(data_offset,31) && PB_FITS(size_offset,8) && PB_FITS(data_size,31) && PB_FITS(array_size,16), FIELDINFO_DOES_NOT_FIT_width4_field ## tag) - -#define PB_FIELDINFO_ASSERT_8(tag, type, data_offset, data_size, size_offset, array_size) \ - PB_STATIC_ASSERT(PB_FITS(tag,30) && PB_FITS(data_offset,31) && PB_FITS(size_offset,8) && PB_FITS(data_size,31) && PB_FITS(array_size,31), FIELDINFO_DOES_NOT_FIT_width8_field ## tag) -#endif - - -/* Automatic picking of FIELDINFO width: - * Uses width 1 when possible, otherwise resorts to width 2. - * This is used when PB_BIND() is called with "AUTO" as the argument. - * The generator will give explicit size argument when it knows that a message - * structure grows beyond 1-word format limits. - */ -#define PB_FIELDINFO_WIDTH_AUTO(atype, htype, ltype) PB_FI_WIDTH ## atype(htype, ltype) -#define PB_FI_WIDTH_PB_ATYPE_STATIC(htype, ltype) PB_FI_WIDTH ## htype(ltype) -#define PB_FI_WIDTH_PB_ATYPE_POINTER(htype, ltype) PB_FI_WIDTH ## htype(ltype) -#define PB_FI_WIDTH_PB_ATYPE_CALLBACK(htype, ltype) 2 -#define PB_FI_WIDTH_PB_HTYPE_REQUIRED(ltype) PB_FI_WIDTH ## ltype -#define PB_FI_WIDTH_PB_HTYPE_SINGULAR(ltype) PB_FI_WIDTH ## ltype -#define PB_FI_WIDTH_PB_HTYPE_OPTIONAL(ltype) PB_FI_WIDTH ## ltype -#define PB_FI_WIDTH_PB_HTYPE_ONEOF(ltype) PB_FI_WIDTH ## ltype -#define PB_FI_WIDTH_PB_HTYPE_REPEATED(ltype) 2 -#define PB_FI_WIDTH_PB_HTYPE_FIXARRAY(ltype) 2 -#define PB_FI_WIDTH_PB_LTYPE_BOOL 1 -#define PB_FI_WIDTH_PB_LTYPE_BYTES 2 -#define PB_FI_WIDTH_PB_LTYPE_DOUBLE 1 -#define PB_FI_WIDTH_PB_LTYPE_ENUM 1 -#define PB_FI_WIDTH_PB_LTYPE_UENUM 1 -#define PB_FI_WIDTH_PB_LTYPE_FIXED32 1 -#define PB_FI_WIDTH_PB_LTYPE_FIXED64 1 -#define PB_FI_WIDTH_PB_LTYPE_FLOAT 1 -#define PB_FI_WIDTH_PB_LTYPE_INT32 1 -#define PB_FI_WIDTH_PB_LTYPE_INT64 1 -#define PB_FI_WIDTH_PB_LTYPE_MESSAGE 2 -#define PB_FI_WIDTH_PB_LTYPE_MSG_W_CB 2 -#define PB_FI_WIDTH_PB_LTYPE_SFIXED32 1 -#define PB_FI_WIDTH_PB_LTYPE_SFIXED64 1 -#define PB_FI_WIDTH_PB_LTYPE_SINT32 1 -#define PB_FI_WIDTH_PB_LTYPE_SINT64 1 -#define PB_FI_WIDTH_PB_LTYPE_STRING 2 -#define PB_FI_WIDTH_PB_LTYPE_UINT32 1 -#define PB_FI_WIDTH_PB_LTYPE_UINT64 1 -#define PB_FI_WIDTH_PB_LTYPE_EXTENSION 1 -#define PB_FI_WIDTH_PB_LTYPE_FIXED_LENGTH_BYTES 2 - -/* The mapping from protobuf types to LTYPEs is done using these macros. */ -#define PB_LTYPE_MAP_BOOL PB_LTYPE_BOOL -#define PB_LTYPE_MAP_BYTES PB_LTYPE_BYTES -#define PB_LTYPE_MAP_DOUBLE PB_LTYPE_FIXED64 -#define PB_LTYPE_MAP_ENUM PB_LTYPE_VARINT -#define PB_LTYPE_MAP_UENUM PB_LTYPE_UVARINT -#define PB_LTYPE_MAP_FIXED32 PB_LTYPE_FIXED32 -#define PB_LTYPE_MAP_FIXED64 PB_LTYPE_FIXED64 -#define PB_LTYPE_MAP_FLOAT PB_LTYPE_FIXED32 -#define PB_LTYPE_MAP_INT32 PB_LTYPE_VARINT -#define PB_LTYPE_MAP_INT64 PB_LTYPE_VARINT -#define PB_LTYPE_MAP_MESSAGE PB_LTYPE_SUBMESSAGE -#define PB_LTYPE_MAP_MSG_W_CB PB_LTYPE_SUBMSG_W_CB -#define PB_LTYPE_MAP_SFIXED32 PB_LTYPE_FIXED32 -#define PB_LTYPE_MAP_SFIXED64 PB_LTYPE_FIXED64 -#define PB_LTYPE_MAP_SINT32 PB_LTYPE_SVARINT -#define PB_LTYPE_MAP_SINT64 PB_LTYPE_SVARINT -#define PB_LTYPE_MAP_STRING PB_LTYPE_STRING -#define PB_LTYPE_MAP_UINT32 PB_LTYPE_UVARINT -#define PB_LTYPE_MAP_UINT64 PB_LTYPE_UVARINT -#define PB_LTYPE_MAP_EXTENSION PB_LTYPE_EXTENSION -#define PB_LTYPE_MAP_FIXED_LENGTH_BYTES PB_LTYPE_FIXED_LENGTH_BYTES - -/* These macros are used for giving out error messages. - * They are mostly a debugging aid; the main error information - * is the true/false return value from functions. - * Some code space can be saved by disabling the error - * messages if not used. - * - * PB_SET_ERROR() sets the error message if none has been set yet. - * msg must be a constant string literal. - * PB_GET_ERROR() always returns a pointer to a string. - * PB_RETURN_ERROR() sets the error and returns false from current - * function. - */ -#ifdef PB_NO_ERRMSG -#define PB_SET_ERROR(stream, msg) PB_UNUSED(stream) -#define PB_GET_ERROR(stream) "(errmsg disabled)" -#else -#define PB_SET_ERROR(stream, msg) (stream->errmsg = (stream)->errmsg ? (stream)->errmsg : (msg)) -#define PB_GET_ERROR(stream) ((stream)->errmsg ? (stream)->errmsg : "(none)") -#endif - -#define PB_RETURN_ERROR(stream, msg) return PB_SET_ERROR(stream, msg), false - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#ifdef __cplusplus -#if __cplusplus >= 201103L -#define PB_CONSTEXPR constexpr -#else // __cplusplus >= 201103L -#define PB_CONSTEXPR -#endif // __cplusplus >= 201103L - -#if __cplusplus >= 201703L -#define PB_INLINE_CONSTEXPR inline constexpr -#else // __cplusplus >= 201703L -#define PB_INLINE_CONSTEXPR PB_CONSTEXPR -#endif // __cplusplus >= 201703L - -extern "C++" -{ -namespace nanopb { -// Each type will be partially specialized by the generator. -template struct MessageDescriptor; -} // namespace nanopb -} -#endif /* __cplusplus */ - -#endif diff --git a/src/network/pb_common.c b/src/network/pb_common.c deleted file mode 100644 index 6aee76b1e..000000000 --- a/src/network/pb_common.c +++ /dev/null @@ -1,388 +0,0 @@ -/* pb_common.c: Common support functions for pb_encode.c and pb_decode.c. - * - * 2014 Petteri Aimonen - */ - -#include "pb_common.h" - -static bool load_descriptor_values(pb_field_iter_t *iter) -{ - uint32_t word0; - uint32_t data_offset; - int_least8_t size_offset; - - if (iter->index >= iter->descriptor->field_count) - return false; - - word0 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index]); - iter->type = (pb_type_t)((word0 >> 8) & 0xFF); - - switch(word0 & 3) - { - case 0: { - /* 1-word format */ - iter->array_size = 1; - iter->tag = (pb_size_t)((word0 >> 2) & 0x3F); - size_offset = (int_least8_t)((word0 >> 24) & 0x0F); - data_offset = (word0 >> 16) & 0xFF; - iter->data_size = (pb_size_t)((word0 >> 28) & 0x0F); - break; - } - - case 1: { - /* 2-word format */ - uint32_t word1 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 1]); - - iter->array_size = (pb_size_t)((word0 >> 16) & 0x0FFF); - iter->tag = (pb_size_t)(((word0 >> 2) & 0x3F) | ((word1 >> 28) << 6)); - size_offset = (int_least8_t)((word0 >> 28) & 0x0F); - data_offset = word1 & 0xFFFF; - iter->data_size = (pb_size_t)((word1 >> 16) & 0x0FFF); - break; - } - - case 2: { - /* 4-word format */ - uint32_t word1 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 1]); - uint32_t word2 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 2]); - uint32_t word3 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 3]); - - iter->array_size = (pb_size_t)(word0 >> 16); - iter->tag = (pb_size_t)(((word0 >> 2) & 0x3F) | ((word1 >> 8) << 6)); - size_offset = (int_least8_t)(word1 & 0xFF); - data_offset = word2; - iter->data_size = (pb_size_t)word3; - break; - } - - default: { - /* 8-word format */ - uint32_t word1 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 1]); - uint32_t word2 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 2]); - uint32_t word3 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 3]); - uint32_t word4 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 4]); - - iter->array_size = (pb_size_t)word4; - iter->tag = (pb_size_t)(((word0 >> 2) & 0x3F) | ((word1 >> 8) << 6)); - size_offset = (int_least8_t)(word1 & 0xFF); - data_offset = word2; - iter->data_size = (pb_size_t)word3; - break; - } - } - - if (!iter->message) - { - /* Avoid doing arithmetic on null pointers, it is undefined */ - iter->pField = NULL; - iter->pSize = NULL; - } - else - { - iter->pField = (char*)iter->message + data_offset; - - if (size_offset) - { - iter->pSize = (char*)iter->pField - size_offset; - } - else if (PB_HTYPE(iter->type) == PB_HTYPE_REPEATED && - (PB_ATYPE(iter->type) == PB_ATYPE_STATIC || - PB_ATYPE(iter->type) == PB_ATYPE_POINTER)) - { - /* Fixed count array */ - iter->pSize = &iter->array_size; - } - else - { - iter->pSize = NULL; - } - - if (PB_ATYPE(iter->type) == PB_ATYPE_POINTER && iter->pField != NULL) - { - iter->pData = *(void**)iter->pField; - } - else - { - iter->pData = iter->pField; - } - } - - if (PB_LTYPE_IS_SUBMSG(iter->type)) - { - iter->submsg_desc = iter->descriptor->submsg_info[iter->submessage_index]; - } - else - { - iter->submsg_desc = NULL; - } - - return true; -} - -static void advance_iterator(pb_field_iter_t *iter) -{ - iter->index++; - - if (iter->index >= iter->descriptor->field_count) - { - /* Restart */ - iter->index = 0; - iter->field_info_index = 0; - iter->submessage_index = 0; - iter->required_field_index = 0; - } - else - { - /* Increment indexes based on previous field type. - * All field info formats have the following fields: - * - lowest 2 bits tell the amount of words in the descriptor (2^n words) - * - bits 2..7 give the lowest bits of tag number. - * - bits 8..15 give the field type. - */ - uint32_t prev_descriptor = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index]); - pb_type_t prev_type = (prev_descriptor >> 8) & 0xFF; - pb_size_t descriptor_len = (pb_size_t)(1 << (prev_descriptor & 3)); - - /* Add to fields. - * The cast to pb_size_t is needed to avoid -Wconversion warning. - * Because the data is is constants from generator, there is no danger of overflow. - */ - iter->field_info_index = (pb_size_t)(iter->field_info_index + descriptor_len); - iter->required_field_index = (pb_size_t)(iter->required_field_index + (PB_HTYPE(prev_type) == PB_HTYPE_REQUIRED)); - iter->submessage_index = (pb_size_t)(iter->submessage_index + PB_LTYPE_IS_SUBMSG(prev_type)); - } -} - -bool pb_field_iter_begin(pb_field_iter_t *iter, const pb_msgdesc_t *desc, void *message) -{ - memset(iter, 0, sizeof(*iter)); - - iter->descriptor = desc; - iter->message = message; - - return load_descriptor_values(iter); -} - -bool pb_field_iter_begin_extension(pb_field_iter_t *iter, pb_extension_t *extension) -{ - const pb_msgdesc_t *msg = (const pb_msgdesc_t*)extension->type->arg; - bool status; - - uint32_t word0 = PB_PROGMEM_READU32(msg->field_info[0]); - if (PB_ATYPE(word0 >> 8) == PB_ATYPE_POINTER) - { - /* For pointer extensions, the pointer is stored directly - * in the extension structure. This avoids having an extra - * indirection. */ - status = pb_field_iter_begin(iter, msg, &extension->dest); - } - else - { - status = pb_field_iter_begin(iter, msg, extension->dest); - } - - iter->pSize = &extension->found; - return status; -} - -bool pb_field_iter_next(pb_field_iter_t *iter) -{ - advance_iterator(iter); - (void)load_descriptor_values(iter); - return iter->index != 0; -} - -bool pb_field_iter_find(pb_field_iter_t *iter, uint32_t tag) -{ - if (iter->tag == tag) - { - return true; /* Nothing to do, correct field already. */ - } - else if (tag > iter->descriptor->largest_tag) - { - return false; - } - else - { - pb_size_t start = iter->index; - uint32_t fieldinfo; - - if (tag < iter->tag) - { - /* Fields are in tag number order, so we know that tag is between - * 0 and our start position. Setting index to end forces - * advance_iterator() call below to restart from beginning. */ - iter->index = iter->descriptor->field_count; - } - - do - { - /* Advance iterator but don't load values yet */ - advance_iterator(iter); - - /* Do fast check for tag number match */ - fieldinfo = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index]); - - if (((fieldinfo >> 2) & 0x3F) == (tag & 0x3F)) - { - /* Good candidate, check further */ - (void)load_descriptor_values(iter); - - if (iter->tag == tag && - PB_LTYPE(iter->type) != PB_LTYPE_EXTENSION) - { - /* Found it */ - return true; - } - } - } while (iter->index != start); - - /* Searched all the way back to start, and found nothing. */ - (void)load_descriptor_values(iter); - return false; - } -} - -bool pb_field_iter_find_extension(pb_field_iter_t *iter) -{ - if (PB_LTYPE(iter->type) == PB_LTYPE_EXTENSION) - { - return true; - } - else - { - pb_size_t start = iter->index; - uint32_t fieldinfo; - - do - { - /* Advance iterator but don't load values yet */ - advance_iterator(iter); - - /* Do fast check for field type */ - fieldinfo = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index]); - - if (PB_LTYPE((fieldinfo >> 8) & 0xFF) == PB_LTYPE_EXTENSION) - { - return load_descriptor_values(iter); - } - } while (iter->index != start); - - /* Searched all the way back to start, and found nothing. */ - (void)load_descriptor_values(iter); - return false; - } -} - -static void *pb_const_cast(const void *p) -{ - /* Note: this casts away const, in order to use the common field iterator - * logic for both encoding and decoding. The cast is done using union - * to avoid spurious compiler warnings. */ - union { - void *p1; - const void *p2; - } t; - t.p2 = p; - return t.p1; -} - -bool pb_field_iter_begin_const(pb_field_iter_t *iter, const pb_msgdesc_t *desc, const void *message) -{ - return pb_field_iter_begin(iter, desc, pb_const_cast(message)); -} - -bool pb_field_iter_begin_extension_const(pb_field_iter_t *iter, const pb_extension_t *extension) -{ - return pb_field_iter_begin_extension(iter, (pb_extension_t*)pb_const_cast(extension)); -} - -bool pb_default_field_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_t *field) -{ - if (field->data_size == sizeof(pb_callback_t)) - { - pb_callback_t *pCallback = (pb_callback_t*)field->pData; - - if (pCallback != NULL) - { - if (istream != NULL && pCallback->funcs.decode != NULL) - { - return pCallback->funcs.decode(istream, field, &pCallback->arg); - } - - if (ostream != NULL && pCallback->funcs.encode != NULL) - { - return pCallback->funcs.encode(ostream, field, &pCallback->arg); - } - } - } - - return true; /* Success, but didn't do anything */ - -} - -#ifdef PB_VALIDATE_UTF8 - -/* This function checks whether a string is valid UTF-8 text. - * - * Algorithm is adapted from https://www.cl.cam.ac.uk/~mgk25/ucs/utf8_check.c - * Original copyright: Markus Kuhn 2005-03-30 - * Licensed under "Short code license", which allows use under MIT license or - * any compatible with it. - */ - -bool pb_validate_utf8(const char *str) -{ - const pb_byte_t *s = (const pb_byte_t*)str; - while (*s) - { - if (*s < 0x80) - { - /* 0xxxxxxx */ - s++; - } - else if ((s[0] & 0xe0) == 0xc0) - { - /* 110XXXXx 10xxxxxx */ - if ((s[1] & 0xc0) != 0x80 || - (s[0] & 0xfe) == 0xc0) /* overlong? */ - return false; - else - s += 2; - } - else if ((s[0] & 0xf0) == 0xe0) - { - /* 1110XXXX 10Xxxxxx 10xxxxxx */ - if ((s[1] & 0xc0) != 0x80 || - (s[2] & 0xc0) != 0x80 || - (s[0] == 0xe0 && (s[1] & 0xe0) == 0x80) || /* overlong? */ - (s[0] == 0xed && (s[1] & 0xe0) == 0xa0) || /* surrogate? */ - (s[0] == 0xef && s[1] == 0xbf && - (s[2] & 0xfe) == 0xbe)) /* U+FFFE or U+FFFF? */ - return false; - else - s += 3; - } - else if ((s[0] & 0xf8) == 0xf0) - { - /* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */ - if ((s[1] & 0xc0) != 0x80 || - (s[2] & 0xc0) != 0x80 || - (s[3] & 0xc0) != 0x80 || - (s[0] == 0xf0 && (s[1] & 0xf0) == 0x80) || /* overlong? */ - (s[0] == 0xf4 && s[1] > 0x8f) || s[0] > 0xf4) /* > U+10FFFF? */ - return false; - else - s += 4; - } - else - { - return false; - } - } - - return true; -} - -#endif - diff --git a/src/network/pb_common.h b/src/network/pb_common.h deleted file mode 100644 index 58aa90f76..000000000 --- a/src/network/pb_common.h +++ /dev/null @@ -1,49 +0,0 @@ -/* pb_common.h: Common support functions for pb_encode.c and pb_decode.c. - * These functions are rarely needed by applications directly. - */ - -#ifndef PB_COMMON_H_INCLUDED -#define PB_COMMON_H_INCLUDED - -#include "pb.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/* Initialize the field iterator structure to beginning. - * Returns false if the message type is empty. */ -bool pb_field_iter_begin(pb_field_iter_t *iter, const pb_msgdesc_t *desc, void *message); - -/* Get a field iterator for extension field. */ -bool pb_field_iter_begin_extension(pb_field_iter_t *iter, pb_extension_t *extension); - -/* Same as pb_field_iter_begin(), but for const message pointer. - * Note that the pointers in pb_field_iter_t will be non-const but shouldn't - * be written to when using these functions. */ -bool pb_field_iter_begin_const(pb_field_iter_t *iter, const pb_msgdesc_t *desc, const void *message); -bool pb_field_iter_begin_extension_const(pb_field_iter_t *iter, const pb_extension_t *extension); - -/* Advance the iterator to the next field. - * Returns false when the iterator wraps back to the first field. */ -bool pb_field_iter_next(pb_field_iter_t *iter); - -/* Advance the iterator until it points at a field with the given tag. - * Returns false if no such field exists. */ -bool pb_field_iter_find(pb_field_iter_t *iter, uint32_t tag); - -/* Find a field with type PB_LTYPE_EXTENSION, or return false if not found. - * There can be only one extension range field per message. */ -bool pb_field_iter_find_extension(pb_field_iter_t *iter); - -#ifdef PB_VALIDATE_UTF8 -/* Validate UTF-8 text string */ -bool pb_validate_utf8(const char *s); -#endif - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif - diff --git a/src/network/pb_decode.c b/src/network/pb_decode.c deleted file mode 100644 index 788998eb9..000000000 --- a/src/network/pb_decode.c +++ /dev/null @@ -1,1727 +0,0 @@ -/* pb_decode.c -- decode a protobuf using minimal resources - * - * 2011 Petteri Aimonen - */ - -/* Use the GCC warn_unused_result attribute to check that all return values - * are propagated correctly. On other compilers and gcc before 3.4.0 just - * ignore the annotation. - */ -#if !defined(__GNUC__) || ( __GNUC__ < 3) || (__GNUC__ == 3 && __GNUC_MINOR__ < 4) - #define checkreturn -#else - #define checkreturn __attribute__((warn_unused_result)) -#endif - -#include "pb.h" -#include "pb_decode.h" -#include "pb_common.h" - -/************************************** - * Declarations internal to this file * - **************************************/ - -static bool checkreturn buf_read(pb_istream_t *stream, pb_byte_t *buf, size_t count); -static bool checkreturn pb_decode_varint32_eof(pb_istream_t *stream, uint32_t *dest, bool *eof); -static bool checkreturn read_raw_value(pb_istream_t *stream, pb_wire_type_t wire_type, pb_byte_t *buf, size_t *size); -static bool checkreturn decode_basic_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field); -static bool checkreturn decode_static_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field); -static bool checkreturn decode_pointer_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field); -static bool checkreturn decode_callback_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field); -static bool checkreturn decode_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field); -static bool checkreturn default_extension_decoder(pb_istream_t *stream, pb_extension_t *extension, uint32_t tag, pb_wire_type_t wire_type); -static bool checkreturn decode_extension(pb_istream_t *stream, uint32_t tag, pb_wire_type_t wire_type, pb_extension_t *extension); -static bool pb_field_set_to_default(pb_field_iter_t *field); -static bool pb_message_set_to_defaults(pb_field_iter_t *iter); -static bool checkreturn pb_dec_bool(pb_istream_t *stream, const pb_field_iter_t *field); -static bool checkreturn pb_dec_varint(pb_istream_t *stream, const pb_field_iter_t *field); -static bool checkreturn pb_dec_bytes(pb_istream_t *stream, const pb_field_iter_t *field); -static bool checkreturn pb_dec_string(pb_istream_t *stream, const pb_field_iter_t *field); -static bool checkreturn pb_dec_submessage(pb_istream_t *stream, const pb_field_iter_t *field); -static bool checkreturn pb_dec_fixed_length_bytes(pb_istream_t *stream, const pb_field_iter_t *field); -static bool checkreturn pb_skip_varint(pb_istream_t *stream); -static bool checkreturn pb_skip_string(pb_istream_t *stream); - -#ifdef PB_ENABLE_MALLOC -static bool checkreturn allocate_field(pb_istream_t *stream, void *pData, size_t data_size, size_t array_size); -static void initialize_pointer_field(void *pItem, pb_field_iter_t *field); -static bool checkreturn pb_release_union_field(pb_istream_t *stream, pb_field_iter_t *field); -static void pb_release_single_field(pb_field_iter_t *field); -#endif - -#ifdef PB_WITHOUT_64BIT -#define pb_int64_t int32_t -#define pb_uint64_t uint32_t -#else -#define pb_int64_t int64_t -#define pb_uint64_t uint64_t -#endif - -typedef struct { - uint32_t bitfield[(PB_MAX_REQUIRED_FIELDS + 31) / 32]; -} pb_fields_seen_t; - -/******************************* - * pb_istream_t implementation * - *******************************/ - -static bool checkreturn buf_read(pb_istream_t *stream, pb_byte_t *buf, size_t count) -{ - const pb_byte_t *source = (const pb_byte_t*)stream->state; - stream->state = (pb_byte_t*)stream->state + count; - - if (buf != NULL) - { - memcpy(buf, source, count * sizeof(pb_byte_t)); - } - - return true; -} - -bool checkreturn pb_read(pb_istream_t *stream, pb_byte_t *buf, size_t count) -{ - if (count == 0) - return true; - -#ifndef PB_BUFFER_ONLY - if (buf == NULL && stream->callback != buf_read) - { - /* Skip input bytes */ - pb_byte_t tmp[16]; - while (count > 16) - { - if (!pb_read(stream, tmp, 16)) - return false; - - count -= 16; - } - - return pb_read(stream, tmp, count); - } -#endif - - if (stream->bytes_left < count) - PB_RETURN_ERROR(stream, "end-of-stream"); - -#ifndef PB_BUFFER_ONLY - if (!stream->callback(stream, buf, count)) - PB_RETURN_ERROR(stream, "io error"); -#else - if (!buf_read(stream, buf, count)) - return false; -#endif - - if (stream->bytes_left < count) - stream->bytes_left = 0; - else - stream->bytes_left -= count; - - return true; -} - -/* Read a single byte from input stream. buf may not be NULL. - * This is an optimization for the varint decoding. */ -static bool checkreturn pb_readbyte(pb_istream_t *stream, pb_byte_t *buf) -{ - if (stream->bytes_left == 0) - PB_RETURN_ERROR(stream, "end-of-stream"); - -#ifndef PB_BUFFER_ONLY - if (!stream->callback(stream, buf, 1)) - PB_RETURN_ERROR(stream, "io error"); -#else - *buf = *(const pb_byte_t*)stream->state; - stream->state = (pb_byte_t*)stream->state + 1; -#endif - - stream->bytes_left--; - - return true; -} - -pb_istream_t pb_istream_from_buffer(const pb_byte_t *buf, size_t msglen) -{ - pb_istream_t stream; - /* Cast away the const from buf without a compiler error. We are - * careful to use it only in a const manner in the callbacks. - */ - union { - void *state; - const void *c_state; - } state; -#ifdef PB_BUFFER_ONLY - stream.callback = NULL; -#else - stream.callback = &buf_read; -#endif - state.c_state = buf; - stream.state = state.state; - stream.bytes_left = msglen; -#ifndef PB_NO_ERRMSG - stream.errmsg = NULL; -#endif - return stream; -} - -/******************** - * Helper functions * - ********************/ - -static bool checkreturn pb_decode_varint32_eof(pb_istream_t *stream, uint32_t *dest, bool *eof) -{ - pb_byte_t byte; - uint32_t result; - - if (!pb_readbyte(stream, &byte)) - { - if (stream->bytes_left == 0) - { - if (eof) - { - *eof = true; - } - } - - return false; - } - - if ((byte & 0x80) == 0) - { - /* Quick case, 1 byte value */ - result = byte; - } - else - { - /* Multibyte case */ - uint_fast8_t bitpos = 7; - result = byte & 0x7F; - - do - { - if (!pb_readbyte(stream, &byte)) - return false; - - if (bitpos >= 32) - { - /* Note: The varint could have trailing 0x80 bytes, or 0xFF for negative. */ - pb_byte_t sign_extension = (bitpos < 63) ? 0xFF : 0x01; - bool valid_extension = ((byte & 0x7F) == 0x00 || - ((result >> 31) != 0 && byte == sign_extension)); - - if (bitpos >= 64 || !valid_extension) - { - PB_RETURN_ERROR(stream, "varint overflow"); - } - } - else if (bitpos == 28) - { - if ((byte & 0x70) != 0 && (byte & 0x78) != 0x78) - { - PB_RETURN_ERROR(stream, "varint overflow"); - } - result |= (uint32_t)(byte & 0x0F) << bitpos; - } - else - { - result |= (uint32_t)(byte & 0x7F) << bitpos; - } - bitpos = (uint_fast8_t)(bitpos + 7); - } while (byte & 0x80); - } - - *dest = result; - return true; -} - -bool checkreturn pb_decode_varint32(pb_istream_t *stream, uint32_t *dest) -{ - return pb_decode_varint32_eof(stream, dest, NULL); -} - -#ifndef PB_WITHOUT_64BIT -bool checkreturn pb_decode_varint(pb_istream_t *stream, uint64_t *dest) -{ - pb_byte_t byte; - uint_fast8_t bitpos = 0; - uint64_t result = 0; - - do - { - if (!pb_readbyte(stream, &byte)) - return false; - - if (bitpos >= 63 && (byte & 0xFE) != 0) - PB_RETURN_ERROR(stream, "varint overflow"); - - result |= (uint64_t)(byte & 0x7F) << bitpos; - bitpos = (uint_fast8_t)(bitpos + 7); - } while (byte & 0x80); - - *dest = result; - return true; -} -#endif - -bool checkreturn pb_skip_varint(pb_istream_t *stream) -{ - pb_byte_t byte; - do - { - if (!pb_read(stream, &byte, 1)) - return false; - } while (byte & 0x80); - return true; -} - -bool checkreturn pb_skip_string(pb_istream_t *stream) -{ - uint32_t length; - if (!pb_decode_varint32(stream, &length)) - return false; - - if ((size_t)length != length) - { - PB_RETURN_ERROR(stream, "size too large"); - } - - return pb_read(stream, NULL, (size_t)length); -} - -bool checkreturn pb_decode_tag(pb_istream_t *stream, pb_wire_type_t *wire_type, uint32_t *tag, bool *eof) -{ - uint32_t temp; - *eof = false; - *wire_type = (pb_wire_type_t) 0; - *tag = 0; - - if (!pb_decode_varint32_eof(stream, &temp, eof)) - { - return false; - } - - *tag = temp >> 3; - *wire_type = (pb_wire_type_t)(temp & 7); - return true; -} - -bool checkreturn pb_skip_field(pb_istream_t *stream, pb_wire_type_t wire_type) -{ - switch (wire_type) - { - case PB_WT_VARINT: return pb_skip_varint(stream); - case PB_WT_64BIT: return pb_read(stream, NULL, 8); - case PB_WT_STRING: return pb_skip_string(stream); - case PB_WT_32BIT: return pb_read(stream, NULL, 4); - default: PB_RETURN_ERROR(stream, "invalid wire_type"); - } -} - -/* Read a raw value to buffer, for the purpose of passing it to callback as - * a substream. Size is maximum size on call, and actual size on return. - */ -static bool checkreturn read_raw_value(pb_istream_t *stream, pb_wire_type_t wire_type, pb_byte_t *buf, size_t *size) -{ - size_t max_size = *size; - switch (wire_type) - { - case PB_WT_VARINT: - *size = 0; - do - { - (*size)++; - if (*size > max_size) - PB_RETURN_ERROR(stream, "varint overflow"); - - if (!pb_read(stream, buf, 1)) - return false; - } while (*buf++ & 0x80); - return true; - - case PB_WT_64BIT: - *size = 8; - return pb_read(stream, buf, 8); - - case PB_WT_32BIT: - *size = 4; - return pb_read(stream, buf, 4); - - case PB_WT_STRING: - /* Calling read_raw_value with a PB_WT_STRING is an error. - * Explicitly handle this case and fallthrough to default to avoid - * compiler warnings. - */ - - default: PB_RETURN_ERROR(stream, "invalid wire_type"); - } -} - -/* Decode string length from stream and return a substream with limited length. - * Remember to close the substream using pb_close_string_substream(). - */ -bool checkreturn pb_make_string_substream(pb_istream_t *stream, pb_istream_t *substream) -{ - uint32_t size; - if (!pb_decode_varint32(stream, &size)) - return false; - - *substream = *stream; - if (substream->bytes_left < size) - PB_RETURN_ERROR(stream, "parent stream too short"); - - substream->bytes_left = (size_t)size; - stream->bytes_left -= (size_t)size; - return true; -} - -bool checkreturn pb_close_string_substream(pb_istream_t *stream, pb_istream_t *substream) -{ - if (substream->bytes_left) { - if (!pb_read(substream, NULL, substream->bytes_left)) - return false; - } - - stream->state = substream->state; - -#ifndef PB_NO_ERRMSG - stream->errmsg = substream->errmsg; -#endif - return true; -} - -/************************* - * Decode a single field * - *************************/ - -static bool checkreturn decode_basic_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field) -{ - switch (PB_LTYPE(field->type)) - { - case PB_LTYPE_BOOL: - if (wire_type != PB_WT_VARINT && wire_type != PB_WT_PACKED) - PB_RETURN_ERROR(stream, "wrong wire type"); - - return pb_dec_bool(stream, field); - - case PB_LTYPE_VARINT: - case PB_LTYPE_UVARINT: - case PB_LTYPE_SVARINT: - if (wire_type != PB_WT_VARINT && wire_type != PB_WT_PACKED) - PB_RETURN_ERROR(stream, "wrong wire type"); - - return pb_dec_varint(stream, field); - - case PB_LTYPE_FIXED32: - if (wire_type != PB_WT_32BIT && wire_type != PB_WT_PACKED) - PB_RETURN_ERROR(stream, "wrong wire type"); - - return pb_decode_fixed32(stream, field->pData); - - case PB_LTYPE_FIXED64: - if (wire_type != PB_WT_64BIT && wire_type != PB_WT_PACKED) - PB_RETURN_ERROR(stream, "wrong wire type"); - -#ifdef PB_CONVERT_DOUBLE_FLOAT - if (field->data_size == sizeof(float)) - { - return pb_decode_double_as_float(stream, (float*)field->pData); - } -#endif - -#ifdef PB_WITHOUT_64BIT - PB_RETURN_ERROR(stream, "invalid data_size"); -#else - return pb_decode_fixed64(stream, field->pData); -#endif - - case PB_LTYPE_BYTES: - if (wire_type != PB_WT_STRING) - PB_RETURN_ERROR(stream, "wrong wire type"); - - return pb_dec_bytes(stream, field); - - case PB_LTYPE_STRING: - if (wire_type != PB_WT_STRING) - PB_RETURN_ERROR(stream, "wrong wire type"); - - return pb_dec_string(stream, field); - - case PB_LTYPE_SUBMESSAGE: - case PB_LTYPE_SUBMSG_W_CB: - if (wire_type != PB_WT_STRING) - PB_RETURN_ERROR(stream, "wrong wire type"); - - return pb_dec_submessage(stream, field); - - case PB_LTYPE_FIXED_LENGTH_BYTES: - if (wire_type != PB_WT_STRING) - PB_RETURN_ERROR(stream, "wrong wire type"); - - return pb_dec_fixed_length_bytes(stream, field); - - default: - PB_RETURN_ERROR(stream, "invalid field type"); - } -} - -static bool checkreturn decode_static_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field) -{ - switch (PB_HTYPE(field->type)) - { - case PB_HTYPE_REQUIRED: - return decode_basic_field(stream, wire_type, field); - - case PB_HTYPE_OPTIONAL: - if (field->pSize != NULL) - *(bool*)field->pSize = true; - return decode_basic_field(stream, wire_type, field); - - case PB_HTYPE_REPEATED: - if (wire_type == PB_WT_STRING - && PB_LTYPE(field->type) <= PB_LTYPE_LAST_PACKABLE) - { - /* Packed array */ - bool status = true; - pb_istream_t substream; - pb_size_t *size = (pb_size_t*)field->pSize; - field->pData = (char*)field->pField + field->data_size * (*size); - - if (!pb_make_string_substream(stream, &substream)) - return false; - - while (substream.bytes_left > 0 && *size < field->array_size) - { - if (!decode_basic_field(&substream, PB_WT_PACKED, field)) - { - status = false; - break; - } - (*size)++; - field->pData = (char*)field->pData + field->data_size; - } - - if (substream.bytes_left != 0) - PB_RETURN_ERROR(stream, "array overflow"); - if (!pb_close_string_substream(stream, &substream)) - return false; - - return status; - } - else - { - /* Repeated field */ - pb_size_t *size = (pb_size_t*)field->pSize; - field->pData = (char*)field->pField + field->data_size * (*size); - - if ((*size)++ >= field->array_size) - PB_RETURN_ERROR(stream, "array overflow"); - - return decode_basic_field(stream, wire_type, field); - } - - case PB_HTYPE_ONEOF: - if (PB_LTYPE_IS_SUBMSG(field->type) && - *(pb_size_t*)field->pSize != field->tag) - { - /* We memset to zero so that any callbacks are set to NULL. - * This is because the callbacks might otherwise have values - * from some other union field. - * If callbacks are needed inside oneof field, use .proto - * option submsg_callback to have a separate callback function - * that can set the fields before submessage is decoded. - * pb_dec_submessage() will set any default values. */ - memset(field->pData, 0, (size_t)field->data_size); - - /* Set default values for the submessage fields. */ - if (field->submsg_desc->default_value != NULL || - field->submsg_desc->field_callback != NULL || - field->submsg_desc->submsg_info[0] != NULL) - { - pb_field_iter_t submsg_iter; - if (pb_field_iter_begin(&submsg_iter, field->submsg_desc, field->pData)) - { - if (!pb_message_set_to_defaults(&submsg_iter)) - PB_RETURN_ERROR(stream, "failed to set defaults"); - } - } - } - *(pb_size_t*)field->pSize = field->tag; - - return decode_basic_field(stream, wire_type, field); - - default: - PB_RETURN_ERROR(stream, "invalid field type"); - } -} - -#ifdef PB_ENABLE_MALLOC -/* Allocate storage for the field and store the pointer at iter->pData. - * array_size is the number of entries to reserve in an array. - * Zero size is not allowed, use pb_free() for releasing. - */ -static bool checkreturn allocate_field(pb_istream_t *stream, void *pData, size_t data_size, size_t array_size) -{ - void *ptr = *(void**)pData; - - if (data_size == 0 || array_size == 0) - PB_RETURN_ERROR(stream, "invalid size"); - -#ifdef __AVR__ - /* Workaround for AVR libc bug 53284: http://savannah.nongnu.org/bugs/?53284 - * Realloc to size of 1 byte can cause corruption of the malloc structures. - */ - if (data_size == 1 && array_size == 1) - { - data_size = 2; - } -#endif - - /* Check for multiplication overflows. - * This code avoids the costly division if the sizes are small enough. - * Multiplication is safe as long as only half of bits are set - * in either multiplicand. - */ - { - const size_t check_limit = (size_t)1 << (sizeof(size_t) * 4); - if (data_size >= check_limit || array_size >= check_limit) - { - const size_t size_max = (size_t)-1; - if (size_max / array_size < data_size) - { - PB_RETURN_ERROR(stream, "size too large"); - } - } - } - - /* Allocate new or expand previous allocation */ - /* Note: on failure the old pointer will remain in the structure, - * the message must be freed by caller also on error return. */ - ptr = pb_realloc(ptr, array_size * data_size); - if (ptr == NULL) - PB_RETURN_ERROR(stream, "realloc failed"); - - *(void**)pData = ptr; - return true; -} - -/* Clear a newly allocated item in case it contains a pointer, or is a submessage. */ -static void initialize_pointer_field(void *pItem, pb_field_iter_t *field) -{ - if (PB_LTYPE(field->type) == PB_LTYPE_STRING || - PB_LTYPE(field->type) == PB_LTYPE_BYTES) - { - *(void**)pItem = NULL; - } - else if (PB_LTYPE_IS_SUBMSG(field->type)) - { - /* We memset to zero so that any callbacks are set to NULL. - * Default values will be set by pb_dec_submessage(). */ - memset(pItem, 0, field->data_size); - } -} -#endif - -static bool checkreturn decode_pointer_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field) -{ -#ifndef PB_ENABLE_MALLOC - PB_UNUSED(wire_type); - PB_UNUSED(field); - PB_RETURN_ERROR(stream, "no malloc support"); -#else - switch (PB_HTYPE(field->type)) - { - case PB_HTYPE_REQUIRED: - case PB_HTYPE_OPTIONAL: - case PB_HTYPE_ONEOF: - if (PB_LTYPE_IS_SUBMSG(field->type) && *(void**)field->pField != NULL) - { - /* Duplicate field, have to release the old allocation first. */ - /* FIXME: Does this work correctly for oneofs? */ - pb_release_single_field(field); - } - - if (PB_HTYPE(field->type) == PB_HTYPE_ONEOF) - { - *(pb_size_t*)field->pSize = field->tag; - } - - if (PB_LTYPE(field->type) == PB_LTYPE_STRING || - PB_LTYPE(field->type) == PB_LTYPE_BYTES) - { - /* pb_dec_string and pb_dec_bytes handle allocation themselves */ - field->pData = field->pField; - return decode_basic_field(stream, wire_type, field); - } - else - { - if (!allocate_field(stream, field->pField, field->data_size, 1)) - return false; - - field->pData = *(void**)field->pField; - initialize_pointer_field(field->pData, field); - return decode_basic_field(stream, wire_type, field); - } - - case PB_HTYPE_REPEATED: - if (wire_type == PB_WT_STRING - && PB_LTYPE(field->type) <= PB_LTYPE_LAST_PACKABLE) - { - /* Packed array, multiple items come in at once. */ - bool status = true; - pb_size_t *size = (pb_size_t*)field->pSize; - size_t allocated_size = *size; - pb_istream_t substream; - - if (!pb_make_string_substream(stream, &substream)) - return false; - - while (substream.bytes_left) - { - if (*size == PB_SIZE_MAX) - { -#ifndef PB_NO_ERRMSG - stream->errmsg = "too many array entries"; -#endif - status = false; - break; - } - - if ((size_t)*size + 1 > allocated_size) - { - /* Allocate more storage. This tries to guess the - * number of remaining entries. Round the division - * upwards. */ - size_t remain = (substream.bytes_left - 1) / field->data_size + 1; - if (remain < PB_SIZE_MAX - allocated_size) - allocated_size += remain; - else - allocated_size += 1; - - if (!allocate_field(&substream, field->pField, field->data_size, allocated_size)) - { - status = false; - break; - } - } - - /* Decode the array entry */ - field->pData = *(char**)field->pField + field->data_size * (*size); - if (field->pData == NULL) - { - /* Shouldn't happen, but satisfies static analyzers */ - status = false; - break; - } - initialize_pointer_field(field->pData, field); - if (!decode_basic_field(&substream, PB_WT_PACKED, field)) - { - status = false; - break; - } - - (*size)++; - } - if (!pb_close_string_substream(stream, &substream)) - return false; - - return status; - } - else - { - /* Normal repeated field, i.e. only one item at a time. */ - pb_size_t *size = (pb_size_t*)field->pSize; - - if (*size == PB_SIZE_MAX) - PB_RETURN_ERROR(stream, "too many array entries"); - - if (!allocate_field(stream, field->pField, field->data_size, (size_t)(*size + 1))) - return false; - - field->pData = *(char**)field->pField + field->data_size * (*size); - (*size)++; - initialize_pointer_field(field->pData, field); - return decode_basic_field(stream, wire_type, field); - } - - default: - PB_RETURN_ERROR(stream, "invalid field type"); - } -#endif -} - -static bool checkreturn decode_callback_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field) -{ - if (!field->descriptor->field_callback) - return pb_skip_field(stream, wire_type); - - if (wire_type == PB_WT_STRING) - { - pb_istream_t substream; - size_t prev_bytes_left; - - if (!pb_make_string_substream(stream, &substream)) - return false; - - do - { - prev_bytes_left = substream.bytes_left; - if (!field->descriptor->field_callback(&substream, NULL, field)) - { - PB_SET_ERROR(stream, substream.errmsg ? substream.errmsg : "callback failed"); - return false; - } - } while (substream.bytes_left > 0 && substream.bytes_left < prev_bytes_left); - - if (!pb_close_string_substream(stream, &substream)) - return false; - - return true; - } - else - { - /* Copy the single scalar value to stack. - * This is required so that we can limit the stream length, - * which in turn allows to use same callback for packed and - * not-packed fields. */ - pb_istream_t substream; - pb_byte_t buffer[10]; - size_t size = sizeof(buffer); - - if (!read_raw_value(stream, wire_type, buffer, &size)) - return false; - substream = pb_istream_from_buffer(buffer, size); - - return field->descriptor->field_callback(&substream, NULL, field); - } -} - -static bool checkreturn decode_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field) -{ -#ifdef PB_ENABLE_MALLOC - /* When decoding an oneof field, check if there is old data that must be - * released first. */ - if (PB_HTYPE(field->type) == PB_HTYPE_ONEOF) - { - if (!pb_release_union_field(stream, field)) - return false; - } -#endif - - switch (PB_ATYPE(field->type)) - { - case PB_ATYPE_STATIC: - return decode_static_field(stream, wire_type, field); - - case PB_ATYPE_POINTER: - return decode_pointer_field(stream, wire_type, field); - - case PB_ATYPE_CALLBACK: - return decode_callback_field(stream, wire_type, field); - - default: - PB_RETURN_ERROR(stream, "invalid field type"); - } -} - -/* Default handler for extension fields. Expects to have a pb_msgdesc_t - * pointer in the extension->type->arg field, pointing to a message with - * only one field in it. */ -static bool checkreturn default_extension_decoder(pb_istream_t *stream, - pb_extension_t *extension, uint32_t tag, pb_wire_type_t wire_type) -{ - pb_field_iter_t iter; - - if (!pb_field_iter_begin_extension(&iter, extension)) - PB_RETURN_ERROR(stream, "invalid extension"); - - if (iter.tag != tag || !iter.message) - return true; - - extension->found = true; - return decode_field(stream, wire_type, &iter); -} - -/* Try to decode an unknown field as an extension field. Tries each extension - * decoder in turn, until one of them handles the field or loop ends. */ -static bool checkreturn decode_extension(pb_istream_t *stream, - uint32_t tag, pb_wire_type_t wire_type, pb_extension_t *extension) -{ - size_t pos = stream->bytes_left; - - while (extension != NULL && pos == stream->bytes_left) - { - bool status; - if (extension->type->decode) - status = extension->type->decode(stream, extension, tag, wire_type); - else - status = default_extension_decoder(stream, extension, tag, wire_type); - - if (!status) - return false; - - extension = extension->next; - } - - return true; -} - -/* Initialize message fields to default values, recursively */ -static bool pb_field_set_to_default(pb_field_iter_t *field) -{ - pb_type_t type; - type = field->type; - - if (PB_LTYPE(type) == PB_LTYPE_EXTENSION) - { - pb_extension_t *ext = *(pb_extension_t* const *)field->pData; - while (ext != NULL) - { - pb_field_iter_t ext_iter; - if (pb_field_iter_begin_extension(&ext_iter, ext)) - { - ext->found = false; - if (!pb_message_set_to_defaults(&ext_iter)) - return false; - } - ext = ext->next; - } - } - else if (PB_ATYPE(type) == PB_ATYPE_STATIC) - { - bool init_data = true; - if (PB_HTYPE(type) == PB_HTYPE_OPTIONAL && field->pSize != NULL) - { - /* Set has_field to false. Still initialize the optional field - * itself also. */ - *(bool*)field->pSize = false; - } - else if (PB_HTYPE(type) == PB_HTYPE_REPEATED || - PB_HTYPE(type) == PB_HTYPE_ONEOF) - { - /* REPEATED: Set array count to 0, no need to initialize contents. - ONEOF: Set which_field to 0. */ - *(pb_size_t*)field->pSize = 0; - init_data = false; - } - - if (init_data) - { - if (PB_LTYPE_IS_SUBMSG(field->type) && - (field->submsg_desc->default_value != NULL || - field->submsg_desc->field_callback != NULL || - field->submsg_desc->submsg_info[0] != NULL)) - { - /* Initialize submessage to defaults. - * Only needed if it has default values - * or callback/submessage fields. */ - pb_field_iter_t submsg_iter; - if (pb_field_iter_begin(&submsg_iter, field->submsg_desc, field->pData)) - { - if (!pb_message_set_to_defaults(&submsg_iter)) - return false; - } - } - else - { - /* Initialize to zeros */ - memset(field->pData, 0, (size_t)field->data_size); - } - } - } - else if (PB_ATYPE(type) == PB_ATYPE_POINTER) - { - /* Initialize the pointer to NULL. */ - *(void**)field->pField = NULL; - - /* Initialize array count to 0. */ - if (PB_HTYPE(type) == PB_HTYPE_REPEATED || - PB_HTYPE(type) == PB_HTYPE_ONEOF) - { - *(pb_size_t*)field->pSize = 0; - } - } - else if (PB_ATYPE(type) == PB_ATYPE_CALLBACK) - { - /* Don't overwrite callback */ - } - - return true; -} - -static bool pb_message_set_to_defaults(pb_field_iter_t *iter) -{ - pb_istream_t defstream = PB_ISTREAM_EMPTY; - uint32_t tag = 0; - pb_wire_type_t wire_type = PB_WT_VARINT; - bool eof; - - if (iter->descriptor->default_value) - { - defstream = pb_istream_from_buffer(iter->descriptor->default_value, (size_t)-1); - if (!pb_decode_tag(&defstream, &wire_type, &tag, &eof)) - return false; - } - - do - { - if (!pb_field_set_to_default(iter)) - return false; - - if (tag != 0 && iter->tag == tag) - { - /* We have a default value for this field in the defstream */ - if (!decode_field(&defstream, wire_type, iter)) - return false; - if (!pb_decode_tag(&defstream, &wire_type, &tag, &eof)) - return false; - - if (iter->pSize) - *(bool*)iter->pSize = false; - } - } while (pb_field_iter_next(iter)); - - return true; -} - -/********************* - * Decode all fields * - *********************/ - -static bool checkreturn pb_decode_inner(pb_istream_t *stream, const pb_msgdesc_t *fields, void *dest_struct, unsigned int flags) -{ - uint32_t extension_range_start = 0; - pb_extension_t *extensions = NULL; - - /* 'fixed_count_field' and 'fixed_count_size' track position of a repeated fixed - * count field. This can only handle _one_ repeated fixed count field that - * is unpacked and unordered among other (non repeated fixed count) fields. - */ - pb_size_t fixed_count_field = PB_SIZE_MAX; - pb_size_t fixed_count_size = 0; - pb_size_t fixed_count_total_size = 0; - - pb_fields_seen_t fields_seen = {{0, 0}}; - const uint32_t allbits = ~(uint32_t)0; - pb_field_iter_t iter; - - if (pb_field_iter_begin(&iter, fields, dest_struct)) - { - if ((flags & PB_DECODE_NOINIT) == 0) - { - if (!pb_message_set_to_defaults(&iter)) - PB_RETURN_ERROR(stream, "failed to set defaults"); - } - } - - while (stream->bytes_left) - { - uint32_t tag; - pb_wire_type_t wire_type; - bool eof; - - if (!pb_decode_tag(stream, &wire_type, &tag, &eof)) - { - if (eof) - break; - else - return false; - } - - if (tag == 0) - { - if (flags & PB_DECODE_NULLTERMINATED) - { - break; - } - else - { - PB_RETURN_ERROR(stream, "zero tag"); - } - } - - if (!pb_field_iter_find(&iter, tag) || PB_LTYPE(iter.type) == PB_LTYPE_EXTENSION) - { - /* No match found, check if it matches an extension. */ - if (extension_range_start == 0) - { - if (pb_field_iter_find_extension(&iter)) - { - extensions = *(pb_extension_t* const *)iter.pData; - extension_range_start = iter.tag; - } - - if (!extensions) - { - extension_range_start = (uint32_t)-1; - } - } - - if (tag >= extension_range_start) - { - size_t pos = stream->bytes_left; - - if (!decode_extension(stream, tag, wire_type, extensions)) - return false; - - if (pos != stream->bytes_left) - { - /* The field was handled */ - continue; - } - } - - /* No match found, skip data */ - if (!pb_skip_field(stream, wire_type)) - return false; - continue; - } - - /* If a repeated fixed count field was found, get size from - * 'fixed_count_field' as there is no counter contained in the struct. - */ - if (PB_HTYPE(iter.type) == PB_HTYPE_REPEATED && iter.pSize == &iter.array_size) - { - if (fixed_count_field != iter.index) { - /* If the new fixed count field does not match the previous one, - * check that the previous one is NULL or that it finished - * receiving all the expected data. - */ - if (fixed_count_field != PB_SIZE_MAX && - fixed_count_size != fixed_count_total_size) - { - PB_RETURN_ERROR(stream, "wrong size for fixed count field"); - } - - fixed_count_field = iter.index; - fixed_count_size = 0; - fixed_count_total_size = iter.array_size; - } - - iter.pSize = &fixed_count_size; - } - - if (PB_HTYPE(iter.type) == PB_HTYPE_REQUIRED - && iter.required_field_index < PB_MAX_REQUIRED_FIELDS) - { - uint32_t tmp = ((uint32_t)1 << (iter.required_field_index & 31)); - fields_seen.bitfield[iter.required_field_index >> 5] |= tmp; - } - - if (!decode_field(stream, wire_type, &iter)) - return false; - } - - /* Check that all elements of the last decoded fixed count field were present. */ - if (fixed_count_field != PB_SIZE_MAX && - fixed_count_size != fixed_count_total_size) - { - PB_RETURN_ERROR(stream, "wrong size for fixed count field"); - } - - /* Check that all required fields were present. */ - { - pb_size_t req_field_count = iter.descriptor->required_field_count; - - if (req_field_count > 0) - { - pb_size_t i; - - if (req_field_count > PB_MAX_REQUIRED_FIELDS) - req_field_count = PB_MAX_REQUIRED_FIELDS; - - /* Check the whole words */ - for (i = 0; i < (req_field_count >> 5); i++) - { - if (fields_seen.bitfield[i] != allbits) - PB_RETURN_ERROR(stream, "missing required field"); - } - - /* Check the remaining bits (if any) */ - if ((req_field_count & 31) != 0) - { - if (fields_seen.bitfield[req_field_count >> 5] != - (allbits >> (uint_least8_t)(32 - (req_field_count & 31)))) - { - PB_RETURN_ERROR(stream, "missing required field"); - } - } - } - } - - return true; -} - -bool checkreturn pb_decode_ex(pb_istream_t *stream, const pb_msgdesc_t *fields, void *dest_struct, unsigned int flags) -{ - bool status; - - if ((flags & PB_DECODE_DELIMITED) == 0) - { - status = pb_decode_inner(stream, fields, dest_struct, flags); - } - else - { - pb_istream_t substream; - if (!pb_make_string_substream(stream, &substream)) - return false; - - status = pb_decode_inner(&substream, fields, dest_struct, flags); - - if (!pb_close_string_substream(stream, &substream)) - return false; - } - -#ifdef PB_ENABLE_MALLOC - if (!status) - pb_release(fields, dest_struct); -#endif - - return status; -} - -bool checkreturn pb_decode(pb_istream_t *stream, const pb_msgdesc_t *fields, void *dest_struct) -{ - bool status; - - status = pb_decode_inner(stream, fields, dest_struct, 0); - -#ifdef PB_ENABLE_MALLOC - if (!status) - pb_release(fields, dest_struct); -#endif - - return status; -} - -#ifdef PB_ENABLE_MALLOC -/* Given an oneof field, if there has already been a field inside this oneof, - * release it before overwriting with a different one. */ -static bool pb_release_union_field(pb_istream_t *stream, pb_field_iter_t *field) -{ - pb_field_iter_t old_field = *field; - pb_size_t old_tag = *(pb_size_t*)field->pSize; /* Previous which_ value */ - pb_size_t new_tag = field->tag; /* New which_ value */ - - if (old_tag == 0) - return true; /* Ok, no old data in union */ - - if (old_tag == new_tag) - return true; /* Ok, old data is of same type => merge */ - - /* Release old data. The find can fail if the message struct contains - * invalid data. */ - if (!pb_field_iter_find(&old_field, old_tag)) - PB_RETURN_ERROR(stream, "invalid union tag"); - - pb_release_single_field(&old_field); - - if (PB_ATYPE(field->type) == PB_ATYPE_POINTER) - { - /* Initialize the pointer to NULL to make sure it is valid - * even in case of error return. */ - *(void**)field->pField = NULL; - field->pData = NULL; - } - - return true; -} - -static void pb_release_single_field(pb_field_iter_t *field) -{ - pb_type_t type; - type = field->type; - - if (PB_HTYPE(type) == PB_HTYPE_ONEOF) - { - if (*(pb_size_t*)field->pSize != field->tag) - return; /* This is not the current field in the union */ - } - - /* Release anything contained inside an extension or submsg. - * This has to be done even if the submsg itself is statically - * allocated. */ - if (PB_LTYPE(type) == PB_LTYPE_EXTENSION) - { - /* Release fields from all extensions in the linked list */ - pb_extension_t *ext = *(pb_extension_t**)field->pData; - while (ext != NULL) - { - pb_field_iter_t ext_iter; - if (pb_field_iter_begin_extension(&ext_iter, ext)) - { - pb_release_single_field(&ext_iter); - } - ext = ext->next; - } - } - else if (PB_LTYPE_IS_SUBMSG(type) && PB_ATYPE(type) != PB_ATYPE_CALLBACK) - { - /* Release fields in submessage or submsg array */ - pb_size_t count = 1; - - if (PB_ATYPE(type) == PB_ATYPE_POINTER) - { - field->pData = *(void**)field->pField; - } - else - { - field->pData = field->pField; - } - - if (PB_HTYPE(type) == PB_HTYPE_REPEATED) - { - count = *(pb_size_t*)field->pSize; - - if (PB_ATYPE(type) == PB_ATYPE_STATIC && count > field->array_size) - { - /* Protect against corrupted _count fields */ - count = field->array_size; - } - } - - if (field->pData) - { - for (; count > 0; count--) - { - pb_release(field->submsg_desc, field->pData); - field->pData = (char*)field->pData + field->data_size; - } - } - } - - if (PB_ATYPE(type) == PB_ATYPE_POINTER) - { - if (PB_HTYPE(type) == PB_HTYPE_REPEATED && - (PB_LTYPE(type) == PB_LTYPE_STRING || - PB_LTYPE(type) == PB_LTYPE_BYTES)) - { - /* Release entries in repeated string or bytes array */ - void **pItem = *(void***)field->pField; - pb_size_t count = *(pb_size_t*)field->pSize; - for (; count > 0; count--) - { - pb_free(*pItem); - *pItem++ = NULL; - } - } - - if (PB_HTYPE(type) == PB_HTYPE_REPEATED) - { - /* We are going to release the array, so set the size to 0 */ - *(pb_size_t*)field->pSize = 0; - } - - /* Release main pointer */ - pb_free(*(void**)field->pField); - *(void**)field->pField = NULL; - } -} - -void pb_release(const pb_msgdesc_t *fields, void *dest_struct) -{ - pb_field_iter_t iter; - - if (!dest_struct) - return; /* Ignore NULL pointers, similar to free() */ - - if (!pb_field_iter_begin(&iter, fields, dest_struct)) - return; /* Empty message type */ - - do - { - pb_release_single_field(&iter); - } while (pb_field_iter_next(&iter)); -} -#else -void pb_release(const pb_msgdesc_t *fields, void *dest_struct) -{ - /* Nothing to release without PB_ENABLE_MALLOC. */ - PB_UNUSED(fields); - PB_UNUSED(dest_struct); -} -#endif - -/* Field decoders */ - -bool pb_decode_bool(pb_istream_t *stream, bool *dest) -{ - uint32_t value; - if (!pb_decode_varint32(stream, &value)) - return false; - - *(bool*)dest = (value != 0); - return true; -} - -bool pb_decode_svarint(pb_istream_t *stream, pb_int64_t *dest) -{ - pb_uint64_t value; - if (!pb_decode_varint(stream, &value)) - return false; - - if (value & 1) - *dest = (pb_int64_t)(~(value >> 1)); - else - *dest = (pb_int64_t)(value >> 1); - - return true; -} - -bool pb_decode_fixed32(pb_istream_t *stream, void *dest) -{ - union { - uint32_t fixed32; - pb_byte_t bytes[4]; - } u; - - if (!pb_read(stream, u.bytes, 4)) - return false; - -#if defined(PB_LITTLE_ENDIAN_8BIT) && PB_LITTLE_ENDIAN_8BIT == 1 - /* fast path - if we know that we're on little endian, assign directly */ - *(uint32_t*)dest = u.fixed32; -#else - *(uint32_t*)dest = ((uint32_t)u.bytes[0] << 0) | - ((uint32_t)u.bytes[1] << 8) | - ((uint32_t)u.bytes[2] << 16) | - ((uint32_t)u.bytes[3] << 24); -#endif - return true; -} - -#ifndef PB_WITHOUT_64BIT -bool pb_decode_fixed64(pb_istream_t *stream, void *dest) -{ - union { - uint64_t fixed64; - pb_byte_t bytes[8]; - } u; - - if (!pb_read(stream, u.bytes, 8)) - return false; - -#if defined(PB_LITTLE_ENDIAN_8BIT) && PB_LITTLE_ENDIAN_8BIT == 1 - /* fast path - if we know that we're on little endian, assign directly */ - *(uint64_t*)dest = u.fixed64; -#else - *(uint64_t*)dest = ((uint64_t)u.bytes[0] << 0) | - ((uint64_t)u.bytes[1] << 8) | - ((uint64_t)u.bytes[2] << 16) | - ((uint64_t)u.bytes[3] << 24) | - ((uint64_t)u.bytes[4] << 32) | - ((uint64_t)u.bytes[5] << 40) | - ((uint64_t)u.bytes[6] << 48) | - ((uint64_t)u.bytes[7] << 56); -#endif - return true; -} -#endif - -static bool checkreturn pb_dec_bool(pb_istream_t *stream, const pb_field_iter_t *field) -{ - return pb_decode_bool(stream, (bool*)field->pData); -} - -static bool checkreturn pb_dec_varint(pb_istream_t *stream, const pb_field_iter_t *field) -{ - if (PB_LTYPE(field->type) == PB_LTYPE_UVARINT) - { - pb_uint64_t value, clamped; - if (!pb_decode_varint(stream, &value)) - return false; - - /* Cast to the proper field size, while checking for overflows */ - if (field->data_size == sizeof(pb_uint64_t)) - clamped = *(pb_uint64_t*)field->pData = value; - else if (field->data_size == sizeof(uint32_t)) - clamped = *(uint32_t*)field->pData = (uint32_t)value; - else if (field->data_size == sizeof(uint_least16_t)) - clamped = *(uint_least16_t*)field->pData = (uint_least16_t)value; - else if (field->data_size == sizeof(uint_least8_t)) - clamped = *(uint_least8_t*)field->pData = (uint_least8_t)value; - else - PB_RETURN_ERROR(stream, "invalid data_size"); - - if (clamped != value) - PB_RETURN_ERROR(stream, "integer too large"); - - return true; - } - else - { - pb_uint64_t value; - pb_int64_t svalue; - pb_int64_t clamped; - - if (PB_LTYPE(field->type) == PB_LTYPE_SVARINT) - { - if (!pb_decode_svarint(stream, &svalue)) - return false; - } - else - { - if (!pb_decode_varint(stream, &value)) - return false; - - /* See issue 97: Google's C++ protobuf allows negative varint values to - * be cast as int32_t, instead of the int64_t that should be used when - * encoding. Nanopb versions before 0.2.5 had a bug in encoding. In order to - * not break decoding of such messages, we cast <=32 bit fields to - * int32_t first to get the sign correct. - */ - if (field->data_size == sizeof(pb_int64_t)) - svalue = (pb_int64_t)value; - else - svalue = (int32_t)value; - } - - /* Cast to the proper field size, while checking for overflows */ - if (field->data_size == sizeof(pb_int64_t)) - clamped = *(pb_int64_t*)field->pData = svalue; - else if (field->data_size == sizeof(int32_t)) - clamped = *(int32_t*)field->pData = (int32_t)svalue; - else if (field->data_size == sizeof(int_least16_t)) - clamped = *(int_least16_t*)field->pData = (int_least16_t)svalue; - else if (field->data_size == sizeof(int_least8_t)) - clamped = *(int_least8_t*)field->pData = (int_least8_t)svalue; - else - PB_RETURN_ERROR(stream, "invalid data_size"); - - if (clamped != svalue) - PB_RETURN_ERROR(stream, "integer too large"); - - return true; - } -} - -static bool checkreturn pb_dec_bytes(pb_istream_t *stream, const pb_field_iter_t *field) -{ - uint32_t size; - size_t alloc_size; - pb_bytes_array_t *dest; - - if (!pb_decode_varint32(stream, &size)) - return false; - - if (size > PB_SIZE_MAX) - PB_RETURN_ERROR(stream, "bytes overflow"); - - alloc_size = PB_BYTES_ARRAY_T_ALLOCSIZE(size); - if (size > alloc_size) - PB_RETURN_ERROR(stream, "size too large"); - - if (PB_ATYPE(field->type) == PB_ATYPE_POINTER) - { -#ifndef PB_ENABLE_MALLOC - PB_RETURN_ERROR(stream, "no malloc support"); -#else - if (stream->bytes_left < size) - PB_RETURN_ERROR(stream, "end-of-stream"); - - if (!allocate_field(stream, field->pData, alloc_size, 1)) - return false; - dest = *(pb_bytes_array_t**)field->pData; -#endif - } - else - { - if (alloc_size > field->data_size) - PB_RETURN_ERROR(stream, "bytes overflow"); - dest = (pb_bytes_array_t*)field->pData; - } - - dest->size = (pb_size_t)size; - return pb_read(stream, dest->bytes, (size_t)size); -} - -static bool checkreturn pb_dec_string(pb_istream_t *stream, const pb_field_iter_t *field) -{ - uint32_t size; - size_t alloc_size; - pb_byte_t *dest = (pb_byte_t*)field->pData; - - if (!pb_decode_varint32(stream, &size)) - return false; - - if (size == (uint32_t)-1) - PB_RETURN_ERROR(stream, "size too large"); - - /* Space for null terminator */ - alloc_size = (size_t)(size + 1); - - if (alloc_size < size) - PB_RETURN_ERROR(stream, "size too large"); - - if (PB_ATYPE(field->type) == PB_ATYPE_POINTER) - { -#ifndef PB_ENABLE_MALLOC - PB_RETURN_ERROR(stream, "no malloc support"); -#else - if (stream->bytes_left < size) - PB_RETURN_ERROR(stream, "end-of-stream"); - - if (!allocate_field(stream, field->pData, alloc_size, 1)) - return false; - dest = *(pb_byte_t**)field->pData; -#endif - } - else - { - if (alloc_size > field->data_size) - PB_RETURN_ERROR(stream, "string overflow"); - } - - dest[size] = 0; - - if (!pb_read(stream, dest, (size_t)size)) - return false; - -#ifdef PB_VALIDATE_UTF8 - if (!pb_validate_utf8((const char*)dest)) - PB_RETURN_ERROR(stream, "invalid utf8"); -#endif - - return true; -} - -static bool checkreturn pb_dec_submessage(pb_istream_t *stream, const pb_field_iter_t *field) -{ - bool status = true; - bool submsg_consumed = false; - pb_istream_t substream; - - if (!pb_make_string_substream(stream, &substream)) - return false; - - if (field->submsg_desc == NULL) - PB_RETURN_ERROR(stream, "invalid field descriptor"); - - /* Submessages can have a separate message-level callback that is called - * before decoding the message. Typically it is used to set callback fields - * inside oneofs. */ - if (PB_LTYPE(field->type) == PB_LTYPE_SUBMSG_W_CB && field->pSize != NULL) - { - /* Message callback is stored right before pSize. */ - pb_callback_t *callback = (pb_callback_t*)field->pSize - 1; - if (callback->funcs.decode) - { - status = callback->funcs.decode(&substream, field, &callback->arg); - - if (substream.bytes_left == 0) - { - submsg_consumed = true; - } - } - } - - /* Now decode the submessage contents */ - if (status && !submsg_consumed) - { - unsigned int flags = 0; - - /* Static required/optional fields are already initialized by top-level - * pb_decode(), no need to initialize them again. */ - if (PB_ATYPE(field->type) == PB_ATYPE_STATIC && - PB_HTYPE(field->type) != PB_HTYPE_REPEATED) - { - flags = PB_DECODE_NOINIT; - } - - status = pb_decode_inner(&substream, field->submsg_desc, field->pData, flags); - } - - if (!pb_close_string_substream(stream, &substream)) - return false; - - return status; -} - -static bool checkreturn pb_dec_fixed_length_bytes(pb_istream_t *stream, const pb_field_iter_t *field) -{ - uint32_t size; - - if (!pb_decode_varint32(stream, &size)) - return false; - - if (size > PB_SIZE_MAX) - PB_RETURN_ERROR(stream, "bytes overflow"); - - if (size == 0) - { - /* As a special case, treat empty bytes string as all zeros for fixed_length_bytes. */ - memset(field->pData, 0, (size_t)field->data_size); - return true; - } - - if (size != field->data_size) - PB_RETURN_ERROR(stream, "incorrect fixed length bytes size"); - - return pb_read(stream, (pb_byte_t*)field->pData, (size_t)field->data_size); -} - -#ifdef PB_CONVERT_DOUBLE_FLOAT -bool pb_decode_double_as_float(pb_istream_t *stream, float *dest) -{ - uint_least8_t sign; - int exponent; - uint32_t mantissa; - uint64_t value; - union { float f; uint32_t i; } out; - - if (!pb_decode_fixed64(stream, &value)) - return false; - - /* Decompose input value */ - sign = (uint_least8_t)((value >> 63) & 1); - exponent = (int)((value >> 52) & 0x7FF) - 1023; - mantissa = (value >> 28) & 0xFFFFFF; /* Highest 24 bits */ - - /* Figure if value is in range representable by floats. */ - if (exponent == 1024) - { - /* Special value */ - exponent = 128; - mantissa >>= 1; - } - else - { - if (exponent > 127) - { - /* Too large, convert to infinity */ - exponent = 128; - mantissa = 0; - } - else if (exponent < -150) - { - /* Too small, convert to zero */ - exponent = -127; - mantissa = 0; - } - else if (exponent < -126) - { - /* Denormalized */ - mantissa |= 0x1000000; - mantissa >>= (-126 - exponent); - exponent = -127; - } - - /* Round off mantissa */ - mantissa = (mantissa + 1) >> 1; - - /* Check if mantissa went over 2.0 */ - if (mantissa & 0x800000) - { - exponent += 1; - mantissa &= 0x7FFFFF; - mantissa >>= 1; - } - } - - /* Combine fields */ - out.i = mantissa; - out.i |= (uint32_t)(exponent + 127) << 23; - out.i |= (uint32_t)sign << 31; - - *dest = out.f; - return true; -} -#endif diff --git a/src/network/pb_decode.h b/src/network/pb_decode.h deleted file mode 100644 index ae1d3ccf2..000000000 --- a/src/network/pb_decode.h +++ /dev/null @@ -1,193 +0,0 @@ -/* pb_decode.h: Functions to decode protocol buffers. Depends on pb_decode.c. - * The main function is pb_decode. You also need an input stream, and the - * field descriptions created by nanopb_generator.py. - */ - -#ifndef PB_DECODE_H_INCLUDED -#define PB_DECODE_H_INCLUDED - -#include "pb.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/* Structure for defining custom input streams. You will need to provide - * a callback function to read the bytes from your storage, which can be - * for example a file or a network socket. - * - * The callback must conform to these rules: - * - * 1) Return false on IO errors. This will cause decoding to abort. - * 2) You can use state to store your own data (e.g. buffer pointer), - * and rely on pb_read to verify that no-body reads past bytes_left. - * 3) Your callback may be used with substreams, in which case bytes_left - * is different than from the main stream. Don't use bytes_left to compute - * any pointers. - */ -struct pb_istream_s -{ -#ifdef PB_BUFFER_ONLY - /* Callback pointer is not used in buffer-only configuration. - * Having an int pointer here allows binary compatibility but - * gives an error if someone tries to assign callback function. - */ - int *callback; -#else - bool (*callback)(pb_istream_t *stream, pb_byte_t *buf, size_t count); -#endif - - void *state; /* Free field for use by callback implementation */ - size_t bytes_left; - -#ifndef PB_NO_ERRMSG - const char *errmsg; -#endif -}; - -#ifndef PB_NO_ERRMSG -#define PB_ISTREAM_EMPTY {0,0,0,0} -#else -#define PB_ISTREAM_EMPTY {0,0,0} -#endif - -/*************************** - * Main decoding functions * - ***************************/ - -/* Decode a single protocol buffers message from input stream into a C structure. - * Returns true on success, false on any failure. - * The actual struct pointed to by dest must match the description in fields. - * Callback fields of the destination structure must be initialized by caller. - * All other fields will be initialized by this function. - * - * Example usage: - * MyMessage msg = {}; - * uint8_t buffer[64]; - * pb_istream_t stream; - * - * // ... read some data into buffer ... - * - * stream = pb_istream_from_buffer(buffer, count); - * pb_decode(&stream, MyMessage_fields, &msg); - */ -bool pb_decode(pb_istream_t *stream, const pb_msgdesc_t *fields, void *dest_struct); - -/* Extended version of pb_decode, with several options to control - * the decoding process: - * - * PB_DECODE_NOINIT: Do not initialize the fields to default values. - * This is slightly faster if you do not need the default - * values and instead initialize the structure to 0 using - * e.g. memset(). This can also be used for merging two - * messages, i.e. combine already existing data with new - * values. - * - * PB_DECODE_DELIMITED: Input message starts with the message size as varint. - * Corresponds to parseDelimitedFrom() in Google's - * protobuf API. - * - * PB_DECODE_NULLTERMINATED: Stop reading when field tag is read as 0. This allows - * reading null terminated messages. - * NOTE: Until nanopb-0.4.0, pb_decode() also allows - * null-termination. This behaviour is not supported in - * most other protobuf implementations, so PB_DECODE_DELIMITED - * is a better option for compatibility. - * - * Multiple flags can be combined with bitwise or (| operator) - */ -#define PB_DECODE_NOINIT 0x01U -#define PB_DECODE_DELIMITED 0x02U -#define PB_DECODE_NULLTERMINATED 0x04U -bool pb_decode_ex(pb_istream_t *stream, const pb_msgdesc_t *fields, void *dest_struct, unsigned int flags); - -/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ -#define pb_decode_noinit(s,f,d) pb_decode_ex(s,f,d, PB_DECODE_NOINIT) -#define pb_decode_delimited(s,f,d) pb_decode_ex(s,f,d, PB_DECODE_DELIMITED) -#define pb_decode_delimited_noinit(s,f,d) pb_decode_ex(s,f,d, PB_DECODE_DELIMITED | PB_DECODE_NOINIT) -#define pb_decode_nullterminated(s,f,d) pb_decode_ex(s,f,d, PB_DECODE_NULLTERMINATED) - -/* Release any allocated pointer fields. If you use dynamic allocation, you should - * call this for any successfully decoded message when you are done with it. If - * pb_decode() returns with an error, the message is already released. - */ -void pb_release(const pb_msgdesc_t *fields, void *dest_struct); - -/************************************** - * Functions for manipulating streams * - **************************************/ - -/* Create an input stream for reading from a memory buffer. - * - * msglen should be the actual length of the message, not the full size of - * allocated buffer. - * - * Alternatively, you can use a custom stream that reads directly from e.g. - * a file or a network socket. - */ -pb_istream_t pb_istream_from_buffer(const pb_byte_t *buf, size_t msglen); - -/* Function to read from a pb_istream_t. You can use this if you need to - * read some custom header data, or to read data in field callbacks. - */ -bool pb_read(pb_istream_t *stream, pb_byte_t *buf, size_t count); - - -/************************************************ - * Helper functions for writing field callbacks * - ************************************************/ - -/* Decode the tag for the next field in the stream. Gives the wire type and - * field tag. At end of the message, returns false and sets eof to true. */ -bool pb_decode_tag(pb_istream_t *stream, pb_wire_type_t *wire_type, uint32_t *tag, bool *eof); - -/* Skip the field payload data, given the wire type. */ -bool pb_skip_field(pb_istream_t *stream, pb_wire_type_t wire_type); - -/* Decode an integer in the varint format. This works for enum, int32, - * int64, uint32 and uint64 field types. */ -#ifndef PB_WITHOUT_64BIT -bool pb_decode_varint(pb_istream_t *stream, uint64_t *dest); -#else -#define pb_decode_varint pb_decode_varint32 -#endif - -/* Decode an integer in the varint format. This works for enum, int32, - * and uint32 field types. */ -bool pb_decode_varint32(pb_istream_t *stream, uint32_t *dest); - -/* Decode a bool value in varint format. */ -bool pb_decode_bool(pb_istream_t *stream, bool *dest); - -/* Decode an integer in the zig-zagged svarint format. This works for sint32 - * and sint64. */ -#ifndef PB_WITHOUT_64BIT -bool pb_decode_svarint(pb_istream_t *stream, int64_t *dest); -#else -bool pb_decode_svarint(pb_istream_t *stream, int32_t *dest); -#endif - -/* Decode a fixed32, sfixed32 or float value. You need to pass a pointer to - * a 4-byte wide C variable. */ -bool pb_decode_fixed32(pb_istream_t *stream, void *dest); - -#ifndef PB_WITHOUT_64BIT -/* Decode a fixed64, sfixed64 or double value. You need to pass a pointer to - * a 8-byte wide C variable. */ -bool pb_decode_fixed64(pb_istream_t *stream, void *dest); -#endif - -#ifdef PB_CONVERT_DOUBLE_FLOAT -/* Decode a double value into float variable. */ -bool pb_decode_double_as_float(pb_istream_t *stream, float *dest); -#endif - -/* Make a limited-length substream for reading a PB_WT_STRING field. */ -bool pb_make_string_substream(pb_istream_t *stream, pb_istream_t *substream); -bool pb_close_string_substream(pb_istream_t *stream, pb_istream_t *substream); - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif diff --git a/src/network/pb_encode.c b/src/network/pb_encode.c deleted file mode 100644 index a4e30b471..000000000 --- a/src/network/pb_encode.c +++ /dev/null @@ -1,1001 +0,0 @@ -/* pb_encode.c -- encode a protobuf using minimal resources - * - * 2011 Petteri Aimonen - */ - -#include "pb.h" -#include "pb_encode.h" -#include "pb_common.h" - -/* Use the GCC warn_unused_result attribute to check that all return values - * are propagated correctly. On other compilers and gcc before 3.4.0 just - * ignore the annotation. - */ -#if !defined(__GNUC__) || ( __GNUC__ < 3) || (__GNUC__ == 3 && __GNUC_MINOR__ < 4) - #define checkreturn -#else - #define checkreturn __attribute__((warn_unused_result)) -#endif - -/************************************** - * Declarations internal to this file * - **************************************/ -static bool checkreturn buf_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count); -static bool checkreturn encode_array(pb_ostream_t *stream, pb_field_iter_t *field); -static bool checkreturn pb_check_proto3_default_value(const pb_field_iter_t *field); -static bool checkreturn encode_basic_field(pb_ostream_t *stream, const pb_field_iter_t *field); -static bool checkreturn encode_callback_field(pb_ostream_t *stream, const pb_field_iter_t *field); -static bool checkreturn encode_field(pb_ostream_t *stream, pb_field_iter_t *field); -static bool checkreturn encode_extension_field(pb_ostream_t *stream, const pb_field_iter_t *field); -static bool checkreturn default_extension_encoder(pb_ostream_t *stream, const pb_extension_t *extension); -static bool checkreturn pb_encode_varint_32(pb_ostream_t *stream, uint32_t low, uint32_t high); -static bool checkreturn pb_enc_bool(pb_ostream_t *stream, const pb_field_iter_t *field); -static bool checkreturn pb_enc_varint(pb_ostream_t *stream, const pb_field_iter_t *field); -static bool checkreturn pb_enc_fixed(pb_ostream_t *stream, const pb_field_iter_t *field); -static bool checkreturn pb_enc_bytes(pb_ostream_t *stream, const pb_field_iter_t *field); -static bool checkreturn pb_enc_string(pb_ostream_t *stream, const pb_field_iter_t *field); -static bool checkreturn pb_enc_submessage(pb_ostream_t *stream, const pb_field_iter_t *field); -static bool checkreturn pb_enc_fixed_length_bytes(pb_ostream_t *stream, const pb_field_iter_t *field); - -#ifdef PB_WITHOUT_64BIT -#define pb_int64_t int32_t -#define pb_uint64_t uint32_t -#else -#define pb_int64_t int64_t -#define pb_uint64_t uint64_t -#endif - -/******************************* - * pb_ostream_t implementation * - *******************************/ - -static bool checkreturn buf_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count) -{ - pb_byte_t *dest = (pb_byte_t*)stream->state; - stream->state = dest + count; - - if ((dest != NULL) && (buf != NULL)) - memcpy(dest, buf, count * sizeof(pb_byte_t)); - - return true; -} - -pb_ostream_t pb_ostream_from_buffer(pb_byte_t *buf, size_t bufsize) -{ - pb_ostream_t stream; -#ifdef PB_BUFFER_ONLY - /* In PB_BUFFER_ONLY configuration the callback pointer is just int*. - * NULL pointer marks a sizing field, so put a non-NULL value to mark a buffer stream. - */ - static const int marker = 0; - stream.callback = ▮ -#else - stream.callback = &buf_write; -#endif - stream.state = buf; - stream.max_size = bufsize; - stream.bytes_written = 0; -#ifndef PB_NO_ERRMSG - stream.errmsg = NULL; -#endif - return stream; -} - -bool checkreturn pb_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count) -{ - if (count > 0 && stream->callback != NULL) - { - if (stream->bytes_written + count < stream->bytes_written || - stream->bytes_written + count > stream->max_size) - { - PB_RETURN_ERROR(stream, "stream full"); - } - -#ifdef PB_BUFFER_ONLY - if (!buf_write(stream, buf, count)) - PB_RETURN_ERROR(stream, "io error"); -#else - if (!stream->callback(stream, buf, count)) - PB_RETURN_ERROR(stream, "io error"); -#endif - } - - stream->bytes_written += count; - return true; -} - -/************************* - * Encode a single field * - *************************/ - -/* Read a bool value without causing undefined behavior even if the value - * is invalid. See issue #434 and - * https://stackoverflow.com/questions/27661768/weird-results-for-conditional - */ -static bool safe_read_bool(const void *pSize) -{ - const char *p = (const char *)pSize; - size_t i; - for (i = 0; i < sizeof(bool); i++) - { - if (p[i] != 0) - return true; - } - return false; -} - -/* Encode a static array. Handles the size calculations and possible packing. */ -static bool checkreturn encode_array(pb_ostream_t *stream, pb_field_iter_t *field) -{ - pb_size_t i; - pb_size_t count; -#ifndef PB_ENCODE_ARRAYS_UNPACKED - size_t size; -#endif - - count = *(pb_size_t*)field->pSize; - - if (count == 0) - return true; - - if (PB_ATYPE(field->type) != PB_ATYPE_POINTER && count > field->array_size) - PB_RETURN_ERROR(stream, "array max size exceeded"); - -#ifndef PB_ENCODE_ARRAYS_UNPACKED - /* We always pack arrays if the datatype allows it. */ - if (PB_LTYPE(field->type) <= PB_LTYPE_LAST_PACKABLE) - { - if (!pb_encode_tag(stream, PB_WT_STRING, field->tag)) - return false; - - /* Determine the total size of packed array. */ - if (PB_LTYPE(field->type) == PB_LTYPE_FIXED32) - { - size = 4 * (size_t)count; - } - else if (PB_LTYPE(field->type) == PB_LTYPE_FIXED64) - { - size = 8 * (size_t)count; - } - else - { - pb_ostream_t sizestream = PB_OSTREAM_SIZING; - void *pData_orig = field->pData; - for (i = 0; i < count; i++) - { - if (!pb_enc_varint(&sizestream, field)) - PB_RETURN_ERROR(stream, PB_GET_ERROR(&sizestream)); - field->pData = (char*)field->pData + field->data_size; - } - field->pData = pData_orig; - size = sizestream.bytes_written; - } - - if (!pb_encode_varint(stream, (pb_uint64_t)size)) - return false; - - if (stream->callback == NULL) - return pb_write(stream, NULL, size); /* Just sizing.. */ - - /* Write the data */ - for (i = 0; i < count; i++) - { - if (PB_LTYPE(field->type) == PB_LTYPE_FIXED32 || PB_LTYPE(field->type) == PB_LTYPE_FIXED64) - { - if (!pb_enc_fixed(stream, field)) - return false; - } - else - { - if (!pb_enc_varint(stream, field)) - return false; - } - - field->pData = (char*)field->pData + field->data_size; - } - } - else /* Unpacked fields */ -#endif - { - for (i = 0; i < count; i++) - { - /* Normally the data is stored directly in the array entries, but - * for pointer-type string and bytes fields, the array entries are - * actually pointers themselves also. So we have to dereference once - * more to get to the actual data. */ - if (PB_ATYPE(field->type) == PB_ATYPE_POINTER && - (PB_LTYPE(field->type) == PB_LTYPE_STRING || - PB_LTYPE(field->type) == PB_LTYPE_BYTES)) - { - bool status; - void *pData_orig = field->pData; - field->pData = *(void* const*)field->pData; - - if (!field->pData) - { - /* Null pointer in array is treated as empty string / bytes */ - status = pb_encode_tag_for_field(stream, field) && - pb_encode_varint(stream, 0); - } - else - { - status = encode_basic_field(stream, field); - } - - field->pData = pData_orig; - - if (!status) - return false; - } - else - { - if (!encode_basic_field(stream, field)) - return false; - } - field->pData = (char*)field->pData + field->data_size; - } - } - - return true; -} - -/* In proto3, all fields are optional and are only encoded if their value is "non-zero". - * This function implements the check for the zero value. */ -static bool checkreturn pb_check_proto3_default_value(const pb_field_iter_t *field) -{ - pb_type_t type = field->type; - - if (PB_ATYPE(type) == PB_ATYPE_STATIC) - { - if (PB_HTYPE(type) == PB_HTYPE_REQUIRED) - { - /* Required proto2 fields inside proto3 submessage, pretty rare case */ - return false; - } - else if (PB_HTYPE(type) == PB_HTYPE_REPEATED) - { - /* Repeated fields inside proto3 submessage: present if count != 0 */ - return *(const pb_size_t*)field->pSize == 0; - } - else if (PB_HTYPE(type) == PB_HTYPE_ONEOF) - { - /* Oneof fields */ - return *(const pb_size_t*)field->pSize == 0; - } - else if (PB_HTYPE(type) == PB_HTYPE_OPTIONAL && field->pSize != NULL) - { - /* Proto2 optional fields inside proto3 message, or proto3 - * submessage fields. */ - return safe_read_bool(field->pSize) == false; - } - else if (field->descriptor->default_value) - { - /* Proto3 messages do not have default values, but proto2 messages - * can contain optional fields without has_fields (generator option 'proto3'). - * In this case they must always be encoded, to make sure that the - * non-zero default value is overwritten. - */ - return false; - } - - /* Rest is proto3 singular fields */ - if (PB_LTYPE(type) <= PB_LTYPE_LAST_PACKABLE) - { - /* Simple integer / float fields */ - pb_size_t i; - const char *p = (const char*)field->pData; - for (i = 0; i < field->data_size; i++) - { - if (p[i] != 0) - { - return false; - } - } - - return true; - } - else if (PB_LTYPE(type) == PB_LTYPE_BYTES) - { - const pb_bytes_array_t *bytes = (const pb_bytes_array_t*)field->pData; - return bytes->size == 0; - } - else if (PB_LTYPE(type) == PB_LTYPE_STRING) - { - return *(const char*)field->pData == '\0'; - } - else if (PB_LTYPE(type) == PB_LTYPE_FIXED_LENGTH_BYTES) - { - /* Fixed length bytes is only empty if its length is fixed - * as 0. Which would be pretty strange, but we can check - * it anyway. */ - return field->data_size == 0; - } - else if (PB_LTYPE_IS_SUBMSG(type)) - { - /* Check all fields in the submessage to find if any of them - * are non-zero. The comparison cannot be done byte-per-byte - * because the C struct may contain padding bytes that must - * be skipped. Note that usually proto3 submessages have - * a separate has_field that is checked earlier in this if. - */ - pb_field_iter_t iter; - if (pb_field_iter_begin(&iter, field->submsg_desc, field->pData)) - { - do - { - if (!pb_check_proto3_default_value(&iter)) - { - return false; - } - } while (pb_field_iter_next(&iter)); - } - return true; - } - } - else if (PB_ATYPE(type) == PB_ATYPE_POINTER) - { - return field->pData == NULL; - } - else if (PB_ATYPE(type) == PB_ATYPE_CALLBACK) - { - if (PB_LTYPE(type) == PB_LTYPE_EXTENSION) - { - const pb_extension_t *extension = *(const pb_extension_t* const *)field->pData; - return extension == NULL; - } - else if (field->descriptor->field_callback == pb_default_field_callback) - { - pb_callback_t *pCallback = (pb_callback_t*)field->pData; - return pCallback->funcs.encode == NULL; - } - else - { - return field->descriptor->field_callback == NULL; - } - } - - return false; /* Not typically reached, safe default for weird special cases. */ -} - -/* Encode a field with static or pointer allocation, i.e. one whose data - * is available to the encoder directly. */ -static bool checkreturn encode_basic_field(pb_ostream_t *stream, const pb_field_iter_t *field) -{ - if (!field->pData) - { - /* Missing pointer field */ - return true; - } - - if (!pb_encode_tag_for_field(stream, field)) - return false; - - switch (PB_LTYPE(field->type)) - { - case PB_LTYPE_BOOL: - return pb_enc_bool(stream, field); - - case PB_LTYPE_VARINT: - case PB_LTYPE_UVARINT: - case PB_LTYPE_SVARINT: - return pb_enc_varint(stream, field); - - case PB_LTYPE_FIXED32: - case PB_LTYPE_FIXED64: - return pb_enc_fixed(stream, field); - - case PB_LTYPE_BYTES: - return pb_enc_bytes(stream, field); - - case PB_LTYPE_STRING: - return pb_enc_string(stream, field); - - case PB_LTYPE_SUBMESSAGE: - case PB_LTYPE_SUBMSG_W_CB: - return pb_enc_submessage(stream, field); - - case PB_LTYPE_FIXED_LENGTH_BYTES: - return pb_enc_fixed_length_bytes(stream, field); - - default: - PB_RETURN_ERROR(stream, "invalid field type"); - } -} - -/* Encode a field with callback semantics. This means that a user function is - * called to provide and encode the actual data. */ -static bool checkreturn encode_callback_field(pb_ostream_t *stream, const pb_field_iter_t *field) -{ - if (field->descriptor->field_callback != NULL) - { - if (!field->descriptor->field_callback(NULL, stream, field)) - PB_RETURN_ERROR(stream, "callback error"); - } - return true; -} - -/* Encode a single field of any callback, pointer or static type. */ -static bool checkreturn encode_field(pb_ostream_t *stream, pb_field_iter_t *field) -{ - /* Check field presence */ - if (PB_HTYPE(field->type) == PB_HTYPE_ONEOF) - { - if (*(const pb_size_t*)field->pSize != field->tag) - { - /* Different type oneof field */ - return true; - } - } - else if (PB_HTYPE(field->type) == PB_HTYPE_OPTIONAL) - { - if (field->pSize) - { - if (safe_read_bool(field->pSize) == false) - { - /* Missing optional field */ - return true; - } - } - else if (PB_ATYPE(field->type) == PB_ATYPE_STATIC) - { - /* Proto3 singular field */ - if (pb_check_proto3_default_value(field)) - return true; - } - } - - if (!field->pData) - { - if (PB_HTYPE(field->type) == PB_HTYPE_REQUIRED) - PB_RETURN_ERROR(stream, "missing required field"); - - /* Pointer field set to NULL */ - return true; - } - - /* Then encode field contents */ - if (PB_ATYPE(field->type) == PB_ATYPE_CALLBACK) - { - return encode_callback_field(stream, field); - } - else if (PB_HTYPE(field->type) == PB_HTYPE_REPEATED) - { - return encode_array(stream, field); - } - else - { - return encode_basic_field(stream, field); - } -} - -/* Default handler for extension fields. Expects to have a pb_msgdesc_t - * pointer in the extension->type->arg field, pointing to a message with - * only one field in it. */ -static bool checkreturn default_extension_encoder(pb_ostream_t *stream, const pb_extension_t *extension) -{ - pb_field_iter_t iter; - - if (!pb_field_iter_begin_extension_const(&iter, extension)) - PB_RETURN_ERROR(stream, "invalid extension"); - - return encode_field(stream, &iter); -} - - -/* Walk through all the registered extensions and give them a chance - * to encode themselves. */ -static bool checkreturn encode_extension_field(pb_ostream_t *stream, const pb_field_iter_t *field) -{ - const pb_extension_t *extension = *(const pb_extension_t* const *)field->pData; - - while (extension) - { - bool status; - if (extension->type->encode) - status = extension->type->encode(stream, extension); - else - status = default_extension_encoder(stream, extension); - - if (!status) - return false; - - extension = extension->next; - } - - return true; -} - -/********************* - * Encode all fields * - *********************/ - -bool checkreturn pb_encode(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct) -{ - pb_field_iter_t iter; - if (!pb_field_iter_begin_const(&iter, fields, src_struct)) - return true; /* Empty message type */ - - do { - if (PB_LTYPE(iter.type) == PB_LTYPE_EXTENSION) - { - /* Special case for the extension field placeholder */ - if (!encode_extension_field(stream, &iter)) - return false; - } - else - { - /* Regular field */ - if (!encode_field(stream, &iter)) - return false; - } - } while (pb_field_iter_next(&iter)); - - return true; -} - -bool checkreturn pb_encode_ex(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct, unsigned int flags) -{ - if ((flags & PB_ENCODE_DELIMITED) != 0) - { - return pb_encode_submessage(stream, fields, src_struct); - } - else if ((flags & PB_ENCODE_NULLTERMINATED) != 0) - { - const pb_byte_t zero = 0; - - if (!pb_encode(stream, fields, src_struct)) - return false; - - return pb_write(stream, &zero, 1); - } - else - { - return pb_encode(stream, fields, src_struct); - } -} - -bool pb_get_encoded_size(size_t *size, const pb_msgdesc_t *fields, const void *src_struct) -{ - pb_ostream_t stream = PB_OSTREAM_SIZING; - - if (!pb_encode(&stream, fields, src_struct)) - return false; - - *size = stream.bytes_written; - return true; -} - -/******************** - * Helper functions * - ********************/ - -/* This function avoids 64-bit shifts as they are quite slow on many platforms. */ -static bool checkreturn pb_encode_varint_32(pb_ostream_t *stream, uint32_t low, uint32_t high) -{ - size_t i = 0; - pb_byte_t buffer[10]; - pb_byte_t byte = (pb_byte_t)(low & 0x7F); - low >>= 7; - - while (i < 4 && (low != 0 || high != 0)) - { - byte |= 0x80; - buffer[i++] = byte; - byte = (pb_byte_t)(low & 0x7F); - low >>= 7; - } - - if (high) - { - byte = (pb_byte_t)(byte | ((high & 0x07) << 4)); - high >>= 3; - - while (high) - { - byte |= 0x80; - buffer[i++] = byte; - byte = (pb_byte_t)(high & 0x7F); - high >>= 7; - } - } - - buffer[i++] = byte; - - return pb_write(stream, buffer, i); -} - -bool checkreturn pb_encode_varint(pb_ostream_t *stream, pb_uint64_t value) -{ - if (value <= 0x7F) - { - /* Fast path: single byte */ - pb_byte_t byte = (pb_byte_t)value; - return pb_write(stream, &byte, 1); - } - else - { -#ifdef PB_WITHOUT_64BIT - return pb_encode_varint_32(stream, value, 0); -#else - return pb_encode_varint_32(stream, (uint32_t)value, (uint32_t)(value >> 32)); -#endif - } -} - -bool checkreturn pb_encode_svarint(pb_ostream_t *stream, pb_int64_t value) -{ - pb_uint64_t zigzagged; - pb_uint64_t mask = ((pb_uint64_t)-1) >> 1; /* Satisfy clang -fsanitize=integer */ - if (value < 0) - zigzagged = ~(((pb_uint64_t)value & mask) << 1); - else - zigzagged = (pb_uint64_t)value << 1; - - return pb_encode_varint(stream, zigzagged); -} - -bool checkreturn pb_encode_fixed32(pb_ostream_t *stream, const void *value) -{ -#if defined(PB_LITTLE_ENDIAN_8BIT) && PB_LITTLE_ENDIAN_8BIT == 1 - /* Fast path if we know that we're on little endian */ - return pb_write(stream, (const pb_byte_t*)value, 4); -#else - uint32_t val = *(const uint32_t*)value; - pb_byte_t bytes[4]; - bytes[0] = (pb_byte_t)(val & 0xFF); - bytes[1] = (pb_byte_t)((val >> 8) & 0xFF); - bytes[2] = (pb_byte_t)((val >> 16) & 0xFF); - bytes[3] = (pb_byte_t)((val >> 24) & 0xFF); - return pb_write(stream, bytes, 4); -#endif -} - -#ifndef PB_WITHOUT_64BIT -bool checkreturn pb_encode_fixed64(pb_ostream_t *stream, const void *value) -{ -#if defined(PB_LITTLE_ENDIAN_8BIT) && PB_LITTLE_ENDIAN_8BIT == 1 - /* Fast path if we know that we're on little endian */ - return pb_write(stream, (const pb_byte_t*)value, 8); -#else - uint64_t val = *(const uint64_t*)value; - pb_byte_t bytes[8]; - bytes[0] = (pb_byte_t)(val & 0xFF); - bytes[1] = (pb_byte_t)((val >> 8) & 0xFF); - bytes[2] = (pb_byte_t)((val >> 16) & 0xFF); - bytes[3] = (pb_byte_t)((val >> 24) & 0xFF); - bytes[4] = (pb_byte_t)((val >> 32) & 0xFF); - bytes[5] = (pb_byte_t)((val >> 40) & 0xFF); - bytes[6] = (pb_byte_t)((val >> 48) & 0xFF); - bytes[7] = (pb_byte_t)((val >> 56) & 0xFF); - return pb_write(stream, bytes, 8); -#endif -} -#endif - -bool checkreturn pb_encode_tag(pb_ostream_t *stream, pb_wire_type_t wiretype, uint32_t field_number) -{ - pb_uint64_t tag = ((pb_uint64_t)field_number << 3) | wiretype; - return pb_encode_varint(stream, tag); -} - -bool pb_encode_tag_for_field ( pb_ostream_t* stream, const pb_field_iter_t* field ) -{ - pb_wire_type_t wiretype; - switch (PB_LTYPE(field->type)) - { - case PB_LTYPE_BOOL: - case PB_LTYPE_VARINT: - case PB_LTYPE_UVARINT: - case PB_LTYPE_SVARINT: - wiretype = PB_WT_VARINT; - break; - - case PB_LTYPE_FIXED32: - wiretype = PB_WT_32BIT; - break; - - case PB_LTYPE_FIXED64: - wiretype = PB_WT_64BIT; - break; - - case PB_LTYPE_BYTES: - case PB_LTYPE_STRING: - case PB_LTYPE_SUBMESSAGE: - case PB_LTYPE_SUBMSG_W_CB: - case PB_LTYPE_FIXED_LENGTH_BYTES: - wiretype = PB_WT_STRING; - break; - - default: - PB_RETURN_ERROR(stream, "invalid field type"); - } - - return pb_encode_tag(stream, wiretype, field->tag); -} - -bool checkreturn pb_encode_string(pb_ostream_t *stream, const pb_byte_t *buffer, size_t size) -{ - if (!pb_encode_varint(stream, (pb_uint64_t)size)) - return false; - - return pb_write(stream, buffer, size); -} - -bool checkreturn pb_encode_submessage(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct) -{ - /* First calculate the message size using a non-writing substream. */ - pb_ostream_t substream = PB_OSTREAM_SIZING; - size_t size; - bool status; - - if (!pb_encode(&substream, fields, src_struct)) - { -#ifndef PB_NO_ERRMSG - stream->errmsg = substream.errmsg; -#endif - return false; - } - - size = substream.bytes_written; - - if (!pb_encode_varint(stream, (pb_uint64_t)size)) - return false; - - if (stream->callback == NULL) - return pb_write(stream, NULL, size); /* Just sizing */ - - if (stream->bytes_written + size > stream->max_size) - PB_RETURN_ERROR(stream, "stream full"); - - /* Use a substream to verify that a callback doesn't write more than - * what it did the first time. */ - substream.callback = stream->callback; - substream.state = stream->state; - substream.max_size = size; - substream.bytes_written = 0; -#ifndef PB_NO_ERRMSG - substream.errmsg = NULL; -#endif - - status = pb_encode(&substream, fields, src_struct); - - stream->bytes_written += substream.bytes_written; - stream->state = substream.state; -#ifndef PB_NO_ERRMSG - stream->errmsg = substream.errmsg; -#endif - - if (substream.bytes_written != size) - PB_RETURN_ERROR(stream, "submsg size changed"); - - return status; -} - -/* Field encoders */ - -static bool checkreturn pb_enc_bool(pb_ostream_t *stream, const pb_field_iter_t *field) -{ - uint32_t value = safe_read_bool(field->pData) ? 1 : 0; - PB_UNUSED(field); - return pb_encode_varint(stream, value); -} - -static bool checkreturn pb_enc_varint(pb_ostream_t *stream, const pb_field_iter_t *field) -{ - if (PB_LTYPE(field->type) == PB_LTYPE_UVARINT) - { - /* Perform unsigned integer extension */ - pb_uint64_t value = 0; - - if (field->data_size == sizeof(uint_least8_t)) - value = *(const uint_least8_t*)field->pData; - else if (field->data_size == sizeof(uint_least16_t)) - value = *(const uint_least16_t*)field->pData; - else if (field->data_size == sizeof(uint32_t)) - value = *(const uint32_t*)field->pData; - else if (field->data_size == sizeof(pb_uint64_t)) - value = *(const pb_uint64_t*)field->pData; - else - PB_RETURN_ERROR(stream, "invalid data_size"); - - return pb_encode_varint(stream, value); - } - else - { - /* Perform signed integer extension */ - pb_int64_t value = 0; - - if (field->data_size == sizeof(int_least8_t)) - value = *(const int_least8_t*)field->pData; - else if (field->data_size == sizeof(int_least16_t)) - value = *(const int_least16_t*)field->pData; - else if (field->data_size == sizeof(int32_t)) - value = *(const int32_t*)field->pData; - else if (field->data_size == sizeof(pb_int64_t)) - value = *(const pb_int64_t*)field->pData; - else - PB_RETURN_ERROR(stream, "invalid data_size"); - - if (PB_LTYPE(field->type) == PB_LTYPE_SVARINT) - return pb_encode_svarint(stream, value); -#ifdef PB_WITHOUT_64BIT - else if (value < 0) - return pb_encode_varint_32(stream, (uint32_t)value, (uint32_t)-1); -#endif - else - return pb_encode_varint(stream, (pb_uint64_t)value); - - } -} - -static bool checkreturn pb_enc_fixed(pb_ostream_t *stream, const pb_field_iter_t *field) -{ -#ifdef PB_CONVERT_DOUBLE_FLOAT - if (field->data_size == sizeof(float) && PB_LTYPE(field->type) == PB_LTYPE_FIXED64) - { - return pb_encode_float_as_double(stream, *(float*)field->pData); - } -#endif - - if (field->data_size == sizeof(uint32_t)) - { - return pb_encode_fixed32(stream, field->pData); - } -#ifndef PB_WITHOUT_64BIT - else if (field->data_size == sizeof(uint64_t)) - { - return pb_encode_fixed64(stream, field->pData); - } -#endif - else - { - PB_RETURN_ERROR(stream, "invalid data_size"); - } -} - -static bool checkreturn pb_enc_bytes(pb_ostream_t *stream, const pb_field_iter_t *field) -{ - const pb_bytes_array_t *bytes = NULL; - - bytes = (const pb_bytes_array_t*)field->pData; - - if (bytes == NULL) - { - /* Treat null pointer as an empty bytes field */ - return pb_encode_string(stream, NULL, 0); - } - - if (PB_ATYPE(field->type) == PB_ATYPE_STATIC && - bytes->size > field->data_size - offsetof(pb_bytes_array_t, bytes)) - { - PB_RETURN_ERROR(stream, "bytes size exceeded"); - } - - return pb_encode_string(stream, bytes->bytes, (size_t)bytes->size); -} - -static bool checkreturn pb_enc_string(pb_ostream_t *stream, const pb_field_iter_t *field) -{ - size_t size = 0; - size_t max_size = (size_t)field->data_size; - const char *str = (const char*)field->pData; - - if (PB_ATYPE(field->type) == PB_ATYPE_POINTER) - { - max_size = (size_t)-1; - } - else - { - /* pb_dec_string() assumes string fields end with a null - * terminator when the type isn't PB_ATYPE_POINTER, so we - * shouldn't allow more than max-1 bytes to be written to - * allow space for the null terminator. - */ - if (max_size == 0) - PB_RETURN_ERROR(stream, "zero-length string"); - - max_size -= 1; - } - - - if (str == NULL) - { - size = 0; /* Treat null pointer as an empty string */ - } - else - { - const char *p = str; - - /* strnlen() is not always available, so just use a loop */ - while (size < max_size && *p != '\0') - { - size++; - p++; - } - - if (*p != '\0') - { - PB_RETURN_ERROR(stream, "unterminated string"); - } - } - -#ifdef PB_VALIDATE_UTF8 - if (!pb_validate_utf8(str)) - PB_RETURN_ERROR(stream, "invalid utf8"); -#endif - - return pb_encode_string(stream, (const pb_byte_t*)str, size); -} - -static bool checkreturn pb_enc_submessage(pb_ostream_t *stream, const pb_field_iter_t *field) -{ - if (field->submsg_desc == NULL) - PB_RETURN_ERROR(stream, "invalid field descriptor"); - - if (PB_LTYPE(field->type) == PB_LTYPE_SUBMSG_W_CB && field->pSize != NULL) - { - /* Message callback is stored right before pSize. */ - pb_callback_t *callback = (pb_callback_t*)field->pSize - 1; - if (callback->funcs.encode) - { - if (!callback->funcs.encode(stream, field, &callback->arg)) - return false; - } - } - - return pb_encode_submessage(stream, field->submsg_desc, field->pData); -} - -static bool checkreturn pb_enc_fixed_length_bytes(pb_ostream_t *stream, const pb_field_iter_t *field) -{ - return pb_encode_string(stream, (const pb_byte_t*)field->pData, (size_t)field->data_size); -} - -#ifdef PB_CONVERT_DOUBLE_FLOAT -bool pb_encode_float_as_double(pb_ostream_t *stream, float value) -{ - union { float f; uint32_t i; } in; - uint_least8_t sign; - int exponent; - uint64_t mantissa; - - in.f = value; - - /* Decompose input value */ - sign = (uint_least8_t)((in.i >> 31) & 1); - exponent = (int)((in.i >> 23) & 0xFF) - 127; - mantissa = in.i & 0x7FFFFF; - - if (exponent == 128) - { - /* Special value (NaN etc.) */ - exponent = 1024; - } - else if (exponent == -127) - { - if (!mantissa) - { - /* Zero */ - exponent = -1023; - } - else - { - /* Denormalized */ - mantissa <<= 1; - while (!(mantissa & 0x800000)) - { - mantissa <<= 1; - exponent--; - } - mantissa &= 0x7FFFFF; - } - } - - /* Combine fields */ - mantissa <<= 29; - mantissa |= (uint64_t)(exponent + 1023) << 52; - mantissa |= (uint64_t)sign << 63; - - return pb_encode_fixed64(stream, &mantissa); -} -#endif diff --git a/src/network/pb_encode.h b/src/network/pb_encode.h deleted file mode 100644 index 891368322..000000000 --- a/src/network/pb_encode.h +++ /dev/null @@ -1,185 +0,0 @@ -/* pb_encode.h: Functions to encode protocol buffers. Depends on pb_encode.c. - * The main function is pb_encode. You also need an output stream, and the - * field descriptions created by nanopb_generator.py. - */ - -#ifndef PB_ENCODE_H_INCLUDED -#define PB_ENCODE_H_INCLUDED - -#include "pb.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/* Structure for defining custom output streams. You will need to provide - * a callback function to write the bytes to your storage, which can be - * for example a file or a network socket. - * - * The callback must conform to these rules: - * - * 1) Return false on IO errors. This will cause encoding to abort. - * 2) You can use state to store your own data (e.g. buffer pointer). - * 3) pb_write will update bytes_written after your callback runs. - * 4) Substreams will modify max_size and bytes_written. Don't use them - * to calculate any pointers. - */ -struct pb_ostream_s -{ -#ifdef PB_BUFFER_ONLY - /* Callback pointer is not used in buffer-only configuration. - * Having an int pointer here allows binary compatibility but - * gives an error if someone tries to assign callback function. - * Also, NULL pointer marks a 'sizing stream' that does not - * write anything. - */ - const int *callback; -#else - bool (*callback)(pb_ostream_t *stream, const pb_byte_t *buf, size_t count); -#endif - void *state; /* Free field for use by callback implementation. */ - size_t max_size; /* Limit number of output bytes written (or use SIZE_MAX). */ - size_t bytes_written; /* Number of bytes written so far. */ - -#ifndef PB_NO_ERRMSG - const char *errmsg; -#endif -}; - -/*************************** - * Main encoding functions * - ***************************/ - -/* Encode a single protocol buffers message from C structure into a stream. - * Returns true on success, false on any failure. - * The actual struct pointed to by src_struct must match the description in fields. - * All required fields in the struct are assumed to have been filled in. - * - * Example usage: - * MyMessage msg = {}; - * uint8_t buffer[64]; - * pb_ostream_t stream; - * - * msg.field1 = 42; - * stream = pb_ostream_from_buffer(buffer, sizeof(buffer)); - * pb_encode(&stream, MyMessage_fields, &msg); - */ -bool pb_encode(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct); - -/* Extended version of pb_encode, with several options to control the - * encoding process: - * - * PB_ENCODE_DELIMITED: Prepend the length of message as a varint. - * Corresponds to writeDelimitedTo() in Google's - * protobuf API. - * - * PB_ENCODE_NULLTERMINATED: Append a null byte to the message for termination. - * NOTE: This behaviour is not supported in most other - * protobuf implementations, so PB_ENCODE_DELIMITED - * is a better option for compatibility. - */ -#define PB_ENCODE_DELIMITED 0x02U -#define PB_ENCODE_NULLTERMINATED 0x04U -bool pb_encode_ex(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct, unsigned int flags); - -/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ -#define pb_encode_delimited(s,f,d) pb_encode_ex(s,f,d, PB_ENCODE_DELIMITED) -#define pb_encode_nullterminated(s,f,d) pb_encode_ex(s,f,d, PB_ENCODE_NULLTERMINATED) - -/* Encode the message to get the size of the encoded data, but do not store - * the data. */ -bool pb_get_encoded_size(size_t *size, const pb_msgdesc_t *fields, const void *src_struct); - -/************************************** - * Functions for manipulating streams * - **************************************/ - -/* Create an output stream for writing into a memory buffer. - * The number of bytes written can be found in stream.bytes_written after - * encoding the message. - * - * Alternatively, you can use a custom stream that writes directly to e.g. - * a file or a network socket. - */ -pb_ostream_t pb_ostream_from_buffer(pb_byte_t *buf, size_t bufsize); - -/* Pseudo-stream for measuring the size of a message without actually storing - * the encoded data. - * - * Example usage: - * MyMessage msg = {}; - * pb_ostream_t stream = PB_OSTREAM_SIZING; - * pb_encode(&stream, MyMessage_fields, &msg); - * printf("Message size is %d\n", stream.bytes_written); - */ -#ifndef PB_NO_ERRMSG -#define PB_OSTREAM_SIZING {0,0,0,0,0} -#else -#define PB_OSTREAM_SIZING {0,0,0,0} -#endif - -/* Function to write into a pb_ostream_t stream. You can use this if you need - * to append or prepend some custom headers to the message. - */ -bool pb_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count); - - -/************************************************ - * Helper functions for writing field callbacks * - ************************************************/ - -/* Encode field header based on type and field number defined in the field - * structure. Call this from the callback before writing out field contents. */ -bool pb_encode_tag_for_field(pb_ostream_t *stream, const pb_field_iter_t *field); - -/* Encode field header by manually specifying wire type. You need to use this - * if you want to write out packed arrays from a callback field. */ -bool pb_encode_tag(pb_ostream_t *stream, pb_wire_type_t wiretype, uint32_t field_number); - -/* Encode an integer in the varint format. - * This works for bool, enum, int32, int64, uint32 and uint64 field types. */ -#ifndef PB_WITHOUT_64BIT -bool pb_encode_varint(pb_ostream_t *stream, uint64_t value); -#else -bool pb_encode_varint(pb_ostream_t *stream, uint32_t value); -#endif - -/* Encode an integer in the zig-zagged svarint format. - * This works for sint32 and sint64. */ -#ifndef PB_WITHOUT_64BIT -bool pb_encode_svarint(pb_ostream_t *stream, int64_t value); -#else -bool pb_encode_svarint(pb_ostream_t *stream, int32_t value); -#endif - -/* Encode a string or bytes type field. For strings, pass strlen(s) as size. */ -bool pb_encode_string(pb_ostream_t *stream, const pb_byte_t *buffer, size_t size); - -/* Encode a fixed32, sfixed32 or float value. - * You need to pass a pointer to a 4-byte wide C variable. */ -bool pb_encode_fixed32(pb_ostream_t *stream, const void *value); - -#ifndef PB_WITHOUT_64BIT -/* Encode a fixed64, sfixed64 or double value. - * You need to pass a pointer to a 8-byte wide C variable. */ -bool pb_encode_fixed64(pb_ostream_t *stream, const void *value); -#endif - -#ifdef PB_CONVERT_DOUBLE_FLOAT -/* Encode a float value so that it appears like a double in the encoded - * message. */ -bool pb_encode_float_as_double(pb_ostream_t *stream, float value); -#endif - -/* Encode a submessage field. - * You need to pass the pb_field_t array and pointer to struct, just like - * with pb_encode(). This internally encodes the submessage twice, first to - * calculate message size and then to actually write it out. - */ -bool pb_encode_submessage(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct); - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index 1c61f5559..b7d39109e 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -275,10 +275,6 @@ if(EMU_BUILD_NUM) ) endif() -if(NETSWITCH) - target_compile_definitions(ui PRIVATE USE_NETSWITCH) -endif() - if(RTMIDI) target_compile_definitions(ui PRIVATE USE_RTMIDI) endif() diff --git a/src/qt/icons/copy_raw_screenshot.ico b/src/qt/icons/copy_raw_screenshot.ico new file mode 100644 index 000000000..a153305b7 Binary files /dev/null and b/src/qt/icons/copy_raw_screenshot.ico differ diff --git a/src/qt/icons/copy_screenshot.ico b/src/qt/icons/copy_screenshot.ico new file mode 100644 index 000000000..5ea6bc705 Binary files /dev/null and b/src/qt/icons/copy_screenshot.ico differ diff --git a/src/qt/icons/pause.ico b/src/qt/icons/pause.ico index a71a38e91..3f7079fd7 100644 Binary files a/src/qt/icons/pause.ico and b/src/qt/icons/pause.ico differ diff --git a/src/qt/icons/record.ico b/src/qt/icons/record.ico index 357563594..3715837b1 100644 Binary files a/src/qt/icons/record.ico and b/src/qt/icons/record.ico differ diff --git a/src/qt/icons/run.ico b/src/qt/icons/run.ico index dfb43b05b..890914672 100644 Binary files a/src/qt/icons/run.ico and b/src/qt/icons/run.ico differ diff --git a/src/qt/icons/take_raw_screenshot.ico b/src/qt/icons/take_raw_screenshot.ico new file mode 100644 index 000000000..19a0a15e8 Binary files /dev/null and b/src/qt/icons/take_raw_screenshot.ico differ diff --git a/src/qt/icons/take_screenshot.ico b/src/qt/icons/take_screenshot.ico new file mode 100644 index 000000000..66f83ebbd Binary files /dev/null and b/src/qt/icons/take_screenshot.ico differ diff --git a/src/qt/languages/86box.pot b/src/qt/languages/86box.pot index 1eeb6bfc7..cbf086210 100644 --- a/src/qt/languages/86box.pot +++ b/src/qt/languages/86box.pot @@ -204,6 +204,15 @@ msgstr "" msgid "Take s&creenshot" msgstr "" +msgid "Take &raw screenshot" +msgstr "" + +msgid "C&opy screenshot" +msgstr "" + +msgid "Copy r&aw screenshot" +msgstr "" + msgid "S&ound" msgstr "" @@ -2993,3 +3002,6 @@ msgstr "" msgid "&Allow recompilation" msgstr "" + +msgid "&Fast forward" +msgstr "" diff --git a/src/qt/languages/ca-ES.po b/src/qt/languages/ca-ES.po index 75e0a593d..d7e279110 100644 --- a/src/qt/languages/ca-ES.po +++ b/src/qt/languages/ca-ES.po @@ -210,6 +210,15 @@ msgstr "&Actualitza icones a la barra d'estat" msgid "Take s&creenshot" msgstr "Desa captura de &pantalla" +msgid "Take &raw screenshot" +msgstr "Desa captura en &brut de pantalla" + +msgid "C&opy screenshot" +msgstr "C&opia captura de pantalla" + +msgid "Copy r&aw screenshot" +msgstr "Copy captura en b&rut de pantalla" + msgid "S&ound" msgstr "S&o" @@ -2999,3 +3008,6 @@ msgstr "&Força interpretació" msgid "&Allow recompilation" msgstr "&Permetre recompilació" + +msgid "&Fast forward" +msgstr "" diff --git a/src/qt/languages/cs-CZ.po b/src/qt/languages/cs-CZ.po index dbb0d2826..e82f95f26 100644 --- a/src/qt/languages/cs-CZ.po +++ b/src/qt/languages/cs-CZ.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2025-12-04 18:56+0000\n" +"PO-Revision-Date: 2026-01-02 21:56+0000\n" "Last-Translator: David Hrdlička \n" "Language-Team: Czech \n" "Language: cs-CZ\n" @@ -210,6 +210,15 @@ msgstr "&Aktualizovat ikony stavového řádku" msgid "Take s&creenshot" msgstr "Pořídit &screenshot" +msgid "Take &raw screenshot" +msgstr "Pořídit n&ezpracovaný screenshot" + +msgid "C&opy screenshot" +msgstr "&Zkopírovat screenshot" + +msgid "Copy r&aw screenshot" +msgstr "Zkopírovat nez&pracovaný screenshot" + msgid "S&ound" msgstr "&Zvuk" @@ -2999,3 +3008,6 @@ msgstr "&Vynutit interpretaci" msgid "&Allow recompilation" msgstr "&Povolit rekompilaci" + +msgid "&Fast forward" +msgstr "" diff --git a/src/qt/languages/de-DE.po b/src/qt/languages/de-DE.po index e426d1e2d..ad7e66b08 100644 --- a/src/qt/languages/de-DE.po +++ b/src/qt/languages/de-DE.po @@ -210,6 +210,15 @@ msgstr "&Statusleistenicons aktualisieren" msgid "Take s&creenshot" msgstr "S&creenshot aufnehmen" +msgid "Take &raw screenshot" +msgstr "&Roh-Screenshot aufnehmen" + +msgid "C&opy screenshot" +msgstr "Screenshot k&opieren" + +msgid "Copy r&aw screenshot" +msgstr "R&oh-Screenshot kopieren" + msgid "S&ound" msgstr "&Ton" @@ -2999,3 +3008,6 @@ msgstr "&Erzwingen der Interpretation" msgid "&Allow recompilation" msgstr "Recompilierung &zulassen" + +msgid "&Fast forward" +msgstr "" diff --git a/src/qt/languages/el-GR.po b/src/qt/languages/el-GR.po new file mode 100644 index 000000000..db28daa81 --- /dev/null +++ b/src/qt/languages/el-GR.po @@ -0,0 +1,3070 @@ +msgid "" +msgstr "" +"PO-Revision-Date: 2026-01-07 16:56+0000\n" +"Last-Translator: DimMan88 \n" +"Language-Team: Greek \n" +"Language: el-GR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.12.2\n" +"X-Language: el_GR\n" +"X-Source-Language: en_US\n" + +msgid "&Action" +msgstr "&Ενέργειες" + +msgid "&Keyboard requires capture" +msgstr "&Το πληκτρολόγιο απαιτεί αγκίστρωση" + +msgid "&Right CTRL is left ALT" +msgstr "&Αντιστοίχιση Right CTRL σε left ALT" + +msgid "&Hard reset" +msgstr "&Ολική επανεκκίνηση" + +msgid "&Ctrl+Alt+Del" +msgstr "&Ctrl+Alt+Del" + +msgid "Ctrl+Alt+&Esc" +msgstr "Ctrl+Alt+&Esc" + +msgid "&Pause" +msgstr "&Παύση" + +msgid "Pause" +msgstr "Παύση" + +msgid "Re&sume" +msgstr "Συ&νέχια" + +msgid "E&xit" +msgstr "Έ&ξοδος" + +msgid "&View" +msgstr "&Προβολή" + +msgid "&Hide status bar" +msgstr "&Απόκρυψη γραμμής κατάστασης" + +msgid "Hide &toolbar" +msgstr "Απόκρυψη &γραμμής εργαλείων" + +msgid "&Resizeable window" +msgstr "&Προσαρμόσιμο παράθυρο" + +msgid "R&emember size && position" +msgstr "Α&πομνήμευση μεγέθους && θέσης" + +msgid "Remember size && position" +msgstr "Απομνήμευση μεγέθους && θέσης" + +msgid "Re&nderer" +msgstr "Re&nderer" + +msgid "&Qt (Software)" +msgstr "&Qt (Software)" + +msgid "Open&GL (3.0 Core)" +msgstr "Open&GL (3.0 Core)" + +msgid "&VNC" +msgstr "&VNC" + +msgid "Specify &dimensions…" +msgstr "Ορισμός &διαστάσεων…" + +msgid "Force &4:3 display ratio" +msgstr "Εξαναγκασμός &αναλογίας οθόνης 4:3" + +msgid "&Window scale factor" +msgstr "&Συντελεστής κλίμακας παραθύρου" + +msgid "&0.5x" +msgstr "&0.5x" + +msgid "&1x" +msgstr "&1x" + +msgid "1.&5x" +msgstr "1.&5x" + +msgid "&2x" +msgstr "&2x" + +msgid "&3x" +msgstr "&3x" + +msgid "&4x" +msgstr "&4x" + +msgid "&5x" +msgstr "&5x" + +msgid "&6x" +msgstr "&6x" + +msgid "&7x" +msgstr "&7x" + +msgid "&8x" +msgstr "&8x" + +msgid "Fi<er method" +msgstr "Τύ&πος φιλτραρίσματος" + +msgid "&Nearest" +msgstr "&Nearest" + +msgid "&Linear" +msgstr "&Linear" + +msgid "Hi&DPI scaling" +msgstr "Κλ&ίμακα HiDPI" + +msgid "&Fullscreen" +msgstr "&Πλήρης οθόνη" + +msgid "Fullscreen &stretch mode" +msgstr "Πλήρης οθόνη &σε τύπο τεντώματος" + +msgid "&Full screen stretch" +msgstr "&Τέντωμα πλήρους οθόνης" + +msgid "&4:3" +msgstr "&4:3" + +msgid "&Square pixels (Keep ratio)" +msgstr "&Τετράγωνα εικονοστοιχεία (Διατήρηση αναλογίας)" + +msgid "&Integer scale" +msgstr "&Aκέραια κλίμακα" + +msgid "4:&3 Integer scale" +msgstr "Aκέραια κλίμακα 4:&3" + +msgid "EGA/(S)&VGA settings" +msgstr "Ρυθμίσε&ις EGA/(S)VGA" + +msgid "&Inverted VGA monitor" +msgstr "&Αντεστραμμένο VGA monitor" + +msgid "VGA screen &type" +msgstr "Τύπος οθόνης& VGA" + +msgid "RGB &Color" +msgstr "Χρώματα RGB" + +msgid "RGB (no brown)" +msgstr "RGB (χωρίς το καφέ)" + +msgid "&RGB Grayscale" +msgstr "&RGB κλίμακας γκρι" + +msgid "Generic RGBI color monitor" +msgstr "Γενική έγχρωμη οθόνη RGBI" + +msgid "&Amber monitor" +msgstr "&Κεχριμπάρι οθόνη" + +msgid "&Green monitor" +msgstr "&Πράσινη οθόνη" + +msgid "&White monitor" +msgstr "&Λευκή οθόνη" + +msgid "Grayscale &conversion type" +msgstr "Τύπος μετα&τροπής κλίμακας του γκρι" + +msgid "BT&601 (NTSC/PAL)" +msgstr "BT&601 (NTSC/PAL)" + +msgid "BT&709 (HDTV)" +msgstr "BT&709 (HDTV)" + +msgid "&Average" +msgstr "&Σχετικό" + +msgid "CGA/PCjr/Tandy/E&GA/(S)VGA overscan" +msgstr "Υπερσάρωση CGA/PCjr/Tandy/E&GA/(S)VGA" + +msgid "Change contrast for &monochrome display" +msgstr "Αλλαγή αντίθεσης γι&α μονόχρωμη οθόνη" + +msgid "&Media" +msgstr "&Μέσα" + +msgid "&Tools" +msgstr "&Εργαλεία" + +msgid "&Settings…" +msgstr "&Ρυθμίσεις…" + +msgid "Settings…" +msgstr "Ρυθμίσεις…" + +msgid "&Update status bar icons" +msgstr "&Ανανέωση εικονιδίων γραμμής κατάστασης" + +msgid "Take s&creenshot" +msgstr "Λήψη σ&τιγμιότυπου" + +msgid "Take &raw screenshot" +msgstr "Λήψη &αρχικού στιγμιότυπου" + +msgid "C&opy screenshot" +msgstr "&Αντιγραφή στιγμιότυπου" + +msgid "Copy r&aw screenshot" +msgstr "Αντιγραφή α&ρχικού στιγμιότυπου" + +msgid "S&ound" +msgstr "Ή&χος" + +msgid "&Preferences…" +msgstr "&Προτιμήσεις…" + +msgid "Enable &Discord integration" +msgstr "Ενεργοποίηση διασύνδεσης &Discord" + +msgid "Sound &gain…" +msgstr "&Ενίσχυση ήχου…" + +msgid "Begin trace" +msgstr "Αρχλη καταγραφής" + +msgid "End trace" +msgstr "Τέλος καταγραφής" + +msgid "&Help" +msgstr "&Βοήθεια" + +msgid "&Documentation…" +msgstr "&Εγχειρίδια…" + +msgid "&About 86Box…" +msgstr "&Σχετικά με το 86Box…" + +msgid "&New image…" +msgstr "&Δημιουργία εικόνας…" + +msgid "&Existing image…" +msgstr "&Υπάρχουσα εικόνα…" + +msgid "Existing image (&Write-protected)…" +msgstr "Υπάρχουσα εικόνα (&Προστασία-εγγραφής)…" + +msgid "&Record" +msgstr "&Καταγραφή" + +msgid "&Play" +msgstr "&Αναπαραγωγή" + +msgid "&Rewind to the beginning" +msgstr "&Γύρισμα στην αρχή" + +msgid "&Fast forward to the end" +msgstr "&Γρήγορα μπροστά στο τέλος" + +msgid "E&ject" +msgstr "&Εξαγωγή" + +msgid "&Image…" +msgstr "&Εικόνα…" + +msgid "E&xport to 86F…" +msgstr "&Εξαγωγή σε 86F…" + +msgid "&Mute" +msgstr "&Σίγαση" + +msgid "E&mpty" +msgstr "&Κενό" + +msgid "Reload previous image" +msgstr "Επαναφώρτωση προηγύμενης εικόνας" + +msgid "&Folder…" +msgstr "&Φάκελος…" + +msgid "Preferences" +msgstr "Προτιμήσεις" + +msgid "Sound Gain" +msgstr "Ενίσχυση Ήχου" + +msgid "New Image" +msgstr "Δημιουργία εικόνας" + +msgid "Settings" +msgstr "Ρυθμίσεις" + +msgid "Specify Main Window Dimensions" +msgstr "Ορισμός Διαστάσεων Κύριου Παραθύρου" + +msgid "OK" +msgstr "ΟΚ" + +msgid "Cancel" +msgstr "Άκυρο" + +msgid "&Default" +msgstr "&Προεπιλογή" + +msgid "Language:" +msgstr "Γλώσσα:" + +msgid "Gain" +msgstr "Ενίσχυση" + +msgid "File name:" +msgstr "Όνομα αρχείου:" + +msgid "Disk size:" +msgstr "Μέγεθος δίσκου:" + +msgid "RPM mode:" +msgstr "Τύπος RPM:" + +msgid "Progress:" +msgstr "Πρόοδος:" + +msgid "Width:" +msgstr "Πλάτος:" + +msgid "Height:" +msgstr "Ύψος:" + +msgid "Lock to this size" +msgstr "Κλείδωμα σε αυτό το μέγεθος" + +msgid "Machine type:" +msgstr "Τύπος συστήματος:" + +msgid "Machine:" +msgstr "Σύστημα:" + +msgid "Configure" +msgstr "Προσαρμογή" + +msgid "CPU:" +msgstr "CPU:" + +msgid "CPU type:" +msgstr "Τύπος CPU:" + +msgid "Speed:" +msgstr "Ταχύτητα:" + +msgid "Frequency:" +msgstr "Συχνότητα:" + +msgid "FPU:" +msgstr "FPU:" + +msgid "Wait states:" +msgstr "Καταστάσεις αναμονής:" + +msgid "MB" +msgstr "MB" + +msgid "Memory:" +msgstr "Μνήμη:" + +msgid "Time synchronization" +msgstr "Συγχρονισμός ώρας" + +msgid "Disabled" +msgstr "Ανενεργό" + +msgid "Enabled (local time)" +msgstr "Ενεργό (τοπική ώρα)" + +msgid "Enabled (UTC)" +msgstr "Ενεργό (UTC)" + +msgid "Dynamic Recompiler" +msgstr "Dynamic Recompiler" + +msgid "CPU frame size" +msgstr "Μέγεθος πλαισίου CPU" + +msgid "Larger frames (less smooth)" +msgstr "Μεγαλύτερα πλαίσια (λιγότερο ομαλό)" + +msgid "Smaller frames (smoother)" +msgstr "Μικρότερα πλαίσια (πιο ομαλό)" + +msgid "Video:" +msgstr "Κάρτα γραφικών:" + +msgid "Video #2:" +msgstr "Κάρτα γραφικών #2:" + +msgid "Voodoo 1 or 2 Graphics" +msgstr "Γραφικά Voodoo 1 ή 2" + +msgid "IBM 8514/A Graphics" +msgstr "Γραφικά IBM 8514/A" + +msgid "XGA Graphics" +msgstr "Γραφικά XGA" + +msgid "IBM PS/55 Display Adapter Graphics" +msgstr "Προσαρμογέας Γραφικών IBM PS/55" + +msgid "Keyboard:" +msgstr "Πληκτρολόγιο:" + +msgid "Keyboard" +msgstr "Πληκτρολόγιο" + +msgid "Mouse:" +msgstr "Ποντίκι:" + +msgid "Mouse" +msgstr "Ποντίκι" + +msgid "Joystick:" +msgstr "Joystick:" + +msgid "Joystick" +msgstr "Joystick" + +msgid "Joystick 1…" +msgstr "Joystick 1…" + +msgid "Joystick 2…" +msgstr "Joystick 2…" + +msgid "Joystick 3…" +msgstr "Joystick 3…" + +msgid "Joystick 4…" +msgstr "Joystick 4…" + +msgid "Sound card #1:" +msgstr "Κάρτα ήχου #1:" + +msgid "Sound card #2:" +msgstr "Κάρτα ήχου #2:" + +msgid "Sound card #3:" +msgstr "Κάρτα ήχου #3:" + +msgid "Sound card #4:" +msgstr "Κάρτα ήχου #4:" + +msgid "MIDI Out Device:" +msgstr "Συσκευή MIDI Out:" + +msgid "MIDI In Device:" +msgstr "Συσκευή MIDI In:" + +msgid "MIDI Out:" +msgstr "MIDI Out:" + +msgid "Standalone MPU-401" +msgstr "Ξέχωρο MPU-401" + +msgid "Use FLOAT32 sound" +msgstr "Χρήση ήχου FLOAT32" + +msgid "FM synth driver" +msgstr "Οδηγός FM synth" + +msgid "Nuked (more accurate)" +msgstr "Nuked (πιο ακριβές)" + +msgid "YMFM (faster)" +msgstr "YMFM (γρήγορο)" + +msgid "COM1 Device:" +msgstr "Συσκευή COM1:" + +msgid "COM2 Device:" +msgstr "Συσκευή COM2:" + +msgid "COM3 Device:" +msgstr "Συσκευή COM3:" + +msgid "COM4 Device:" +msgstr "Συσκευή COM4:" + +msgid "LPT1 Device:" +msgstr "Συσκευή LPT1:" + +msgid "LPT2 Device:" +msgstr "Συσκευή LPT2:" + +msgid "LPT3 Device:" +msgstr "Συσκευή LPT3:" + +msgid "LPT4 Device:" +msgstr "Συσκευή LPT4:" + +msgid "Internal LPT ECP DMA:" +msgstr "Εσωτερικό LPT ECP DMA:" + +msgid "Serial port 1" +msgstr "Σειριακή θύρα 1" + +msgid "Serial port 2" +msgstr "Σειριακή θύρα 2" + +msgid "Serial port 3" +msgstr "Σειριακή θύρα 3" + +msgid "Serial port 4" +msgstr "Σειριακή θύρα 4" + +msgid "Parallel port 1" +msgstr "Παράλληλη θύρα 1" + +msgid "Parallel port 2" +msgstr "Παράλληλη θύρα 2" + +msgid "Parallel port 3" +msgstr "Παράλληλη θύρα 3" + +msgid "Parallel port 4" +msgstr "Παράλληλη θύρα 4" + +msgid "Floppy disk controller:" +msgstr "Ελεγκτής δισκέτας:" + +msgid "CD-ROM controller:" +msgstr "Ελεγκτής CD-ROM:" + +msgid "[ISA16] Tertiary IDE Controller" +msgstr "[ISA16] Τριτογενής Ελεγκτής IDE" + +msgid "[ISA16] Quaternary IDE Controller" +msgstr "[ISA16] Τεταρτογενής Ελεγκτής IDE" + +msgid "Hard disk controllers" +msgstr "Ελεγκτές σκληρού δίσκου" + +msgid "SCSI controllers" +msgstr "Ελεγκτές SCSI" + +msgid "Controller 1:" +msgstr "Ελεγκτής 1:" + +msgid "Controller 2:" +msgstr "Ελεγκτής 2:" + +msgid "Controller 3:" +msgstr "Ελεγκτής 3:" + +msgid "Controller 4:" +msgstr "Ελεγκτής 4:" + +msgid "Cassette" +msgstr "Κασέτα" + +msgid "Hard disks:" +msgstr "Σκληροί δίσκοι:" + +msgid "Firmware Version" +msgstr "Έκδοση Firmware" + +msgid "&New…" +msgstr "&Δημιουργία…" + +msgid "&Existing…" +msgstr "&Υπάρχων…" + +msgid "&Remove" +msgstr "&Κατάργηση" + +msgid "Bus:" +msgstr "Δίαυλος:" + +msgid "Channel:" +msgstr "Κανάλι:" + +msgid "ID:" +msgstr "ID:" + +msgid "Sectors:" +msgstr "Τομείς:" + +msgid "Heads:" +msgstr "Κεφαλές:" + +msgid "Cylinders:" +msgstr "Κύλινδροι:" + +msgid "Size (MB):" +msgstr "Μέγεθος (MB):" + +msgid "Type:" +msgstr "Τύπος:" + +msgid "Image Format:" +msgstr "Τύπος Εικόνας:" + +msgid "Block Size:" +msgstr "Block Size:" + +msgid "Floppy drives:" +msgstr "Οδηγοί δισκέτας:" + +msgid "Turbo timings" +msgstr "Χρονισμοί Turbo" + +msgid "Check BPB" +msgstr "Έλεγχος BPB" + +msgid "CD-ROM drives:" +msgstr "Οδηγοί CD-ROM:" + +msgid "MO drives:" +msgstr "Οδηγοί MO:" + +msgid "MO:" +msgstr "MO:" + +msgid "Removable disks:" +msgstr "Αφαιρούμενοι δίσκοι:" + +msgid "Removable disk drives:" +msgstr "Οδηγοί αφαιρούμενων δίσκων:" + +msgid "ZIP 250" +msgstr "ZIP 250" + +msgid "ISA RTC:" +msgstr "ISA RTC:" + +msgid "ISA Memory Expansion" +msgstr "Επέκταση Μνήμης ISA" + +msgid "ISA ROM Cards" +msgstr "Κάρτες ROM ISA" + +msgid "Card 1:" +msgstr "Κάρτα 1:" + +msgid "Card 2:" +msgstr "Κάρτα 2:" + +msgid "Card 3:" +msgstr "Κάρτα 3:" + +msgid "Card 4:" +msgstr "Κάρτα 4:" + +msgid "Generic ISA ROM Board" +msgstr "Γενική ISA ROM πλακέτα" + +msgid "Generic Dual ISA ROM Board" +msgstr "Γενική Διπλή ISA ROM πλακέτα" + +msgid "Generic Quad ISA ROM Board" +msgstr "Γενική Τετραπλή ISA ROM πλακέτα" + +msgid "ISABugger device" +msgstr "Συσκευή ISABugger" + +msgid "POST card" +msgstr "κάρτα POST" + +msgid "Error" +msgstr "Σφάλμα" + +msgid "Fatal error" +msgstr "Οριστικό σφάλμα" + +msgid " - PAUSED" +msgstr " - ΣΤΑΜΑΤΗΜΕΝΟ" + +msgid "Speed" +msgstr "Ταχύτητα" + +msgid "Removable disk %1 (%2): %3" +msgstr "Αφαιρούμενος δίσκος %1 (%2): %3" + +msgid "&Removable disk %1 (%2): %3" +msgstr "&Αφαιρούμενος δίσκος %1 (%2): %3" + +msgid "Removable disk images" +msgstr "Εικόνες αφαιρούμενων δίσκων" + +msgid "Image %1" +msgstr "Εικόνα %1" + +msgid "86Box could not find any usable ROM images.\n\nPlease download a ROM set and extract it into the \"roms\" directory." +msgstr "Το 86Box δεν μπόρεσε να εντοπίσει καμία ROM.\n\nΠαρακαλώ κατεβάστε το πακέτο ROM και εξάγετέ το στον κατάλογο \"roms\"." + +msgid "(empty)" +msgstr "(κενό)" + +msgid "All files" +msgstr "Όλα τα αρχεία" + +msgid "Turbo" +msgstr "Turbo" + +msgid "On" +msgstr "On" + +msgid "Off" +msgstr "Off" + +msgid "All images" +msgstr "Όλες οι εικόνες" + +msgid "Basic sector images" +msgstr "Εικόνες βασικού τόμου" + +msgid "Surface images" +msgstr "Εικόνες επιφάνειας" + +msgid "Machine \"%hs\" is not available due to missing ROMs in the roms/machines directory. Switching to an available machine." +msgstr "Το σύστημα \"%hs\" δεν είναι διαθέσιμο λόγω έλλειψης των ROMs στον κατάλογο roms/machines. Αλλάξτε σε διαθέσιμο σύστημα." + +msgid "Video card \"%hs\" is not available due to missing ROMs in the roms/video directory. Switching to an available video card." +msgstr "Η κάρτα γραφικών \"%hs\" δεν είναι διαθέσιμη λόγω έλλειψης των ROMs στον κατάλογο roms/video. Αλλάξτε σε διαθέσιμη κάρτα γραφικών." + +msgid "Video card #2 \"%hs\" is not available due to missing ROMs in the roms/video directory. Disabling the second video card." +msgstr "Η κάρτα γραφικών #2 \"%hs\" δεν είναι διαθέσιμη λόγω έλλειψης των ROMs στον κατάλογο roms/video. Αλλάξτε σε διαθέσιμη κάρτα γραφικών." + +msgid "Device \"%hs\" is not available due to missing ROMs. Ignoring the device." +msgstr "Η συσκευή \"%hs\" δεν είναι διαθέσιμη λόγω έλλειψης των ROMs. Παράκαμψη συσκευής." + +msgid "Machine" +msgstr "Σύστημα" + +msgid "Display" +msgstr "Οθόνη" + +msgid "Input devices" +msgstr "Συσκευές εισόδου" + +msgid "Sound" +msgstr "Ήχος" + +msgid "Network" +msgstr "Δίκτυο" + +msgid "Ports (COM & LPT)" +msgstr "Θύρες (COM & LPT)" + +msgid "Ports" +msgstr "Θύρες" + +msgid "Serial ports:" +msgstr "Σειριακές θύρες:" + +msgid "Parallel ports:" +msgstr "Παράλληλες θύρες:" + +msgid "Storage controllers" +msgstr "Ελεγκτές αποθήκευσης" + +msgid "Hard disks" +msgstr "Σκληροί δίσκοι" + +msgid "Disks:" +msgstr "Δίσκοι:" + +msgid "Floppy:" +msgstr "Δισκέτες:" + +msgid "Controllers:" +msgstr "Ελεγκτές:" + +msgid "Floppy & CD-ROM drives" +msgstr "Οδηγοί Δισκέτας & CD-ROM" + +msgid "Other removable devices" +msgstr "Άλλες αφαιρούμενες συσκευές" + +msgid "Other peripherals" +msgstr "Άλλα περιφερειακά" + +msgid "Other devices" +msgstr "Άλλες συσκευές" + +msgid "Click to capture mouse" +msgstr "Κλικ για αγκίστρωση ποντικιού" + +msgid "Press %1 to release mouse" +msgstr "Πατήστε %1 για απαγκίστρωση ποντικιού" + +msgid "Press %1 or middle button to release mouse" +msgstr "Πατήστε %1 ή το μεσαίο κουμπί για απαγκίστρωση ποντικιού" + +msgid "Bus" +msgstr "Δίαυλος" + +msgid "File" +msgstr "Αρχείο" + +msgid "C" +msgstr "C" + +msgid "H" +msgstr "H" + +msgid "S" +msgstr "S" + +msgid "KB" +msgstr "KB" + +msgid "Default" +msgstr "Προεπιλογή" + +msgid "%1 Wait state(s)" +msgstr "%1 Κατάσταση αναμονής(ών)" + +msgid "Type" +msgstr "Τύπος" + +msgid "No PCap devices found" +msgstr "Δεν βέθηκαν PCap συσκευές" + +msgid "Invalid PCap device" +msgstr "Μη έγκυρη PCap συσκευή" + +msgid "Generic paddle controller(s)" +msgstr "Generic paddle controller(s)" + +msgid "2-axis, 1-button joystick(s)" +msgstr "2-axis, 1-button joystick(s)" + +msgid "2-axis, 2-button joystick(s)" +msgstr "2-axis, 2-button joystick(s)" + +msgid "2-axis, 3-button joystick" +msgstr "2-axis, 3-button joystick" + +msgid "2-axis, 4-button joystick" +msgstr "2-axis, 4-button joystick" + +msgid "2-axis, 6-button joystick" +msgstr "2-axis, 6-button joystick" + +msgid "2-axis, 8-button joystick" +msgstr "2-axis, 8-button joystick" + +msgid "3-axis, 2-button joystick" +msgstr "3-axis, 2-button joystick" + +msgid "3-axis, 3-button joystick" +msgstr "3-axis, 3-button joystick" + +msgid "3-axis, 4-button joystick" +msgstr "3-axis, 4-button joystick" + +msgid "4-axis, 2-button joystick" +msgstr "4-axis, 2-button joystick" + +msgid "4-axis, 3-button joystick" +msgstr "4-axis, 3-button joystick" + +msgid "4-axis, 4-button joystick" +msgstr "4-axis, 4-button joystick" + +msgid "2-button gamepad(s)" +msgstr "2-button gamepad(s)" + +msgid "3-button gamepad" +msgstr "3-button gamepad" + +msgid "4-button gamepad" +msgstr "4-button gamepad" + +msgid "6-button gamepad" +msgstr "6-button gamepad" + +msgid "2-button flight yoke" +msgstr "2-button flight yoke" + +msgid "3-button flight yoke" +msgstr "3-button flight yoke" + +msgid "4-button flight yoke" +msgstr "4-button flight yoke" + +msgid "2-button flight yoke with throttle" +msgstr "2-button flight yoke with throttle" + +msgid "3-button flight yoke with throttle" +msgstr "3-button flight yoke with throttle" + +msgid "4-button flight yoke with throttle" +msgstr "4-button flight yoke with throttle" + +msgid "Steering wheel (3-axis, 2-button)" +msgstr "Steering wheel (3-axis, 2-button)" + +msgid "Steering wheel (3-axis, 3-button)" +msgstr "Steering wheel (3-axis, 3-button)" + +msgid "Steering wheel (3-axis, 4-button)" +msgstr "Steering wheel (3-axis, 4-button)" + +msgid "Thrustmaster Formula T1/T2 with adapter" +msgstr "Thrustmaster Formula T1/T2 with adapter" + +msgid "Thrustmaster Formula T1/T2 without adapter" +msgstr "Thrustmaster Formula T1/T2 without adapter" + +msgid "None" +msgstr "Κανένα" + +msgid "%1 MB (CHS: %2, %3, %4)" +msgstr "%1 MB (CHS: %2, %3, %4)" + +msgid "Floppy %1 (%2): %3" +msgstr "Δισκέτα %1 (%2): %3" + +msgid "&Floppy %1 (%2): %3" +msgstr "&Δισκέτα %1 (%2): %3" + +msgid "Advanced sector images" +msgstr "Εικόνες προχωρημένου τομέα" + +msgid "Flux images" +msgstr "Εικόνες Flux" + +msgid "Are you sure you want to hard reset the emulated machine?" +msgstr "" +"Είστε σίγουρος οτι θέλετε να κάνετε ολική επανεκκίνηση του εξομειωμένου " +"σύστηματος;" + +msgid "Are you sure you want to exit 86Box?" +msgstr "Είστε σίγουρος για την έξοδο απο το 86Box;" + +msgid "Unable to initialize Ghostscript" +msgstr "Αδυναμία αρχικοποίησης Ghostscript" + +msgid "Unable to initialize GhostPCL" +msgstr "Αδυναμία αρχικοποίησης GhostPCL" + +msgid "MO %1 (%2): %3" +msgstr "MO %1 (%2): %3" + +msgid "&MO %1 (%2): %3" +msgstr "&MO %1 (%2): %3" + +msgid "MO images" +msgstr "Εικόνες MO" + +msgid "Welcome to 86Box!" +msgstr "Καλως ορίσατε στο 86Box!" + +msgid "Internal device" +msgstr "Εσωτερική συσκευή" + +msgid "&File" +msgstr "&Αρχείο" + +msgid "&New machine…" +msgstr "&Νέα μηχανή…" + +msgid "New machine…" +msgstr "Νέα μηχανή…" + +msgid "New machine" +msgstr "Νέα μηχανή" + +msgid "&Check for updates…" +msgstr "&Έλεγχος για ενημερώσεις…" + +msgid "Exit" +msgstr "Έξοδος" + +msgid "No ROMs found" +msgstr "Δεν βρέθηκαν ROMs" + +msgid "Do you want to save the settings?" +msgstr "Επιθυμείτε να αποθηκεύσετε τις ρυθμίσεις;" + +msgid "This will hard reset the emulated machine." +msgstr "Αυτό θα οδηγήσει σε ολική επανεκκίνηση της εξομοιωμένης μηχανής." + +msgid "Save" +msgstr "Αποθήκευση" + +msgid "About %1" +msgstr "Σχετικά με το %1" + +msgid "An emulator of old computers\n\nAuthors: Miran Grča (OBattler), RichardG867, Jasmine Iwanek, TC1995, coldbrewed, Teemu Korhonen (Manaatti), Joakim L. Gilje, Adrien Moulin (elyosh), Daniel Balsom (gloriouscow), Cacodemon345, Fred N. van Kempen (waltje), Tiseno100, reenigne, and others.\n\nWith previous core contributions from Sarah Walker, leilei, JohnElliott, greatpsycho, and others.\n\nReleased under the GNU General Public License version 2 or later. See LICENSE for more information." +msgstr "Εξομειωτής παλαιών υπολογιστών\n\nAuthors: Miran Grča (OBattler), RichardG867, Jasmine Iwanek, TC1995, coldbrewed, Teemu Korhonen (Manaatti), Joakim L. Gilje, Adrien Moulin (elyosh), Daniel Balsom (gloriouscow), Cacodemon345, Fred N. van Kempen (waltje), Tiseno100, reenigne, and others.\n\nWith previous core contributions from Sarah Walker, leilei, JohnElliott, greatpsycho, and others.\n\nReleased under the GNU General Public License version 2 or later. See LICENSE for more information." + +msgid "Hardware not available" +msgstr "Μη διαθέσιμο υλικό" + +msgid "Make sure %1 is installed and that you are on a %1-compatible network connection." +msgstr "Βεβαιωθήτε οτι το %1 είναι εγκατεστημένο και οτι είστε σε %1-συμβατό δίκτυο." + +msgid "Invalid configuration" +msgstr "Μη έγκυρη προσαρμογή" + +msgid "%1 is required for automatic conversion of PostScript files to PDF.\n\nAny documents sent to the generic PostScript printer will be saved as PostScript (.ps) files." +msgstr "%1 απαιτείται για αυτόματη μετατροπή αρχείων PostScript σε PDF.\n\nΈγγραφα που στέλνονται στον γενικό εκτυπωτή PostScript θα αποθηκεύονται ως αρχεία PostScript (.ps)." + +msgid "%1 is required for automatic conversion of PCL files to PDF.\n\nAny documents sent to the generic PCL printer will be saved as Printer Command Language (.pcl) files." +msgstr "%1 απαιτείται για αυτόματη μετατροπή αρχείων PCL σε PDF.\n\nΈγγραφα που στέλνονται στον γενικό εκτυπωτή PCL θα αποθηκεύονται ως αρχεία Printer Command Language (.pcl)." + +msgid "Don't show this message again" +msgstr "Να μην ξαναεμφανιστεί αυτό το μήνυμα" + +msgid "Don't exit" +msgstr "Παραμονή" + +msgid "Reset" +msgstr "Επαναφορά" + +msgid "Don't reset" +msgstr "Οχι επαναφορά" + +msgid "CD-ROM images" +msgstr "Εικόνες CD-ROM" + +msgid "%1 Device Configuration" +msgstr "%1 Προσαρμογή Συσκευής" + +msgid "Monitor in sleep mode" +msgstr "Οθόνη σε sleep mode" + +msgid "GLSL shaders" +msgstr "GLSL shaders" + +msgid "You are loading an unsupported configuration" +msgstr "Προσπαθείτε να φορτώσετε μη υποστηριζόμενη προσαρμογή" + +msgid "CPU type filtering based on selected machine is disabled for this emulated machine.\n\nThis makes it possible to choose a CPU that is otherwise incompatible with the selected machine. However, you may run into incompatibilities with the machine BIOS or other software.\n\nEnabling this setting is not officially supported and any bug reports filed may be closed as invalid." +msgstr "" +"Το φιλτράρισμα τύπου CPU βασισμένο στην επιλεγμένη μηχανή είναι " +"απενεργοποιημένο για αυτήν την εξομειωμένη μηχανή.\n" +"\n" +"Αυτό δίνει την δυνατότητα να επιλέξετε CPU που δεν είναι συμβατός με την " +"επιλεγμένη μηχανή. Παρ'όλ' αυτά υπάρχει περίτωση να έχετε ασυμβατότητες με " +"το BIOS της μηχανής ή άλλο λογισμικό.\n" +"\n" +"Ενεργοποιώντας αυτή την ρύθμιση η οποία δεν υποστηρίζεται επίσημα και οι " +"οποιεσδήποτε αναφορές σφαλμάτων θα κλείνουν ως μη έκγυρες." + +msgid "Continue" +msgstr "Συνέχεια" + +msgid "Cassette: %1" +msgstr "Κασέτα: %1" + +msgid "C&assette: %1" +msgstr "Κ&ασέτα: %1" + +msgid "Cassette images" +msgstr "Εικόνες κασέτας" + +msgid "Cartridge %1: %2" +msgstr "Κασέτα δεδομένων %1: %2" + +msgid "Car&tridge %1: %2" +msgstr "Car&tridge %1: %2" + +msgid "Cartridge images" +msgstr "Εικόνες κασέτας δεδομένων" + +msgid "Resume execution" +msgstr "Συνέχεια εκτέλεσης" + +msgid "Pause execution" +msgstr "Παύση εκτέλεσης" + +msgid "Ctrl+Alt+Del" +msgstr "Ctrl+Alt+Del" + +msgid "Press Ctrl+Alt+Del" +msgstr "Πατήστε Ctrl+Alt+Del" + +msgid "Press Ctrl+Alt+Esc" +msgstr "Πατήστε Ctrl+Alt+Esc" + +msgid "Hard reset" +msgstr "Ολική επανεκκίνηση" + +msgid "Force shutdown" +msgstr "Εξαναγκασμός τερματισμού" + +msgid "Start" +msgstr "Εκκίνηση" + +msgid "&Force shutdown" +msgstr "&Εξαναγκασμός τερματισμού" + +msgid "&Start" +msgstr "&Εκκίνηση" + +msgid "Not running" +msgstr "Ανενεργή" + +msgid "Running" +msgstr "Σε λειτουργία" + +msgid "Paused" +msgstr "Σε παύση" + +msgid "Waiting" +msgstr "Αναμονή" + +msgid "Powered Off" +msgstr "Κλειστή" + +msgid "%n running" +msgstr "%n τρέχει" + +msgid "%n paused" +msgstr "%n σε παύση" + +msgid "%n waiting" +msgstr "%n σε αναμονή" + +msgid "%1 total" +msgstr "%1 σύνολο" + +msgid "VMs: %1" +msgstr "VMs: %1" + +msgid "System Directory:" +msgstr "Κατάλογος Συστήματος:" + +msgid "Choose directory" +msgstr "Επιλέξτε κατάλογο" + +msgid "Choose configuration file" +msgstr "Επιλέξτε αρχείο προσαρμογής" + +msgid "86Box configuration files (86box.cfg)" +msgstr "Αρχεία προσαρμογής 86Box (86box.cfg)" + +msgid "Configuration read failed" +msgstr "Αποτυχία ανάγνωσης προσαρμογής" + +msgid "Unable to open the selected configuration file for reading: %1" +msgstr "Αδυναμία ανοίγματος επιλεγμένου αρχείου προσαρμογής για ανάγνωση: %1" + +msgid "Use regular expressions in search box" +msgstr "Χρήση συχνών εκφράσεων στην αναζήτηση" + +msgid "%1 machine(s) are currently active. Are you sure you want to exit the VM manager anyway?" +msgstr "%1 μηχανής(ών) είναι εν ενεργεία. Είστε σίγουγος για την έξοδο απο το VM manager;" + +msgid "Add new system wizard" +msgstr "Οδηγός προσθήκης νέο συστήματος" + +msgid "Introduction" +msgstr "Παρουσίαση" + +msgid "This will help you add a new system to 86Box." +msgstr "Αυτός ο οδηγός θα σας βοηθήσει να εισάγετε νέο σύστημα στο 86Box." + +msgid "New configuration" +msgstr "Νέα προσαρμογή" + +msgid "Complete" +msgstr "Ολοκληρώθηκε" + +msgid "The wizard will now launch the configuration for the new system." +msgstr "Ο οδηγός θα εκκινήσει τώρα την προσαρμογή για το νέο σύστημα." + +msgid "Use existing configuration" +msgstr "Χρήση υπάρχουσας προσαρμογής" + +msgid "Type some notes here" +msgstr "Κρατήστε σημειώσεις εδώ" + +msgid "Paste the contents of the existing configuration file into the box below." +msgstr "Επικόλληση των περιεχομένων υπάρχον αρχείου προσαρμογής στο παρακάτω πλαίσιο." + +msgid "Load configuration from file" +msgstr "Φόρτωση προσαρμογής απο αρχείο" + +msgid "System name" +msgstr "Όνομα συστήματος" + +msgid "System name:" +msgstr "Όνομα συστήματος:" + +msgid "System name cannot contain certain characters" +msgstr "Το όνομα συστήματος δεν πρέπει να περιλαμβάνει ορισμένους χαρακτήρες" + +msgid "System name already exists" +msgstr "Το όνομα συστήματος υπάρχει ήδη" + +msgid "Please enter a directory for the system" +msgstr "Παρακαλώ ορίστε κατάλογο για το σύστημα" + +msgid "Directory does not exist" +msgstr "Ο κατάλογος δεν υπάρχει" + +msgid "A new directory for the system will be created in the selected directory above" +msgstr "Ένας νέος κατάλογος για το σύστημα θα δημιουργηθεί στον παραπάνω επιλεγμένο κατάλογο" + +msgid "System location:" +msgstr "Τοποθεσία συστήματος:" + +msgid "System name and location" +msgstr "Όνομα και τοποθεσία συστήματος" + +msgid "Enter the name of the system and choose the location" +msgstr "Εισάγετε το όνομα συστήματος και επιλέξτε τοποθεσία" + +msgid "Enter the name of the system" +msgstr "Εισάγετε το όνομα συστήματος" + +msgid "Please enter a system name" +msgstr "Παρακαλώ εισάγετε ένα όνομα συστήματος" + +msgid "Display name (optional):" +msgstr "Εμφανιζόμενο όνομα (προαιρετικό):" + +msgid "Display name:" +msgstr "Εμφανιζόμενο όνομα:" + +msgid "Set display name" +msgstr "Ορισμός ονόματος εμφάνισης" + +msgid "Enter the new display name (blank to reset)" +msgstr "Εισάγετε το νέο όνομα εμφάνισης (κενό για επαναφορά)" + +msgid "Change &display name…" +msgstr "Αλλαγή &ονόματος εμφάνισης…" + +msgid "Context Menu" +msgstr "Μενού επιλογών" + +msgid "&Open folder…" +msgstr "&Άνοιγμα φακέλου…" + +msgid "Open p&rinter tray…" +msgstr "Άνοιγμα δίσκου &εκτυπωτή…" + +msgid "Set &icon…" +msgstr "Ορισμός &εικονιδίου…" + +msgid "Select an icon" +msgstr "Επιλέξτε εικονίδιο" + +msgid "C&lone…" +msgstr "&Κλωνοποίηση…" + +msgid "Virtual machine \"%1\" (%2) will be cloned into:" +msgstr "Η εικονική μηχανή \"%1\" (%2) θα κλωνοποιηθεί σε:" + +msgid "Directory %1 already exists" +msgstr "Ο κατάλογος %1 υπάρχει ήδη" + +msgid "You cannot use the following characters in the name: %1" +msgstr "Δεν μπορείτε να χρησιμοποιήσετε τους ακόλουθους χαρακτήρες στο όνομα: %1" + +msgid "Clone" +msgstr "Κλωνοποίηση" + +msgid "Failed to create directory for cloned VM" +msgstr "Αποτυχία δημιουργίας καταλόγου για την κλωνοποιημένη VM" + +msgid "Failed to clone VM." +msgstr "Αποτυχία κλωνοποίησης VM." + +msgid "Directory in use" +msgstr "Κατάλογος σε χρήση" + +msgid "The selected directory is already in use. Please select a different directory." +msgstr "Ο επιλεγμένος κατάλογος χρησιμοποείται ήδη. Παρακαλώ επιλέξτε έναν διαφορετικό κατάλογο." + +msgid "Create directory failed" +msgstr "Αποτυχία δημιουργίας καταλόγου" + +msgid "Unable to create the directory for the new system" +msgstr "Αποτυχία δημιουργίας καταλόγου για το νέο σύστημα" + +msgid "Configuration write failed" +msgstr "Αποτυχία εγγραφής προσαρμογής" + +msgid "Unable to open the configuration file at %1 for writing" +msgstr "Αδυναμία ανοίγματος αρχείου προσαρμογής στο %1 για εγγραφή" + +msgid "Error adding system" +msgstr "Σφάλμα προσθήκης συστήματος" + +msgid "Remove directory failed" +msgstr "Αποτυχία αφαίρεσης καταλόγου" + +msgid "Some files in the machine's directory were unable to be deleted. Please delete them manually." +msgstr "Ορισμένα αρχεία στον κατάλογο μηχανής ήταν αδύνατο να διαγραφούν. Παρακαλώ διαγράψτε τα χειροκίνητα." + +msgid "Build" +msgstr "Δομή" + +msgid "Version" +msgstr "Έκδοση" + +msgid "An update to 86Box is available: %1 %2" +msgstr "Μία ενημέρωση είναι διαθέσιμη για το 86Box: %1 %2" + +msgid "An error has occurred while checking for updates: %1" +msgstr "Προέκυψε σφάλμα κατά τον έλεγχο ενημερώσεων: %1" + +msgid "An update to 86Box is available!" +msgstr "Μία ενημέρωση είναι διαθέσιμη για το 86Box!" + +msgid "Warning" +msgstr "Προειδοποίση" + +msgid "&Kill" +msgstr "&Εξαναγκαστικός τέρματισμός" + +msgid "Killing a virtual machine can cause data loss. Only do this if the 86Box process gets stuck.\n\nDo you really wish to kill the virtual machine \"%1\"?" +msgstr "Ο εξαναγκαστικός τέρματισμός της εικονικής μηχανής μπορεί να επιφέρει απώλεια δεδομένων. Εκτελέστε τον μόνο αν το 86Box εχει κολλήσει.\n\nΘέλετε οποσήποτε να κάνετε εξαναγκαστικό τέρματισμό της εικονικής μηχανής \"%1\";" + +msgid "&Delete" +msgstr "&Διαγραφή" + +msgid "Do you really want to delete the virtual machine \"%1\" and all its files? This action cannot be undone!" +msgstr "" +"Επιθυμείτε σίγουρα να διαγράψετε την εικονική μηχανή \"%1\" και όλα τα " +"αρχεία της; Αυτή η ενέργεια είναι ανεπανόρθωτη!" + +msgid "Show &config file" +msgstr "Εμφάνιση αρχείου &προσαρμογής" + +msgid "No screenshot" +msgstr "Κανένα στιγμιότυπο" + +msgid "Search" +msgstr "Αναζήτηση" + +msgid "Searching for VMs…" +msgstr "Αναζήτηση για VMs…" + +msgid "Found %1" +msgstr "Βρέθηκε %1" + +msgid "System" +msgstr "Σύστημα" + +msgid "Storage" +msgstr "Αποθηκευτικός χώρος" + +msgid "Disk %1:" +msgstr "Δισκέτα %1:" + +msgid "No disks" +msgstr "Κανένας δίσκος" + +msgid "Audio" +msgstr "Ήχος" + +msgid "Audio:" +msgstr "Ήχος:" + +msgid "ACPI shutdown" +msgstr "Τερματισμός ACPI" + +msgid "ACP&I shutdown" +msgstr "Τερματισμός ACP&I" + +msgid "Hard disk (%1)" +msgstr "Σκληρός δίσκος (%1)" + +msgid "MFM/RLL or ESDI CD-ROM drives never existed" +msgstr "Δεν υπήρξαν ποτέ οδηγοί MFM/RLL ή ESDI CD-ROM" + +msgid "Custom…" +msgstr "Προσαρμογή…" + +msgid "Custom (large)…" +msgstr "Προσαρμογή (μεγάλο)…" + +msgid "Add New Hard Disk" +msgstr "Προσθήκη Νέου Σκληρού Δίσκου" + +msgid "Add Existing Hard Disk" +msgstr "Προσθήκη Υπάρχων Σκληρού Δίσκου" + +msgid "HDI disk images cannot be larger than 4 GB." +msgstr "Οι εικόνες δίσκου HDI δεν μπορούν να είναι μεγαλύτερες απο 4 GB." + +msgid "Disk images cannot be larger than 127 GB." +msgstr "Οι εικόνες δίσκου δεν μπορούν να είναι μεγαλύτερες απο 127 GB." + +msgid "Hard disk images" +msgstr "Εικόνες σκληρού δίσκου" + +msgid "Unable to read file" +msgstr "Αδυναμία ανάγνωσης αρχείου" + +msgid "Unable to write file" +msgstr "Αδυναμία εγγραφής αρχείου" + +msgid "HDI or HDX images with a sector size other than 512 are not supported." +msgstr "Δεν υποστηρίζονται εικόνες HDI ή HDX με μέγεθος sector διαφορετικό από 512." + +msgid "Disk image file already exists" +msgstr "Το αρχείο εικόνας δίσκου υπάρχει ήδη" + +msgid "Please specify a valid file name." +msgstr "Παρακαλώ ορίστε άνα έγκυρο όνομα αρχείου." + +msgid "Disk image created" +msgstr "Η εικόνα δίσκου δημιουργήθηκε" + +msgid "Make sure the file exists and is readable." +msgstr "Βεβαιωθείτε οτι το αρχείο υπάρχει και οτι είναι αναγνώσιμο." + +msgid "Make sure the file is being saved to a writable directory." +msgstr "Βεβαιωθείτε οτι το αρχείο αποθηκεύεται σε έναν εγγράψιμο κατάλογο." + +msgid "Disk image too large" +msgstr "Η εικόνα δίσκου είναι αρκετά μεγάλη" + +msgid "Remember to partition and format the newly-created drive." +msgstr "Μην ξεχάσετε να κάνετε partition και format τον νέο-δημιουργημένο δίσκο." + +msgid "The selected file will be overwritten. Are you sure you want to use it?" +msgstr "Το επιλεγμένο αρχείο θα επανεγγραφεί. Θέλετε σίγουρα να το χρησιμοποιήσετε;" + +msgid "Unsupported disk image" +msgstr "Μη υποστηριζόμενη εικόνα δίσκου" + +msgid "Overwrite" +msgstr "Επανεγγραφή" + +msgid "Don't overwrite" +msgstr "Να μην γίνει επανεγγραφή" + +msgid "Raw image" +msgstr "Εικόνα Raw" + +msgid "HDI image" +msgstr "Εικόνα HDI" + +msgid "HDX image" +msgstr "Εικόνα HDX" + +msgid "Fixed-size VHD" +msgstr "Fixed-size VHD" + +msgid "Dynamic-size VHD" +msgstr "Dynamic-size VHD" + +msgid "Differencing VHD" +msgstr "Differencing VHD" + +msgid "(N/A)" +msgstr "(Μ/Δ)" + +msgid "Raw image (.img)" +msgstr "Εικόνα Raw (.img)" + +msgid "HDI image (.hdi)" +msgstr "Εικόνα HDI (.hdi)" + +msgid "HDX image (.hdx)" +msgstr "Εικόνα HDX (.hdx)" + +msgid "Fixed-size VHD (.vhd)" +msgstr "Fixed-size VHD (.vhd)" + +msgid "Dynamic-size VHD (.vhd)" +msgstr "Dynamic-size VHD (.vhd)" + +msgid "Differencing VHD (.vhd)" +msgstr "Differencing VHD (.vhd)" + +msgid "Large blocks (2 MB)" +msgstr "Large blocks (2 MB)" + +msgid "Small blocks (512 KB)" +msgstr "Small blocks (512 KB)" + +msgid "VHD files" +msgstr "Αρχεία VHD" + +msgid "Select the parent VHD" +msgstr "Επιλέξτε το αρχικό VHD" + +msgid "This could mean that the parent image was modified after the differencing image was created.\n\nIt can also happen if the image files were moved or copied, or by a bug in the program that created this disk.\n\nDo you want to fix the timestamps?" +msgstr "Αυτό μπορεί να σημαίνει οτι η αρχική εικόνα έχει τροποποιηθεί μετά την δημιουργία differencing image.\n\nΜπορεί επίσης να συμβεί αν τα αρχεία εικόνας έχουν μετακινηθεί ή αντιγραφεί, ή ένα bug του προγράμματος που δημιουργήθηκε αυτός ο δίσκος.\n\nΕπιθυμείτε να διορθώσετε τις χρονικές σημάνσεις;" + +msgid "Parent and child disk timestamps do not match" +msgstr "Οι χρονικές σημάνσεις αρχικού και δευτερεύον δίσκου δεν ταιριάζουν" + +msgid "Could not fix VHD timestamp." +msgstr "Αποτυχία διόρθωσης χρονικής σημάνσης VHD." + +msgid "CD-ROM %1 (%2): %3" +msgstr "CD-ROM %1 (%2): %3" + +msgid "&CD-ROM %1 (%2): %3" +msgstr "&CD-ROM %1 (%2): %3" + +msgid "160 KB" +msgstr "160 KB" + +msgid "180 KB" +msgstr "180 KB" + +msgid "320 KB" +msgstr "320 KB" + +msgid "360 KB" +msgstr "360 KB" + +msgid "640 KB" +msgstr "640 KB" + +msgid "720 KB" +msgstr "720 KB" + +msgid "1.2 MB" +msgstr "1.2 MB" + +msgid "1.25 MB" +msgstr "1.25 MB" + +msgid "1.44 MB" +msgstr "1.44 MB" + +msgid "DMF (cluster 1024)" +msgstr "DMF (cluster 1024)" + +msgid "DMF (cluster 2048)" +msgstr "DMF (cluster 2048)" + +msgid "2.88 MB" +msgstr "2.88 MB" + +msgid "ZIP 100" +msgstr "ZIP 100" + +msgid "3.5\" 128 MB (ISO 10090)" +msgstr "3.5\" 128 MB (ISO 10090)" + +msgid "3.5\" 230 MB (ISO 13963)" +msgstr "3.5\" 230 MB (ISO 13963)" + +msgid "3.5\" 540 MB (ISO 15498)" +msgstr "3.5\" 540 MB (ISO 15498)" + +msgid "3.5\" 640 MB (ISO 15498)" +msgstr "3.5\" 640 MB (ISO 15498)" + +msgid "3.5\" 1.3 GB (GigaMO)" +msgstr "3.5\" 1.3 GB (GigaMO)" + +msgid "3.5\" 2.3 GB (GigaMO 2)" +msgstr "3.5\" 2.3 GB (GigaMO 2)" + +msgid "5.25\" 600 MB" +msgstr "5.25\" 600 MB" + +msgid "5.25\" 650 MB" +msgstr "5.25\" 650 MB" + +msgid "5.25\" 1 GB" +msgstr "5.25\" 1 GB" + +msgid "5.25\" 1.3 GB" +msgstr "5.25\" 1.3 GB" + +msgid "Perfect RPM" +msgstr "Ιδανικό RPM" + +msgid "1% below perfect RPM" +msgstr "1% κάτω απο το ιδανικό RPM" + +msgid "1.5% below perfect RPM" +msgstr "1.5% κάτω απο το ιδανικό RPM" + +msgid "2% below perfect RPM" +msgstr "2% κάτω απο το ιδανικό RPM" + +msgid "(System Default)" +msgstr "(Προεπιλογή Συστήματος)" + +msgid "Failed to initialize network driver" +msgstr "Αποτυχία αρχικοποίησης οδηγού δικτύου" + +msgid "The network configuration will be switched to the null driver" +msgstr "Η παραμετροποίηση δικτύου θα αλλάξει σε άκυρο οδηγό" + +msgid "Mouse sensitivity:" +msgstr "Ευαισθησία ποντικιού:" + +msgid "Select media images from program working directory" +msgstr "Επιλέξτε αρχεία πολυμέσων απο τον ενεργό κατάλογο προγράμματος" + +msgid "PIT mode:" +msgstr "PIT mode:" + +msgid "Auto" +msgstr "Αυτόματο" + +msgid "Slow" +msgstr "Αργό" + +msgid "Fast" +msgstr "Γρήγορο" + +msgid "&Auto-pause on focus loss" +msgstr "&Αυτόματο-πάγωμα σε απώλεια εστίασης" + +msgid "WinBox is no longer supported" +msgstr "Το WinBox δεν υποστηρίζεται πλέον" + +msgid "Development of the WinBox manager stopped in 2022 due to a lack of maintainers. As we direct our efforts towards making 86Box even better, we have made the decision to no longer support WinBox as a manager.\n\nNo further updates will be provided through WinBox, and you may encounter incorrect behavior should you continue using it with newer versions of 86Box. Any bug reports related to WinBox behavior will be closed as invalid.\n\nGo to 86box.net for a list of other managers you can use." +msgstr "" +"Η ανάπτυξη του WinBox σταμάτησε το 2022 λόγω έλλειψης maintainers. Καθώς " +"καταβάλλουμε κάθε δυνατή προσπάθεια για να βελτιώσουμε ακόμη περισσότερο το " +"86Box, έχουμε λάβει την απόφαση να μην υποστηρίζουμε πλέον το WinBox ως " +"διαχειριστή μηχανών.\n" +"\n" +"Δεν θα παρέχονται περαιτέρω ενημερώσεις μέσω του WinBox και ενδέχεται να " +"αντιμετωπίσετε εσφαλμένη συμπεριφορά σε περίπτωση που συνεχίσετε να το " +"χρησιμοποιείτε με νεότερες εκδόσεις του 86Box. Οποιεσδήποτε αναφορές " +"σφαλμάτων που σχετίζονται με τη συμπεριφορά του WinBox θα κλείνουν ως άκυρες." +"\n" +"\n" +"Μεταβείτε στο 86box.net για την λίστα με τους διαχειριστές μηχανών που " +"μπορείτε να χρησιμοποιήσετε." + +msgid "Generate" +msgstr "Δημιουργία" + +msgid "Joystick configuration" +msgstr "Ρύθμιση Joystick" + +msgid "Device" +msgstr "Συσκευή" + +msgid "%1 (X axis)" +msgstr "%1 (άξονας X)" + +msgid "%1 (Y axis)" +msgstr "%1 (άξονας Y)" + +msgid "MCA devices" +msgstr "Συσκευές MCA" + +msgid "List of MCA devices:" +msgstr "Λίστα συσκευών MCA:" + +msgid "&Tablet tool" +msgstr "Εργαλείο &Tablet" + +msgid "About &Qt" +msgstr "Σχετικά με το &Qt" + +msgid "&MCA devices…" +msgstr "Συσκευές &MCA…" + +msgid "Show non-&primary monitors" +msgstr "Εμφάνιση μη-&κύριων οθονών" + +msgid "Open screenshots &folder…" +msgstr "Άνοιγμα &φακέλου στιγμιότυπων…" + +msgid "Appl&y fullscreen stretch mode when maximized" +msgstr "Εφαρμο&γή πλήρους οθόνης σε τέντωμα κατα την μεγιστοποίηση" + +msgid "&Cursor/Puck" +msgstr "&Cursor/Puck" + +msgid "&Pen" +msgstr "&Γραφίδα" + +msgid "&Host CD/DVD Drive (%1:)" +msgstr "&Host CD/DVD Drive (%1:)" + +msgid "&Connected" +msgstr "&Συνδεδεμένο" + +msgid "Clear image &history" +msgstr "Καθαρισμός &ιστορικού εικόνων" + +msgid "Create…" +msgstr "Δημιουργία…" + +msgid "Host CD/DVD Drive (%1)" +msgstr "Host CD/DVD Drive (%1)" + +msgid "Unknown Bus" +msgstr "Άγνωστος δίαυλος" + +msgid "Null Driver" +msgstr "Κενός Οδηγός" + +msgid "NIC:" +msgstr "NIC:" + +msgid "NIC %1 (%2) %3" +msgstr "NIC %1 (%2) %3" + +msgid "&NIC %1 (%2) %3" +msgstr "&NIC %1 (%2) %3" + +msgid "Render behavior" +msgstr "Συμπεριφορά απεικόνισης" + +msgid "Use target framerate:" +msgstr "Χρήση στόχου καρέ:" + +msgid " fps" +msgstr " fps" + +msgid "VSync" +msgstr "VSync" + +msgid "Synchronize with video" +msgstr "Συγχρονισμός με βίντεο" + +msgid "Shaders" +msgstr "Shaders" + +msgid "Remove" +msgstr "Κατάργηση" + +msgid "Browse…" +msgstr "Αναζήτηση…" + +msgid "Couldn't create OpenGL context." +msgstr "Αδυναμία δημιουργίας περιεχομένου OpenGL." + +msgid "Couldn't switch to OpenGL context." +msgstr "Αδυναμία εναλλαγής σε περιεχόμενο OpenGL." + +msgid "OpenGL version 3.0 or greater is required. Current version is %1.%2" +msgstr "Απαιτείται έκδοση OpenGL 3.0 ή νεότερη. Η τρέχουσα έκδοση είναι %1.%2" + +msgid "Error initializing OpenGL" +msgstr "Σφάλμα αρχικοποίησης OpenGL" + +msgid "\nFalling back to software rendering." +msgstr "" +"\n" +"Εναλλαγή σε απεικόνιση λογισμικού." + +msgid "

When selecting media images (CD-ROM, floppy, etc.) the open dialog will start in the same directory as the 86Box configuration file. This setting will likely only make a difference on macOS.

" +msgstr "" +"

Όταν επιλέγετε εικόνες πολυμέσων (CD-ROM, δισκέτα κλπ.)" +", το παράθυρο διαλόγου ανοίγματος θα ξεκινήσει στον ίδιο κατάλογο με το " +"αρχείο διαμόρφωσης του 86Box. Αυτή η ρύθμιση πιθανότατα θα κάνει τη διαφορά " +"μόνο σε macOS.

" + +msgid "This machine might have been moved or copied." +msgstr "Αυτή η μηχανή ίσως έχει μετακινηθεί ή αντιγραφεί." + +msgid "In order to ensure proper networking functionality, 86Box needs to know if this machine was moved or copied.\n\nSelect \"I Copied It\" if you are not sure." +msgstr "" +"Για να διασφαλιστεί η σωστή λειτουργία δικτύου, το 86Box πρέπει να γνωρίζει " +"αν αυτή η μηχανή έχει μετακινηθεί ή αντιγραφεί.\n" +"\n" +"Επιλέξτε \"Αντιγράφηκε\" εάν δεν είστε σίγουρος." + +msgid "I Moved It" +msgstr "Μετακινήθηκε" + +msgid "I Copied It" +msgstr "Αντιγράφηκε" + +msgid "86Box Monitor #%1" +msgstr "Οθόνη 86Box #%1" + +msgid "No MCA devices." +msgstr "Καμία συσκευή MCA." + +msgid "MiB" +msgstr "MiB" + +msgid "GiB" +msgstr "GiB" + +msgid "Network Card #1" +msgstr "Κάρτα δικτύου #1" + +msgid "Network Card #2" +msgstr "Κάρτα δικτύου #2" + +msgid "Network Card #3" +msgstr "Κάρτα δικτύου #3" + +msgid "Network Card #4" +msgstr "Κάρτα δικτύου #4" + +msgid "Mode:" +msgstr "Τύπος:" + +msgid "Interface:" +msgstr "Διασύνδεση:" + +msgid "Adapter:" +msgstr "Προσαρμογέας:" + +msgid "VDE Socket:" +msgstr "VDE Socket:" + +msgid "TAP Bridge Device:" +msgstr "Συσκευή TAP Γέφυρας:" + +msgid "86Box Unit Tester" +msgstr "Μονάδα δοκιμής 86Box" + +msgid "Novell NetWare 2.x Key Card" +msgstr "Novell NetWare 2.x Key Card" + +msgid "Serial port passthrough 1" +msgstr "Διέλευση σειριακής πόρτας 1" + +msgid "Serial port passthrough 2" +msgstr "Διέλευση σειριακής πόρτας 2" + +msgid "Serial port passthrough 3" +msgstr "Διέλευση σειριακής πόρτας 3" + +msgid "Serial port passthrough 4" +msgstr "Διέλευση σειριακής πόρτας 4" + +msgid "Renderer &options…" +msgstr "&Επιλογές απεικόνισης…" + +msgid "PC/XT Keyboard" +msgstr "Πληκτρολόγιο PC/XT" + +msgid "AT Keyboard" +msgstr "Πληκτρολόγιο AT" + +msgid "AX Keyboard" +msgstr "Πληκτρολόγιο AX" + +msgid "PS/2 Keyboard" +msgstr "Πληκτρολόγιο PS/2" + +msgid "PS/55 Keyboard" +msgstr "Πληκτρολόγιο PS/55" + +msgid "Keys" +msgstr "Πλήκτρα" + +msgid "Logitech/Microsoft Bus Mouse" +msgstr "Ποντίκι διαύλου Logitech/Microsoft" + +msgid "Microsoft Bus Mouse (InPort)" +msgstr "Ποντίκι διαύλου Microsoft (InPort)" + +msgid "Mouse Systems Serial Mouse" +msgstr "Σειριακό ποντίκι Mouse Systems" + +msgid "Mouse Systems Bus Mouse" +msgstr "Ποντίκι διαύλου Mouse Systems" + +msgid "Microsoft Serial Mouse" +msgstr "Σειριακό ποντίκι Microsoft" + +msgid "Microsoft Serial BallPoint" +msgstr "Σειριακό ποντίκι Microsoft BallPoint" + +msgid "Logitech Serial Mouse" +msgstr "Σειριακό ποντίκι Logitech" + +msgid "PS/2 Mouse" +msgstr "Ποντίκι PS/2" + +msgid "PS/2 QuickPort Mouse" +msgstr "Ποντίκι PS/2 QuickPort" + +msgid "3M MicroTouch (Serial)" +msgstr "3M MicroTouch (Σειριακό)" + +msgid "Default Baud rate" +msgstr "Προεπιλεγμένος ρυθμός Baud" + +msgid "[COM] Standard Hayes-compliant Modem" +msgstr "[COM] Τυπικό μόντεμ συμβατό με Hayes" + +msgid "Roland MT-32 Emulation" +msgstr "Εξομοίωση Roland MT-32" + +msgid "Roland MT-32 (New) Emulation" +msgstr "Εξομοίωση Roland MT-32 (Νέο)" + +msgid "Roland CM-32L Emulation" +msgstr "Εξομοίωση Roland CM-32L" + +msgid "Roland CM-32LN Emulation" +msgstr "Εξομοίωση Roland CM-32LN" + +msgid "OPL4-ML Daughterboard" +msgstr "Θυγατρική κάρτα OPL4-ML" + +msgid "System MIDI" +msgstr "MIDI συστήματος" + +msgid "MIDI Input Device" +msgstr "Συσκευή Εισαγωγής MIDI" + +msgid "BIOS file" +msgstr "Αρχείο BIOS" + +msgid "BIOS file (ROM #1)" +msgstr "Αρχείο BIOS (ROM #1)" + +msgid "BIOS file (ROM #2)" +msgstr "Αρχείο BIOS (ROM #2)" + +msgid "BIOS file (ROM #3)" +msgstr "Αρχείο BIOS (ROM #3)" + +msgid "BIOS file (ROM #4)" +msgstr "Αρχείο BIOS (ROM #4)" + +msgid "BIOS address" +msgstr "Διεύθυνση BIOS" + +msgid "BIOS address (ROM #1)" +msgstr "Διεύθυνση BIOS (ROM #1)" + +msgid "BIOS address (ROM #2)" +msgstr "Διεύθυνση BIOS (ROM #2)" + +msgid "BIOS address (ROM #3)" +msgstr "Διεύθυνση BIOS (ROM #3)" + +msgid "BIOS address (ROM #4)" +msgstr "Διεύθυνση BIOS (ROM #4)" + +msgid "Enable BIOS extension ROM Writes" +msgstr "Ενεργοποίηση επέκτασης BIOS εγγραφών ROM" + +msgid "Enable BIOS extension ROM Writes (ROM #1)" +msgstr "Ενεργοποίηση επέκτασης BIOS εγγραφών ROM (ROM #1)" + +msgid "Enable BIOS extension ROM Writes (ROM #2)" +msgstr "Ενεργοποίηση επέκτασης BIOS εγγραφών ROM (ROM #2)" + +msgid "Enable BIOS extension ROM Writes (ROM #3)" +msgstr "Ενεργοποίηση επέκτασης BIOS εγγραφών ROM (ROM #3)" + +msgid "Enable BIOS extension ROM Writes (ROM #4)" +msgstr "Ενεργοποίηση επέκτασης BIOS εγγραφών ROM (ROM #4)" + +msgid "Linear framebuffer base" +msgstr "Βάση γραμμικού framebuffer" + +msgid "Address" +msgstr "Διεύθυνση" + +msgid "IRQ" +msgstr "IRQ" + +msgid "Serial port IRQ" +msgstr "IRQ σειριακής θύρας" + +msgid "Parallel port IRQ" +msgstr "IRQ παράλληλης θύρας" + +msgid "Hard disk" +msgstr "Σκληρός δίσκος" + +msgid "BIOS Revision" +msgstr "Αναθεώρηση BIOS" + +msgid "BIOS Version" +msgstr "Έκδοση BIOS" + +msgid "BIOS Language" +msgstr "Γλώσσα BIOS" + +msgid "IBM 5161 Expansion Unit" +msgstr "Επέκταση μονάδας IBM 5161" + +msgid "IBM Cassette Basic" +msgstr "IBM Cassette Basic" + +msgid "Translate 26 -> 17" +msgstr "Μετάφρασε 26 -> 17" + +msgid "Language" +msgstr "Γλώσσα" + +msgid "Enable backlight" +msgstr "Ενεργοποίηση οπίσθιου φωτισμού" + +msgid "Invert colors" +msgstr "Αντιστροφή χρωμάτων" + +msgid "BIOS size" +msgstr "Μέγεθος BIOS" + +msgid "BIOS size (ROM #1)" +msgstr "Μέγεθος BIOS (ROM #1)" + +msgid "BIOS size (ROM #2)" +msgstr "Μέγεθος BIOS (ROM #2)" + +msgid "BIOS size (ROM #3)" +msgstr "Μέγεθος BIOS (ROM #3)" + +msgid "BIOS size (ROM #4)" +msgstr "Μέγεθος BIOS (ROM #4)" + +msgid "Map C0000-C7FFF as UMB" +msgstr "Ορισμός C0000-C7FFF ως UMB" + +msgid "Map C8000-CFFFF as UMB" +msgstr "Ορισμός C8000-CFFFF ως UMB" + +msgid "Map D0000-D7FFF as UMB" +msgstr "Ορισμός D0000-D7FFF ως UMB" + +msgid "Map D8000-DFFFF as UMB" +msgstr "Ορισμός D8000-DFFFF ως UMB" + +msgid "Map E0000-E7FFF as UMB" +msgstr "Ορισμός E0000-E7FFF ως UMB" + +msgid "Map E8000-EFFFF as UMB" +msgstr "Ορισμός E8000-EFFFF ως UMB" + +msgid "JS9 Jumper (JIM)" +msgstr "JS9 Jumper (JIM)" + +msgid "MIDI Output Device" +msgstr "Συσκευή Εξόδου MIDI" + +msgid "MIDI Real time" +msgstr "MIDI Πραγματικού χρόνου" + +msgid "MIDI Thru" +msgstr "MIDI Thru" + +msgid "MIDI Clockout" +msgstr "MIDI Clockout" + +msgid "Output Gain" +msgstr "Ενίσχυση Έντασης" + +msgid "Chorus" +msgstr "Chorus" + +msgid "Chorus Voices" +msgstr "Chorus Voices" + +msgid "Chorus Level" +msgstr "Επίπεδο Chorus" + +msgid "Chorus Speed" +msgstr "Ταχύτητα Chorus" + +msgid "Chorus Depth" +msgstr "Βάθος Chorus" + +msgid "Chorus Waveform" +msgstr "Chorus Waveform" + +msgid "Reverb" +msgstr "Reverb" + +msgid "Reverb Room Size" +msgstr "Μέγεθος Δωματίου Reverb" + +msgid "Reverb Damping" +msgstr "Απόσβεση Reverb" + +msgid "Reverb Width" +msgstr "Πλάτος Reverb" + +msgid "Reverb Level" +msgstr "Επίπεδο Reverb" + +msgid "Interpolation Method" +msgstr "Μέθοδος παρεμβολής" + +msgid "Dynamic Sample Loading" +msgstr "Φόρτωση Δυναμικού Δείγματος" + +msgid "Reverb Output Gain" +msgstr "Ενίσχυση Εξόδου Reverb" + +msgid "Reversed stereo" +msgstr "Αντίστροφο Στέρεο" + +msgid "Nice ramp" +msgstr "Ωραία ράμπα" + +msgid "Hz" +msgstr "Hz" + +msgid "Buttons" +msgstr "Κουμπιά" + +msgid "Serial Port" +msgstr "Σειριακή θύρα" + +msgid "RTS toggle" +msgstr "RTS toggle" + +msgid "Revision" +msgstr "Αναθεώρηση" + +msgid "Controller" +msgstr "Controller" + +msgid "Show Crosshair" +msgstr "Εμφάνιση στόχαστρου" + +msgid "DMA" +msgstr "DMA" + +msgid "MAC Address" +msgstr "Διεύθυνση MAC" + +msgid "MAC Address OUI" +msgstr "Διεύθυνση MAC OUI" + +msgid "Enable BIOS" +msgstr "Ενεργοποίηση BIOS" + +msgid "Baud Rate" +msgstr "Τιμή Baud" + +msgid "TCP/IP listening port" +msgstr "Θύρα ακρόασης TCP/IP" + +msgid "Phonebook File" +msgstr "Αρχείο Τηλεφωνικού καταλόγου" + +msgid "Telnet emulation" +msgstr "Εξομοίωση Telnet" + +msgid "RAM Address" +msgstr "Διεύθυνση RAM" + +msgid "RAM size" +msgstr "Μέγεθος RAM" + +msgid "Initial RAM size" +msgstr "Αρχικό μέγεθος RAM" + +msgid "Serial Number" +msgstr "Σειριακός Αριθμός" + +msgid "Host ID" +msgstr "ID οικοδεσπότη" + +msgid "FDC Address" +msgstr "Διεύθυνση FDC" + +msgid "MPU-401 Address" +msgstr "Διεύθυνση MPU-401" + +msgid "MPU-401 IRQ" +msgstr "MPU-401 IRQ" + +msgid "Receive MIDI input" +msgstr "Λήψη εισαγωγής MIDI" + +msgid "Low DMA" +msgstr "Χαμηλό DMA" + +msgid "Enable Game port" +msgstr "Ενεργοποίηση Game port" + +msgid "Enable Adlib ports" +msgstr "Ενεργοποίηση θυρών Adlib" + +msgid "SID Model" +msgstr "Μοντέλο SID" + +msgid "SID Filter Strength" +msgstr "Ισχύς Φίλτρου SID" + +msgid "Surround module" +msgstr "Μονάδα Surround" + +msgid "SB Address" +msgstr "Διεύθυνση SB" + +msgid "Adlib Address" +msgstr "Διεύθυνση Adlib" + +msgid "Use EEPROM setting" +msgstr "Χρήση ρύθμισης EEPROM" + +msgid "WSS IRQ" +msgstr "WSS IRQ" + +msgid "WSS DMA" +msgstr "WSS DMA" + +msgid "RTC IRQ" +msgstr "RTC IRQ" + +msgid "RTC Port Address" +msgstr "Διεύθυνση Θύρας RTC" + +msgid "Onboard RTC" +msgstr "Ενσωματωμένο RTC" + +msgid "Not installed" +msgstr "Μη εγκατεστημένο" + +msgid "Enable OPL" +msgstr "Ενεργοποίηση OPL" + +msgid "Receive MIDI input (MPU-401)" +msgstr "Λήψη εισαγωγής MIDI (MPU-401)" + +msgid "SB low DMA" +msgstr "χαμηλό DMA της SB" + +msgid "6CH variant (6-channel)" +msgstr "Παραλλαγή 6CH (6-κάναλο)" + +msgid "Enable CMS" +msgstr "Ενεργοποίηση CMS" + +msgid "Mixer" +msgstr "Μίκτης" + +msgid "High DMA" +msgstr "Υψηλό DMA" + +msgid "Control PC speaker" +msgstr "Έλεγχος ηχείου PC" + +msgid "Memory size" +msgstr "Μέγεθος μνήμης" + +msgid "EMU8000 Address" +msgstr "Διεύθυνση EMU8000" + +msgid "IDE Controller" +msgstr "Ελεγκτής IDE" + +msgid "Codec" +msgstr "Codec" + +msgid "GUS type" +msgstr "Τύπος GUS" + +msgid "Enable 0x04 \"Exit 86Box\" command" +msgstr "Ενεργοποίηση εντολής 0x04 \"Έξοδος 86Box\"" + +msgid "Display type" +msgstr "Τύπος εμφάνισης" + +msgid "Composite type" +msgstr "Τύπος Composite" + +msgid "RGB type" +msgstr "Τύπος RGB" + +msgid "Line doubling type" +msgstr "Τύπος line doubling" + +msgid "Snow emulation" +msgstr "Εξομοίωση εφέ \"χιονιού\"" + +msgid "Monitor type" +msgstr "Τύπος οθόνης" + +msgid "Character set" +msgstr "Σετ χαρακτήρων" + +msgid "XGA type" +msgstr "Τύπος XGA" + +msgid "Instance" +msgstr "Παράδειγμα" + +msgid "MMIO Address" +msgstr "Διεύθυνση MMIO" + +msgid "RAMDAC type" +msgstr "Τύπος RAMDAC" + +msgid "Blend" +msgstr "Ανάμιξη" + +msgid "Font" +msgstr "Γραμματοσειρά" + +msgid "Bilinear filtering" +msgstr "Διγραμμικό φίλτρο" + +msgid "Video chroma-keying" +msgstr "Video chroma-keying" + +msgid "Dithering" +msgstr "Χρωματική αντιπαράθεση" + +msgid "Enable NMI for CGA emulation" +msgstr "Ενεργοποίηση NMI για εξομοίωση CGA" + +msgid "Voodoo type" +msgstr "Τύπος Voodoo" + +msgid "Framebuffer memory size" +msgstr "Μέγεθος μνήμης Framebuffer" + +msgid "Texture memory size" +msgstr "Μέγεθος μνήμης υφών" + +msgid "Dither subtraction" +msgstr "Αφαίρεση τρέμουλου" + +msgid "Screen Filter" +msgstr "Φίλτρο οθόνης" + +msgid "Render threads" +msgstr "Νήματα απεικονιστή" + +msgid "SLI" +msgstr "SLI" + +msgid "Start Address" +msgstr "Διεύθυνση εκκίνησης" + +msgid "Contiguous Size" +msgstr "Μέγεθος συνέχειας" + +msgid "I/O Width" +msgstr "Πλάτος I/O" + +msgid "Transfer Speed" +msgstr "Ταχύτητα Μεταφοράς" + +msgid "EMS mode" +msgstr "Τύπος EMS" + +msgid "EMS Address" +msgstr "Διεύθυνση EMS" + +msgid "EMS 1 Address" +msgstr "Διεύθυνση EMS 1" + +msgid "EMS 2 Address" +msgstr "Διεύθυνση EMS 2" + +msgid "EMS Memory Size" +msgstr "Μέγεθος μνήμης EMS" + +msgid "EMS 1 Memory Size" +msgstr "Μέγεθος μνήμης EMS 1" + +msgid "EMS 2 Memory Size" +msgstr "Μέγεθος μνήμης EMS 2" + +msgid "Enable EMS" +msgstr "Ενεργοποίηση EMS" + +msgid "Enable EMS 1" +msgstr "Ενεργοποίηση EMS 1" + +msgid "Enable EMS 2" +msgstr "Ενεργοποίηση EMS 2" + +msgid "Address for > 2 MB" +msgstr "Διεύθυνση για > 2 MB" + +msgid "Frame Address" +msgstr "Διεύθυνση πλαισίου" + +msgid "USA" +msgstr "ΗΠΑ" + +msgid "Danish" +msgstr "Δανικά" + +msgid "Always at selected speed" +msgstr "Πάντα στην επιλεγμένη ταχύτητα" + +msgid "BIOS setting + Hotkeys (off during POST)" +msgstr "Ρύθμιση BIOS + Συντόμευση πλήκτρων (ανενεργά κατά το POST)" + +msgid "64 KB starting from F0000" +msgstr "64 KB αρχή από F0000" + +msgid "128 KB starting from E0000 (address MSB inverted, last 64 KB first)" +msgstr "" +"128 KB αρχή από E0000 (διεύθυνση MSB αντεστραμμένη, τελευταία 64 KB πρώτα)" + +msgid "Sine" +msgstr "Ημίτονο" + +msgid "Triangle" +msgstr "Τρίγωνο" + +msgid "Linear" +msgstr "Γραμμικό" + +msgid "4th Order" +msgstr "4η Τάξη" + +msgid "7th Order" +msgstr "7η Τάξη" + +msgid "Non-timed (original)" +msgstr "Χωρίς χρονικό περιορισμό (πρωτότυπο)" + +msgid "45 Hz (JMP2 not populated)" +msgstr "45 Hz (JMP2 not populated)" + +msgid "Two" +msgstr "Δύο" + +msgid "Three" +msgstr "Τρία" + +msgid "Wheel" +msgstr "Τροχό" + +msgid "Five + Wheel" +msgstr "Πέντε + Τροχό" + +msgid "Five + 2 Wheels" +msgstr "Πέντε + 2 Τροχοί" + +msgid "A3 - SMT2 Serial / SMT3(R)V" +msgstr "A3 - SMT2 Serial / SMT3(R)V" + +msgid "Q1 - SMT3(R) Serial" +msgstr "Q1 - SMT3(R) Serial" + +msgid "8 KB" +msgstr "8 KB" + +msgid "32 KB" +msgstr "32 KB" + +msgid "16 KB" +msgstr "16 KB" + +msgid "64 KB" +msgstr "64 KB" + +msgid "Disable BIOS" +msgstr "Απενεργοποίηση BIOS" + +msgid "512 KB" +msgstr "512 KB" + +msgid "2 MB" +msgstr "2 MB" + +msgid "8 MB" +msgstr "8 MB" + +msgid "28 MB" +msgstr "28 MB" + +msgid "1 MB" +msgstr "1 MB" + +msgid "4 MB" +msgstr "4 MB" + +msgid "12 MB" +msgstr "12 MB" + +msgid "16 MB" +msgstr "16 MB" + +msgid "20 MB" +msgstr "20 MB" + +msgid "24 MB" +msgstr "24 MB" + +msgid "SigmaTel STAC9721T (stereo)" +msgstr "SigmaTel STAC9721T (stereo)" + +msgid "256 KB" +msgstr "256 KB" + +msgid "Composite" +msgstr "Composite" + +msgid "True color" +msgstr "Ζωντανά χρώματα" + +msgid "Old" +msgstr "Παλαιό" + +msgid "New" +msgstr "Νέο" + +msgid "Color (generic)" +msgstr "Έγχρωμο (γενικό)" + +msgid "Green Monochrome" +msgstr "Μονόχρωμο Πράσινο" + +msgid "Amber Monochrome" +msgstr "Μονόχρωμο Κεχριμπάρι" + +msgid "Gray Monochrome" +msgstr "Μονόχρωμο Γκρι" + +msgid "Color (no brown)" +msgstr "Έγχρωμο (χωρίς το καφέ)" + +msgid "Color (IBM 5153)" +msgstr "Έγχρωμο (IBM 5153)" + +msgid "Simple doubling" +msgstr "Απλός διπλασιασμός" + +msgid "sRGB interpolation" +msgstr "Παρεμβολή sRGB" + +msgid "Linear interpolation" +msgstr "Γραμμική παρεμβολή" + +msgid "Has secondary 8x8 character set" +msgstr "Έχει δευτερεύον σετ χαρακτήρων 8x8" + +msgid "Has Quadcolor II daughter board" +msgstr "Έχει θυγατρική πλακέτα Quadcolor II" + +msgid "Alternate monochrome contrast" +msgstr "Εναλλακτική μονόχρωμη αντίθεση" + +msgid "128 KB" +msgstr "128 KB" + +msgid "Monochrome (5151/MDA) (white)" +msgstr "Μονόχρωμο (5151/MDA) (λευκό)" + +msgid "Monochrome (5151/MDA) (green)" +msgstr "Μονόχρωμο (5151/MDA) πράσινο)" + +msgid "Monochrome (5151/MDA) (amber)" +msgstr "Μονόχρωμο (5151/MDA) (κεχριμπάρι)" + +msgid "Color 40x25 (5153/CGA)" +msgstr "Έγχρωμο 40x25 (5153/CGA)" + +msgid "Color 80x25 (5153/CGA)" +msgstr "Έγχρωμο 80x25 (5153/CGA)" + +msgid "Enhanced Color - Normal Mode (5154/ECD)" +msgstr "Ενισχυμένο Έγχρωμο - Κανονικός Τύπος (5154/ECD)" + +msgid "Enhanced Color - Enhanced Mode (5154/ECD)" +msgstr "Ενισχυμένο Έγχρωμο - Ενισχυμένος Τύπος (5154/ECD)" + +msgid "Green" +msgstr "Πράσινο" + +msgid "Amber" +msgstr "Κεχριμπάρι" + +msgid "Gray" +msgstr "Γκρι" + +msgid "Grayscale" +msgstr "Κλίμακα του γκρι" + +msgid "Color" +msgstr "Έγχρωμο" + +msgid "U.S. English" +msgstr "Αγγλικά Η.Π." + +msgid "Scandinavian" +msgstr "Σκανδιναβικά" + +msgid "Other languages" +msgstr "Άλλες γλώσσες" + +msgid "Bochs latest" +msgstr "Τελευταία έκδοση Bochs" + +msgid "Apply overscan deltas" +msgstr "Εφαρμογή υπερσάρωσης δέλτα" + +msgid "Mono Interlaced" +msgstr "Μονόχρωμο πλεγμένο" + +msgid "Mono Non-Interlaced" +msgstr "Μονόχρωμο Μη-πλεγμένο" + +msgid "Color Interlaced" +msgstr "Έγχρωμο πλεγμένο" + +msgid "Color Non-Interlaced" +msgstr "Έγχρωμο Μη-πλεγμένο" + +msgid "3Dfx Voodoo Graphics" +msgstr "Γραφικά 3Dfx Voodoo" + +msgid "3Dfx Voodoo 2" +msgstr "3Dfx Voodoo 2" + +msgid "Obsidian SB50 + Amethyst (2 TMUs)" +msgstr "Obsidian SB50 + Amethyst (2 TMUs)" + +msgid "8-bit" +msgstr "8-bit" + +msgid "16-bit" +msgstr "16-bit" + +msgid "Standard (150ns)" +msgstr "Κανονικό (150ns)" + +msgid "High-Speed (120ns)" +msgstr "Υψηλής-Ταχύτητας (120ns)" + +msgid "Enabled" +msgstr "Ενεργοποιημένο" + +msgid "Standard" +msgstr "Κανονικό" + +msgid "High-Speed" +msgstr "Υψηλής-Ταχύτητας" + +msgid "Stereo LPT DAC" +msgstr "Στέρεο LPT DAC" + +msgid "Generic Text Printer" +msgstr "Γενικός Εκτυπωτής Κειμένου" + +msgid "Generic ESC/P 2 Dot-Matrix Printer" +msgstr "Γενικός Εκτυπωτής ESC/P 2 Dot-Matrix" + +msgid "Generic PostScript Printer" +msgstr "Γενικός Εκτυπωτής PostScript" + +msgid "Generic PCL5e Printer" +msgstr "Γενικός Εκτυπωτής PCL5e" + +msgid "Parallel Line Internet Protocol" +msgstr "Πρωτόκολλο Παράλληλης Γραμμής Internet" + +msgid "Protection Dongle for Savage Quest" +msgstr "Dongle προστασίας για το Savage Quest" + +msgid "Serial Passthrough Device" +msgstr "Συσκευή Σειριακής Διέλευσης" + +msgid "Passthrough Mode" +msgstr "Τύπος Διέλευσης" + +msgid "Host Serial Device" +msgstr "Σειριακή Συσκευή Οικοδεσπότη" + +msgid "Name of pipe" +msgstr "Όνομα pipe" + +msgid "Data bits" +msgstr "Bits δεδομένων" + +msgid "Stop bits" +msgstr "Bits σταματήματος" + +msgid "Baud Rate of Passthrough" +msgstr "Ρυθμός Baud της Διέλευσης" + +msgid "Named Pipe (Server)" +msgstr "Ονομασμένο Pipe (Διακομιστής)" + +msgid "Named Pipe (Client)" +msgstr "Ονομασμένο Pipe (Πελάτης)" + +msgid "Host Serial Passthrough" +msgstr "Σειριακή Διέλευση Οικοδεσπότη" + +msgid "E&ject %1" +msgstr "&Εξαγωγή %1" + +msgid "&Unmute" +msgstr "&Κατάργηση σίγασης" + +msgid "Softfloat FPU" +msgstr "FPU Softfloat" + +msgid "High performance impact" +msgstr "Μεγάλη επίπτωση στην απόδοση" + +msgid "[Generic] RAM Disk (max. speed)" +msgstr "[Γενικό] RAM Disk (τέρμα ταχύτητα)" + +msgid "[Generic] 1989 (3500 RPM)" +msgstr "[Γενικό] 1989 (3500 RPM)" + +msgid "[Generic] 1992 (3600 RPM)" +msgstr "[Γενικό] 1992 (3600 RPM)" + +msgid "[Generic] 1994 (4500 RPM)" +msgstr "[Γενικό] 1994 (4500 RPM)" + +msgid "[Generic] 1996 (5400 RPM)" +msgstr "[Γενικό] 1996 (5400 RPM)" + +msgid "[Generic] 1997 (5400 RPM)" +msgstr "[Γενικό] 1997 (5400 RPM)" + +msgid "[Generic] 1998 (5400 RPM)" +msgstr "[Γενικό] 1998 (5400 RPM)" + +msgid "[Generic] 2000 (7200 RPM)" +msgstr "[Γενικό] 2000 (7200 RPM)" + +msgid "IBM 8514/A clone (ISA)" +msgstr "Κλώνος IBM 8514/A (ISA)" + +msgid "Vendor" +msgstr "Πωλητής" + +msgid "30 Hz (JMP2 = 1)" +msgstr "30 Hz (JMP2 = 1)" + +msgid "60 Hz (JMP2 = 2)" +msgstr "60 Hz (JMP2 = 2)" + +msgid "Generic PC/XT Memory Expansion" +msgstr "Γενική Επέκταση Μνήμης PC/XT" + +msgid "Generic PC/AT Memory Expansion" +msgstr "Γενική Επέκταση Μνήμης PC/AT" + +msgid "Unable to find Dot-Matrix fonts" +msgstr "Αδυναμία εύρεσης γραμματοσειρών Dot-Matrix" + +msgid "TrueType fonts in the \"roms/printer/fonts\" directory are required for the emulation of the Generic ESC/P 2 Dot-Matrix Printer." +msgstr "" +"Απαιτούνται γραμματοσειρές TrueType στον κατάλογο \"roms/printer/fonts\" για " +"την εξομοίωση Γενικού εκτυπωτή ESC/P 2 Dot-Matrix." + +msgid "Inhibit multimedia keys" +msgstr "Αναστολή πλήκτρων πολυμέσων" + +msgid "Ask for confirmation before saving settings" +msgstr "Ερώτηση επιβεβαίωσης πριν από την αποθήκευση των ρυθμίσεων" + +msgid "Ask for confirmation before hard resetting" +msgstr "Ερώτηση επιβεβαίωσης πριν από την ολική επανεκκίνηση" + +msgid "Ask for confirmation before quitting" +msgstr "Ερώτηση επιβεβαίωσης κατά την έξοδο" + +msgid "Options" +msgstr "Επιλογές" + +msgid "Model" +msgstr "Μοντέλο" + +msgid "Model:" +msgstr "Μοντέλο:" + +msgid "Failed to initialize Vulkan renderer." +msgstr "Αποτυχία αρχικοποίησης απεικονιστή Vulkan." + +msgid "GLSL Error" +msgstr "Σφάλμα GLSL" + +msgid "Could not load shader: %1" +msgstr "Αδυναμία φόρτωσης σκίασης: %1" + +msgid "Could not load texture: %1" +msgstr "Αδυναμία φόρτωσης υφής: %1" + +msgid "Could not compile shader:\n\n%1" +msgstr "" +"Αδυναμία σύνταξης σκίασης:\n" +"\n" +"%1" + +msgid "Program not linked:\n\n%1" +msgstr "" +"Το πρόγραμμα δεν είναι συνδεδεμένο:\n" +"\n" +"%1" + +msgid "Shader Manager" +msgstr "Διαχειριστής Σκίασης" + +msgid "Shader Configuration" +msgstr "Διαμόρφωση Σκίασης" + +msgid "Add" +msgstr "Προσθήκη" + +msgid "Move up" +msgstr "Μετακίνηση επάνω" + +msgid "Move down" +msgstr "Μετακίνηση κάτω" + +msgid "Could not load file %1" +msgstr "Αδυναμία φόρτωσης αρχείου %1" + +msgid "Key Bindings:" +msgstr "Αντιστοιχίσεις Πλήκτρων:" + +msgid "Action" +msgstr "Ενέργεια" + +msgid "Keybind" +msgstr "Αντιστοιχία πλήκτρου" + +msgid "Clear binding" +msgstr "Καθαρισμός αντιστοιχίας" + +msgid "Bind" +msgstr "Αντιστοίχησε" + +msgid "Bind Key" +msgstr "Αντιστοίχησε Πλήκτρο" + +msgid "Enter key combo:" +msgstr "Εισαγωγή συνδυασμού πλήκτρων:" + +msgid "Bind conflict" +msgstr "Αντίκρουση αντιστοιχίας" + +msgid "This key combo is already in use." +msgstr "Αυτός ο συνδυασμός πλήκτρων χρησιμοποιείται ήδη." + +msgid "Send Control+Alt+Del" +msgstr "Αποστολή Control+Alt+Del" + +msgid "Send Control+Alt+Escape" +msgstr "Αποστολή Control+Alt+Escape" + +msgid "Toggle fullscreen" +msgstr "Εναλλαγή σε πλήρη οθόνη" + +msgid "Toggle UI in fullscreen" +msgstr "Εναλλαγή ΠΧ σε πλήρη οθόνη" + +msgid "Screenshot" +msgstr "Λήψη στιγμιότυπου" + +msgid "Release mouse pointer" +msgstr "Απελευθέρωση δείκτη ποντικιού" + +msgid "Toggle pause" +msgstr "Εναλλαγή παύσης" + +msgid "Toggle mute" +msgstr "Εναλλαγή σίγασης" + +msgid "Text files" +msgstr "Αρχεία κειμένου" + +msgid "ROM files" +msgstr "Αρχεία ROM" + +msgid "SoundFont files" +msgstr "Αρχεία SoundFont" + +msgid "Local Switch" +msgstr "Τοπικός Διακόπτης" + +msgid "Remote Switch" +msgstr "Απομακρυσμένος Διακόπτης" + +msgid "Switch:" +msgstr "Διακόπτης:" + +msgid "Hub Mode" +msgstr "Λειτουργία Hub" + +msgid "Hostname:" +msgstr "Όνομα οικοδεσπότη:" + +msgid "ISA RAM:" +msgstr "ISA RAM:" + +msgid "ISA ROM:" +msgstr "ISA ROM:" + +msgid "&Wipe NVRAM" +msgstr "&Εκκαθάριση NVRAM" + +msgid "This will delete all NVRAM (and related) files of the virtual machine located in the \"nvr\" subdirectory. You'll have to reconfigure the BIOS (and possibly other devices inside the VM) settings again if applicable.\n\nAre you sure you want to wipe all NVRAM contents of the virtual machine \"%1\"?" +msgstr "" +"Αυτό θα διαγράψει όλα τα αρχεία NVRAM (και τα σχετικά) της εικονικής μηχανής " +"που βρίσκονται στον υποκατάλογο \"nvr\". Θα πρέπει να επαναρυθμίσετε το BIOS " +"(και πιθανώς άλλε συσκευές εντός της εικονικής μηχανής), εάν είναι " +"απαραίτητο.\n" +"\n" +"Είστε βέβαιοι ότι θέλετε να διαγράψετε όλα τα περιεχόμενα NVRAM της " +"εικονικής μηχανής \"%1\";" + +msgid "Success" +msgstr "Επιτυχία" + +msgid "Successfully wiped the NVRAM contents of the virtual machine \"%1\"" +msgstr "Επιτυχής διαγραφή των περιεχομένων NVRAM της εικονικής μηχανής \"%1\"" + +msgid "An error occurred trying to wipe the NVRAM contents of the virtual machine \"%1\"" +msgstr "" + +msgid "%1 VM Manager" +msgstr "" + +msgid "%n disk(s)" +msgstr "" + +msgid "Unknown Status" +msgstr "" + +msgid "No Machines Found!" +msgstr "" + +msgid "Check for updates on startup" +msgstr "Έλεγχος για ενημερώσεις κατα την εκκίνηση" + +msgid "Unable to determine release information" +msgstr "" + +msgid "There was an error checking for updates:\n\n%1\n\nPlease try again later." +msgstr "" +"Υπήρξε σφάλμα κατα τον έλεγχο για ενημερώσεις :\n" +"\n" +"%1\n" +"\n" +"Παρακαλώ προσπαθήστε ξανά αργότερα." + +msgid "Update check complete" +msgstr "" + +msgid "stable" +msgstr "" + +msgid "beta" +msgstr "" + +msgid "You are running the latest %1 version of 86Box: %2" +msgstr "" + +msgid "version" +msgstr "" + +msgid "build" +msgstr "" + +msgid "You are currently running version %1." +msgstr "" + +msgid "Version %1 is now available." +msgstr "" + +msgid "You are currently running build %1." +msgstr "" + +msgid "Build %1 is now available." +msgstr "" + +msgid "Would you like to visit the download page?" +msgstr "" + +msgid "Visit download page" +msgstr "" + +msgid "Update check" +msgstr "" + +msgid "Checking for updates…" +msgstr "Έλεγχος για ενημερώσεις…" + +msgid "86Box Update" +msgstr "" + +msgid "Release notes:" +msgstr "" + +msgid "%1 Hz" +msgstr "" + +msgid "Virtual machine crash" +msgstr "" + +msgid "The virtual machine \"%1\"'s process has unexpectedly terminated with exit code %2." +msgstr "" + +msgid "The system will not be added." +msgstr "" + +msgid "&Update mouse every CPU frame" +msgstr "&Ανανέωση ποντικιού ανα καρέ CPU" + +msgid "Hue" +msgstr "" + +msgid "Saturation" +msgstr "" + +msgid "Contrast" +msgstr "" + +msgid "Brightness" +msgstr "" + +msgid "Sharpness" +msgstr "" + +msgid "&CGA composite settings…" +msgstr "Ρυθμίσεις &CGA composite…" + +msgid "CGA composite settings" +msgstr "Ρυθμίσεις CGA composite" + +msgid "Monitor EDID" +msgstr "Οθόνη EDID" + +msgid "Export…" +msgstr "Εξαγωγή…" + +msgid "Export EDID" +msgstr "Εξαγωγή EDID" + +msgid "EDID file \"%ls\" is too large." +msgstr "Το αρχείο EDID \"%ls\" είναι πολύ μεγάλο." + +msgid "OpenGL input scale" +msgstr "Κλίμακα OpenGL input" + +msgid "OpenGL input stretch mode" +msgstr "OpenGL input σε τύπο τεντώματος" + +msgid "Color scheme" +msgstr "Συνδυασμός χρωμάτων" + +msgid "Light" +msgstr "Φωτεινό" + +msgid "Dark" +msgstr "Σκοτεινό" + +msgid "Search:" +msgstr "Αναζήτηση:" + +msgid "Force interpretation" +msgstr "Εξαναγκασμός ερμηνείας" + +msgid "Allow recompilation" +msgstr "Να επιτρέπεται ανασύνταξη" + +msgid "&Force interpretation" +msgstr "&Εξαναγκασμός ερμηνείας" + +msgid "&Allow recompilation" +msgstr "&Να επιτρέπεται ανασύνταξη" + +msgid "&Fast forward" +msgstr "" diff --git a/src/qt/languages/es-ES.po b/src/qt/languages/es-ES.po index 53bd643b2..f64574e71 100644 --- a/src/qt/languages/es-ES.po +++ b/src/qt/languages/es-ES.po @@ -25,7 +25,7 @@ msgid "&Hard reset" msgstr "&Hard reset" msgid "&Ctrl+Alt+Del" -msgstr "&Ctrl+Alt+Del" +msgstr "&Ctrl+Alt+Supr" msgid "Ctrl+Alt+&Esc" msgstr "Ctrl+Alt+&Esc" @@ -210,6 +210,15 @@ msgstr "&Actualizar iconos en barra de estado" msgid "Take s&creenshot" msgstr "Tomar cap&tura" +msgid "Take &raw screenshot" +msgstr "Tomar captura &plana" + +msgid "C&opy screenshot" +msgstr "C&opiar captura" + +msgid "Copy r&aw screenshot" +msgstr "Copiar captura p&lana" + msgid "S&ound" msgstr "S&onido" @@ -604,7 +613,7 @@ msgid "Floppy drives:" msgstr "Unidades de disquete:" msgid "Turbo timings" -msgstr "Temporizaciones Turbo" +msgstr "Temporizaciones turbo" msgid "Check BPB" msgstr "Chequear BPB" @@ -631,7 +640,7 @@ msgid "ISA RTC:" msgstr "ISA RTC:" msgid "ISA Memory Expansion" -msgstr "Expansión de Memoria ISA" +msgstr "Expansión de memoria ISA" msgid "ISA ROM Cards" msgstr "Tarjetas ROM ISA" @@ -709,7 +718,7 @@ msgid "All images" msgstr "Todas las imagenes" msgid "Basic sector images" -msgstr "Imaáenes básicas de sector" +msgstr "Imágenes básicas de sector" msgid "Surface images" msgstr "Imágenes de superficie" @@ -718,10 +727,10 @@ msgid "Machine \"%hs\" is not available due to missing ROMs in the roms/machines msgstr "La máquina \"%hs\" no está disponible debido a ROMs faltantes en el directorio roms/machines. Cambiando a una máquina disponible." msgid "Video card \"%hs\" is not available due to missing ROMs in the roms/video directory. Switching to an available video card." -msgstr "La tarjeta de vídeo \"%hs\" no está disponible debido a ROMs faltantes en el directorio roms/machines. Cambiando a una tarjeta de vídeo disponible." +msgstr "La tarjeta de vídeo \"%hs\" no está disponible debido a ROMs faltantes en el directorio roms/video. Cambiando a una tarjeta de vídeo disponible." msgid "Video card #2 \"%hs\" is not available due to missing ROMs in the roms/video directory. Disabling the second video card." -msgstr "La tarjeta de vídeo 2 \"%hs\" no está disponible debido a ROMs faltantes en el directorio roms/machines. Deshabilitanto la segunda tarjeta de vídeo." +msgstr "La tarjeta de vídeo 2 \"%hs\" no está disponible debido a ROMs faltantes en el directorio roms/video. Deshabilitanto la segunda tarjeta de vídeo." msgid "Device \"%hs\" is not available due to missing ROMs. Ignoring the device." msgstr "El dispositivo \"%hs\" no está disponible debido a ROMs faltantes. Ignorando el dispositivo." @@ -811,7 +820,7 @@ msgid "Default" msgstr "Por defecto" msgid "%1 Wait state(s)" -msgstr "%1 estado(s) de Espera" +msgstr "%1 estado(s) de espera" msgid "Type" msgstr "Tipo" @@ -997,7 +1006,7 @@ msgid "Invalid configuration" msgstr "Configuración inválida" msgid "%1 is required for automatic conversion of PostScript files to PDF.\n\nAny documents sent to the generic PostScript printer will be saved as PostScript (.ps) files." -msgstr "%1 es necesaria para la conversión automática de archivos PostScript a PDF.\n\nCualquier documento enviado a la impresora genérica postScript se guardará como archivo PostScript (.ps)." +msgstr "%1 es necesaria para la conversión automática de archivos PostScript a PDF.\n\nCualquier documento enviado a la impresora genérica PostScript se guardará como archivo PostScript (.ps)." msgid "%1 is required for automatic conversion of PCL files to PDF.\n\nAny documents sent to the generic PCL printer will be saved as Printer Command Language (.pcl) files." msgstr "%1 es necesaria para la conversión automática de archivos PCL a PDF.\n\nCualquier documento enviado a la impresora genérica PCL se guardará como archivo Printer Command Language (.pcl)." @@ -1030,7 +1039,7 @@ msgid "You are loading an unsupported configuration" msgstr "Está cargando una configuración no soportada" msgid "CPU type filtering based on selected machine is disabled for this emulated machine.\n\nThis makes it possible to choose a CPU that is otherwise incompatible with the selected machine. However, you may run into incompatibilities with the machine BIOS or other software.\n\nEnabling this setting is not officially supported and any bug reports filed may be closed as invalid." -msgstr "El Filtrado de tipo de CPU basado en máquina seleccionada está deshabilitado para la esta máquina.\n\nEsto hace posible seleccionar una CPU que sea incompatible con esta máquina. Por ello, pueden aparecer incompatibilidader con la BIOS de la máquina u otro software.\n\nActivar esta configuración no está oficialmente soportado y cualquier reporte de fallo puede ser cerrado como inválido." +msgstr "El filtrado de tipo de CPU basado en máquina seleccionada está deshabilitado para la esta máquina.\n\nEsto hace posible seleccionar una CPU que sea incompatible con esta máquina. Por ello, pueden aparecer incompatibilidader con la BIOS de la máquina u otro software.\n\nActivar esta configuración no está oficialmente soportado y cualquier reporte de fallo puede ser cerrado como inválido." msgid "Continue" msgstr "Continuar" @@ -1060,7 +1069,7 @@ msgid "Pause execution" msgstr "Pausar la ejecución" msgid "Ctrl+Alt+Del" -msgstr "Ctrl+Alt+Del" +msgstr "Ctrl+Alt+Supr" msgid "Press Ctrl+Alt+Del" msgstr "Pulsar Ctrl+Alt+Supr" @@ -1252,7 +1261,7 @@ msgid "Failed to create directory for cloned VM" msgstr "Error al crear el directório para la MV clonada" msgid "Failed to clone VM." -msgstr "Error al clonar la VM." +msgstr "Error al clonar la MV." msgid "Directory in use" msgstr "Directório en uso" @@ -1291,7 +1300,7 @@ msgid "An update to 86Box is available: %1 %2" msgstr "Está disponible una actualización para 86Box: %1 %2" msgid "An error has occurred while checking for updates: %1" -msgstr "Ha ocurrido un error al verificar las actualizacioens: %1" +msgstr "Ha ocurrido un error al verificar las actualizaciones: %1" msgid "An update to 86Box is available!" msgstr "¡Una actualización para 86Box está disponible!" @@ -1735,7 +1744,7 @@ msgid "86Box Monitor #%1" msgstr "Monitor de 86Box %1" msgid "No MCA devices." -msgstr "No hay dispositovos MCA." +msgstr "No hay dispositivos MCA." msgid "MiB" msgstr "MiB" @@ -2689,10 +2698,10 @@ msgid "60 Hz (JMP2 = 2)" msgstr "60 Hz (JMP2 = 2)" msgid "Generic PC/XT Memory Expansion" -msgstr "Expansión de Memoria Generica PC/XT" +msgstr "Expansión de memoria generica PC/XT" msgid "Generic PC/AT Memory Expansion" -msgstr "Expansión de Memoria Generica PC/AT" +msgstr "Expansión de memoria generica PC/AT" msgid "Unable to find Dot-Matrix fonts" msgstr "No fué posible encontrar las fuentes matriciales" @@ -2785,7 +2794,7 @@ msgid "This key combo is already in use." msgstr "Esta combinación de teclas ya está en uso." msgid "Send Control+Alt+Del" -msgstr "Enviar Control+Alt+Del" +msgstr "Enviar Control+Alt+Supr" msgid "Send Control+Alt+Escape" msgstr "Enviar Control+Alt+Escape" @@ -2842,7 +2851,7 @@ msgid "&Wipe NVRAM" msgstr "&Limpiar el NVRAM" msgid "This will delete all NVRAM (and related) files of the virtual machine located in the \"nvr\" subdirectory. You'll have to reconfigure the BIOS (and possibly other devices inside the VM) settings again if applicable.\n\nAre you sure you want to wipe all NVRAM contents of the virtual machine \"%1\"?" -msgstr "Estó borrará todos los archivos de NVRAM (y relacionados) de la máquina virtual ubicados en el subdirectório \"nvr\". Tendrá que reconifigurar la definiciones del BIOS (y talvez de otros dispositivoes dentro de la MV) otra vez si aplicable.\n\n¿Está seguro de que quierere limpiar todos los contenidos de la NVRAM de la máquina virtual \"%1\"?" +msgstr "Estó borrará todos los archivos de NVRAM (y relacionados) de la máquina virtual ubicados en el subdirectório \"nvr\". Tendrá que reconfigurar la definiciones del BIOS (y tal vez de otros dispositivos dentro de la MV) otra vez si aplicable.\n\n¿Está seguro de que quiere limpiar todos los contenidos de la NVRAM de la máquina virtual \"%1\"?" msgid "Success" msgstr "Éxito" @@ -2899,7 +2908,7 @@ msgid "Version %1 is now available." msgstr "La versión %1 está ahora disponible." msgid "You are currently running build %1." -msgstr "Actualmente está a ejecutar la compialación %1." +msgstr "Actualmente está a ejecutar la compilación %1." msgid "Build %1 is now available." msgstr "La compilación %1 está ahora disponible." @@ -2999,3 +3008,6 @@ msgstr "&Forzar interpretación" msgid "&Allow recompilation" msgstr "&Permitir recompilación" + +msgid "&Fast forward" +msgstr "" diff --git a/src/qt/languages/fi-FI.po b/src/qt/languages/fi-FI.po index 0db30bede..987e73529 100644 --- a/src/qt/languages/fi-FI.po +++ b/src/qt/languages/fi-FI.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2025-12-06 18:56+0000\n" +"PO-Revision-Date: 2025-12-29 09:54+0000\n" "Last-Translator: Daniel Gurney \n" "Language-Team: Finnish \n" "Language: fi-FI\n" @@ -210,6 +210,15 @@ msgstr "&Päivitä tilapalkin kuvakkeita" msgid "Take s&creenshot" msgstr "Ota &kuvakaappaus" +msgid "Take &raw screenshot" +msgstr "Ota &raaka kuvakaappaus" + +msgid "C&opy screenshot" +msgstr "K&opioi kuvakaappaus" + +msgid "Copy r&aw screenshot" +msgstr "Kopioi r&aaka kuvakaappaus" + msgid "S&ound" msgstr "&Ääni" @@ -2999,3 +3008,6 @@ msgstr "&Pakota tulkinta" msgid "&Allow recompilation" msgstr "&Salli uudelleenkääntäminen" + +msgid "&Fast forward" +msgstr "" diff --git a/src/qt/languages/fr-FR.po b/src/qt/languages/fr-FR.po index 6a5628f29..976d9afb0 100644 --- a/src/qt/languages/fr-FR.po +++ b/src/qt/languages/fr-FR.po @@ -55,10 +55,10 @@ msgid "&Resizeable window" msgstr "Fenêtre &redimensionnable" msgid "R&emember size && position" -msgstr "S&auvegarder taille && position" +msgstr "S&auvegarder taille et position" msgid "Remember size && position" -msgstr "Sauvegarder taille && position" +msgstr "Sauvegarder taille et position" msgid "Re&nderer" msgstr "Moteur de re&ndu vidéo" @@ -208,7 +208,16 @@ msgid "&Update status bar icons" msgstr "Mettre à jour la barre de stat&us" msgid "Take s&creenshot" -msgstr "Copie d'é&cran" +msgstr "Faire copie d'é&cran" + +msgid "Take &raw screenshot" +msgstr "Faire copie &brute d'écran" + +msgid "C&opy screenshot" +msgstr "C&opier copie d'écran" + +msgid "Copy r&aw screenshot" +msgstr "Copier copie b&rute d'écran" msgid "S&ound" msgstr "S&on" @@ -682,7 +691,7 @@ msgid "&Removable disk %1 (%2): %3" msgstr "&Disque amovible %1 (%2) : %3" msgid "Removable disk images" -msgstr "Imges de disque amovible" +msgstr "Images de disque amovible" msgid "Image %1" msgstr "Image %1" @@ -922,7 +931,7 @@ msgid "Advanced sector images" msgstr "Images secteur avancé" msgid "Flux images" -msgstr "Images Flux" +msgstr "Images de flux" msgid "Are you sure you want to hard reset the emulated machine?" msgstr "Etes-vous sûr de vouloir réinitialiser la machine émulée ?" @@ -1948,7 +1957,7 @@ msgid "Translate 26 -> 17" msgstr "Traduire 26 -> 17" msgid "Language" -msgstr "Langage" +msgstr "Langue" msgid "Enable backlight" msgstr "Activer le rétro-éclairage" @@ -2653,28 +2662,28 @@ msgid "High performance impact" msgstr "Impact important sur la performance" msgid "[Generic] RAM Disk (max. speed)" -msgstr "[Generic] Disque RAM (vitesse maximale)" +msgstr "[Générique] Disque RAM (vitesse maximale)" msgid "[Generic] 1989 (3500 RPM)" -msgstr "[Generic] 1989 (3500 RPM)" +msgstr "[Générique] 1989 (3500 RPM)" msgid "[Generic] 1992 (3600 RPM)" -msgstr "[Generic] 1992 (3600 RPM)" +msgstr "[Générique] 1992 (3600 RPM)" msgid "[Generic] 1994 (4500 RPM)" -msgstr "[Generic] 1994 (4500 RPM)" +msgstr "[Générique] 1994 (4500 RPM)" msgid "[Generic] 1996 (5400 RPM)" -msgstr "[Generic] 1996 (5400 RPM)" +msgstr "[Générique] 1996 (5400 RPM)" msgid "[Generic] 1997 (5400 RPM)" -msgstr "[Generic] 1997 (5400 RPM)" +msgstr "[Générique] 1997 (5400 RPM)" msgid "[Generic] 1998 (5400 RPM)" -msgstr "[Generic] 1998 (5400 RPM)" +msgstr "[Générique] 1998 (5400 RPM)" msgid "[Generic] 2000 (7200 RPM)" -msgstr "[Generic] 2000 (7200 RPM)" +msgstr "[Générique] 2000 (7200 RPM)" msgid "IBM 8514/A clone (ISA)" msgstr "Clone IBM 8514/A (ISA)" @@ -2902,7 +2911,7 @@ msgid "You are currently running build %1." msgstr "Vous exécutez actuellement le build %1." msgid "Build %1 is now available." -msgstr "Le Build %1 est désormais disponible." +msgstr "Le build %1 est désormais disponible." msgid "Would you like to visit the download page?" msgstr "Souhaitez-vous visiter la page de téléchargement ?" @@ -2999,3 +3008,6 @@ msgstr "&Forcer l'interprétation" msgid "&Allow recompilation" msgstr "&Permettre la recompilation" + +msgid "&Fast forward" +msgstr "" diff --git a/src/qt/languages/hr-HR.po b/src/qt/languages/hr-HR.po index 5261ab338..15c2b39cb 100644 --- a/src/qt/languages/hr-HR.po +++ b/src/qt/languages/hr-HR.po @@ -212,6 +212,15 @@ msgstr "&Ažuriraj ikone statusnog redka" msgid "Take s&creenshot" msgstr "Napravi &snimku zaslona" +msgid "Take &raw screenshot" +msgstr "Napravi &neobrađenu snimku zaslona" + +msgid "C&opy screenshot" +msgstr "K&opiraj snimku zalsona" + +msgid "Copy r&aw screenshot" +msgstr "Kopiraj n&eobrađenu snimku zaslona" + msgid "S&ound" msgstr "&Zvuk" @@ -3001,3 +3010,6 @@ msgstr "&Prisilna interpretacija" msgid "&Allow recompilation" msgstr "&Omogući rekompilaciju" + +msgid "&Fast forward" +msgstr "" diff --git a/src/qt/languages/it-IT.po b/src/qt/languages/it-IT.po index 67937b089..0a675ac1a 100644 --- a/src/qt/languages/it-IT.po +++ b/src/qt/languages/it-IT.po @@ -154,13 +154,13 @@ msgid "VGA screen &type" msgstr "Schermi &VGA" msgid "RGB &Color" -msgstr "RGB a &Colori" +msgstr "RGB a &colori" msgid "RGB (no brown)" msgstr "RGB (senza marrone)" msgid "&RGB Grayscale" -msgstr "&RGB a Scala di grigi" +msgstr "&RGB a scala di grigi" msgid "Generic RGBI color monitor" msgstr "Monitor a colori RGBI generico" @@ -210,6 +210,15 @@ msgstr "&Aggiorna icone della barra di stato" msgid "Take s&creenshot" msgstr "&Cattura schermata" +msgid "Take &raw screenshot" +msgstr "Cattura &grezza della schermata" + +msgid "C&opy screenshot" +msgstr "C&opia cattura della schermata" + +msgid "Copy r&aw screenshot" +msgstr "Copia cattura g&rezza della schermata" + msgid "S&ound" msgstr "A&udio" @@ -1114,7 +1123,7 @@ msgid "VMs: %1" msgstr "Macchine virtuali: %1" msgid "System Directory:" -msgstr "Directory Sistema:" +msgstr "Directory sistema:" msgid "Choose directory" msgstr "Scegli la directory" @@ -1420,7 +1429,7 @@ msgid "Don't overwrite" msgstr "Non sovrascrivere" msgid "Raw image" -msgstr "Immagine RAW" +msgstr "Immagine grezza" msgid "HDI image" msgstr "Immagine HDI" @@ -2383,10 +2392,10 @@ msgid "Wheel" msgstr "Rotellina" msgid "Five + Wheel" -msgstr "Cinque + Rotellina" +msgstr "Cinque + rotellina" msgid "Five + 2 Wheels" -msgstr "Cinque + 2 Rotelline" +msgstr "Cinque + 2 rotelline" msgid "A3 - SMT2 Serial / SMT3(R)V" msgstr "A3 - SMT2 seriale / SMT3(R)V" @@ -2999,3 +3008,6 @@ msgstr "&Forza interpretazione" msgid "&Allow recompilation" msgstr "&Permetti ricompilazione" + +msgid "&Fast forward" +msgstr "" diff --git a/src/qt/languages/ja-JP.po b/src/qt/languages/ja-JP.po index cfd44daba..8371ac36e 100644 --- a/src/qt/languages/ja-JP.po +++ b/src/qt/languages/ja-JP.po @@ -211,6 +211,15 @@ msgstr "ステータスバーのアイコンを更新(&U)" msgid "Take s&creenshot" msgstr "スクリーンショットを撮る(&C)" +msgid "Take &raw screenshot" +msgstr "生のスクリーンショットを撮る(&R)" + +msgid "C&opy screenshot" +msgstr "スクリーンショットをコピーする(&O)" + +msgid "Copy r&aw screenshot" +msgstr "生のスクリーンショットをコピーする(&A)" + msgid "S&ound" msgstr "サウンド(&O)" @@ -1421,7 +1430,7 @@ msgid "Don't overwrite" msgstr "上書きしない" msgid "Raw image" -msgstr "Rawイメージ" +msgstr "生のイメージ" msgid "HDI image" msgstr "HDIイメージ" @@ -3000,3 +3009,6 @@ msgstr "解釈を強制する(&F)" msgid "&Allow recompilation" msgstr "再コンパイルを許可する(&A)" + +msgid "&Fast forward" +msgstr "" diff --git a/src/qt/languages/ko-KR.po b/src/qt/languages/ko-KR.po index e49476e40..12f67f14f 100644 --- a/src/qt/languages/ko-KR.po +++ b/src/qt/languages/ko-KR.po @@ -204,6 +204,15 @@ msgstr "상태 바 아이콘 갱신하기(&U)" msgid "Take s&creenshot" msgstr "스크린샷 찍기(&C)" +msgid "Take &raw screenshot" +msgstr "원본 스크린샷 찍기(&R)" + +msgid "C&opy screenshot" +msgstr "스크린샷 복사하세요(&O)" + +msgid "Copy r&aw screenshot" +msgstr "원본 스크린샷 복사하세요(&A)" + msgid "S&ound" msgstr "사운드(&O)" @@ -1414,7 +1423,7 @@ msgid "Don't overwrite" msgstr "덮어쓰지 않음" msgid "Raw image" -msgstr "Raw 이미지" +msgstr "원본 이미지" msgid "HDI image" msgstr "HDI 이미지" @@ -2993,3 +3002,6 @@ msgstr "강제 해석(&F)" msgid "&Allow recompilation" msgstr "재컴파일 허용(&A)" + +msgid "&Fast forward" +msgstr "" diff --git a/src/qt/languages/nb-NO.po b/src/qt/languages/nb-NO.po index 5b2748210..c6c27836b 100644 --- a/src/qt/languages/nb-NO.po +++ b/src/qt/languages/nb-NO.po @@ -204,6 +204,15 @@ msgstr "&Oppdater statuslinjeikoner" msgid "Take s&creenshot" msgstr "Ta s&kjermbilde" +msgid "Take &raw screenshot" +msgstr "Ta &rå skjermbilde" + +msgid "C&opy screenshot" +msgstr "K&opiera skjermbilde" + +msgid "Copy r&aw screenshot" +msgstr "Kopiera r&å skjermbilde" + msgid "S&ound" msgstr "L&yd" @@ -2993,3 +3002,6 @@ msgstr "&Tving tolkning" msgid "&Allow recompilation" msgstr "&Tillat rekompilering" + +msgid "&Fast forward" +msgstr "" diff --git a/src/qt/languages/nl-NL.po b/src/qt/languages/nl-NL.po index 88a0964ec..3b10383f7 100644 --- a/src/qt/languages/nl-NL.po +++ b/src/qt/languages/nl-NL.po @@ -202,7 +202,16 @@ msgid "&Update status bar icons" msgstr "&Statusbalkpictogrammen bijwerken" msgid "Take s&creenshot" -msgstr "Maak een schermafbeelding" +msgstr "Maak een s&chermafbeelding" + +msgid "Take &raw screenshot" +msgstr "Maak een &ruw schermafbeelding" + +msgid "C&opy screenshot" +msgstr "K&opieer een schermafbeelding" + +msgid "Copy r&aw screenshot" +msgstr "Kopieer een r&uw schermafbeelding" msgid "S&ound" msgstr "&Geluid" @@ -2993,3 +3002,6 @@ msgstr "Interpretatie &afdwingen" msgid "&Allow recompilation" msgstr "Recompilatie &toestaan" + +msgid "&Fast forward" +msgstr "" diff --git a/src/qt/languages/pl-PL.po b/src/qt/languages/pl-PL.po index 43b89a8da..2b9610084 100644 --- a/src/qt/languages/pl-PL.po +++ b/src/qt/languages/pl-PL.po @@ -211,6 +211,15 @@ msgstr "&Aktualizuj ikony na pasku statusu" msgid "Take s&creenshot" msgstr "Zrób &zrzut ekranu" +msgid "Take &raw screenshot" +msgstr "Zrób &surowy zrzut ekranu" + +msgid "C&opy screenshot" +msgstr "S&kopiuj zrzut ekranu" + +msgid "Copy r&aw screenshot" +msgstr "Skopiuj s&urowy zrzut ekranu" + msgid "S&ound" msgstr "Dź&więk" @@ -3000,3 +3009,6 @@ msgstr "&Wymuś interpretację" msgid "&Allow recompilation" msgstr "&Zezwól na rekompilację" + +msgid "&Fast forward" +msgstr "" diff --git a/src/qt/languages/pt-BR.po b/src/qt/languages/pt-BR.po index 04b96c62d..b82c1a7c9 100644 --- a/src/qt/languages/pt-BR.po +++ b/src/qt/languages/pt-BR.po @@ -204,6 +204,15 @@ msgstr "&Atualizar ícones da barra de status" msgid "Take s&creenshot" msgstr "Capturar &tela" +msgid "Take &raw screenshot" +msgstr "Fazer captura &bruta da tela" + +msgid "C&opy screenshot" +msgstr "C&opiar captura da tela" + +msgid "Copy r&aw screenshot" +msgstr "Copiar captura b&ruta da tela" + msgid "S&ound" msgstr "&Som" @@ -2993,3 +3002,6 @@ msgstr "&Forçar interpretação" msgid "&Allow recompilation" msgstr "&Permitir recompilação" + +msgid "&Fast forward" +msgstr "" diff --git a/src/qt/languages/pt-PT.po b/src/qt/languages/pt-PT.po index f81d1ec94..b68b91a8b 100644 --- a/src/qt/languages/pt-PT.po +++ b/src/qt/languages/pt-PT.po @@ -211,6 +211,15 @@ msgstr "&Atualizar ícones da barra de estado" msgid "Take s&creenshot" msgstr "Gravar imagem de &ecrã" +msgid "Take &raw screenshot" +msgstr "Gravar imagem &bruta de ecrã" + +msgid "C&opy screenshot" +msgstr "Copiar imagem de ecrã" + +msgid "Copy r&aw screenshot" +msgstr "Copiar imagem b&ruta de ecrã" + msgid "S&ound" msgstr "&Som" @@ -3000,3 +3009,6 @@ msgstr "&Forçar interpretação" msgid "&Allow recompilation" msgstr "&Permitir recompilação" + +msgid "&Fast forward" +msgstr "" diff --git a/src/qt/languages/ru-RU.po b/src/qt/languages/ru-RU.po index ce3fe75a3..b39d0d84c 100644 --- a/src/qt/languages/ru-RU.po +++ b/src/qt/languages/ru-RU.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2025-12-07 13:33+0000\n" +"PO-Revision-Date: 2025-12-30 13:56+0000\n" "Last-Translator: Alexander Babikov \n" "Language-Team: Russian \n" "Language: ru-RU\n" @@ -211,6 +211,15 @@ msgstr "&Обновление значков строки состояния" msgid "Take s&creenshot" msgstr "Сделать с&криншот" +msgid "Take &raw screenshot" +msgstr "Сделать н&еобработанный скриншот" + +msgid "C&opy screenshot" +msgstr "Ско&пировать скриншот" + +msgid "Copy r&aw screenshot" +msgstr "Скопировать необработанный скрин&шот" + msgid "S&ound" msgstr "&Звук" @@ -1433,7 +1442,7 @@ msgid "Don't overwrite" msgstr "Не перезаписывать" msgid "Raw image" -msgstr "RAW образ" +msgstr "Сырой образ" msgid "HDI image" msgstr "Образ HDI" @@ -3012,3 +3021,6 @@ msgstr "&Принудительная интерпретация" msgid "&Allow recompilation" msgstr "&Разрешить рекомпиляцию" + +msgid "&Fast forward" +msgstr "" diff --git a/src/qt/languages/sk-SK.po b/src/qt/languages/sk-SK.po index f049c3f48..3586f6c12 100644 --- a/src/qt/languages/sk-SK.po +++ b/src/qt/languages/sk-SK.po @@ -210,6 +210,15 @@ msgstr "&Aktualizovať ikony na stavovom riadku" msgid "Take s&creenshot" msgstr "Urobiť snímku &obrazovky" +msgid "Take &raw screenshot" +msgstr "Urobiť &surovú snímku obrazovky" + +msgid "C&opy screenshot" +msgstr "S&kopírovať snímku obrazovky" + +msgid "Copy r&aw screenshot" +msgstr "Skopírovať s&urovú snímku obrazovky" + msgid "S&ound" msgstr "&Zvuk" @@ -2999,3 +3008,6 @@ msgstr "&Vynútiť interpretáciu" msgid "&Allow recompilation" msgstr "&Povoliť rekompiláciu" + +msgid "&Fast forward" +msgstr "" diff --git a/src/qt/languages/sl-SI.po b/src/qt/languages/sl-SI.po index d6a4c20ea..a7cf7dc29 100644 --- a/src/qt/languages/sl-SI.po +++ b/src/qt/languages/sl-SI.po @@ -212,6 +212,15 @@ msgstr "&Posodabljaj ikone statusne vrstice" msgid "Take s&creenshot" msgstr "&Zajemi posnetek zaslona" +msgid "Take &raw screenshot" +msgstr "Zajemi &neobdelan posnetek zaslona" + +msgid "C&opy screenshot" +msgstr "K&opiraj posnetek zaslona" + +msgid "Copy r&aw screenshot" +msgstr "Kopriaj n&eobdelan posenetk zaslona" + msgid "S&ound" msgstr "Z&vok" @@ -3001,3 +3010,6 @@ msgstr "&Vsili interpretacijo" msgid "&Allow recompilation" msgstr "&Dovoli prevajanje" + +msgid "&Fast forward" +msgstr "" diff --git a/src/qt/languages/sv-SE.po b/src/qt/languages/sv-SE.po index 4e626a47b..3cac62eaf 100644 --- a/src/qt/languages/sv-SE.po +++ b/src/qt/languages/sv-SE.po @@ -204,6 +204,15 @@ msgstr "&Uppdatera statusfältets ikoner" msgid "Take s&creenshot" msgstr "Tag s&kärmbild" +msgid "Take &raw screenshot" +msgstr "Tag &rå skärmbild" + +msgid "C&opy screenshot" +msgstr "Kopiera skärmbild" + +msgid "Copy r&aw screenshot" +msgstr "Kopera r&å skärmbild" + msgid "S&ound" msgstr "L&jud" @@ -2993,3 +3002,6 @@ msgstr "&Tvinga tolkning" msgid "&Allow recompilation" msgstr "&Tillåt omkompilering" + +msgid "&Fast forward" +msgstr "" diff --git a/src/qt/languages/tr-TR.po b/src/qt/languages/tr-TR.po index 91301e0b3..22ccc4910 100644 --- a/src/qt/languages/tr-TR.po +++ b/src/qt/languages/tr-TR.po @@ -1,8 +1,14 @@ msgid "" msgstr "" +"PO-Revision-Date: 2026-01-01 20:06+0000\n" +"Last-Translator: Umut Çağan Uçanok \n" +"Language-Team: Turkish \n" +"Language: tr-TR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.12.2\n" "X-Language: tr_TR\n" "X-Source-Language: en_US\n" @@ -204,6 +210,15 @@ msgstr "Durum &çubuğu simgelerini güncelle" msgid "Take s&creenshot" msgstr "&Ekran görüntüsü al" +msgid "Take &raw screenshot" +msgstr "Ekran &ham görüntüsü al" + +msgid "C&opy screenshot" +msgstr "Ekran görüntüsü k&opyala" + +msgid "Copy r&aw screenshot" +msgstr "Ekran h&am görüntüsü kopyala" + msgid "S&ound" msgstr "&Ses" @@ -1762,7 +1777,7 @@ msgid "VDE Socket:" msgstr "VDE Soketi:" msgid "TAP Bridge Device:" -msgstr "" +msgstr "TAP Köprü Cihazı:" msgid "86Box Unit Tester" msgstr "86Box Test Cihazı" @@ -1801,7 +1816,7 @@ msgid "PS/55 Keyboard" msgstr "PS/55 Klavyesi" msgid "Keys" -msgstr "Keys" +msgstr "Tuşlar" msgid "Logitech/Microsoft Bus Mouse" msgstr "Logitech/Microsoft Bus Faresi" @@ -2044,7 +2059,7 @@ msgid "Reverb Output Gain" msgstr "Yankı Çıkış Sesi Artışı" msgid "Reversed stereo" -msgstr "Tersine çevirilmiş stereo" +msgstr "Tersine çevrilmiş stereo" msgid "Nice ramp" msgstr "Güzel rampa" @@ -2155,16 +2170,16 @@ msgid "WSS DMA" msgstr "WSS DMA" msgid "RTC IRQ" -msgstr "" +msgstr "RTC IRQ" msgid "RTC Port Address" -msgstr "" +msgstr "RTC Bağlantı Noktası Adresi" msgid "Onboard RTC" -msgstr "" +msgstr "Yerleşik RTC" msgid "Not installed" -msgstr "" +msgstr "Kurulu değil" msgid "Enable OPL" msgstr "OPL'yi etkinleştir" @@ -2788,7 +2803,7 @@ msgid "Toggle fullscreen" msgstr "Tam ekran modunu ayarla" msgid "Toggle UI in fullscreen" -msgstr "" +msgstr "Arayüzü tam ekranda ayarla" msgid "Screenshot" msgstr "Ekran görüntüsü" @@ -2993,3 +3008,6 @@ msgstr "&Yorumlanmasını zorla" msgid "&Allow recompilation" msgstr "&Derlenmesine izin ver" + +msgid "&Fast forward" +msgstr "" diff --git a/src/qt/languages/uk-UA.po b/src/qt/languages/uk-UA.po index 49eac891c..0fb2c8033 100644 --- a/src/qt/languages/uk-UA.po +++ b/src/qt/languages/uk-UA.po @@ -212,6 +212,15 @@ msgstr "&Обновлення значків рядка стану" msgid "Take s&creenshot" msgstr "Зробити &знімок" +msgid "Take &raw screenshot" +msgstr "Зробити &сирий знімок" + +msgid "C&opy screenshot" +msgstr "С&копійовати знімок" + +msgid "Copy r&aw screenshot" +msgstr "Скопійовати с&ирий знімок" + msgid "S&ound" msgstr "&Звук" @@ -1422,7 +1431,7 @@ msgid "Don't overwrite" msgstr "Не перезаписувати" msgid "Raw image" -msgstr "RAW образ" +msgstr "Сирий образ" msgid "HDI image" msgstr "Образ HDI" @@ -3001,3 +3010,6 @@ msgstr "&Примусове тлумачення" msgid "&Allow recompilation" msgstr "&Дозволити рекомпіляцію" + +msgid "&Fast forward" +msgstr "" diff --git a/src/qt/languages/vi-VN.po b/src/qt/languages/vi-VN.po index 0471fa1f0..35782e86d 100644 --- a/src/qt/languages/vi-VN.po +++ b/src/qt/languages/vi-VN.po @@ -204,6 +204,15 @@ msgstr "Cậ&p nhật biểu tượng thanh trạng thái" msgid "Take s&creenshot" msgstr "Chụp &màn hình" +msgid "Take &raw screenshot" +msgstr "Chụp màn hình &thô" + +msgid "C&opy screenshot" +msgstr "S&ao chép ảnh chụp màn hình" + +msgid "Copy r&aw screenshot" +msgstr "Sao chép ảnh chụp màn hình t&hô" + msgid "S&ound" msgstr "&Âm thanh" @@ -2993,3 +3002,6 @@ msgstr "&Buộc giải thích" msgid "&Allow recompilation" msgstr "&Cho phép biên dịch lại" + +msgid "&Fast forward" +msgstr "" diff --git a/src/qt/languages/zh-CN.po b/src/qt/languages/zh-CN.po index cf31d6975..d99fb54d5 100644 --- a/src/qt/languages/zh-CN.po +++ b/src/qt/languages/zh-CN.po @@ -204,6 +204,15 @@ msgstr "更新状态栏图标(&U)" msgid "Take s&creenshot" msgstr "截图(&C)" +msgid "Take &raw screenshot" +msgstr "原始截图(&R)" + +msgid "C&opy screenshot" +msgstr "复制截图(&O)" + +msgid "Copy r&aw screenshot" +msgstr "复制原始截图(&A)" + msgid "S&ound" msgstr "声音(&O)" @@ -2993,3 +3002,6 @@ msgstr "强制解释执行(&F)" msgid "&Allow recompilation" msgstr "允许重编译(&A)" + +msgid "&Fast forward" +msgstr "" diff --git a/src/qt/languages/zh-TW.po b/src/qt/languages/zh-TW.po index 156dbb2fe..299e68b70 100644 --- a/src/qt/languages/zh-TW.po +++ b/src/qt/languages/zh-TW.po @@ -1,8 +1,15 @@ msgid "" msgstr "" +"PO-Revision-Date: 2026-01-02 21:56+0000\n" +"Last-Translator: Alexander Babikov \n" +"Language-Team: Chinese (Traditional Han script) \n" +"Language: zh-TW\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Weblate 5.12.2\n" "X-Language: zh_TW\n" "X-Source-Language: en_US\n" @@ -202,7 +209,16 @@ msgid "&Update status bar icons" msgstr "更新狀態列圖示(&U)" msgid "Take s&creenshot" -msgstr "擷圖(&C)" +msgstr "擷取螢幕畫面(&C)" + +msgid "Take &raw screenshot" +msgstr "擷取原始螢幕畫面(&R)" + +msgid "C&opy screenshot" +msgstr "擷取螢幕畫面至剪貼簿(&O)" + +msgid "Copy r&aw screenshot" +msgstr "擷取原始螢幕畫面至剪貼薄(&A)" msgid "S&ound" msgstr "聲音(&O)" @@ -331,7 +347,7 @@ msgid "Machine:" msgstr "機型:" msgid "Configure" -msgstr "設定" +msgstr "組態" msgid "CPU:" msgstr "CPU:" @@ -643,13 +659,13 @@ msgid "Card 4:" msgstr "擴充卡 4:" msgid "Generic ISA ROM Board" -msgstr "通用 ISA ROM 板" +msgstr "通用 ISA 單 ROM 板" msgid "Generic Dual ISA ROM Board" -msgstr "通用雙 ISA ROM 板" +msgstr "通用 ISA 雙 ROM 板" msgid "Generic Quad ISA ROM Board" -msgstr "通用四通道 ISA ROM 板" +msgstr "通用 ISA 四 ROM 板" msgid "ISABugger device" msgstr "ISABugger 裝置" @@ -895,10 +911,10 @@ msgid "Steering wheel (3-axis, 4-button)" msgstr "方向盤 (3 軸, 4 鍵搖桿)" msgid "Thrustmaster Formula T1/T2 with adapter" -msgstr "附轉接器 Thrustmaster Formula T1/T2" +msgstr "Thrustmaster Formula T1/T2 附轉接器" msgid "Thrustmaster Formula T1/T2 without adapter" -msgstr "不附轉接器 Thrustmaster Formula T1/T2" +msgstr "Thrustmaster Formula T1/T2 不附轉接器" msgid "None" msgstr "無" @@ -997,7 +1013,7 @@ msgid "%1 is required for automatic conversion of PCL files to PDF.\n\nAny docum msgstr "自動將 PCL 檔案轉換為 PDF 需要 %1。\n\n使用通用 PCL 印表機列印的文件將被儲存為 Printer Command Language (.pcl) 檔案。" msgid "Don't show this message again" -msgstr "不要再顯示此消息" +msgstr "不要再顯示此訊息" msgid "Don't exit" msgstr "不退出" @@ -1012,7 +1028,7 @@ msgid "CD-ROM images" msgstr "光碟影像" msgid "%1 Device Configuration" -msgstr "%1 裝置設定" +msgstr "%1 裝置組態" msgid "Monitor in sleep mode" msgstr "監視器處在睡眠狀態" @@ -1288,7 +1304,7 @@ msgid "An error has occurred while checking for updates: %1" msgstr "檢查更新時發生錯誤: %1" msgid "An update to 86Box is available!" -msgstr "86Box 的更新可用!" +msgstr "有 86Box 的更新可用!" msgid "Warning" msgstr "警告" @@ -1303,10 +1319,10 @@ msgid "&Delete" msgstr "刪除(&D)" msgid "Do you really want to delete the virtual machine \"%1\" and all its files? This action cannot be undone!" -msgstr "您真的想要刪除虛擬機器 \"%1\" 及其所有檔案嗎?此操作無法撤銷!" +msgstr "您真的想要刪除虛擬機器 \"%1\" 及其所有檔案嗎?此操作無法回復!" msgid "Show &config file" -msgstr "顯示設定檔(&C)" +msgstr "顯示組態檔(&C)" msgid "No screenshot" msgstr "沒有螢幕畫面擷取" @@ -1906,7 +1922,7 @@ msgid "Enable BIOS extension ROM Writes (ROM #4)" msgstr "啟用 BIOS 擴充 ROM 寫入 (ROM 4)" msgid "Linear framebuffer base" -msgstr "線性圖框緩衝記憶體的起始位址" +msgstr "線性影格緩衝記憶體的起始位址" msgid "Address" msgstr "位址" @@ -2326,7 +2342,7 @@ msgid "Address for > 2 MB" msgstr "> 2 MB 的位址" msgid "Frame Address" -msgstr "影格位址" +msgstr "頁框位址" msgid "USA" msgstr "美國" @@ -2545,13 +2561,13 @@ msgid "Mono Interlaced" msgstr "單色隔行掃描" msgid "Mono Non-Interlaced" -msgstr "單色非隔行掃描" +msgstr "單色逐行掃描" msgid "Color Interlaced" msgstr "彩色隔行掃描" msgid "Color Non-Interlaced" -msgstr "彩色非隔行掃描" +msgstr "彩色逐行掃描" msgid "3Dfx Voodoo Graphics" msgstr "3Dfx Voodoo 圖形" @@ -2752,28 +2768,28 @@ msgid "Could not load file %1" msgstr "無法載入檔案 %1" msgid "Key Bindings:" -msgstr "按鍵綁定:" +msgstr "按鍵組合:" msgid "Action" -msgstr "行動" +msgstr "動作" msgid "Keybind" -msgstr "鍵盤綁定" +msgstr "按鍵組合" msgid "Clear binding" -msgstr "解除綁定" +msgstr "解除組合" msgid "Bind" -msgstr "綁定" +msgstr "組合" msgid "Bind Key" -msgstr "綁定按鍵" +msgstr "組合按鍵" msgid "Enter key combo:" msgstr "輸入組合鍵:" msgid "Bind conflict" -msgstr "綁定衝突" +msgstr "組合衝突" msgid "This key combo is already in use." msgstr "此組合鍵已在使用中。" @@ -2923,7 +2939,7 @@ msgid "Virtual machine crash" msgstr "虛擬機當機" msgid "The virtual machine \"%1\"'s process has unexpectedly terminated with exit code %2." -msgstr "虛擬機 \"%1\" 的進程以退出代碼 %2 意外終止。" +msgstr "虛擬機 \"%1\" 的行程以結束碼 %2 意外終止。" msgid "The system will not be added." msgstr "系統將不會被新增。" @@ -2993,3 +3009,6 @@ msgstr "強制解譯執行(&F)" msgid "&Allow recompilation" msgstr "允許重編譯(&A)" + +msgid "&Fast forward" +msgstr "" diff --git a/src/qt/qt_iconindicators.cpp b/src/qt/qt_iconindicators.cpp index b93c375b7..771a9ac59 100644 --- a/src/qt/qt_iconindicators.cpp +++ b/src/qt/qt_iconindicators.cpp @@ -6,10 +6,16 @@ QIcon getIndicatorIcon(IconIndicator indicator) { switch (indicator) { + case Play: + return QIcon(":/menuicons/qt/icons/run.ico"); + case Pause: + return QIcon(":/menuicons/qt/icons/pause.ico"); case Active: return QIcon(":/settings/qt/icons/active.ico"); case WriteActive: return QIcon(":/settings/qt/icons/write_active.ico"); + case Record: + return QIcon(":/settings/qt/icons/record.ico"); case Disabled: return QIcon(":/settings/qt/icons/disabled.ico"); case WriteProtected: @@ -36,13 +42,22 @@ getIconWithIndicator(const QIcon &icon, const QSize &size, QIcon::Mode iconMode, return iconPixmap; auto painter = QPainter(&iconPixmap); - auto indicatorPixmap = getIndicatorIcon((indicator == ReadWriteActive || indicator == WriteProtectedActive) ? Active : indicator).pixmap(size); + auto indicatorPixmap = getIndicatorIcon((indicator == ReadWriteActive || indicator == WriteProtectedActive + || indicator == PlayActive || indicator == PauseActive) ? Active : + (indicator == RecordWriteActive) ? Record : indicator) + .pixmap((indicator == Play || indicator == Pause || indicator == Record || indicator == RecordWriteActive) ? size / 2. : size); if (indicator == WriteProtectedBrowse) indicatorPixmap = getIndicatorIcon(WriteProtected).pixmap(size); - painter.drawPixmap(0, 0, indicatorPixmap); - if ((indicator == ReadWriteActive) || (indicator == WriteProtectedActive)) { + if (indicator == Record || indicator == RecordWriteActive) + painter.drawPixmap(size.width() / 2, size.height() / 2, indicatorPixmap); + else + painter.drawPixmap(0, (indicator == Play || indicator == Pause) ? (size.height() / 2) : 0, indicatorPixmap); + if (indicator == PlayActive || indicator == PauseActive) { + auto playPauseIndicatorPixmap = getIndicatorIcon(indicator == PlayActive ? Play : Pause).pixmap(size / 2.); + painter.drawPixmap(0, size.height() / 2, playPauseIndicatorPixmap); + } else if ((indicator == ReadWriteActive) || (indicator == WriteProtectedActive) || (indicator == RecordWriteActive)) { auto writeIndicatorPixmap = getIndicatorIcon(indicator == WriteProtectedActive ? WriteProtected : WriteActive).pixmap(size); painter.drawPixmap(0, 0, writeIndicatorPixmap); } else if (indicator == WriteProtectedBrowse) { diff --git a/src/qt/qt_iconindicators.hpp b/src/qt/qt_iconindicators.hpp index 24ce80ad3..971ab6ead 100644 --- a/src/qt/qt_iconindicators.hpp +++ b/src/qt/qt_iconindicators.hpp @@ -16,7 +16,13 @@ enum IconIndicator { Browse, WriteProtectedBrowse, Export, - Eject + Eject, + Play, + Pause, + PlayActive, + PauseActive, + Record, + RecordWriteActive }; QPixmap getIconWithIndicator(const QIcon &icon, const QSize &size, QIcon::Mode iconMode, IconIndicator indicator); diff --git a/src/qt/qt_machinestatus.cpp b/src/qt/qt_machinestatus.cpp index e6ab80688..e6f6c824e 100644 --- a/src/qt/qt_machinestatus.cpp +++ b/src/qt/qt_machinestatus.cpp @@ -83,9 +83,15 @@ struct PixmapSetEmpty { struct PixmapSetEmptyActive { QPixmap normal; QPixmap active; + QPixmap record; + QPixmap play; + QPixmap pause; + QPixmap play_active; + QPixmap pause_active; QPixmap empty; QPixmap empty_active; QPixmap write_active; + QPixmap record_write_active; QPixmap read_write_active; QPixmap empty_write_active; QPixmap empty_read_write_active; @@ -169,6 +175,36 @@ struct StateEmptyActive { bool active = false; bool write_active = false; bool wp = false; + bool play = false; + bool pause = false; + bool record = false; + + void setRecord(bool b) + { + if (!label || b == record) + return; + + record = b; + refresh(); + } + + void setPlay(bool b) + { + if (!label || b == play) + return; + + play = b; + refresh(); + } + + void setPause(bool b) + { + if (!label || b == pause) + return; + + pause = b; + refresh(); + } void setActive(bool b) { @@ -212,10 +248,14 @@ struct StateEmptyActive { else label->setPixmap(write_active ? pixmaps->empty_write_active : (active ? pixmaps->empty_active : pixmaps->empty)); } else { - if (wp) + if (wp && !(play || pause)) label->setPixmap(active ? pixmaps->wp_active : pixmaps->wp); - else if (active && write_active) + else if (active && write_active && !wp) label->setPixmap(pixmaps->read_write_active); + else if (record && !active && !wp) + label->setPixmap(write_active ? pixmaps->record_write_active : pixmaps->record); + else if ((play || pause) && !write_active) + label->setPixmap(play ? (active ? pixmaps->play_active : pixmaps->play) : (active ? pixmaps->pause_active : pixmaps->pause)); else label->setPixmap(write_active ? pixmaps->write_active : (active ? pixmaps->active : pixmaps->normal)); } @@ -252,10 +292,16 @@ void PixmapSetEmptyActive::load(const QIcon &icon) { normal = getIconWithIndicator(icon, pixmap_size, QIcon::Normal, None); + play = getIconWithIndicator(icon, pixmap_size, QIcon::Normal, Play); + pause = getIconWithIndicator(icon, pixmap_size, QIcon::Normal, Pause); + record = getIconWithIndicator(icon, pixmap_size, QIcon::Normal, Record); + play_active = getIconWithIndicator(icon, pixmap_size, QIcon::Normal, PlayActive); + pause_active = getIconWithIndicator(icon, pixmap_size, QIcon::Normal, PauseActive); wp = getIconWithIndicator(icon, pixmap_size, QIcon::Normal, WriteProtected); wp_active = getIconWithIndicator(icon, pixmap_size, QIcon::Normal, WriteProtectedActive); active = getIconWithIndicator(icon, pixmap_size, QIcon::Normal, Active); write_active = getIconWithIndicator(icon, pixmap_size, QIcon::Normal, WriteActive); + record_write_active = getIconWithIndicator(icon, pixmap_size, QIcon::Normal, RecordWriteActive); read_write_active = getIconWithIndicator(icon, pixmap_size, QIcon::Normal, ReadWriteActive); empty = getIconWithIndicator(icon, pixmap_size, QIcon::Disabled, None); empty_active = getIconWithIndicator(icon, pixmap_size, QIcon::Disabled, Active); @@ -495,10 +541,21 @@ MachineStatus::refreshEmptyIcons() void MachineStatus::refreshIcons() { + /* Always show record/play statuses of cassette even if icon updates are disabled, since it's important to indicate play/record modes. */ + if (cassette_enable && cassette) { + d->cassette.setRecord(!!cassette->save); + d->cassette.setPlay(!cassette->save); + } + /* Check if icons should show activity. */ if (!update_icons) return; + if (cassette_enable) { + d->cassette.setWriteActive(machine_status.cassette.write_active); + d->cassette.setActive(machine_status.cassette.active); + } + for (size_t i = 0; i < FDD_NUM; ++i) { d->fdd[i].setActive(machine_status.fdd[i].active); d->fdd[i].setWriteActive(machine_status.fdd[i].write_active); @@ -506,6 +563,8 @@ MachineStatus::refreshIcons() for (size_t i = 0; i < CDROM_NUM; ++i) { d->cdrom[i].setActive(machine_status.cdrom[i].active); d->cdrom[i].setWriteActive(machine_status.cdrom[i].write_active); + d->cdrom[i].setPlay(cdrom_is_playing(i)); + d->cdrom[i].setPause(cdrom_is_paused(i)); if (machine_status.cdrom[i].active) { ui_sb_update_icon(SB_CDROM | i, 0); } diff --git a/src/qt/qt_main.cpp b/src/qt/qt_main.cpp index 5526eee37..9d55ba7ca 100644 --- a/src/qt/qt_main.cpp +++ b/src/qt/qt_main.cpp @@ -100,6 +100,7 @@ extern int qt_nvr_save(void); extern void exit_pause(void); bool cpu_thread_running = false; +bool fast_forward = false; } #include @@ -452,7 +453,7 @@ main_thread_fn() #endif drawits += static_cast(new_time - old_time); old_time = new_time; - if (drawits > 0 && !dopause) { + if ((drawits > 0 || fast_forward) && !dopause) { /* Yes, so run frames now. */ do { #ifdef USE_INSTRUMENT @@ -478,8 +479,9 @@ main_thread_fn() } drawits -= force_10ms ? 10 : 1; - if (drawits > 50) + if (drawits > 50 || fast_forward) drawits = 0; + } while (drawits > 0); } else { /* Just so we dont overload the host OS. */ @@ -889,6 +891,7 @@ main(int argc, char *argv[]) QObject::connect(&discordupdate, &QTimer::timeout, &app, [] { discord_run_callbacks(); }); + discordupdate.setInterval(1000); if (enable_discord) discordupdate.start(1000); } diff --git a/src/qt/qt_mainwindow.cpp b/src/qt/qt_mainwindow.cpp index cd7523985..231d49112 100644 --- a/src/qt/qt_mainwindow.cpp +++ b/src/qt/qt_mainwindow.cpp @@ -65,6 +65,7 @@ extern int qt_nvr_save(void); #endif extern bool cpu_thread_running; +extern bool fast_forward; }; #include @@ -238,20 +239,26 @@ MainWindow::MainWindow(QWidget *parent) QTimer *ledKeyboardTimer = new QTimer(this); ledKeyboardTimer->setTimerType(Qt::CoarseTimer); - ledKeyboardTimer->setInterval(1); + ledKeyboardTimer->setInterval(20); connect(ledKeyboardTimer, &QTimer::timeout, this, [this]() { + uint8_t prev_caps = 255, prev_num = 255, prev_scroll = 255, prev_kana = 255; uint8_t caps, num, scroll, kana; keyboard_get_states(&caps, &num, &scroll, &kana); - if (num_label->isVisible()) + if (num_label->isVisible() && prev_num != num) num_label->setPixmap(num ? this->num_icon.pixmap(QSize(16, 16)) : this->num_icon_off.pixmap(QSize(16, 16))); - if (caps_label->isVisible()) + if (caps_label->isVisible() && prev_caps != caps) caps_label->setPixmap(caps ? this->caps_icon.pixmap(QSize(16, 16)) : this->caps_icon_off.pixmap(QSize(16, 16))); - if (scroll_label->isVisible()) + if (scroll_label->isVisible() && prev_scroll != scroll) scroll_label->setPixmap(scroll ? this->scroll_icon.pixmap(QSize(16, 16)) : this->scroll_icon_off.pixmap(QSize(16, 16))); - if (kana_label->isVisible()) + if (kana_label->isVisible() && prev_kana != kana) kana_label->setPixmap(kana ? this->kana_icon.pixmap(QSize(16, 16)) : this->kana_icon_off.pixmap(QSize(16, 16))); + + prev_caps = caps; + prev_num = num; + prev_scroll = scroll; + prev_kana = kana; }); ledKeyboardTimer->start(); @@ -2129,6 +2136,12 @@ MainWindow::on_actionUpdate_mouse_every_CPU_frame_triggered() config_save(); } +void +MainWindow::on_actionFast_forward_triggered() +{ + fast_forward ^= 1; +} + void MainWindow::on_actionRemember_size_and_position_triggered() { @@ -2263,6 +2276,36 @@ MainWindow::on_actionTake_screenshot_triggered() device_force_redraw(); } +void +MainWindow::on_actionTake_raw_screenshot_triggered() +{ + startblit(); + for (auto &monitor : monitors) + ++monitor.mon_screenshots_raw; + endblit(); + device_force_redraw(); +} + +void +MainWindow::on_actionCopy_screenshot_triggered() +{ + startblit(); + for (auto &monitor : monitors) + ++monitor.mon_screenshots_clipboard; + endblit(); + device_force_redraw(); +} + +void +MainWindow::on_actionCopy_raw_screenshot_triggered() +{ + startblit(); + for (auto &monitor : monitors) + ++monitor.mon_screenshots_raw_clipboard; + endblit(); + device_force_redraw(); +} + void MainWindow::on_actionMute_Unmute_triggered() { diff --git a/src/qt/qt_mainwindow.hpp b/src/qt/qt_mainwindow.hpp index a31900767..2fa2ab3b9 100644 --- a/src/qt/qt_mainwindow.hpp +++ b/src/qt/qt_mainwindow.hpp @@ -132,12 +132,16 @@ private slots: void on_actionHide_tool_bar_triggered(); void on_actionUpdate_status_bar_icons_triggered(); void on_actionTake_screenshot_triggered(); + void on_actionTake_raw_screenshot_triggered(); + void on_actionCopy_screenshot_triggered(); + void on_actionCopy_raw_screenshot_triggered(); void toggleFullscreenUI(); void on_actionMute_Unmute_triggered(); void on_actionSound_gain_triggered(); void on_actionPreferences_triggered(); void on_actionEnable_Discord_integration_triggered(bool checked); void on_actionRenderer_options_triggered(); + void on_actionFast_forward_triggered(); void refreshMediaMenu(); void showMessage_(int flags, const QString &header, const QString &message, bool richText, std::atomic_bool *done = nullptr); diff --git a/src/qt/qt_mainwindow.ui b/src/qt/qt_mainwindow.ui index 777455d5b..ec5d245cb 100644 --- a/src/qt/qt_mainwindow.ui +++ b/src/qt/qt_mainwindow.ui @@ -70,8 +70,6 @@ - - @@ -80,6 +78,10 @@ + + + + @@ -106,6 +108,10 @@ + + + + @@ -295,26 +301,24 @@ false - - + - + + + :/menuicons/qt/icons/interpreter.ico:/menuicons/qt/icons/interpreter.ico - - true - &Force interpretation @@ -374,9 +378,6 @@ Press Ctrl+Alt+Del - - Ctrl+F12 - @@ -426,9 +427,6 @@ &Fullscreen - - Ctrl+Alt+PgUp - @@ -746,8 +744,36 @@ Take s&creenshot - - Ctrl+F11 + + + :/menuicons/qt/icons/take_screenshot.ico:/menuicons/qt/icons/take_screenshot.ico + + + + + Take &raw screenshot + + + + :/menuicons/qt/icons/take_raw_screenshot.ico:/menuicons/qt/icons/take_raw_screenshot.ico + + + + + C&opy screenshot + + + + :/menuicons/qt/icons/copy_screenshot.ico:/menuicons/qt/icons/copy_screenshot.ico + + + + + Copy r&aw screenshot + + + + :/menuicons/qt/icons/copy_raw_screenshot.ico:/menuicons/qt/icons/copy_raw_screenshot.ico @@ -1045,6 +1071,18 @@ &8x + + + true + + + + :/settings/qt/icons/fast_forward.ico:/settings/qt/icons/fast_forward.ico + + + &Fast forward + + diff --git a/src/qt/qt_mediamenu.cpp b/src/qt/qt_mediamenu.cpp index c5611ec2e..c9defba96 100644 --- a/src/qt/qt_mediamenu.cpp +++ b/src/qt/qt_mediamenu.cpp @@ -292,7 +292,7 @@ MediaMenu::cassetteMount(const QString &filename, bool wp) pc_cas_set_fname(cassette, cassette_fname); } - ui_sb_update_icon_state(SB_CASSETTE, filename.isEmpty() ? 1 : 0); + ui_sb_update_icon_state(SB_CASSETTE, cassette->fname == nullptr); ui_sb_update_icon_wp(SB_CASSETTE, cassette_ui_writeprot); mhm.addImageToHistory(0, ui::MediaType::Cassette, previous_image.filePath(), filename); cassetteUpdateMenu(); @@ -352,7 +352,7 @@ MediaMenu::cartridgeMount(int i, const QString &filename) QByteArray filenameBytes = filename.toUtf8(); cart_load(i, filenameBytes.data()); - ui_sb_update_icon_state(SB_CARTRIDGE | i, filename.isEmpty() ? 1 : 0); + ui_sb_update_icon_state(SB_CARTRIDGE | i, cart_fns[i][0] == 0); mhm.addImageToHistory(i, ui::MediaType::Cartridge, previous_image.filePath(), filename); cartridgeUpdateMenu(i); ui_sb_update_tip(SB_CARTRIDGE | i); @@ -468,7 +468,7 @@ MediaMenu::floppyMount(int i, const QString &filename, bool wp) mhm.addImageToHistory(i, ui::MediaType::Floppy, previous_image.filePath(), QString(filenameBytes)); } else mhm.addImageToHistory(i, ui::MediaType::Floppy, previous_image.filePath(), filename); - ui_sb_update_icon_state(SB_FLOPPY | i, filename.isEmpty() ? 1 : 0); + ui_sb_update_icon_state(SB_FLOPPY | i, drive_empty[i]); ui_sb_update_icon_wp(SB_FLOPPY | i, ui_writeprot[i]); floppyUpdateMenu(i); ui_sb_update_tip(SB_FLOPPY | i); @@ -902,7 +902,7 @@ MediaMenu::rdiskMount(int i, const QString &filename, bool wp) } mhm.addImageToHistory(i, ui::MediaType::RDisk, rdisk_drives[i].prev_image_path, rdisk_drives[i].image_path); - ui_sb_update_icon_state(SB_RDISK | i, filename.isEmpty() ? 1 : 0); + ui_sb_update_icon_state(SB_RDISK | i, dev->drv->fp == NULL); ui_sb_update_icon_wp(SB_RDISK | i, wp); rdiskUpdateMenu(i); ui_sb_update_tip(SB_RDISK | i); @@ -1082,7 +1082,7 @@ MediaMenu::moMount(int i, const QString &filename, bool wp) } mhm.addImageToHistory(i, ui::MediaType::Mo, mo_drives[i].prev_image_path, mo_drives[i].image_path); - ui_sb_update_icon_state(SB_MO | i, filename.isEmpty() ? 1 : 0); + ui_sb_update_icon_state(SB_MO | i, dev->drv->fp == nullptr); moUpdateMenu(i); ui_sb_update_tip(SB_MO | i); @@ -1160,9 +1160,10 @@ MediaMenu::nicUpdateMenu(int i) if (!netMenus.contains(i)) return; - QString netType = tr("Null Driver"); + QString netType; switch (net_cards_conf[i].net_type) { default: + netType = tr("Null Driver"); break; case NET_TYPE_SLIRP: netType = "SLiRP"; @@ -1176,11 +1177,11 @@ MediaMenu::nicUpdateMenu(int i) case NET_TYPE_TAP: netType = "TAP"; break; - case NET_TYPE_NMSWITCH: - netType = "Local Switch"; + case NET_TYPE_NLSWITCH: + netType = tr("Local Switch"); break; case NET_TYPE_NRSWITCH: - netType = "Remote Switch"; + netType = tr("Remote Switch"); break; } diff --git a/src/qt/qt_openglrenderer.cpp b/src/qt/qt_openglrenderer.cpp index 8e01bfe6c..22e400c65 100644 --- a/src/qt/qt_openglrenderer.cpp +++ b/src/qt/qt_openglrenderer.cpp @@ -26,6 +26,7 @@ extern MainWindow *main_window; #include #include #include +#include #include #include #include @@ -1147,6 +1148,8 @@ OpenGLRenderer::finalize() isFinalized = true; } +extern void take_screenshot_clipboard_monitor(int sx, int sy, int sw, int sh, int i); + void OpenGLRenderer::onBlit(int buf_idx, int x, int y, int w, int h) { @@ -1184,6 +1187,10 @@ OpenGLRenderer::onBlit(int buf_idx, int x, int y, int w, int h) if (video_framerate == -1) render(); + + if (monitors[r_monitor_index].mon_screenshots_raw_clipboard) { + take_screenshot_clipboard_monitor(x, y, w, h, r_monitor_index); + } } std::vector> @@ -1725,6 +1732,20 @@ OpenGLRenderer::render() monitors[r_monitor_index].mon_screenshots--; free(rgb); } + if (monitors[r_monitor_index].mon_screenshots_clipboard) { + int width = destination.width(), height = destination.height(); + + unsigned char *rgb = (unsigned char *) calloc(1, (size_t) width * height * 4); + + glw.glFinish(); + glw.glReadPixels(window_rect.x, window_rect.y, width, height, GL_RGB, GL_UNSIGNED_BYTE, rgb); + + QImage image((uchar*)rgb, width, height, width * 3, QImage::Format_RGB888); + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setImage(image.mirrored(false, true), QClipboard::Clipboard); + monitors[r_monitor_index].mon_screenshots_clipboard--; + free(rgb); + } glw.glDisable(GL_FRAMEBUFFER_SRGB); diff --git a/src/qt/qt_platform.cpp b/src/qt/qt_platform.cpp index 37653c5a3..9fd16d3f1 100644 --- a/src/qt/qt_platform.cpp +++ b/src/qt/qt_platform.cpp @@ -30,8 +30,11 @@ #include +#include +#include #include #include +#include #include #include #include @@ -978,3 +981,37 @@ plat_break(void) raise(SIGTRAP); #endif } + +static unsigned char *rgb_ = NULL; +static int width_ = 0; +static int height_ = 0; +static volatile int waiting = 0; + +static void +send_to_clipboard(void) +{ + unsigned char *rgb = (unsigned char *) calloc(1, height_ * width_ * 4); + memcpy(rgb, rgb_, height_ * width_ * 3); + QImage image(rgb, width_, height_, width_ * 3, QImage::Format_RGB888); + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setImage(image, QClipboard::Clipboard); + free(rgb); + waiting = 0; +} + +void +plat_send_to_clipboard(unsigned char *rgb, int width, int height) +{ + rgb_ = rgb; + width_ = width; + height_ = height; + waiting = 1; + + QTimer::singleShot(0, main_window, &send_to_clipboard); + while (waiting) + ; + + height_ = 0; + width_ = 0; + rgb_ = NULL; +} diff --git a/src/qt/qt_progsettings.cpp b/src/qt/qt_progsettings.cpp index 58d22c63b..62b63bbcc 100644 --- a/src/qt/qt_progsettings.cpp +++ b/src/qt/qt_progsettings.cpp @@ -53,6 +53,7 @@ QVector> ProgSettings::languages = { { "hr-HR", "Croatian (Croatia)" }, { "cs-CZ", "Czech (Czech Republic)" }, { "de-DE", "German (Germany)" }, + { "el-GR", "Greek (Greece)" }, { "en-GB", "English (United Kingdom)" }, { "en-US", "English (United States)" }, { "fi-FI", "Finnish (Finland)" }, diff --git a/src/qt/qt_rendererstack.cpp b/src/qt/qt_rendererstack.cpp index 41d17d897..99a6bf649 100644 --- a/src/qt/qt_rendererstack.cpp +++ b/src/qt/qt_rendererstack.cpp @@ -33,6 +33,9 @@ #include #include +#include +#include + #include #include #ifdef TOUCH_PR @@ -66,9 +69,12 @@ # include #endif +#include + extern "C" { #include <86box/86box.h> #include <86box/config.h> +#include <86box/path.h> #include <86box/plat.h> #include <86box/video.h> #include <86box/mouse.h> @@ -87,6 +93,8 @@ extern MainWindow *main_window; HWND rw_hwnd; #endif +static unsigned char *screenshot_rgb = NULL; + RendererStack::RendererStack(QWidget *parent, int monitor_index) : QWidget(parent) , boxLayout(new QBoxLayout(QBoxLayout::TopToBottom, this)) @@ -158,12 +166,20 @@ RendererStack::RendererStack(QWidget *parent, int monitor_index) if (monitor_index == 0) mouse_set_raw(raw); + + screenshot_rgb = (unsigned char *) calloc(1, (size_t) 2048 * 2048 * 4); } RendererStack::~RendererStack() { + if (screenshot_rgb != NULL) { + free(screenshot_rgb); + screenshot_rgb = NULL; + } + while (QApplication::overrideCursor()) QApplication::restoreOverrideCursor(); + delete ui; } @@ -372,11 +388,7 @@ RendererStack::createRenderer(Renderer renderer) auto sw = new SoftwareRenderer(this); rendererWindow = sw; connect(this, &RendererStack::blitToRenderer, sw, &SoftwareRenderer::onBlit, Qt::QueuedConnection); -#ifdef __HAIKU__ current.reset(sw); -#else - current.reset(this->createWindowContainer(sw)); -#endif } break; case Renderer::OpenGL3: @@ -460,6 +472,32 @@ RendererStack::createRenderer(Renderer renderer) } } +uint32_t *screenshot_buf = NULL; + +void +take_screenshot_clipboard_monitor(int sx, int sy, int sw, int sh, int i) +{ + uint32_t temp = 0x00000000; + + for (int y = 0; y < sh; ++y) { + for (int x = 0; x < sw; ++x) { + if (screenshot_buf == NULL) + memset(&(screenshot_rgb[(y * sw * 3) + (x * 3)]), 0x00, 3); + else { + temp = screenshot_buf[((sy + y) * 2048) + sx + x]; + screenshot_rgb[(y * sw * 3) + (x * 3)] = (temp >> 16) & 0xff; + screenshot_rgb[(y * sw * 3) + (x * 3) + 1] = (temp >> 8) & 0xff; + screenshot_rgb[(y * sw * 3) + (x * 3) + 2] = temp & 0xff; + } + } + } + + QImage image(screenshot_rgb, sw, sh, sw * 3, QImage::Format_RGB888); + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setImage(image, QClipboard::Clipboard); + monitors[i].mon_screenshots_raw_clipboard--; +} + // called from blitter thread void RendererStack::blit(int x, int y, int w, int h) @@ -478,10 +516,11 @@ RendererStack::blit(int x, int y, int w, int h) video_copy(scanline, &(monitors[m_monitor_index].target_buffer->line[y1][x]), w * 4); } - if (monitors[m_monitor_index].mon_screenshots && !rendererTakesScreenshots) { + if (monitors[m_monitor_index].mon_screenshots_raw) { video_screenshot_monitor((uint32_t *) imagebits, x, y, 2048, m_monitor_index); } video_blit_complete_monitor(m_monitor_index); + screenshot_buf = (uint32_t *) imagebits; emit blitToRenderer(currentBuf, sx, sy, sw, sh); currentBuf = (currentBuf + 1) % imagebufs.size(); } diff --git a/src/qt/qt_settingsdisplay.cpp b/src/qt/qt_settingsdisplay.cpp index 6df4d5edc..d5fbe77d5 100644 --- a/src/qt/qt_settingsdisplay.cpp +++ b/src/qt/qt_settingsdisplay.cpp @@ -106,6 +106,9 @@ SettingsDisplay::onCurrentMachineChanged(int machineId) } if (video_card_available(c) && device_is_valid(video_dev, machineId)) { + if (c == 1 && machine_get_vid_device(machineId)) { + name += QString(" (%1)").arg(DeviceConfig::DeviceName(machine_get_vid_device(machineId), machine_get_vid_device(machineId)->internal_name, 0)); + } int row = Models::AddEntry(model, name, c); if (c == curVideoCard) { selectedRow = row - removeRows; diff --git a/src/qt/qt_settingsharddisks.cpp b/src/qt/qt_settingsharddisks.cpp index 0f12c8e6f..849c34305 100644 --- a/src/qt/qt_settingsharddisks.cpp +++ b/src/qt/qt_settingsharddisks.cpp @@ -20,6 +20,7 @@ extern "C" { #include <86box/86box.h> #include <86box/hdd.h> +#include <86box/hdd_audio.h> } #include @@ -36,6 +37,7 @@ const int ColumnHeads = 3; const int ColumnSectors = 4; const int ColumnSize = 5; const int ColumnSpeed = 6; +const int ColumnAudio = 7; const int DataBus = Qt::UserRole; const int DataBusChannel = Qt::UserRole + 1; @@ -103,6 +105,11 @@ addRow(QAbstractItemModel *model, hard_disk_t *hd) auto speedIndex = model->index(row, ColumnSpeed); model->setData(speedIndex, QObject::tr(hdd_preset_getname(hd->speed_preset))); model->setData(speedIndex, hd->speed_preset, Qt::UserRole); + + auto audioIndex = model->index(row, ColumnAudio); + const char *audioName = hdd_audio_get_profile_name(hd->audio_profile); + model->setData(audioIndex, audioName ? QObject::tr(audioName) : QObject::tr("None")); + model->setData(audioIndex, hd->audio_profile, Qt::UserRole); } SettingsHarddisks::SettingsHarddisks(QWidget *parent) @@ -113,7 +120,7 @@ SettingsHarddisks::SettingsHarddisks(QWidget *parent) hard_disk_icon = QIcon(":/settings/qt/icons/hard_disk.ico"); - QAbstractItemModel *model = new QStandardItemModel(0, 7, this); + QAbstractItemModel *model = new QStandardItemModel(0, 8, this); model->setHeaderData(ColumnBus, Qt::Horizontal, tr("Bus")); model->setHeaderData(ColumnFilename, Qt::Horizontal, tr("File")); model->setHeaderData(ColumnCylinders, Qt::Horizontal, tr("C")); @@ -121,6 +128,7 @@ SettingsHarddisks::SettingsHarddisks(QWidget *parent) model->setHeaderData(ColumnSectors, Qt::Horizontal, tr("S")); model->setHeaderData(ColumnSize, Qt::Horizontal, tr("MiB")); model->setHeaderData(ColumnSpeed, Qt::Horizontal, tr("Model")); + model->setHeaderData(ColumnAudio, Qt::Horizontal, tr("Audio")); ui->tableView->setModel(model); for (int i = 0; i < HDD_NUM; i++) { @@ -139,6 +147,7 @@ SettingsHarddisks::SettingsHarddisks(QWidget *parent) onTableRowChanged(QModelIndex()); Harddrives::populateBuses(ui->comboBoxBus->model()); + on_comboBoxBus_currentIndexChanged(0); } @@ -162,6 +171,7 @@ SettingsHarddisks::save() hdd[i].hpc = idx.siblingAtColumn(ColumnHeads).data().toUInt(); hdd[i].spt = idx.siblingAtColumn(ColumnSectors).data().toUInt(); hdd[i].speed_preset = idx.siblingAtColumn(ColumnSpeed).data(Qt::UserRole).toUInt(); + hdd[i].audio_profile = idx.siblingAtColumn(ColumnAudio).data(Qt::UserRole).toUInt(); QByteArray fileName = idx.siblingAtColumn(ColumnFilename).data(Qt::UserRole).toString().toUtf8(); strncpy(hdd[i].fn, fileName.data(), sizeof(hdd[i].fn) - 1); @@ -269,6 +279,53 @@ SettingsHarddisks::on_comboBoxSpeed_currentIndexChanged(int index) auto col = idx.siblingAtColumn(ColumnSpeed); model->setData(col, ui->comboBoxSpeed->currentData(Qt::UserRole), Qt::UserRole); model->setData(col, QObject::tr(hdd_preset_getname(ui->comboBoxSpeed->currentData(Qt::UserRole).toUInt()))); + + /* Reset audio profile to None when speed/model changes */ + auto audioCol = idx.siblingAtColumn(ColumnAudio); + model->setData(audioCol, 0, Qt::UserRole); + model->setData(audioCol, QObject::tr("None")); + } + + /* Repopulate audio profiles based on the selected speed preset's RPM */ + populateAudioProfiles(); +} + +void +SettingsHarddisks::populateAudioProfiles() +{ + ui->comboBoxAudio->clear(); + + /* Get RPM from currently selected speed preset */ + uint32_t target_rpm = hdd_preset_get_rpm(ui->comboBoxSpeed->currentData(Qt::UserRole).toUInt()); + + /* Populate audio profile combobox with matching RPM profiles */ + int profile_count = hdd_audio_get_profile_count(); + for (int i = 0; i < profile_count; i++) { + const char *name = hdd_audio_get_profile_name(i); + uint32_t profile_rpm = hdd_audio_get_profile_rpm(i); + + /* Include profile if it has no RPM set (0) or matches target RPM */ + if (name && (profile_rpm == 0 || profile_rpm == target_rpm)) { + ui->comboBoxAudio->addItem(name, i); + } + } +} + +void +SettingsHarddisks::on_comboBoxAudio_currentIndexChanged(int index) +{ + if (index < 0) + return; + + auto idx = ui->tableView->selectionModel()->currentIndex(); + if (idx.isValid()) { + auto *model = ui->tableView->model(); + auto col = idx.siblingAtColumn(ColumnAudio); + int prof = ui->comboBoxAudio->currentData(Qt::UserRole).toInt(); + model->setData(col, prof, Qt::UserRole); + + const char *audioName = hdd_audio_get_profile_name(prof); + model->setData(col, audioName ? QObject::tr(audioName) : QObject::tr("None")); } } @@ -279,13 +336,16 @@ SettingsHarddisks::onTableRowChanged(const QModelIndex ¤t) ui->labelBus->setHidden(hidden); ui->labelChannel->setHidden(hidden); ui->labelSpeed->setHidden(hidden); + ui->labelAudio->setHidden(hidden); ui->comboBoxBus->setHidden(hidden); ui->comboBoxChannel->setHidden(hidden); ui->comboBoxSpeed->setHidden(hidden); + ui->comboBoxAudio->setHidden(hidden); uint32_t bus = current.siblingAtColumn(ColumnBus).data(DataBus).toUInt(); uint32_t busChannel = current.siblingAtColumn(ColumnBus).data(DataBusChannel).toUInt(); uint32_t speed = current.siblingAtColumn(ColumnSpeed).data(Qt::UserRole).toUInt(); + uint32_t audio = current.siblingAtColumn(ColumnAudio).data(Qt::UserRole).toUInt(); auto *model = ui->comboBoxBus->model(); auto match = model->match(model->index(0, 0), Qt::UserRole, bus); @@ -302,6 +362,14 @@ SettingsHarddisks::onTableRowChanged(const QModelIndex ¤t) if (!match.isEmpty()) ui->comboBoxSpeed->setCurrentIndex(match.first().row()); + /* Populate audio profiles based on selected speed preset's RPM */ + populateAudioProfiles(); + + model = ui->comboBoxAudio->model(); + match = model->match(model->index(0, 0), Qt::UserRole, audio); + if (!match.isEmpty()) + ui->comboBoxAudio->setCurrentIndex(match.first().row()); + reloadBusChannels(); } diff --git a/src/qt/qt_settingsharddisks.hpp b/src/qt/qt_settingsharddisks.hpp index f892a79cd..f082c7de6 100644 --- a/src/qt/qt_settingsharddisks.hpp +++ b/src/qt/qt_settingsharddisks.hpp @@ -24,6 +24,7 @@ private slots: void on_comboBoxBus_currentIndexChanged(int index); void on_comboBoxChannel_currentIndexChanged(int index); void on_comboBoxSpeed_currentIndexChanged(int index); + void on_comboBoxAudio_currentIndexChanged(int index); void on_pushButtonNew_clicked(); void on_pushButtonExisting_clicked(); @@ -34,6 +35,7 @@ private slots: private: Ui::SettingsHarddisks *ui; void enableCurrentlySelectedChannel(); + void populateAudioProfiles(); bool buschangeinprogress = false; }; diff --git a/src/qt/qt_settingsharddisks.ui b/src/qt/qt_settingsharddisks.ui index d65b1d0c1..a6f134bdc 100644 --- a/src/qt/qt_settingsharddisks.ui +++ b/src/qt/qt_settingsharddisks.ui @@ -90,6 +90,20 @@ + + + + Audio: + + + + + + + 30 + + + diff --git a/src/qt/qt_settingsnetwork.cpp b/src/qt/qt_settingsnetwork.cpp index fec607b97..878944e60 100644 --- a/src/qt/qt_settingsnetwork.cpp +++ b/src/qt/qt_settingsnetwork.cpp @@ -54,8 +54,8 @@ SettingsNetwork::enableElements(Ui::SettingsNetwork *ui) // auto *switch_group_hlayout = findChild(QString("HLayoutSwitch%1").arg(i + 1)); // auto *switch_group_hspacer = findChild(QString("horizontalSpacerSwitch%1").arg(i + 1)); auto *switch_group_value = findChild(QString("spinnerSwitch%1").arg(i + 1)); - switch_group_value->setMinimum(1); - switch_group_value->setMaximum(10); + switch_group_value->setMinimum(NET_SWITCH_GRP_MIN); + switch_group_value->setMaximum(NET_SWITCH_GRP_MAX); // Promiscuous option auto *promisc_label = findChild(QString("labelPromisc%1").arg(i + 1)); @@ -137,8 +137,7 @@ SettingsNetwork::enableElements(Ui::SettingsNetwork *ui) break; #endif -#ifdef USE_NETSWITCH - case NET_TYPE_NMSWITCH: + case NET_TYPE_NLSWITCH: // option_list_label->setText("Local Switch Options"); option_list_label->setVisible(true); option_list_line->setVisible(true); @@ -167,7 +166,6 @@ SettingsNetwork::enableElements(Ui::SettingsNetwork *ui) hostname_label->setVisible(true); hostname_value->setVisible(true); break; -#endif /* USE_NETSWITCH */ case NET_TYPE_SLIRP: default: @@ -215,11 +213,9 @@ SettingsNetwork::save() cbox = findChild(QString("comboBoxNet%1").arg(i + 1)); net_cards_conf[i].net_type = cbox->currentData().toInt(); cbox = findChild(QString("comboBoxIntf%1").arg(i + 1)); -#ifdef USE_NETSWITCH auto *hostname_value = findChild(QString("hostnameSwitch%1").arg(i + 1)); auto *promisc_value = findChild(QString("boxPromisc%1").arg(i + 1)); auto *switch_group_value = findChild(QString("spinnerSwitch%1").arg(i + 1)); -#endif /* USE_NETSWITCH */ memset(net_cards_conf[i].host_dev_name, '\0', sizeof(net_cards_conf[i].host_dev_name)); if (net_cards_conf[i].net_type == NET_TYPE_PCAP) strncpy(net_cards_conf[i].host_dev_name, network_devs[cbox->currentData().toInt()].device, sizeof(net_cards_conf[i].host_dev_name) - 1); @@ -231,16 +227,14 @@ SettingsNetwork::save() else if (net_cards_conf[i].net_type == NET_TYPE_TAP) strncpy(net_cards_conf[i].host_dev_name, bridge_line->text().toUtf8().constData(), sizeof(net_cards_conf[i].host_dev_name)); #endif -#ifdef USE_NETSWITCH else if (net_cards_conf[i].net_type == NET_TYPE_NRSWITCH) { memset(net_cards_conf[i].nrs_hostname, '\0', sizeof(net_cards_conf[i].nrs_hostname)); strncpy(net_cards_conf[i].nrs_hostname, hostname_value->text().toUtf8().constData(), sizeof(net_cards_conf[i].nrs_hostname) - 1); - net_cards_conf[i].switch_group = switch_group_value->value() - 1; - } else if (net_cards_conf[i].net_type == NET_TYPE_NMSWITCH) { + net_cards_conf[i].switch_group = switch_group_value->value(); + } else if (net_cards_conf[i].net_type == NET_TYPE_NLSWITCH) { net_cards_conf[i].promisc_mode = promisc_value->isChecked(); - net_cards_conf[i].switch_group = switch_group_value->value() - 1; + net_cards_conf[i].switch_group = switch_group_value->value(); } -#endif /* USE_NETSWITCH */ } } @@ -313,12 +307,10 @@ SettingsNetwork::onCurrentMachineChanged(int machineId) Models::AddEntry(model, "TAP", NET_TYPE_TAP); #endif -#ifdef USE_NETSWITCH - Models::AddEntry(model, "Local Switch", NET_TYPE_NMSWITCH); -# ifdef ENABLE_NET_NRSWITCH + Models::AddEntry(model, "Local Switch", NET_TYPE_NLSWITCH); +#ifdef ENABLE_NET_NRSWITCH Models::AddEntry(model, "Remote Switch", NET_TYPE_NRSWITCH); -# endif /* ENABLE_NET_NRSWITCH */ -#endif /* USE_NETSWITCH */ +#endif /* ENABLE_NET_NRSWITCH */ model->removeRows(0, removeRows); cbox->setCurrentIndex(cbox->findData(net_cards_conf[i].net_type)); @@ -354,18 +346,16 @@ SettingsNetwork::onCurrentMachineChanged(int machineId) auto editline = findChild(QString("bridgeTAPNIC%1").arg(i + 1)); editline->setText(currentTapDevice); #endif -#ifdef USE_NETSWITCH - } else if (net_cards_conf[i].net_type == NET_TYPE_NMSWITCH) { + } else if (net_cards_conf[i].net_type == NET_TYPE_NLSWITCH) { auto *promisc_value = findChild(QString("boxPromisc%1").arg(i + 1)); promisc_value->setCheckState(net_cards_conf[i].promisc_mode == 1 ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); auto *switch_group_value = findChild(QString("spinnerSwitch%1").arg(i + 1)); - switch_group_value->setValue(net_cards_conf[i].switch_group + 1); + switch_group_value->setValue(net_cards_conf[i].switch_group); } else if (net_cards_conf[i].net_type == NET_TYPE_NRSWITCH) { auto *hostname_value = findChild(QString("hostnameSwitch%1").arg(i + 1)); hostname_value->setText(net_cards_conf[i].nrs_hostname); auto *switch_group_value = findChild(QString("spinnerSwitch%1").arg(i + 1)); - switch_group_value->setValue(net_cards_conf[i].switch_group + 1); -#endif /* USE_NETSWITCH */ + switch_group_value->setValue(net_cards_conf[i].switch_group); } } } diff --git a/src/qt/qt_settingssound.cpp b/src/qt/qt_settingssound.cpp index 6307451a5..cb5368def 100644 --- a/src/qt/qt_settingssound.cpp +++ b/src/qt/qt_settingssound.cpp @@ -89,8 +89,8 @@ SettingsSound::onCurrentMachineChanged(const int machineId) c = 0; while (true) { - const QString name = DeviceConfig::DeviceName(sound_card_getdevice(c), - sound_card_get_internal_name(c), 1); + QString name = DeviceConfig::DeviceName(sound_card_getdevice(c), + sound_card_get_internal_name(c), 1); if (name.isEmpty()) break; @@ -99,6 +99,9 @@ SettingsSound::onCurrentMachineChanged(const int machineId) if (device_is_valid(sound_card_getdevice(c), machineId)) { for (uint8_t i = 0; i < SOUND_CARD_MAX; ++i) { if ((c != 1) || ((i == 0) && m_has_snd)) { + if (i == 0 && c == 1 && m_has_snd && machine_get_snd_device(machineId)) { + name += QString(" (%1)").arg(DeviceConfig::DeviceName(machine_get_snd_device(machineId), machine_get_snd_device(machineId)->internal_name, 0)); + } int row = Models::AddEntry(models[i], name, c); if (c == sound_card_current[i]) diff --git a/src/qt/qt_softwarerenderer.cpp b/src/qt/qt_softwarerenderer.cpp index 0a4492a3e..d9e217ed3 100644 --- a/src/qt/qt_softwarerenderer.cpp +++ b/src/qt/qt_softwarerenderer.cpp @@ -18,21 +18,19 @@ */ #include "qt_softwarerenderer.hpp" #include +#include #include #include extern "C" { #include <86box/86box.h> +#include <86box/path.h> +#include <86box/plat.h> #include <86box/video.h> } SoftwareRenderer::SoftwareRenderer(QWidget *parent) -#ifdef __HAIKU__ : QWidget(parent) -#else - : QWindow(parent->windowHandle()) - , m_backingStore(new QBackingStore(this)) -#endif { RendererCommon::parentWidget = parent; @@ -42,25 +40,23 @@ SoftwareRenderer::SoftwareRenderer(QWidget *parent) buf_usage = std::vector(2); buf_usage[0].clear(); buf_usage[1].clear(); -#ifdef __HAIKU__ this->setMouseTracking(true); -#endif } -#ifdef __HAIKU__ void SoftwareRenderer::paintEvent(QPaintEvent *event) { (void) event; onPaint(this); } -#endif void SoftwareRenderer::render() { +#ifdef __HAIKU__ if (!isExposed()) return; +#endif QRect rect(0, 0, width(), height()); m_backingStore->beginPaint(rect); @@ -78,6 +74,8 @@ SoftwareRenderer::exposeEvent(QExposeEvent *event) render(); } +extern void take_screenshot_clipboard_monitor(int sx, int sy, int sw, int sh, int i); + void SoftwareRenderer::onBlit(int buf_idx, int x, int y, int w, int h) { @@ -94,24 +92,52 @@ SoftwareRenderer::onBlit(int buf_idx, int x, int y, int w, int h) if (source != origSource) onResize(this->width(), this->height()); -#ifdef __HAIKU__ + update(); -#else - render(); -#endif + + if (monitors[r_monitor_index].mon_screenshots) { + char path[1024]; + char fn[256]; + + memset(fn, 0, sizeof(fn)); + memset(path, 0, sizeof(path)); + + path_append_filename(path, usr_path, SCREENSHOT_PATH); + + if (!plat_dir_check(path)) + plat_dir_create(path); + + path_slash(path); + strcat(path, "Monitor_"); + snprintf(&path[strlen(path)], 42, "%d_", r_monitor_index + 1); + + plat_tempfile(fn, NULL, (char *) ".png"); + strcat(path, fn); + + QPixmap pixmap(RendererCommon::parentWidget->size()); + RendererCommon::parentWidget->render(&pixmap); + QImage image = pixmap.toImage(); + image.save(path, "png"); + monitors[r_monitor_index].mon_screenshots--; + } + if (monitors[r_monitor_index].mon_screenshots_clipboard) { + QPixmap pixmap(RendererCommon::parentWidget->size()); + RendererCommon::parentWidget->render(&pixmap); + QImage image = pixmap.toImage(); + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setImage(image, QClipboard::Clipboard); + monitors[r_monitor_index].mon_screenshots_clipboard--; + } + if (monitors[r_monitor_index].mon_screenshots_raw_clipboard) { + take_screenshot_clipboard_monitor(x, y, w, h, r_monitor_index); + } } void SoftwareRenderer::resizeEvent(QResizeEvent *event) { onResize(width(), height()); -#ifdef __HAIKU__ QWidget::resizeEvent(event); -#else - QWindow::resizeEvent(event); - m_backingStore->resize(event->size()); - render(); -#endif } bool @@ -119,11 +145,7 @@ SoftwareRenderer::event(QEvent *event) { bool res = false; if (!eventDelegate(event, res)) -#ifdef __HAIKU__ return QWidget::event(event); -#else - return QWindow::event(event); -#endif return res; } @@ -142,9 +164,7 @@ SoftwareRenderer::onPaint(QPaintDevice *device) #endif painter.setCompositionMode(QPainter::CompositionMode_Plus); painter.drawImage(destination, *images[cur_image], source); -#ifndef __HAIKU__ painter.end(); -#endif } std::vector> diff --git a/src/qt/qt_softwarerenderer.hpp b/src/qt/qt_softwarerenderer.hpp index e80410956..a2b11c012 100644 --- a/src/qt/qt_softwarerenderer.hpp +++ b/src/qt/qt_softwarerenderer.hpp @@ -11,21 +11,19 @@ #include "qt_renderercommon.hpp" class SoftwareRenderer : -#ifdef __HAIKU__ public QWidget, -#else - public QWindow, -#endif public RendererCommon { Q_OBJECT public: explicit SoftwareRenderer(QWidget *parent = nullptr); -#ifdef __HAIKU__ void paintEvent(QPaintEvent *event) override; -#endif +#ifdef __HAIKU__ void exposeEvent(QExposeEvent *event) override; +#else + void exposeEvent(QExposeEvent *event); +#endif std::vector> getBuffers() override; diff --git a/src/qt/qt_soundgain.ui b/src/qt/qt_soundgain.ui index 8d3f2c3f6..06ea38c10 100644 --- a/src/qt/qt_soundgain.ui +++ b/src/qt/qt_soundgain.ui @@ -31,7 +31,20 @@ Sound Gain - + + + + + Qt::Horizontal + + + + 40 + 20 + + + + @@ -48,7 +61,107 @@ - + + + + +18 dB + + + Qt::AlignRight + + + + + + + +16 dB + + + Qt::AlignRight + + + + + + + +14 dB + + + Qt::AlignRight + + + + + + + +12 dB + + + Qt::AlignRight + + + + + + + +10 dB + + + Qt::AlignRight + + + + + + + +8 dB + + + Qt::AlignRight + + + + + + + +6 dB + + + Qt::AlignRight + + + + + + + +4 dB + + + Qt::AlignRight + + + + + + + +2 dB + + + Qt::AlignRight + + + + + + + 0 dB + + + Qt::AlignRight + + + + 18 @@ -70,7 +183,7 @@ - + diff --git a/src/qt/qt_translations.qrc.in b/src/qt/qt_translations.qrc.in index 46541b103..016160b52 100644 --- a/src/qt/qt_translations.qrc.in +++ b/src/qt/qt_translations.qrc.in @@ -3,6 +3,7 @@ 86box_ca-ES.qm 86box_cs-CZ.qm 86box_de-DE.qm + 86box_el-GR.qm 86box_en-US.qm 86box_en-GB.qm 86box_es-ES.qm diff --git a/src/qt/qt_vmmanager_system.cpp b/src/qt/qt_vmmanager_system.cpp index 5c9fd3d01..eb91ef220 100644 --- a/src/qt/qt_vmmanager_system.cpp +++ b/src/qt/qt_vmmanager_system.cpp @@ -521,14 +521,11 @@ VMManagerSystem::setupVars() auto machine_name = QString(); int i = 0; int ram_granularity = 0; + int ci = machine_get_machine_from_internal_name(machine_config["machine"].toUtf8()); // Machine - for (int ci = 0; ci < machine_count(); ++ci) { - if (machine_available(ci)) { - if (machines[ci].internal_name == machine_config["machine"]) { - machine_name = machines[ci].name; - ram_granularity = machines[ci].ram.step; - } - } + if (ci != -1 && machine_available(ci)) { + machine_name = machines[ci].name; + ram_granularity = machines[ci].ram.step; } display_table[VMManager::Display::Name::Machine] = machine_name; @@ -895,7 +892,7 @@ VMManagerSystem::setupVars() net_type = "SLiRP"; else if (net_type == "pcap") net_type = "PCap"; - else if (net_type == "nmswitch") + else if ((net_type == "nlswitch") || (net_type == "nmswitch")) net_type = tr("Local Switch"); else if (net_type == "nrswitch") net_type = tr("Remote Switch"); diff --git a/src/qt/qt_vulkanrenderer.cpp b/src/qt/qt_vulkanrenderer.cpp index be588a8d2..d34757ba7 100644 --- a/src/qt/qt_vulkanrenderer.cpp +++ b/src/qt/qt_vulkanrenderer.cpp @@ -428,8 +428,6 @@ VulkanRenderer2::updateSamplers() void VulkanRenderer2::initResources() { - qDebug("initResources"); - VkDevice dev = m_window->device(); m_devFuncs = m_window->vulkanInstance()->deviceFunctions(dev); @@ -811,8 +809,6 @@ VulkanRenderer2::initResources() void VulkanRenderer2::initSwapChainResources() { - qDebug("initSwapChainResources"); - // Projection matrix m_proj = m_window->clipCorrectionMatrix(); // adjust for Vulkan-OpenGL clip space differences } @@ -820,14 +816,11 @@ VulkanRenderer2::initSwapChainResources() void VulkanRenderer2::releaseSwapChainResources() { - qDebug("releaseSwapChainResources"); } void VulkanRenderer2::releaseResources() { - qDebug("releaseResources"); - VkDevice dev = m_window->device(); if (m_sampler) { diff --git a/src/qt/qt_vulkanwindowrenderer.cpp b/src/qt/qt_vulkanwindowrenderer.cpp index 79dbc8f87..c27d382a5 100644 --- a/src/qt/qt_vulkanwindowrenderer.cpp +++ b/src/qt/qt_vulkanwindowrenderer.cpp @@ -32,6 +32,8 @@ ****************************************************************************/ #include "qt_vulkanwindowrenderer.hpp" +#include +#include #include #include @@ -46,6 +48,8 @@ extern "C" { # include <86box/86box.h> +# include <86box/path.h> +# include <86box/plat.h> # include <86box/video.h> } # if 0 @@ -861,6 +865,8 @@ VulkanWindowRenderer::event(QEvent *event) return res; } +extern void take_screenshot_clipboard_monitor(int sx, int sy, int sw, int sh, int i); + void VulkanWindowRenderer::onBlit(int buf_idx, int x, int y, int w, int h) { @@ -873,6 +879,40 @@ VulkanWindowRenderer::onBlit(int buf_idx, int x, int y, int w, int h) this->pixelRatio = devicePixelRatio(); onResize(this->width(), this->height()); } + + if (monitors[r_monitor_index].mon_screenshots) { + char path[1024]; + char fn[256]; + + memset(fn, 0, sizeof(fn)); + memset(path, 0, sizeof(path)); + + path_append_filename(path, usr_path, SCREENSHOT_PATH); + + if (!plat_dir_check(path)) + plat_dir_create(path); + + path_slash(path); + strcat(path, "Monitor_"); + snprintf(&path[strlen(path)], 42, "%d_", r_monitor_index + 1); + + plat_tempfile(fn, NULL, (char *) ".png"); + strcat(path, fn); + + QImage image = this->grab(); + image.rgbSwapped().save(path, "png"); + monitors[r_monitor_index].mon_screenshots--; + } + if (monitors[r_monitor_index].mon_screenshots_clipboard) { + QImage image = this->grab(); + image = image.rgbSwapped(); + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setImage(image, QClipboard::Clipboard); + monitors[r_monitor_index].mon_screenshots_clipboard--; + } + if (monitors[r_monitor_index].mon_screenshots_raw_clipboard) { + take_screenshot_clipboard_monitor(x, y, w, h, r_monitor_index); + } } uint32_t diff --git a/src/qt_resources.qrc b/src/qt_resources.qrc index dda79c0b3..4b069cb3f 100644 --- a/src/qt_resources.qrc +++ b/src/qt_resources.qrc @@ -71,6 +71,10 @@ qt/icons/interpreter.ico qt/icons/recompiler.ico qt/icons/new_vm.ico + qt/icons/take_screenshot.ico + qt/icons/take_raw_screenshot.ico + qt/icons/copy_screenshot.ico + qt/icons/copy_raw_screenshot.ico qt/icons/warning.ico diff --git a/src/sound/CMakeLists.txt b/src/sound/CMakeLists.txt index 2c039fa92..3f6b1ef62 100644 --- a/src/sound/CMakeLists.txt +++ b/src/sound/CMakeLists.txt @@ -54,6 +54,7 @@ add_library(snd OBJECT snd_opl_esfm.c snd_ymf701.c snd_ymf71x.c + sound_util.c ) # TODO: Should platform-specific audio driver be here? diff --git a/src/sound/audio4.c b/src/sound/audio4.c index 060e574e6..48b0a6ce1 100644 --- a/src/sound/audio4.c +++ b/src/sound/audio4.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -37,17 +38,18 @@ #define I_WT 2 #define I_CD 3 #define I_FDD 4 -#define I_MIDI 5 +#define I_HDD 5 +#define I_MIDI 6 -static int audio[6] = {-1, -1, -1, -1, -1, -1}; +static int audio[7] = {-1, -1, -1, -1, -1, -1, -1}; +extern bool fast_forward; #ifdef USE_NEW_API -static struct audio_swpar info[5]; +static struct audio_swpar info[7]; #else -static audio_info_t info[6]; +static audio_info_t info[7]; #endif -static int freqs[6] = {SOUND_FREQ, MUSIC_FREQ, WT_FREQ, CD_FREQ, SOUND_FREQ, 0}; - +static int freqs[7] = {SOUND_FREQ, MUSIC_FREQ, WT_FREQ, CD_FREQ, SOUND_FREQ, SOUND_FREQ, 0}; void closeal(void) { @@ -104,7 +106,7 @@ givealbuffer_common(const void *buf, const uint8_t src, const int size) double gain; int target_rate; - if(audio[src] == -1) + if(audio[src] == -1 || fast_forward) return; gain = sound_muted ? 0.0 : pow(10.0, (double) sound_gain / 20.0); @@ -173,6 +175,12 @@ givealbuffer_fdd(const void *buf, const uint32_t size) givealbuffer_common(buf, I_FDD, (int) size); } +void +givealbuffer_hdd(const void *buf, const uint32_t size) +{ + givealbuffer_common(buf, I_HDD, (int) size); +} + void givealbuffer_midi(const void *buf, const uint32_t size) { diff --git a/src/sound/openal.c b/src/sound/openal.c index d163150af..399e4ac8e 100644 --- a/src/sound/openal.c +++ b/src/sound/openal.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #undef AL_API #undef ALC_API @@ -40,15 +41,17 @@ #define I_WT 2 #define I_CD 3 #define I_FDD 4 -#define I_MIDI 5 +#define I_HDD 5 +#define I_MIDI 6 ALuint buffers[4]; /* front and back buffers */ ALuint buffers_music[4]; /* front and back buffers */ ALuint buffers_wt[4]; /* front and back buffers */ ALuint buffers_cd[4]; /* front and back buffers */ ALuint buffers_fdd[4]; /* front and back buffers */ +ALuint buffers_hdd[4]; /* front and back buffers */ ALuint buffers_midi[4]; /* front and back buffers */ -static ALuint source[6]; /* audio source - CHANGED FROM 5 TO 6 */ +static ALuint source[7]; /* audio sources */ static int midi_freq = 44100; static int midi_buf_size = 4410; @@ -105,9 +108,10 @@ closeal(void) alSourceStopv(sources, source); alDeleteSources(sources, source); - if (sources >= 6) + if (sources >= 7) alDeleteBuffers(4, buffers_midi); alDeleteBuffers(4, buffers_fdd); + alDeleteBuffers(4, buffers_hdd); alDeleteBuffers(4, buffers_cd); alDeleteBuffers(4, buffers_music); alDeleteBuffers(4, buffers); @@ -126,12 +130,14 @@ inital(void) float *cd_buf = NULL; float *midi_buf = NULL; float *fdd_buf = NULL; + float *hdd_buf = NULL; int16_t *buf_int16 = NULL; int16_t *music_buf_int16 = NULL; int16_t *wt_buf_int16 = NULL; int16_t *cd_buf_int16 = NULL; int16_t *midi_buf_int16 = NULL; int16_t *fdd_buf_int16 = NULL; + int16_t *hdd_buf_int16 = NULL; int init_midi = 0; @@ -146,13 +152,14 @@ inital(void) init_midi = 1; /* If the device is neither none, nor system MIDI, initialize the MIDI buffer and source, otherwise, do not. */ - sources = 5 + !!init_midi; + sources = 6 + !!init_midi; if (sound_is_float) { buf = (float *) calloc((BUFLEN << 1), sizeof(float)); music_buf = (float *) calloc((MUSICBUFLEN << 1), sizeof(float)); wt_buf = (float *) calloc((WTBUFLEN << 1), sizeof(float)); cd_buf = (float *) calloc((CD_BUFLEN << 1), sizeof(float)); fdd_buf = (float *) calloc((BUFLEN << 1), sizeof(float)); + hdd_buf = (float *) calloc((BUFLEN << 1), sizeof(float)); if (init_midi) midi_buf = (float *) calloc(midi_buf_size, sizeof(float)); } else { @@ -161,6 +168,7 @@ inital(void) wt_buf_int16 = (int16_t *) calloc((WTBUFLEN << 1), sizeof(int16_t)); cd_buf_int16 = (int16_t *) calloc((CD_BUFLEN << 1), sizeof(int16_t)); fdd_buf_int16 = (int16_t *) calloc((BUFLEN << 1), sizeof(int16_t)); + hdd_buf_int16 = (int16_t *) calloc((BUFLEN << 1), sizeof(int16_t)); if (init_midi) midi_buf_int16 = (int16_t *) calloc(midi_buf_size, sizeof(int16_t)); } @@ -168,18 +176,17 @@ inital(void) alGenBuffers(4, buffers); alGenBuffers(4, buffers_cd); alGenBuffers(4, buffers_fdd); + alGenBuffers(4, buffers_hdd); alGenBuffers(4, buffers_music); alGenBuffers(4, buffers_wt); if (init_midi) alGenBuffers(4, buffers_midi); - // Create sources: 0=main, 1=music, 2=wt, 3=cd, 4=fdd, 5=midi(optional) - alGenSources(sources, source); - + // Create sources: 0=main, 1=music, 2=wt, 3=cd, 4=fdd, 5=hdd, 6=midi(optional) if (init_midi) - alGenSources(5, source); + alGenSources(7, source); else - alGenSources(4, source); + alGenSources(6, source); alSource3f(source[I_NORMAL], AL_POSITION, 0.0f, 0.0f, 0.0f); alSource3f(source[I_NORMAL], AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -210,7 +217,13 @@ inital(void) alSource3f(source[I_FDD], AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSourcef(source[I_FDD], AL_ROLLOFF_FACTOR, 0.0f); alSourcei(source[I_FDD], AL_SOURCE_RELATIVE, AL_TRUE); - + + alSource3f(source[I_HDD], AL_POSITION, 0.0f, 0.0f, 0.0f); + alSource3f(source[I_HDD], AL_VELOCITY, 0.0f, 0.0f, 0.0f); + alSource3f(source[I_HDD], AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSourcef(source[I_HDD], AL_ROLLOFF_FACTOR, 0.0f); + alSourcei(source[I_HDD], AL_SOURCE_RELATIVE, AL_TRUE); + if (init_midi) { alSource3f(source[I_MIDI], AL_POSITION, 0.0f, 0.0f, 0.0f); alSource3f(source[I_MIDI], AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -225,6 +238,7 @@ inital(void) memset(music_buf, 0, MUSICBUFLEN * 2 * sizeof(float)); memset(wt_buf, 0, WTBUFLEN * 2 * sizeof(float)); memset(fdd_buf, 0, BUFLEN * 2 * sizeof(float)); + memset(hdd_buf, 0, BUFLEN * 2 * sizeof(float)); if (init_midi) memset(midi_buf, 0, midi_buf_size * sizeof(float)); } else { @@ -233,6 +247,7 @@ inital(void) memset(music_buf_int16, 0, MUSICBUFLEN * 2 * sizeof(int16_t)); memset(wt_buf_int16, 0, WTBUFLEN * 2 * sizeof(int16_t)); memset(fdd_buf_int16, 0, BUFLEN * 2 * sizeof(int16_t)); + memset(hdd_buf_int16, 0, BUFLEN * 2 * sizeof(int16_t)); if (init_midi) memset(midi_buf_int16, 0, midi_buf_size * sizeof(int16_t)); } @@ -244,6 +259,7 @@ inital(void) alBufferData(buffers_wt[c], AL_FORMAT_STEREO_FLOAT32, wt_buf, WTBUFLEN * 2 * sizeof(float), WT_FREQ); alBufferData(buffers_cd[c], AL_FORMAT_STEREO_FLOAT32, cd_buf, CD_BUFLEN * 2 * sizeof(float), CD_FREQ); alBufferData(buffers_fdd[c], AL_FORMAT_STEREO_FLOAT32, fdd_buf, BUFLEN * 2 * sizeof(float), FREQ); + alBufferData(buffers_hdd[c], AL_FORMAT_STEREO_FLOAT32, hdd_buf, BUFLEN * 2 * sizeof(float), FREQ); if (init_midi) alBufferData(buffers_midi[c], AL_FORMAT_STEREO_FLOAT32, midi_buf, midi_buf_size * (int) sizeof(float), midi_freq); } else { @@ -252,6 +268,7 @@ inital(void) alBufferData(buffers_wt[c], AL_FORMAT_STEREO16, wt_buf_int16, WTBUFLEN * 2 * sizeof(int16_t), WT_FREQ); alBufferData(buffers_cd[c], AL_FORMAT_STEREO16, cd_buf_int16, CD_BUFLEN * 2 * sizeof(int16_t), CD_FREQ); alBufferData(buffers_fdd[c], AL_FORMAT_STEREO16, fdd_buf_int16, BUFLEN * 2 * sizeof(int16_t), FREQ); + alBufferData(buffers_hdd[c], AL_FORMAT_STEREO16, hdd_buf_int16, BUFLEN * 2 * sizeof(int16_t), FREQ); if (init_midi) alBufferData(buffers_midi[c], AL_FORMAT_STEREO16, midi_buf_int16, midi_buf_size * (int) sizeof(int16_t), midi_freq); } @@ -262,6 +279,7 @@ inital(void) alSourceQueueBuffers(source[I_WT], 4, buffers_wt); alSourceQueueBuffers(source[I_CD], 4, buffers_cd); alSourceQueueBuffers(source[I_FDD], 4, buffers_fdd); + alSourceQueueBuffers(source[I_HDD], 4, buffers_hdd); if (init_midi) alSourceQueueBuffers(source[I_MIDI], 4, buffers_midi); alSourcePlay(source[I_NORMAL]); @@ -269,6 +287,7 @@ inital(void) alSourcePlay(source[I_WT]); alSourcePlay(source[I_CD]); alSourcePlay(source[I_FDD]); + alSourcePlay(source[I_HDD]); if (init_midi) alSourcePlay(source[I_MIDI]); @@ -280,6 +299,7 @@ inital(void) free(music_buf); free(buf); free(fdd_buf); + free(hdd_buf); } else { if (init_midi) free(midi_buf_int16); @@ -288,11 +308,13 @@ inital(void) free(music_buf_int16); free(buf_int16); free(fdd_buf_int16); + free(hdd_buf_int16); } initialized = 1; } +extern bool fast_forward; void givealbuffer_common(const void *buf, const uint8_t src, const int size, const int freq) { @@ -300,7 +322,7 @@ givealbuffer_common(const void *buf, const uint8_t src, const int size, const in int state; ALuint buffer; - if (!initialized) + if (!initialized || fast_forward) return; alGetSourcei(source[src], AL_SOURCE_STATE, &state); @@ -311,7 +333,7 @@ givealbuffer_common(const void *buf, const uint8_t src, const int size, const in alGetSourcei(source[src], AL_BUFFERS_PROCESSED, &processed); if (processed >= 1) { - const double gain = sound_muted ? 0.0 : pow(10.0, (double) sound_gain / 20.0); + const double gain = (sound_muted) ? 0.0 : pow(10.0, (double) sound_gain / 20.0); alListenerf(AL_GAIN, (float) gain); alSourceUnqueueBuffers(source[src], 1, &buffer); @@ -328,35 +350,41 @@ givealbuffer_common(const void *buf, const uint8_t src, const int size, const in void givealbuffer(const void *buf) { - givealbuffer_common(buf, 0, BUFLEN << 1, FREQ); + givealbuffer_common(buf, I_NORMAL, BUFLEN << 1, FREQ); } void givealbuffer_music(const void *buf) { - givealbuffer_common(buf, 1, MUSICBUFLEN << 1, MUSIC_FREQ); + givealbuffer_common(buf, I_MUSIC, MUSICBUFLEN << 1, MUSIC_FREQ); } void givealbuffer_wt(const void *buf) { - givealbuffer_common(buf, 2, WTBUFLEN << 1, WT_FREQ); + givealbuffer_common(buf, I_WT, WTBUFLEN << 1, WT_FREQ); } void givealbuffer_cd(const void *buf) { - givealbuffer_common(buf, 3, CD_BUFLEN << 1, CD_FREQ); + givealbuffer_common(buf, I_CD, CD_BUFLEN << 1, CD_FREQ); } void givealbuffer_midi(const void *buf, const uint32_t size) { - givealbuffer_common(buf, 5, (int) size, midi_freq); + givealbuffer_common(buf, I_MIDI, (int) size, midi_freq); } void givealbuffer_fdd(const void *buf, const uint32_t size) { - givealbuffer_common(buf, 4, (int) size, FREQ); + givealbuffer_common(buf, I_FDD, (int) size, FREQ); +} + +void +givealbuffer_hdd(const void *buf, const uint32_t size) +{ + givealbuffer_common(buf, I_HDD, (int) size, FREQ); } \ No newline at end of file diff --git a/src/sound/snd_ad1816.c b/src/sound/snd_ad1816.c index 2ba79fc4c..4e929794d 100644 --- a/src/sound/snd_ad1816.c +++ b/src/sound/snd_ad1816.c @@ -129,12 +129,12 @@ ad1816_update_mastervol(void *priv) if (ad1816->iregs[14] & 0x8000) ad1816->master_l = 0; else - ad1816->master_l = ad1816_vols_5bits[(ad1816->iregs[14] >> 8) & 0x1f] / 65536.0; + ad1816->master_l = ad1816_vols_5bits[((ad1816->iregs[14] >> 8) & 0x1f) >> 1] / 65536.0; if (ad1816->iregs[14] & 0x0080) ad1816->master_r = 0; else - ad1816->master_r = ad1816_vols_5bits[(ad1816->iregs[14]) & 0x1f] / 65536.0; + ad1816->master_r = ad1816_vols_5bits[((ad1816->iregs[14]) & 0x1f) >> 1] / 65536.0; } void @@ -327,17 +327,16 @@ ad1816_poll(void *priv) if (ad1816->iregs[4] & 0x8000) ad1816->out_l = 0; else - ad1816->out_l = (int16_t) ((ad1816->out_l * ad1816_vols_6bits[(ad1816->iregs[4] >> 8) & 0x3f]) >> 16); + ad1816->out_l = (int16_t) ((ad1816->out_l * ad1816_vols_6bits[((ad1816->iregs[4] >> 8) & 0x3f) >> 1]) >> 16); if (ad1816->iregs[4] & 0x0080) ad1816->out_r = 0; else - ad1816->out_r = (int16_t) ((ad1816->out_r * ad1816_vols_6bits[ad1816->iregs[4] & 0x3f]) >> 16); + ad1816->out_r = (int16_t) ((ad1816->out_r * ad1816_vols_6bits[(ad1816->iregs[4] & 0x3f) >> 1]) >> 16); if (ad1816->count < 0) { ad1816->count = ad1816->iregs[8]; ad1816->regs[1] |= 0x80; - ad1816->playback_pos = 0; if (ad1816->iregs[1] & 0x8000) { ad1816_log(ad1816->log, "AD1816 Playback interrupt fired\n"); picint(1 << ad1816->cur_irq); diff --git a/src/sound/snd_azt2316a.c b/src/sound/snd_azt2316a.c index 47cf0296b..08fca0f87 100644 --- a/src/sound/snd_azt2316a.c +++ b/src/sound/snd_azt2316a.c @@ -1,4 +1,25 @@ /* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * This file is part of the 86Box distribution. + * + * Aztech Sound Galaxy series audio controller emulation. + * + * Authors: Cacodemon345 + * Eluan Costa Miranda + * win2kgamer + * + * Copyright 2022 Cacodemon345. + * Copyright 2020 Eluan Costa Miranda. + * Copyright 2025-2026 win2kgamer + */ + +/* + * Original header and notes: + * * TYPE 0x11: (Washington) * Aztech MMPRO16AB, * Aztech Sound Galaxy Pro 16 AB @@ -66,7 +87,7 @@ * to call the original functions, except for initialization. * * TODO/Notes: - * -Some stuff still not understood on I/O addresses 0x624 and 0x530-0x533. + * -Some stuff still not understood on I/O address 0x624. * -Is the CS42xx dither mode used anywhere? Implement. * -What are the voice commands mode in Win95 mixer again? * -Configuration options not present on Aztech's CONFIG.EXE have been commented @@ -100,14 +121,6 @@ * this behaves like a card with a regular interface disabled by jumper and * the configuration just adds/removes the drivers (which will see other IDE * interfaces present) from the boot process. - * -Continue reverse engineering to see if the AZT1605 shares the SB DMA with - * WSS or if it is set separately by the TSR loaded on boot. Currently it is - * only set by PCem config and then saved to EEPROM (which would make it fixed - * if loading from EEPROM too). - * -Related to the previous note, part of the SBPROV2 emulation on the CLINTON - * family appears to be implemented with a TSR. It's better to remove the TSR - * for now. I need to investigate this. Mixer is also weird under DOS (and - * wants to save to EEPROM? I don't think the WASHINGTON does this). * -Search for TODO in this file. :-) * * Misc things I use to test for regressions: Windows sounds, Descent under @@ -147,6 +160,7 @@ #include <86box/nvr.h> #include <86box/pic.h> #include <86box/sound.h> +#include <86box/gameport.h> #include <86box/snd_ad1848.h> #include <86box/snd_azt2316a.h> #include <86box/snd_sb.h> @@ -159,7 +173,7 @@ int aztech_do_log = ENABLE_AZTECH_LOG; static void aztech_log(void *priv, const char *fmt, ...) { - if (aztech_log) { + if (aztech_do_log) { va_list ap; va_start(ap, fmt); log_out(priv, fmt, ap); @@ -187,6 +201,8 @@ static int azt2316a_wss_irq[8] = { 5, 7, 9, 10, 11, 12, 14, 15 }; /* W95 only us static uint16_t azt2316a_wss_addr[4] = {0x530, 0x604, 0xe80, 0xf40}; #endif +static double aztpr16_vols_5bits[32]; + typedef struct azt2316a_t { int type; @@ -200,8 +216,11 @@ typedef struct azt2316a_t { int cur_wss_enabled; int cur_wss_irq; int cur_wss_dma; + int cur_wss_dma16; /* AZTPR16 16-bit DMA */ int cur_mpu401_irq; int cur_mpu401_enabled; + int gameport_enabled; + void *gameport; uint32_t config_word; uint32_t config_word_unlocked; @@ -216,6 +235,24 @@ typedef struct azt2316a_t { void * log; /* New logging system */ } azt2316a_t; +void +aztpr16_update_mixer(void *priv) +{ + azt2316a_t *azt2316a = (azt2316a_t *) priv; + + aztech_log(azt2316a->log, "Aztech AZTPR16 Mixer update\n"); + + azt2316a->ad1848.regs[2] = ((~azt2316a->sb->mixer_sbpro.regs[0xa0]) & 0x1f); /* CD L */ + azt2316a->ad1848.cd_vol_l = aztpr16_vols_5bits[azt2316a->ad1848.regs[2] & 0x1f]; + azt2316a->ad1848.regs[3] = ((~azt2316a->sb->mixer_sbpro.regs[0xa2]) & 0x1f); /* CD R */ + azt2316a->ad1848.cd_vol_r = aztpr16_vols_5bits[azt2316a->ad1848.regs[3] & 0x1f]; + azt2316a->ad1848.regs[4] = ((~azt2316a->sb->mixer_sbpro.regs[0x8c]) & 0x1f); /* FM L */ + azt2316a->ad1848.regs[5] = ((~azt2316a->sb->mixer_sbpro.regs[0x8e]) & 0x1f); /* FM R */ + azt2316a->ad1848.regs[6] = (((~azt2316a->sb->mixer_sbpro.regs[0x84]) & 0x3f) >> 1); /* Master L */ + azt2316a->ad1848.regs[7] = (((~azt2316a->sb->mixer_sbpro.regs[0x86]) & 0x3f) >> 1); /* Master R */ + +} + static void azt1605_filter_opl(void *priv, double *out_l, double *out_r) { @@ -230,9 +267,6 @@ azt2316a_wss_read(uint16_t addr, void *priv) const azt2316a_t *azt2316a = (azt2316a_t *) priv; uint8_t temp; - /* TODO: when windows is initializing, writing 0x48, 0x58 and 0x60 to - 0x530 makes reading from 0x533 return 0x44, but writing 0x50 - makes this return 0x04. Why? */ if (addr & 1) temp = 4 | (azt2316a->wss_config & 0x40); else @@ -274,6 +308,190 @@ azt2316a_wss_write(uint16_t addr, uint8_t val, void *priv) } /* generate a config word based on current settings */ +static void +aztpr16_create_config_word(void *priv) +{ + azt2316a_t *azt2316a = (azt2316a_t *) priv; + uint32_t temp = 0; + + /* not implemented / hardcoded */ + uint8_t cd_type = 1; + uint8_t cd_irq = 0; + + switch (azt2316a->cur_addr) { + case 0x220: + // do nothing + break; + case 0x240: + temp += 1 << 0; + break; + case 0x260: + temp += 2 << 0; + break; + case 0x280: + temp += 3 << 0; + break; + default: + break; + } + + switch (azt2316a->cur_irq) { + case 2: + temp += 1 << 8; + break; + case 5: + temp += 1 << 9; + break; + case 7: + temp += 1 << 10; + break; + case 10: + temp += 1 << 11; + break; + + default: + break; + } + + switch (azt2316a->cur_wss_addr) { + case 0x530: + // do nothing + break; + case 0x604: + temp += 1 << 16; + break; + case 0xE80: + temp += 2 << 16; + break; + case 0xF40: + temp += 3 << 16; + break; + + default: + break; + } + + if (azt2316a->cur_wss_enabled) + temp += 1 << 18; + + if (azt2316a->gameport_enabled) + temp += 1 << 4; + + switch (azt2316a->cur_mpu401_addr) { + case 0x300: + // do nothing + break; + case 0x330: + temp += 1 << 2; + break; + + default: + break; + } + + if (azt2316a->cur_mpu401_enabled) + temp += 1 << 3; + + switch (cd_type) { + case 0: // disabled + //do nothing + temp += 0 << 5; + break; + case 1: // panasonic + temp += 1 << 5; + break; + case 2: // mitsumi/sony/aztech + temp += 2 << 5; + break; + case 3: // all enabled + temp += 3 << 5; + break; + case 4: // unused + temp += 4 << 5; + break; + case 5: // unused + temp += 5 << 5; + break; + case 6: // unused + temp += 6 << 5; + break; + case 7: // unused + temp += 7 << 5; + break; + + default: + break; + } + + switch (azt2316a->cur_dma) { + case 0: + temp += 1 << 24; + break; + case 1: + temp += 1 << 25; + break; + case 3: + temp += 1 << 26; + break; + default: + break; + } + + switch (azt2316a->cur_wss_dma16) { + case 5: + temp += 1 << 27; + break; + case 6: + temp += 1 << 28; + break; + case 7: + temp += 1 << 29; + break; + default: + break; + } + + switch (azt2316a->cur_mpu401_irq) { + case 2: + temp += 1 << 12; + break; + case 5: + temp += 1 << 13; + break; + case 7: + temp += 1 << 14; + break; + case 10: + temp += 1 << 15; + break; + + default: + break; + } + + switch (cd_irq) { + case 0: // disabled + // do nothing + break; + case 11: + temp += 1 << 20; + break; + case 12: + temp += 1 << 21; + break; + case 15: + temp += 1 << 22; + break; + + default: + break; + } + + azt2316a->config_word = temp; + aztech_log(azt2316a->log, "Aztech PR16 Config Word Create: %08X\n", temp); +} + + static void azt1605_create_config_word(void *priv) { @@ -281,7 +499,6 @@ azt1605_create_config_word(void *priv) uint32_t temp = 0; /* not implemented / hardcoded */ - uint8_t game_enable = 1; uint8_t cd_type = 0; /* TODO: see if the cd-rom was originally connected there on the real machines emulated by 86Box (Packard Bell Legend 100CD, Itautec Infoway Multimidia, etc) */ uint8_t cd_dma8 = -1; uint8_t cd_irq = 0; @@ -289,21 +506,10 @@ azt1605_create_config_word(void *priv) switch (azt2316a->cur_addr) { case 0x220: // do nothing -#if 0 - temp += 0 << 0; -#endif break; case 0x240: temp += 1 << 0; break; -#if 0 - case 0x260: // TODO: INVALID? - temp += 2 << 0; - break; - case 0x280: // TODO: INVALID? - temp += 3 << 0; - break; -#endif default: break; } @@ -329,9 +535,6 @@ azt1605_create_config_word(void *priv) switch (azt2316a->cur_wss_addr) { case 0x530: // do nothing -#if 0 - temp += 0 << 16; -#endif break; case 0x604: temp += 1 << 16; @@ -350,15 +553,12 @@ azt1605_create_config_word(void *priv) if (azt2316a->cur_wss_enabled) temp += 1 << 18; - if (game_enable) + if (azt2316a->gameport_enabled) temp += 1 << 4; switch (azt2316a->cur_mpu401_addr) { case 0x300: // do nothing -#if 0 - temp += 0 << 2; -#endif break; case 0x330: temp += 1 << 2; @@ -372,9 +572,8 @@ azt1605_create_config_word(void *priv) temp += 1 << 3; switch (cd_type) { - case 0: /* disabled - do nothing - temp += 0 << 5; */ + case 0: // disabled + // do nothing break; case 1: // panasonic temp += 1 << 5; @@ -403,9 +602,8 @@ azt1605_create_config_word(void *priv) } switch (cd_dma8) { - case 0xFF: /* -1 - do nothing - temp += 0 << 22;*/ + case 0xFF: // -1 + //do nothing break; case 0: temp += 1 << 22; @@ -468,7 +666,6 @@ azt2316a_create_config_word(void *priv) uint32_t temp = 0; /* not implemented / hardcoded */ - uint8_t game_enable = 1; uint16_t cd_addr = 0x310; uint8_t cd_type = 0; /* TODO: see if the cd-rom was originally connected there on the real machines emulated by 86Box (Packard Bell Legend 100CD, Itautec Infoway Multimidia, etc) */ uint8_t cd_dma8 = -1; @@ -480,24 +677,18 @@ azt2316a_create_config_word(void *priv) return; } + if (azt2316a->type == SB_SUBTYPE_CLONE_AZTPR16_0X09) { + aztpr16_create_config_word(priv); + return; + } + switch (azt2316a->cur_addr) { case 0x220: // do nothing -#if 0 - temp += 0 << 0; -#endif break; case 0x240: temp += 1 << 0; break; -#if 0 - case 0x260: // TODO: INVALID? - temp += 2 << 0; - break; - case 0x280: // TODO: INVALID? - temp += 3 << 0; - break; -#endif default: break; } @@ -521,13 +712,6 @@ azt2316a_create_config_word(void *priv) } switch (azt2316a->cur_dma) { -#if 0 - // TODO: INVALID? - case 0xFF: // -1 - // do nothing - //temp += 0 << 6; - break; -#endif case 0: temp += 1 << 6; break; @@ -545,9 +729,6 @@ azt2316a_create_config_word(void *priv) switch (azt2316a->cur_wss_addr) { case 0x530: // do nothing -#if 0 - temp += 0 << 8; -#endif break; case 0x604: temp += 1 << 8; @@ -564,14 +745,11 @@ azt2316a_create_config_word(void *priv) } if (azt2316a->cur_wss_enabled) temp += 1 << 10; - if (game_enable) + if (azt2316a->gameport_enabled) temp += 1 << 11; switch (azt2316a->cur_mpu401_addr) { case 0x300: // do nothing -#if 0 - temp += 0 << 12; -#endif break; case 0x330: temp += 1 << 12; @@ -587,9 +765,6 @@ azt2316a_create_config_word(void *priv) switch (cd_addr) { case 0x310: // do nothing -#if 0 - temp += 0 << 14; -#endif break; case 0x320: temp += 1 << 14; @@ -607,9 +782,6 @@ azt2316a_create_config_word(void *priv) switch (cd_type) { case 0: /* disabled */ // do nothing -#if 0 - temp += 0 << 16; */ -#endif break; case 1: /* panasonic */ temp += 1 << 16; @@ -638,18 +810,15 @@ azt2316a_create_config_word(void *priv) } switch (cd_dma8) { - case 0xFF: /* -1 - do nothing - temp += 0 << 20; */ + case 0xFF: // -1 + //do nothing break; case 0: temp += 1 << 20; break; -#if 0 - case 1: // TODO: INVALID? + case 1: temp += 2 << 20; break; -#endif case 3: temp += 3 << 20; break; @@ -659,9 +828,8 @@ azt2316a_create_config_word(void *priv) } switch (cd_dma16) { - case 0xFF: /* -1 - do nothing - temp += 0 << 22; */ + case 0xFF: // -1 + //do nothing break; case 5: temp += 1 << 22; @@ -826,6 +994,141 @@ azt2316a_config_read(uint16_t addr, void *priv) return temp; } +static void +aztpr16_config_write(uint16_t addr, uint8_t val, void *priv) +{ + azt2316a_t *azt2316a = (azt2316a_t *) priv; + uint8_t temp; + + aztech_log(azt2316a->log, "Aztech PR16 Config Word Write: (%04X) = %02X\n", addr, val); + + if (addr == (azt2316a->cur_addr + 0x404)) { + if (val & 0x80) + azt2316a->config_word_unlocked = 1; + else + azt2316a->config_word_unlocked = 0; + } else if (azt2316a->config_word_unlocked) { + if (val == 0xFF) { + return; + } + switch (addr & 3) { + case 0: + azt2316a->config_word = (azt2316a->config_word & 0xFFFFFF00) | val; + + temp = val & 3; + if (temp == 0) + azt2316a->cur_addr = 0x220; + else if (temp == 1) + azt2316a->cur_addr = 0x240; + else if (temp == 2) + azt2316a->cur_addr = 0x260; + else if (temp == 3) + azt2316a->cur_addr = 0x280; + + if (val & 0x4) + azt2316a->cur_mpu401_addr = 0x330; + else + azt2316a->cur_mpu401_addr = 0x300; + + if (val & 0x8) + azt2316a->cur_mpu401_enabled = 1; + else + azt2316a->cur_mpu401_enabled = 0; + + if (val & 0x10) + azt2316a->gameport_enabled = 1; + else + azt2316a->gameport_enabled = 0; + break; + case 1: + azt2316a->config_word = (azt2316a->config_word & 0xFFFF00FF) | (val << 8); + + if (val & 0x1) + azt2316a->cur_irq = 2; + else if (val & 0x2) + azt2316a->cur_irq = 5; + else if (val & 0x4) + azt2316a->cur_irq = 7; + else if (val & 0x8) + azt2316a->cur_irq = 10; + /* else undefined? */ + + if (val & 0x10) + azt2316a->cur_mpu401_irq = 2; + else if (val & 0x20) + azt2316a->cur_mpu401_irq = 5; + else if (val & 0x40) + azt2316a->cur_mpu401_irq = 7; + else if (val & 0x80) + azt2316a->cur_mpu401_irq = 10; + /* else undefined? */ + break; + case 2: + azt2316a->config_word = (azt2316a->config_word & 0xFF00FFFF) | (val << 16); + + io_removehandler(azt2316a->cur_wss_addr, 0x0004, azt2316a_wss_read, NULL, NULL, azt2316a_wss_write, NULL, NULL, azt2316a); + io_removehandler(azt2316a->cur_wss_addr + 0x0004, 0x0004, ad1848_read, NULL, NULL, ad1848_write, NULL, NULL, &azt2316a->ad1848); + + temp = val & 0x3; + if (temp == 0) + azt2316a->cur_wss_addr = 0x530; + else if (temp == 1) + azt2316a->cur_wss_addr = 0x604; + else if (temp == 2) + azt2316a->cur_wss_addr = 0xE80; + else if (temp == 3) + azt2316a->cur_wss_addr = 0xF40; + + io_sethandler(azt2316a->cur_wss_addr, 0x0004, azt2316a_wss_read, NULL, NULL, azt2316a_wss_write, NULL, NULL, azt2316a); + io_sethandler(azt2316a->cur_wss_addr + 0x0004, 0x0004, ad1848_read, NULL, NULL, ad1848_write, NULL, NULL, &azt2316a->ad1848); + + /* no actual effect */ + if (val & 0x4) + azt2316a->cur_wss_enabled = 1; + else + azt2316a->cur_wss_enabled = 0; + break; + case 3: + azt2316a->config_word = (azt2316a->config_word & 0x00FFFFFF) | (val << 24); + + if (val & 0x01) { + azt2316a->cur_dma = 0; + azt2316a->cur_wss_dma = 0; + } else if (val & 0x02) { + azt2316a->cur_dma = 1; + azt2316a->cur_wss_dma = 1; + } else if (val & 0x04) { + azt2316a->cur_dma = 3; + azt2316a->cur_wss_dma = 3; + } + + if (val & 0x08) + azt2316a->cur_wss_dma16 = 5; + else if (val & 0x10) + azt2316a->cur_wss_dma16 = 6; + else if (val & 0x20) + azt2316a->cur_wss_dma16 = 7; + break; + + default: + break; + } + /* update sbprov2 configs */ + sb_dsp_setaddr(&azt2316a->sb->dsp, azt2316a->cur_addr); + sb_dsp_setirq(&azt2316a->sb->dsp, azt2316a->cur_irq); + sb_dsp_setdma8(&azt2316a->sb->dsp, azt2316a->cur_dma); + + mpu401_change_addr(azt2316a->mpu, azt2316a->cur_mpu401_addr); + mpu401_setirq(azt2316a->mpu, azt2316a->cur_mpu401_irq); + + ad1848_setdma(&azt2316a->ad1848, azt2316a->cur_wss_dma); + ad1848_setirq(&azt2316a->ad1848, azt2316a->cur_irq); + azt2316a->cur_wss_irq = azt2316a->cur_irq; + + gameport_remap(azt2316a->gameport, (azt2316a->gameport_enabled) ? 0x200 : 0x00); + } +} + static void azt1605_config_write(uint16_t addr, uint8_t val, void *priv) { @@ -840,7 +1143,7 @@ azt1605_config_write(uint16_t addr, uint8_t val, void *priv) else azt2316a->config_word_unlocked = 0; } else if (azt2316a->config_word_unlocked) { - if (val == 0xFF) { /* TODO: check if this still happens on eeprom.sys after having more complete emulation! */ + if (val == 0xFF) { return; } switch (addr & 3) { @@ -852,12 +1155,7 @@ azt1605_config_write(uint16_t addr, uint8_t val, void *priv) azt2316a->cur_addr = 0x220; else if (temp == 1) azt2316a->cur_addr = 0x240; -#if 0 - else if (temp == 2) - azt2316a->cur_addr = 0x260; // TODO: INVALID - else if (temp == 3) - azt2316a->cur_addr = 0x280; // TODO: INVALID -#endif + if (val & 0x4) azt2316a->cur_mpu401_addr = 0x330; else @@ -867,6 +1165,11 @@ azt1605_config_write(uint16_t addr, uint8_t val, void *priv) azt2316a->cur_mpu401_enabled = 1; else azt2316a->cur_mpu401_enabled = 0; + + if (val & 0x10) + azt2316a->gameport_enabled = 1; + else + azt2316a->gameport_enabled = 0; break; case 1: azt2316a->config_word = (azt2316a->config_word & 0xFFFF00FF) | (val << 8); @@ -929,6 +1232,8 @@ azt1605_config_write(uint16_t addr, uint8_t val, void *priv) mpu401_change_addr(azt2316a->mpu, azt2316a->cur_mpu401_addr); mpu401_setirq(azt2316a->mpu, azt2316a->cur_mpu401_irq); + + gameport_remap(azt2316a->gameport, (azt2316a->gameport_enabled) ? 0x200 : 0x00); } } @@ -1000,6 +1305,11 @@ azt2316a_config_write(uint16_t addr, uint8_t val, void *priv) else azt2316a->cur_wss_enabled = 0; + if (val & 0x8) + azt2316a->gameport_enabled = 1; + else + azt2316a->gameport_enabled = 0; + if (val & 0x10) azt2316a->cur_mpu401_addr = 0x330; else @@ -1037,6 +1347,8 @@ azt2316a_config_write(uint16_t addr, uint8_t val, void *priv) mpu401_change_addr(azt2316a->mpu, azt2316a->cur_mpu401_addr); mpu401_setirq(azt2316a->mpu, azt2316a->cur_mpu401_irq); + + gameport_remap(azt2316a->gameport, (azt2316a->gameport_enabled) ? 0x200 : 0x00); } } @@ -1062,6 +1374,33 @@ azt2316a_enable_wss(uint8_t enable, void *priv) } } +void +aztpr16_wss_mode(uint8_t mode, void *priv) +{ + azt2316a_t *azt2316a = (azt2316a_t *) priv; + + sound_set_cd_audio_filter(NULL, NULL); + + if (mode) { + azt2316a->cur_mode = 1; + sound_set_cd_audio_filter(ad1848_filter_cd_audio, &azt2316a->ad1848); + azt2316a->sb->opl_mixer = azt2316a; + azt2316a->sb->opl_mix = azt1605_filter_opl; + } + else { + azt2316a->cur_mode = 0; + sound_set_cd_audio_filter(sbpro_filter_cd_audio, azt2316a->sb); + azt2316a->sb->opl_mixer = NULL; + azt2316a->sb->opl_mix = NULL; + } + + if (mode == 0x03) + ad1848_setdma(&azt2316a->ad1848, azt2316a->cur_wss_dma16); + else + ad1848_setdma(&azt2316a->ad1848, azt2316a->cur_wss_dma); + +} + static void azt2316a_get_buffer(int32_t *buffer, int len, void *priv) { @@ -1097,6 +1436,8 @@ azt_init(const device_t *info) fn = "azt1605.nvr"; } else if (azt2316a->type == SB_SUBTYPE_CLONE_AZT2316A_0X11) { fn = "azt2316a.nvr"; + } else if (azt2316a->type == SB_SUBTYPE_CLONE_AZTPR16_0X09) { + fn = "aztpr16.nvr"; } /* config */ @@ -1121,13 +1462,13 @@ azt_init(const device_t *info) if (!loaded_from_eeprom) { if (azt2316a->type == SB_SUBTYPE_CLONE_AZT2316A_0X11) { - read_eeprom[0] = 0x00; - read_eeprom[1] = 0x00; - read_eeprom[2] = 0x00; - read_eeprom[3] = 0x00; - read_eeprom[4] = 0x00; - read_eeprom[5] = 0x00; - read_eeprom[6] = 0x00; + read_eeprom[0] = 0xee; /* SB Voice mixer value */ + read_eeprom[1] = 0x00; /* SB Mic mixer value (bits 2-0) */ + read_eeprom[2] = 0x00; /* SB Record Source */ + read_eeprom[3] = 0xee; /* SB Master mixer value */ + read_eeprom[4] = 0xee; /* SB FM mixer value */ + read_eeprom[5] = 0xee; /* SB CD mixer value */ + read_eeprom[6] = 0x00; /* SB Line mixer value */ read_eeprom[7] = 0x00; read_eeprom[8] = 0x00; read_eeprom[9] = 0x00; @@ -1154,6 +1495,28 @@ azt_init(const device_t *info) read_eeprom[13] = 0x14; read_eeprom[14] = 0x04; read_eeprom[15] = 0xFF; /* SBPro Master volume (EMUTSR) */ + } else if (azt2316a->type == SB_SUBTYPE_CLONE_AZTPR16_0X09) { + read_eeprom[0] = 0x00; + read_eeprom[1] = 0x00; + read_eeprom[2] = 0x00; + read_eeprom[3] = 0x3f; /* Master Volume L */ + read_eeprom[4] = 0x3f; /* Master Volume R */ + read_eeprom[5] = 0x1f; /* Wave Volume L */ + read_eeprom[6] = 0x1f; /* Wave Volume R */ + read_eeprom[7] = 0x1f; /* FM Volume L */ + read_eeprom[8] = 0x1f; /* FM Volume R */ + read_eeprom[9] = 0x1f; /* CD Volume L */ + read_eeprom[10] = 0x1f; /* CD Volume R */ + read_eeprom[11] = 0x1f; /* Line Volume L */ + read_eeprom[12] = 0x1f; /* Line Volume R */ + read_eeprom[13] = 0x1f; /* Mic Volume L */ + read_eeprom[14] = 0x1f; /* Mic Volume R */ + read_eeprom[15] = 0x1f; /* WSynth Volume L */ + read_eeprom[16] = 0x1f; /* WSynth Volume R */ + read_eeprom[32] = 0x1c; + read_eeprom[33] = 0x12; + read_eeprom[34] = 0x04; + read_eeprom[35] = 0x02; } } @@ -1218,6 +1581,11 @@ azt_init(const device_t *info) else azt2316a->cur_wss_enabled = 0; + if (azt2316a->config_word & (1 << 11)) + azt2316a->gameport_enabled = 1; + else + azt2316a->gameport_enabled = 0; + if (azt2316a->config_word & (1 << 12)) azt2316a->cur_mpu401_addr = 0x330; else @@ -1267,6 +1635,11 @@ azt_init(const device_t *info) else azt2316a->cur_mpu401_enabled = 0; + if (azt2316a->config_word & (1 << 4)) + azt2316a->gameport_enabled = 1; + else + azt2316a->gameport_enabled = 0; + if (azt2316a->config_word & (1 << 8)) azt2316a->cur_irq = 9; else if (azt2316a->config_word & (1 << 9)) @@ -1312,18 +1685,122 @@ azt_init(const device_t *info) azt2316a->cur_wss_enabled = 0; // these are not present on the EEPROM - azt2316a->cur_dma = device_get_config_int("sb_dma8"); // TODO: investigate TSR to make this work with it - there is no software configurable DMA8? + azt2316a->cur_dma = device_get_config_int("sb_dma8"); azt2316a->cur_wss_irq = device_get_config_int("wss_irq"); azt2316a->cur_wss_dma = device_get_config_int("wss_dma"); azt2316a->cur_mode = 0; + } else if (azt2316a->type == SB_SUBTYPE_CLONE_AZTPR16_0X09) { + azt2316a->config_word = read_eeprom[32] + (read_eeprom[33] << 8) + (read_eeprom[34] << 16) + (read_eeprom[35] << 24); + aztech_log(azt2316a->log, "AZTPR16 Config Word = %08X\n", azt2316a->config_word); + + switch (azt2316a->config_word & (3 << 0)) { + case 0: + azt2316a->cur_addr = 0x220; + break; + case 1: + azt2316a->cur_addr = 0x240; + break; + case 2: + azt2316a->cur_addr = 0x260; + break; + case 3: + azt2316a->cur_addr = 0x280; + break; + default: + fatal("AZTPR16: invalid sb addr in config word %08X\n", azt2316a->config_word); + break; + } + + if (azt2316a->config_word & (1 << 2)) + azt2316a->cur_mpu401_addr = 0x330; + else + azt2316a->cur_mpu401_addr = 0x300; + + if (azt2316a->config_word & (1 << 3)) + azt2316a->cur_mpu401_enabled = 1; + else + azt2316a->cur_mpu401_enabled = 0; + + if (azt2316a->config_word & (1 << 4)) + azt2316a->gameport_enabled = 1; + else + azt2316a->gameport_enabled = 0; + + if (azt2316a->config_word & (1 << 8)) + azt2316a->cur_irq = 2; + else if (azt2316a->config_word & (1 << 9)) + azt2316a->cur_irq = 5; + else if (azt2316a->config_word & (1 << 10)) + azt2316a->cur_irq = 7; + else if (azt2316a->config_word & (1 << 11)) + azt2316a->cur_irq = 10; + else + fatal("AZTPR16: invalid sb irq in config word %08X\n", azt2316a->config_word); + + if (azt2316a->config_word & (1 << 12)) + azt2316a->cur_mpu401_irq = 2; + else if (azt2316a->config_word & (1 << 13)) + azt2316a->cur_mpu401_irq = 5; + else if (azt2316a->config_word & (1 << 14)) + azt2316a->cur_mpu401_irq = 7; + else if (azt2316a->config_word & (1 << 15)) + azt2316a->cur_mpu401_irq = 10; + else + fatal("AZTPR16: invalid mpu401 irq in config word %08X\n", azt2316a->config_word); + switch (azt2316a->config_word & (3 << 16)) { + case 0: + azt2316a->cur_wss_addr = 0x530; + break; + case 1 << 16: + azt2316a->cur_wss_addr = 0x604; + break; + case 2 << 16: + azt2316a->cur_wss_addr = 0xE80; + break; + case 3 << 16: + azt2316a->cur_wss_addr = 0xF40; + break; + default: + fatal("AZTPR16: invalid wss addr in config word %08X\n", azt2316a->config_word); + break; + } + + if (azt2316a->config_word & (1 << 18)) + azt2316a->cur_wss_enabled = 1; + else + azt2316a->cur_wss_enabled = 0; + + if (azt2316a->config_word & (1 << 24)) + azt2316a->cur_dma = 0; + else if (azt2316a->config_word & (1 << 25)) + azt2316a->cur_dma = 1; + else if (azt2316a->config_word & (1 << 26)) + azt2316a->cur_dma = 3; + + if (azt2316a->config_word & (1 << 27)) + azt2316a->cur_wss_dma16 = 5; + else if (azt2316a->config_word & (1 << 28)) + azt2316a->cur_wss_dma16 = 6; + else if (azt2316a->config_word & (1 << 29)) + azt2316a->cur_wss_dma16 = 7; + + // these are not present on the EEPROM + azt2316a->cur_wss_irq = azt2316a->cur_irq; + azt2316a->cur_wss_dma = azt2316a->cur_dma; + azt2316a->cur_mode = 0; } - addr_setting = device_get_config_hex16("addr"); - if (addr_setting) - azt2316a->cur_addr = addr_setting; + if (azt2316a->type != SB_SUBTYPE_CLONE_AZTPR16_0X09) { + addr_setting = device_get_config_hex16("addr"); + if (addr_setting) + azt2316a->cur_addr = addr_setting; + } /* wss part */ - ad1848_init(&azt2316a->ad1848, device_get_config_int("codec")); + if (azt2316a->type == SB_SUBTYPE_CLONE_AZTPR16_0X09) + ad1848_init(&azt2316a->ad1848, AD1848_TYPE_DEFAULT); /* AZTPR16 has an internal AD1848-compatible (non-Mode 2 capable) WSS codec */ + else + ad1848_init(&azt2316a->ad1848, device_get_config_int("codec")); if (azt2316a->type == SB_SUBTYPE_CLONE_AZT2316A_0X11) ad1848_set_cd_audio_channel(&azt2316a->ad1848, (device_get_config_int("codec") == AD1848_TYPE_CS4248) ? AD1848_AUX1 : AD1848_LINE_IN); else @@ -1334,6 +1811,8 @@ azt_init(const device_t *info) if (azt2316a->type == SB_SUBTYPE_CLONE_AZT2316A_0X11) io_sethandler(azt2316a->cur_addr + 0x0400, 0x0040, azt2316a_config_read, NULL, NULL, azt2316a_config_write, NULL, NULL, azt2316a); + else if (azt2316a->type == SB_SUBTYPE_CLONE_AZTPR16_0X09) + io_sethandler(azt2316a->cur_addr + 0x0400, 0x0010, azt1605_config_read, NULL, NULL, aztpr16_config_write, NULL, NULL, azt2316a); else /* Aztech 1605 only needs 62x/64x */ io_sethandler(azt2316a->cur_addr + 0x0400, 0x0010, azt1605_config_read, NULL, NULL, azt1605_config_write, NULL, NULL, azt2316a); io_sethandler(azt2316a->cur_wss_addr, 0x0004, azt2316a_wss_read, NULL, NULL, azt2316a_wss_write, NULL, NULL, azt2316a); @@ -1395,6 +1874,17 @@ azt_init(const device_t *info) if (device_get_config_int("receive_input")) midi_in_handler(1, sb_dsp_input_msg, sb_dsp_input_sysex, &azt2316a->sb->dsp); + /* Restore SBPro mixer settings from EEPROM on AZT2316A cards */ + if (azt2316a->type == SB_SUBTYPE_CLONE_AZT2316A_0X11) { + azt2316a->sb->mixer_sbpro.regs[0x04] = read_eeprom[0]; /* SBPro Voice */ + azt2316a->sb->mixer_sbpro.regs[0x0a] = read_eeprom[1]; /* SBPro Mic */ + azt2316a->sb->mixer_sbpro.regs[0x0c] = read_eeprom[2]; /* SBPro Record Source */ + azt2316a->sb->mixer_sbpro.regs[0x22] = read_eeprom[3]; /* SBPro Master */ + azt2316a->sb->mixer_sbpro.regs[0x26] = read_eeprom[4]; /* SBPro FM */ + azt2316a->sb->mixer_sbpro.regs[0x28] = read_eeprom[5]; /* SBPro CD */ + azt2316a->sb->mixer_sbpro.regs[0x2e] = read_eeprom[6]; /* SBPro Line */ + } + /* Restore WSS mixer settings from EEPROM on AZT1605 cards */ if (azt2316a->type == SB_SUBTYPE_CLONE_AZT1605_0X0C) { azt2316a->ad1848.regs[0] = read_eeprom[0]; /* WSS ADC L */ @@ -1409,6 +1899,72 @@ azt_init(const device_t *info) azt2316a->ad1848.regs[19] = read_eeprom[9]; /* CS4231 LINE/SB Voice R */ azt2316a->ad1848.regs[26] = read_eeprom[10]; /* CS4231 Mic */ } + /* Restore mixer settings from EEPROM on AZTPR16 cards */ + if (azt2316a->type == SB_SUBTYPE_CLONE_AZTPR16_0X09) { + azt2316a->sb->mixer_sbpro.regs[0x84] = read_eeprom[3]; /* Master L */ + azt2316a->sb->mixer_sbpro.regs[0x86] = read_eeprom[4]; /* Master R */ + azt2316a->sb->mixer_sbpro.regs[0x88] = read_eeprom[5]; /* Wave L */ + azt2316a->sb->mixer_sbpro.regs[0x8a] = read_eeprom[6]; /* Wave R */ + azt2316a->sb->mixer_sbpro.regs[0x8c] = read_eeprom[7]; /* FM L */ + azt2316a->sb->mixer_sbpro.regs[0x8e] = read_eeprom[8]; /* FM R */ + azt2316a->sb->mixer_sbpro.regs[0xa0] = read_eeprom[9]; /* CD L */ + azt2316a->sb->mixer_sbpro.regs[0xa2] = read_eeprom[10]; /* CD R */ + azt2316a->sb->mixer_sbpro.regs[0xa4] = read_eeprom[11]; /* Line L */ + azt2316a->sb->mixer_sbpro.regs[0xa6] = read_eeprom[12]; /* Line R */ + azt2316a->sb->mixer_sbpro.regs[0xa8] = read_eeprom[13]; /* Mic L */ + azt2316a->sb->mixer_sbpro.regs[0xaa] = read_eeprom[14]; /* Mic R */ + azt2316a->sb->mixer_sbpro.regs[0xac] = read_eeprom[15]; /* WSynth L */ + azt2316a->sb->mixer_sbpro.regs[0xae] = read_eeprom[16]; /* WSynth R */ + azt2316a->sb->mixer_sbpro.regs[0xc2] = read_eeprom[18]; /* Speaker */ + azt2316a->sb->mixer_sbpro.regs[0xc4] = read_eeprom[19]; /* Bass */ + azt2316a->sb->mixer_sbpro.regs[0xc6] = read_eeprom[20]; /* Treble */ + azt2316a->sb->mixer_sbpro.regs[0xc8] = read_eeprom[21]; /* I/O settings byte 1 */ + azt2316a->sb->mixer_sbpro.regs[0xca] = read_eeprom[22]; /* I/O settings byte 2 */ + azt2316a->sb->mixer_sbpro.regs[0xcc] = read_eeprom[23]; /* I/O settings byte 3 */ + azt2316a->sb->mixer_sbpro.regs[0xce] = read_eeprom[24]; /* I/O settings byte 4 */ + azt2316a->sb->mixer_sbpro.regs[0xe0] = read_eeprom[25]; /* Record Gain R */ + azt2316a->sb->mixer_sbpro.regs[0xe2] = read_eeprom[26]; /* Record Gain L */ + azt2316a->sb->mixer_sbpro.regs[0xe4] = read_eeprom[27]; /* Output Gain L */ + azt2316a->sb->mixer_sbpro.regs[0xe6] = read_eeprom[28]; /* Output Gain L */ + azt2316a->sb->mixer_sbpro.regs[0xe8] = read_eeprom[29]; /* Output Gain L */ + + /* Sane initial WSS values */ + azt2316a->ad1848.regs[0] = 0x08; /* WSS ADC L */ + azt2316a->ad1848.regs[1] = 0x08; /* WSS ADC R */ + azt2316a->ad1848.regs[2] = 0x08; /* WSS AUX1/CD L */ + azt2316a->ad1848.regs[3] = 0x08; /* WSS AUX1/CD R */ + azt2316a->ad1848.regs[4] = 0x08; /* WSS AUX2/FM L */ + azt2316a->ad1848.regs[5] = 0x08; /* WSS AUX2/FM R */ + azt2316a->ad1848.regs[6] = 0x08; /* WSS DAC L */ + azt2316a->ad1848.regs[7] = 0x08; /* WSS DAC R */ + + /* Set up CD volume table */ + uint8_t c; + double attenuation; + + for (c = 0; c < 32; c++) { + attenuation = 12.0; + if (c & 0x01) + attenuation -= 1.5; + if (c & 0x02) + attenuation -= 3.0; + if (c & 0x04) + attenuation -= 6.0; + if (c & 0x08) + attenuation -= 12.0; + if (c & 0x10) + attenuation -= 24.0; + + attenuation = pow(10, attenuation / 10); + + aztpr16_vols_5bits[c] = (attenuation * 65536); + } + + aztpr16_update_mixer(azt2316a); + } + + azt2316a->gameport = gameport_add(&gameport_pnp_device); + gameport_remap(azt2316a->gameport, (azt2316a->gameport_enabled) ? 0x200: 0x00); return azt2316a; } @@ -1425,6 +1981,8 @@ azt_close(void *priv) fn = "azt1605.nvr"; } else if (azt2316a->type == SB_SUBTYPE_CLONE_AZT2316A_0X11) { fn = "azt2316a.nvr"; + } else if (azt2316a->type == SB_SUBTYPE_CLONE_AZTPR16_0X09) { + fn = "aztpr16.nvr"; } /* always save to eeprom (recover from bad values) */ @@ -1434,7 +1992,6 @@ azt_close(void *priv) checksum += azt2316a->sb->dsp.azt_eeprom[i]; fwrite(azt2316a->sb->dsp.azt_eeprom, AZTECH_EEPROM_SIZE, 1, fp); - // TODO: confirm any models saving mixer settings to EEPROM and implement reading back // TODO: should remember to save wss duplex setting if 86Box has voice recording implemented in the future? Also, default azt2316a->wss_config // TODO: azt2316a->cur_mode is not saved to EEPROM? fwrite(&checksum, sizeof(checksum), 1, fp); @@ -1463,6 +2020,45 @@ azt_speed_changed(void *priv) sb_speed_changed(azt2316a->sb); } +static const device_config_t aztpr16_config[] = { + // clang-format off + { + .name = "opl", + .description = "Enable OPL", + .type = CONFIG_BINARY, + .default_string = NULL, + .default_int = 1, + .file_filter = NULL, + .spinner = { 0 }, + .selection = { { 0 } }, + .bios = { { 0 } } + }, + { + .name = "receive_input", + .description = "Receive MIDI input", + .type = CONFIG_BINARY, + .default_string = NULL, + .default_int = 1, + .file_filter = NULL, + .spinner = { 0 }, + .selection = { { 0 } }, + .bios = { { 0 } } + }, + { + .name = "receive_input401", + .description = "Receive MIDI input (MPU-401)", + .type = CONFIG_BINARY, + .default_string = NULL, + .default_int = 0, + .file_filter = NULL, + .spinner = { 0 }, + .selection = { { 0 } }, + .bios = { { 0 } } + }, + { .name = "", .description = "", .type = CONFIG_END } + // clang-format on +}; + static const device_config_t azt1605_config[] = { // clang-format off { @@ -1710,3 +2306,17 @@ const device_t azt1605_device = { .force_redraw = NULL, .config = azt1605_config }; + +const device_t aztpr16_device = { + .name = "Aztech Sound Galaxy Pro 16 (AZTPR16)", + .internal_name = "aztpr16", + .flags = DEVICE_ISA16, + .local = SB_SUBTYPE_CLONE_AZTPR16_0X09, + .init = azt_init, + .close = azt_close, + .reset = NULL, + .available = NULL, + .speed_changed = azt_speed_changed, + .force_redraw = NULL, + .config = aztpr16_config +}; diff --git a/src/sound/snd_sb.c b/src/sound/snd_sb.c index f041ebb8c..c2193869d 100644 --- a/src/sound/snd_sb.c +++ b/src/sound/snd_sb.c @@ -43,6 +43,7 @@ #include <86box/timer.h> #include <86box/snd_sb.h> #include <86box/plat_unused.h> +#include <86box/snd_azt2316a.h> #define SB_1 0 #define SB_15 1 @@ -863,6 +864,76 @@ sb_ct1345_mixer_write(uint16_t addr, uint8_t val, void *priv) mixer->regs[0x26] = mixer->regs[0x28] = 0xee; mixer->regs[0x2e] = 0x00; sb_dsp_set_stereo(&sb->dsp, mixer->regs[0x0e] & 2); + } else if (sb->dsp.sb_subtype == SB_SUBTYPE_CLONE_AZTPR16_0X09) { + mixer->regs[mixer->index] = val; + sb_log("sb_ct1345: Register WRITE, AZTPR16: %02X\t%02X\n", mixer->index, mixer->regs[mixer->index]); + aztpr16_update_mixer(sb->dsp.parent); + + switch (mixer->index) { + /* Compatibility: chain registers 0x02 and 0x22 as well as 0x06 and 0x26 */ + case 0x02: + case 0x06: + case 0x08: + mixer->regs[mixer->index + 0x20] = ((val & 0xe) << 4) | (val & 0xe); + break; + + case 0x22: + case 0x26: + case 0x28: + mixer->regs[mixer->index - 0x20] = (val & 0xe); + break; + + /* More compatibility: + SoundBlaster Pro selects register 020h for 030h, 022h for 032h, + 026h for 036h, and 028h for 038h. */ + case 0x30: + case 0x32: + case 0x36: + case 0x38: + mixer->regs[mixer->index - 0x10] = (val & 0xee); + break; + + case 0x00: + case 0x04: + case 0x0a: + case 0x0c: + case 0x0e: + case 0x2e: + break; + + /* Aztech AZTPR16 mixer */ + case 0x84: + case 0x86: + case 0x88: + case 0x8a: + case 0x8c: + case 0x8e: + case 0xa0: + case 0xa2: + case 0xa4: + case 0xa6: + case 0xa8: + case 0xaa: + case 0xac: + case 0xae: + case 0xc2: + case 0xc4: + case 0xc6: + case 0xc8: + case 0xca: + case 0xcc: + case 0xce: + case 0xe0: + case 0xe2: + case 0xe4: + case 0xe6: + case 0xe8: + break; + + default: + sb_log("sb_ct1345: Unknown register WRITE: %02X\t%02X\n", mixer->index, mixer->regs[mixer->index]); + break; + } } else { mixer->regs[mixer->index] = val; @@ -967,6 +1038,36 @@ sb_ct1345_mixer_read(uint16_t addr, void *priv) case 0x38: return mixer->regs[mixer->index]; + /* Aztech AZTPR16 mixer */ + case 0x84: + case 0x86: + case 0x88: + case 0x8a: + case 0x8c: + case 0x8e: + case 0xa0: + case 0xa2: + case 0xa4: + case 0xa6: + case 0xa8: + case 0xaa: + case 0xac: + case 0xae: + case 0xc2: + case 0xc4: + case 0xc6: + case 0xc8: + case 0xca: + case 0xcc: + case 0xce: + case 0xe0: + case 0xe2: + case 0xe4: + case 0xe6: + case 0xe8: + if (sb->dsp.sb_subtype == SB_SUBTYPE_CLONE_AZTPR16_0X09) + return mixer->regs[mixer->index]; + default: sb_log("sb_ct1345: Unknown register READ: %02X\t%02X\n", mixer->index, mixer->regs[mixer->index]); break; diff --git a/src/sound/snd_sb_dsp.c b/src/sound/snd_sb_dsp.c index bfc2847ed..8adb46c5d 100644 --- a/src/sound/snd_sb_dsp.c +++ b/src/sound/snd_sb_dsp.c @@ -1287,6 +1287,8 @@ sb_exec_command(sb_dsp_t *dsp) sb_add_data(dsp, 0x11); /* AZTECH get type, WASHINGTON/latest - according to devkit. E.g.: The one in the Itautec Infoway Multimidia */ else if ((dsp->sb_data[0] == 0x05 || dsp->sb_data[0] == 0x55) && dsp->sb_subtype == SB_SUBTYPE_CLONE_AZT1605_0X0C) sb_add_data(dsp, 0x0C); /* AZTECH get type, CLINTON - according to devkit. E.g.: The one in the Packard Bell Legend 100CD */ + else if ((dsp->sb_data[0] == 0x05 || dsp->sb_data[0] == 0x55) && dsp->sb_subtype == SB_SUBTYPE_CLONE_AZTPR16_0X09) + sb_add_data(dsp, 0x09); /* AZTECH get type, AZTPR16 */ else if (dsp->sb_data[0] == 0x08) { /* EEPROM address to write followed by byte */ if (dsp->sb_data[1] < 0 || dsp->sb_data[1] >= AZTECH_EEPROM_SIZE) @@ -1307,6 +1309,9 @@ sb_exec_command(sb_dsp_t *dsp) /* HACK: Aztech HWSET seems to rely on RP being incremented for detection to work after EMUTSR is run */ dsp->sb_read_rp++; break; + } else if ((dsp->sb_data[0] == 0x0f) && (dsp->sb_data[1] == 0xff) && (dsp->sb_subtype == SB_SUBTYPE_CLONE_AZTPR16_0X09)) { + sb_dsp_log("AZTPR16: Command 0x08, Subcommand 0x0f/0xff\n"); + sb_add_data(dsp, 0x80); } else sb_dsp_log("AZT2316A: UNKNOWN 0x08 COMMAND: %02X\n", dsp->sb_data[0]); /* 0x08 (when shutting down, driver tries to read 1 byte of response), 0x55, 0x0D, 0x08D seen */ break; @@ -1324,6 +1329,9 @@ sb_exec_command(sb_dsp_t *dsp) } else if (dsp->sb_data[0] == 0x01) { sb_dsp_log("AZT2316A: SB8PROV2 MODE!\n"); azt2316a_enable_wss(0, dsp->parent); + } else if ((dsp->sb_data[0] == 0x0f) && (dsp->sb_subtype == SB_SUBTYPE_CLONE_AZTPR16_0X09)) { + sb_dsp_log("AZTPR16: Mode switch command, params = %02X, %02X\n", dsp->sb_data[0], dsp->sb_data[1]); + aztpr16_wss_mode(dsp->sb_data[1], dsp->parent); } else sb_dsp_log("AZT2316A: UNKNOWN MODE! = %02x\n", dsp->sb_data[0]); // sequences 0x02->0xFF, 0x04->0xFF seen } @@ -1738,6 +1746,9 @@ sb_exec_command(sb_dsp_t *dsp) } else if (dsp->sb_subtype == SB_SUBTYPE_CLONE_AZT1605_0X0C) { sb_add_data(dsp, 0x2); sb_add_data(dsp, 0x1); + } else if (dsp->sb_subtype == SB_SUBTYPE_CLONE_AZTPR16_0X09) { + sb_add_data(dsp, 0x2); + sb_add_data(dsp, 0x1); } break; } @@ -1948,7 +1959,9 @@ sb_write(uint16_t addr, uint8_t val, void *priv) /* variable length commands */ if (dsp->sb_command == 0x08 && dsp->sb_data_stat == 1 && dsp->sb_data[0] == 0x08) sb_commands[dsp->sb_command] = 3; - else if (dsp->sb_command == 0x08 && dsp->sb_data_stat == 1 && dsp->sb_data[0] == 0x07) + else if (dsp->sb_command == 0x08 && dsp->sb_data_stat == 1 && (dsp->sb_data[0] == 0x07 || dsp->sb_data[0] == 0x0f)) + sb_commands[dsp->sb_command] = 2; + else if (dsp->sb_command == 0x09 && dsp->sb_data_stat == 1 && dsp->sb_data[0] == 0x0f) sb_commands[dsp->sb_command] = 2; } } @@ -1957,7 +1970,7 @@ sb_write(uint16_t addr, uint8_t val, void *priv) dsp->sb_data_stat = -1; if (IS_AZTECH(dsp)) { /* variable length commands */ - if (dsp->sb_command == 0x08) + if (dsp->sb_command == 0x08 || dsp->sb_command == 0x09) sb_commands[dsp->sb_command] = 1; } } diff --git a/src/sound/sndio.c b/src/sound/sndio.c index 6363163a2..3f8c90b89 100644 --- a/src/sound/sndio.c +++ b/src/sound/sndio.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -30,11 +31,12 @@ #define I_CD 3 #define I_MIDI 4 #define I_FDD 5 +#define I_HDD 6 -static struct sio_hdl* audio[6] = {NULL, NULL, NULL, NULL, NULL, NULL}; -static struct sio_par info[6]; -static int freqs[6] = { SOUND_FREQ, MUSIC_FREQ, WT_FREQ, CD_FREQ, SOUND_FREQ, 0 }; - +extern bool fast_forward; +static struct sio_hdl* audio[7] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL}; +static struct sio_par info[7]; +static int freqs[7] = { SOUND_FREQ, MUSIC_FREQ, WT_FREQ, CD_FREQ, SOUND_FREQ, SOUND_FREQ, 0 }; void closeal(void) { @@ -83,7 +85,7 @@ givealbuffer_common(const void *buf, const uint8_t src, const int size) int conv_size; double gain; int target_rate; - if (audio[src] == NULL) + if (audio[src] == NULL || fast_forward) return; gain = sound_muted ? 0.0 : pow(10.0, (double) sound_gain / 20.0); @@ -153,6 +155,12 @@ givealbuffer_fdd(const void *buf, const uint32_t size) { givealbuffer_common(buf, I_FDD, (int) size); } + +void +givealbuffer_hdd(const void *buf, const uint32_t size) +{ + givealbuffer_common(buf, I_HDD, (int) size); +} void al_set_midi(const int freq, UNUSED(const int buf_size)) diff --git a/src/sound/sound.c b/src/sound/sound.c index a5a7b07ed..cbf358327 100644 --- a/src/sound/sound.c +++ b/src/sound/sound.c @@ -37,6 +37,7 @@ #include <86box/snd_mpu401.h> #include <86box/sound.h> #include <86box/fdd_audio.h> +#include <86box/hdd_audio.h> typedef struct { const device_t *device; @@ -96,6 +97,12 @@ static event_t *sound_fdd_start_event; static volatile int fddaudioon = 0; static int fdd_thread_enable = 0; +static thread_t *sound_hdd_thread_h; +static event_t *sound_hdd_event; +static event_t *sound_hdd_start_event; +static volatile int hddaudioon = 0; +static int hdd_thread_enable = 0; + static void (*filter_cd_audio)(int channel, double *buffer, void *priv) = NULL; static void *filter_cd_audio_p = NULL; @@ -140,6 +147,7 @@ static const SOUND_CARD sound_cards[] = { { &ad1816_device }, { &azt2316a_device }, { &azt1605_device }, + { &aztpr16_device }, { &sb_goldfinch_device }, { &cs4232_device }, { &cs4235_device }, @@ -614,6 +622,10 @@ sound_poll(UNUSED(void *priv)) if (fdd_thread_enable) { thread_set_event(sound_fdd_event); } + + if (hdd_thread_enable) { + thread_set_event(sound_hdd_event); + } sound_pos_global = 0; } } @@ -857,3 +869,59 @@ sound_fdd_thread_end(void) } } } + +static void +sound_hdd_thread(UNUSED(void *param)) +{ + thread_set_event(sound_hdd_start_event); + while (hddaudioon) { + thread_wait_event(sound_hdd_event, -1); + thread_reset_event(sound_hdd_event); + + if (!hddaudioon) + break; + + static float hdd_float_buffer[SOUNDBUFLEN * 2]; + memset(hdd_float_buffer, 0, sizeof(hdd_float_buffer)); + hdd_audio_callback((int16_t*)hdd_float_buffer, SOUNDBUFLEN * 2); + givealbuffer_hdd(hdd_float_buffer, SOUNDBUFLEN * 2); + } +} + +void +sound_hdd_thread_init(void) +{ + if (!hddaudioon) { + hddaudioon = 1; + hdd_thread_enable = 1; + sound_hdd_start_event = thread_create_event(); + sound_hdd_event = thread_create_event(); + sound_hdd_thread_h = thread_create(sound_hdd_thread, NULL); + + thread_wait_event(sound_hdd_start_event, -1); + thread_reset_event(sound_hdd_start_event); + } +} + +void +sound_hdd_thread_end(void) +{ + if (hddaudioon) { + hddaudioon = 0; + hdd_thread_enable = 0; + thread_set_event(sound_hdd_event); + thread_wait(sound_hdd_thread_h); + + if (sound_hdd_event) { + thread_destroy_event(sound_hdd_event); + sound_hdd_event = NULL; + } + + sound_hdd_thread_h = NULL; + if (sound_hdd_start_event) { + thread_destroy_event(sound_hdd_start_event); + sound_hdd_start_event = NULL; + } + } +} + diff --git a/src/sound/sound_util.c b/src/sound/sound_util.c new file mode 100644 index 000000000..d6d3e7495 --- /dev/null +++ b/src/sound/sound_util.c @@ -0,0 +1,85 @@ +#include +#include +#include +#include + +#include <86box/86box.h> +#include <86box/mem.h> +#include <86box/rom.h> +#include <86box/plat.h> +#include <86box/sound_util.h> + +int16_t * +sound_load_wav(const char *filename, int *sample_count) +{ + if ((filename == NULL) || (strlen(filename) == 0)) + return NULL; + + if (strstr(filename, "..") != NULL) + return NULL; + + FILE *f = asset_fopen(filename, "rb"); + if (f == NULL) + return NULL; + + wav_header_t hdr; + if (fread(&hdr, sizeof(hdr), 1, f) != 1) { + fclose(f); + return NULL; + } + + if (memcmp(hdr.riff, "RIFF", 4) || memcmp(hdr.wave, "WAVE", 4) || + memcmp(hdr.fmt, "fmt ", 4) || memcmp(hdr.data, "data", 4)) { + fclose(f); + return NULL; + } + + /* Accept both mono and stereo, 16-bit PCM */ + if (hdr.audio_format != 1 || hdr.bits_per_sample != 16 || + (hdr.num_channels != 1 && hdr.num_channels != 2)) { + fclose(f); + return NULL; + } + + int input_samples = hdr.data_size / 2; + int16_t *input_data = malloc(hdr.data_size); + if (!input_data) { + fclose(f); + return NULL; + } + + if (fread(input_data, 1, hdr.data_size, f) != hdr.data_size) { + free(input_data); + fclose(f); + return NULL; + } + fclose(f); + + int16_t *output_data; + int output_samples; + + if (hdr.num_channels == 1) { + /* Convert mono to stereo */ + output_samples = input_samples; + output_data = malloc(input_samples * 2 * sizeof(int16_t)); + if (!output_data) { + free(input_data); + return NULL; + } + + for (int i = 0; i < input_samples; i++) { + output_data[i * 2] = input_data[i]; + output_data[i * 2 + 1] = input_data[i]; + } + + free(input_data); + } else { + output_data = input_data; + output_samples = input_samples / 2; + } + + if (sample_count) + *sample_count = output_samples; + + return output_data; +} \ No newline at end of file diff --git a/src/sound/xaudio2.c b/src/sound/xaudio2.c index 8596c2a49..4d109bce9 100644 --- a/src/sound/xaudio2.c +++ b/src/sound/xaudio2.c @@ -14,6 +14,7 @@ */ #include #include +#include #include #include #include @@ -54,6 +55,9 @@ static IXAudio2SourceVoice *srcvoicewt = NULL; static IXAudio2SourceVoice *srcvoicemidi = NULL; static IXAudio2SourceVoice *srcvoicecd = NULL; static IXAudio2SourceVoice *srcvoicefdd = NULL; +static IXAudio2SourceVoice *srcvoicehdd = NULL; + +extern bool fast_forward; #define FREQ SOUND_FREQ #define BUFLEN SOUNDBUFLEN @@ -184,6 +188,7 @@ inital(void) fmt.nAvgBytesPerSec = fmt.nSamplesPerSec * fmt.nBlockAlign; (void) IXAudio2_CreateSourceVoice(xaudio2, &srcvoicefdd, &fmt, 0, 2.0f, &callbacks, NULL, NULL); + (void) IXAudio2_CreateSourceVoice(xaudio2, &srcvoicehdd, &fmt, 0, 2.0f, &callbacks, NULL, NULL); (void) IXAudio2SourceVoice_SetVolume(srcvoice, 1, XAUDIO2_COMMIT_NOW); (void) IXAudio2SourceVoice_Start(srcvoice, 0, XAUDIO2_COMMIT_NOW); @@ -191,6 +196,7 @@ inital(void) (void) IXAudio2SourceVoice_Start(srcvoicemusic, 0, XAUDIO2_COMMIT_NOW); (void) IXAudio2SourceVoice_Start(srcvoicewt, 0, XAUDIO2_COMMIT_NOW); (void) IXAudio2SourceVoice_Start(srcvoicefdd, 0, XAUDIO2_COMMIT_NOW); + (void) IXAudio2SourceVoice_Start(srcvoicehdd, 0, XAUDIO2_COMMIT_NOW); const char *mdn = midi_out_device_get_internal_name(midi_output_device_current); @@ -223,6 +229,8 @@ closeal(void) (void) IXAudio2SourceVoice_FlushSourceBuffers(srcvoicecd); (void) IXAudio2SourceVoice_Stop(srcvoicefdd, 0, XAUDIO2_COMMIT_NOW); (void) IXAudio2SourceVoice_FlushSourceBuffers(srcvoicefdd); + (void) IXAudio2SourceVoice_Stop(srcvoicehdd, 0, XAUDIO2_COMMIT_NOW); + (void) IXAudio2SourceVoice_FlushSourceBuffers(srcvoicehdd); if (srcvoicemidi) { (void) IXAudio2SourceVoice_Stop(srcvoicemidi, 0, XAUDIO2_COMMIT_NOW); (void) IXAudio2SourceVoice_FlushSourceBuffers(srcvoicemidi); @@ -231,6 +239,7 @@ closeal(void) IXAudio2SourceVoice_DestroyVoice(srcvoicewt); IXAudio2SourceVoice_DestroyVoice(srcvoicecd); IXAudio2SourceVoice_DestroyVoice(srcvoicefdd); + IXAudio2SourceVoice_DestroyVoice(srcvoicehdd); IXAudio2SourceVoice_DestroyVoice(srcvoicemusic); IXAudio2SourceVoice_DestroyVoice(srcvoice); IXAudio2MasteringVoice_DestroyVoice(mastervoice); @@ -239,6 +248,7 @@ closeal(void) srcvoicecd = NULL; srcvoicemidi = NULL; srcvoicefdd = NULL; + srcvoicehdd = NULL; mastervoice = NULL; xaudio2 = NULL; @@ -251,7 +261,7 @@ closeal(void) void givealbuffer_common(const void *buf, IXAudio2SourceVoice *sourcevoice, const size_t buflen) { - if (!initialized) + if (!initialized || fast_forward) return; (void) IXAudio2MasteringVoice_SetVolume(mastervoice, sound_muted ? 0.0 : pow(10.0, (double) sound_gain / 20.0), @@ -312,6 +322,18 @@ givealbuffer_fdd(const void *buf, const uint32_t size) givealbuffer_common(buf, srcvoicefdd, size); } +void +givealbuffer_hdd(const void *buf, const uint32_t size) +{ + if (!initialized) + return; + + if (!srcvoicefdd) + return; + + givealbuffer_common(buf, srcvoicehdd, size); +} + void al_set_midi(const int freq, const int buf_size) { diff --git a/src/unix/unix.c b/src/unix/unix.c index 97f8b972a..ed16c20c6 100644 --- a/src/unix/unix.c +++ b/src/unix/unix.c @@ -63,6 +63,7 @@ int update_icons; int kbd_req_capture; int hide_status_bar; int hide_tool_bar; +bool fast_forward = false; // Technically unused. int fixed_size_x = 640; int fixed_size_y = 480; extern int title_set; @@ -596,10 +597,10 @@ main_thread(UNUSED(void *param)) #endif old_time = new_time; - if (drawits > 0 && !dopause) { + if ((drawits > 0 || fast_forward) && !dopause) { /* Yes, so do one frame now. */ drawits -= force_10ms ? 10 : 1; - if (drawits > 50) + if (drawits > 50 || fast_forward) drawits = 0; /* Run a block of code. */ @@ -1104,6 +1105,7 @@ unix_executeLine(char *line) "moeject - eject image from MO drive .\n\n" "hardreset - hard reset the emulated system.\n" "pause - pause the the emulated system.\n" + "screenshot - save a screenshot.\n" "fullscreen - toggle fullscreen.\n" "version - print version and license information.\n" "exit - exit " EMU_NAME ".\n"); @@ -1144,6 +1146,11 @@ unix_executeLine(char *line) } else if (strncasecmp(xargv[0], "fullscreen", 10) == 0) { video_fullscreen = video_fullscreen ? 0 : 1; fullscreen_pending = 1; + } else if (strncasecmp(xargv[0], "screenshot", 10) == 0) { + startblit(); + ++monitors[0].mon_screenshots_raw; + endblit(); + device_force_redraw(); } else if (strncasecmp(xargv[0], "pause", 5) == 0) { plat_pause(dopause ^ 1); printf("%s", dopause ? "Paused.\n" : "Unpaused.\n"); diff --git a/src/unix/unix_sdl.c b/src/unix/unix_sdl.c index 8d3a71002..fdd31d901 100644 --- a/src/unix/unix_sdl.c +++ b/src/unix/unix_sdl.c @@ -156,7 +156,7 @@ sdl_blit_shim(int x, int y, int w, int h, int monitor_index) for (int row = 0; row < h; ++row) video_copy(&(((uint8_t *) pixeldata)[row * 2048 * sizeof(uint32_t)]), &(buffer32->line[y + row][x]), w * sizeof(uint32_t)); - if (monitors[monitor_index].mon_screenshots) + if (monitors[monitor_index].mon_screenshots_raw) video_screenshot((uint32_t *) pixeldata, 0, 0, 2048); blitreq = 1; diff --git a/src/video/vid_ati_mach64.c b/src/video/vid_ati_mach64.c index 97c869668..4cecf6cf4 100644 --- a/src/video/vid_ati_mach64.c +++ b/src/video/vid_ati_mach64.c @@ -2169,6 +2169,14 @@ mach64_blit(uint32_t cpu_dat, int count, mach64_t *mach64) uint32_t dest_dat; uint32_t host_dat = 0; int mix = 0; + int draw_pixel = !(mach64->dst_cntl & DST_POLYGON_EN); + + if (mach64->dst_cntl & DST_POLYGON_EN) { + if (mach64->dst_cntl & DST_Y_MAJOR) + draw_pixel = 1; + else if (mach64->accel.err >= 0) + draw_pixel = 1; + } if (mach64->accel.source_host) { host_dat = cpu_dat; @@ -2215,7 +2223,7 @@ mach64_blit(uint32_t cpu_dat, int count, mach64_t *mach64) break; } - if ((mach64->accel.dst_x >= mach64->accel.sc_left) && (mach64->accel.dst_x <= mach64->accel.sc_right) && (mach64->accel.dst_y >= mach64->accel.sc_top) && (mach64->accel.dst_y <= mach64->accel.sc_bottom)) { + if ((mach64->accel.dst_x >= mach64->accel.sc_left) && (mach64->accel.dst_x <= mach64->accel.sc_right) && (mach64->accel.dst_y >= mach64->accel.sc_top) && (mach64->accel.dst_y <= mach64->accel.sc_bottom) && draw_pixel) { switch (mix ? mach64->accel.source_fg : mach64->accel.source_bg) { case SRC_HOST: src_dat = host_dat; @@ -2285,17 +2293,21 @@ mach64_blit(uint32_t cpu_dat, int count, mach64_t *mach64) if (mach64->dst_cntl & DST_Y_MAJOR) { mach64->accel.dst_y += mach64->accel.yinc; + mach64->accel.src_y += mach64->accel.yinc; if (mach64->accel.err >= 0) { mach64->accel.err += mach64->dst_bres_dec; mach64->accel.dst_x += mach64->accel.xinc; + mach64->accel.src_x += mach64->accel.xinc; } else { mach64->accel.err += mach64->dst_bres_inc; } } else { mach64->accel.dst_x += mach64->accel.xinc; + mach64->accel.src_x += mach64->accel.xinc; if (mach64->accel.err >= 0) { mach64->accel.err += mach64->dst_bres_dec; mach64->accel.dst_y += mach64->accel.yinc; + mach64->accel.src_y += mach64->accel.yinc; } else { mach64->accel.err += mach64->dst_bres_inc; } @@ -2347,9 +2359,7 @@ mach64_blit(uint32_t cpu_dat, int count, mach64_t *mach64) if (mach64->dst_cntl & DST_POLYGON_EN) { if (mach64->dst_cntl & DST_Y_MAJOR) draw_pixel = 1; - else if ((mach64->dst_cntl & DST_X_DIR) && mach64->accel.err < (mach64->dst_bres_dec + mach64->dst_bres_inc)) /*X+*/ - draw_pixel = 1; - else if (!(mach64->dst_cntl & DST_X_DIR) && mach64->accel.err >= 0) /*X-*/ + else if (mach64->accel.err >= 0) draw_pixel = 1; } @@ -2406,62 +2416,27 @@ mach64_blit(uint32_t cpu_dat, int count, mach64_t *mach64) return; } - switch (mach64->dst_cntl & 7) { - case 0: - case 2: - mach64->accel.src_x--; - mach64->accel.dst_x--; - break; - case 1: - case 3: - mach64->accel.src_x++; - mach64->accel.dst_x++; - break; - case 4: - case 5: - mach64->accel.src_y--; - mach64->accel.dst_y--; - break; - case 6: - case 7: - mach64->accel.src_y++; - mach64->accel.dst_y++; - break; - - default: - break; - } - mach64_log("x %i y %i err %i inc %i dec %i\n", mach64->accel.dst_x, mach64->accel.dst_y, mach64->accel.err, mach64->dst_bres_inc, mach64->dst_bres_dec); - if (mach64->accel.err >= 0) { - mach64->accel.err += mach64->dst_bres_dec; - - switch (mach64->dst_cntl & 7) { - case 0: - case 1: - mach64->accel.src_y--; - mach64->accel.dst_y--; - break; - case 2: - case 3: - mach64->accel.src_y++; - mach64->accel.dst_y++; - break; - case 4: - case 6: - mach64->accel.src_x--; - mach64->accel.dst_x--; - break; - case 5: - case 7: - mach64->accel.src_x++; - mach64->accel.dst_x++; - break; - - default: - break; + if (mach64->dst_cntl & DST_Y_MAJOR) { + mach64->accel.dst_y += mach64->accel.yinc; + mach64->accel.src_y += mach64->accel.yinc; + if (mach64->accel.err >= 0) { + mach64->accel.err += mach64->dst_bres_dec; + mach64->accel.dst_x += mach64->accel.xinc; + mach64->accel.src_x += mach64->accel.xinc; + } else { + mach64->accel.err += mach64->dst_bres_inc; } - } else - mach64->accel.err += mach64->dst_bres_inc; + } else { + mach64->accel.dst_x += mach64->accel.xinc; + mach64->accel.src_x += mach64->accel.xinc; + if (mach64->accel.err >= 0) { + mach64->accel.err += mach64->dst_bres_dec; + mach64->accel.dst_y += mach64->accel.yinc; + mach64->accel.src_y += mach64->accel.yinc; + } else { + mach64->accel.err += mach64->dst_bres_inc; + } + } } } break; diff --git a/src/video/vid_et4000.c b/src/video/vid_et4000.c index c17495c1e..608cc9601 100644 --- a/src/video/vid_et4000.c +++ b/src/video/vid_et4000.c @@ -57,7 +57,7 @@ #define ET4000_TYPE_ISA 1 /* ISA ET4000AX */ #define ET4000_TYPE_MCA 2 /* MCA ET4000AX */ #define ET4000_TYPE_KOREAN 3 /* Korean ET4000 */ -#define ET4000_TYPE_TRIGEM 4 /* Trigem 286M ET4000 */ +#define ET4000_TYPE_TRIGEM 4 /* TriGem 286M ET4000 */ #define ET4000_TYPE_KASAN 5 /* Kasan ET4000 */ #define BIOS_ROM_PATH "roms/video/et4000/ET4000.BIN" @@ -829,7 +829,7 @@ et4000_init(const device_t *info) break; case ET4000_TYPE_KOREAN: /* Korean ET4000 */ - case ET4000_TYPE_TRIGEM: /* Trigem 286M ET4000 */ + case ET4000_TYPE_TRIGEM: /* TriGem 286M ET4000 */ dev->vram_size = device_get_config_int("memory") << 10; dev->port_22cb_val = 0x60; dev->port_32cb_val = 0; @@ -1133,7 +1133,7 @@ const device_t et4000_mca_device = { }; const device_t et4000k_isa_device = { - .name = "Trigem Korean VGA (Tseng Labs ET4000AX Korean)", + .name = "TriGem Korean VGA (Tseng Labs ET4000AX Korean)", .internal_name = "tgkorvga", .flags = DEVICE_ISA, .local = ET4000_TYPE_KOREAN, @@ -1147,7 +1147,7 @@ const device_t et4000k_isa_device = { }; const device_t et4000k_tg286_isa_device = { - .name = "Trigem Korean VGA (Trigem 286M)", + .name = "TriGem Korean VGA (TriGem 286M)", .internal_name = "et4000k_tg286_isa", .flags = DEVICE_ISA, .local = ET4000_TYPE_TRIGEM, diff --git a/src/video/vid_s3.c b/src/video/vid_s3.c index 175b5048f..101d3ae8b 100644 --- a/src/video/vid_s3.c +++ b/src/video/vid_s3.c @@ -3527,7 +3527,7 @@ s3_in(uint16_t addr, void *priv) } break; case 0x30: - return s3->id; /*Chip ID*/ + return ((svga->crtc[0x38] & 0xcc) != 0x48) ? 0xFF : s3->id; /*Chip ID*/ case 0x31: return (svga->crtc[0x31] & 0xcf) | ((s3->ma_ext & 3) << 4); case 0x35: diff --git a/src/video/vid_s3_virge.c b/src/video/vid_s3_virge.c index 5fa84e555..abeba064e 100644 --- a/src/video/vid_s3_virge.c +++ b/src/video/vid_s3_virge.c @@ -24,6 +24,7 @@ #include #define HAVE_STDARG_H #include <86box/86box.h> +#include "cpu.h" #include <86box/io.h> #include <86box/timer.h> #include <86box/dma.h> @@ -751,7 +752,7 @@ s3_virge_in(uint16_t addr, void *priv) ret = virge->virge_rev; break; case 0x30: - ret = virge->virge_id; + ret = ((svga->crtc[0x38] & 0xcc) != 0x48) ? 0xFF : virge->virge_id; break; /*Chip ID*/ case 0x31: ret = (svga->crtc[0x31] & 0xcf) | ((virge->ma_ext & 3) << 4); @@ -828,6 +829,7 @@ s3_virge_recalctimings(svga_t *svga) if (virge->chip >= S3_TRIO3D2X) { svga_set_ramdac_type(svga, (svga->seqregs[0x1b] & 0x10) ? RAMDAC_8BIT : RAMDAC_6BIT); + svga->lut_map = !!(svga->seqregs[0x1b] & 0x8); } if (!svga->scrblank && svga->attr_palette_enable && (svga->crtc[0x43] & 0x80)) { /* TODO: In case of bug reports, disable 9-dots-wide character clocks in graphics modes. */ @@ -1190,6 +1192,9 @@ s3_virge_mmio_read(uint32_t addr, void *priv) virge_t *virge = (virge_t *) priv; uint8_t ret; + /* Add wait states for MMIO reads to prevent excessive polling */ + cycles -= virge->svga.monitor->mon_video_timing_read_b; + switch (addr & 0xffff) { case 0x8504: if (!virge->virge_busy) @@ -1241,6 +1246,9 @@ s3_virge_mmio_read_w(uint32_t addr, void *priv) virge_t *virge = (virge_t *) priv; uint16_t ret; + /* Add wait states for MMIO reads to prevent excessive polling */ + cycles -= virge->svga.monitor->mon_video_timing_read_w; + switch (addr & 0xfffe) { case 0x8504: ret = 0xc000; @@ -1274,6 +1282,9 @@ s3_virge_mmio_read_l(uint32_t addr, void *priv) virge_t *virge = (virge_t *) priv; uint32_t ret = 0xffffffff; + /* Add wait states for MMIO reads to prevent excessive polling */ + cycles -= virge->svga.monitor->mon_video_timing_read_l; + switch (addr & 0xfffc) { case 0x8180: ret = virge->streams.pri_ctrl; @@ -5377,6 +5388,7 @@ s3_virge_init(const device_t *info) s3_virge_hwcursor_draw, s3_virge_overlay_draw); virge->svga.hwcursor.cur_ysize = 64; + virge->svga.conv_16to32 = tvp3026_conv_16to32; if (bios_fn != NULL) { if (virge->type == S3_VIRGE_GX2) diff --git a/src/video/vid_table.c b/src/video/vid_table.c index 88cd6789e..368ee8254 100644 --- a/src/video/vid_table.c +++ b/src/video/vid_table.c @@ -73,6 +73,7 @@ video_cards[] = { { .device = &compaq_cga_2_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &cpqega_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &g2_gc205_device, .flags = VIDEO_FLAG_TYPE_NONE }, + { .device = &genius_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &hercules_device, .flags = VIDEO_FLAG_TYPE_MDA }, { .device = &herculesplus_device, .flags = VIDEO_FLAG_TYPE_MDA }, { .device = &incolor_device, .flags = VIDEO_FLAG_TYPE_NONE }, @@ -85,7 +86,6 @@ video_cards[] = { { .device = &iskra_ega_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &jega_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &et4000_kasan_isa_device, .flags = VIDEO_FLAG_TYPE_NONE }, - { .device = &genius_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &nga_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &nec_sv9000_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &ogc_device, .flags = VIDEO_FLAG_TYPE_NONE }, @@ -145,13 +145,6 @@ video_cards[] = { /* VLB */ { .device = &mach32_vlb_device, .flags = VIDEO_FLAG_TYPE_8514 }, { .device = &mach64gx_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, - { .device = &et4000w32_machspeed_vga_gui_2400s_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, - { .device = &et4000w32i_hercules_dynamite_pro_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, - { .device = &et4000w32p_videomagic_revb_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, - { .device = &et4000w32p_cardex_revc_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, - { .device = &et4000w32p_generic_revd_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, - { .device = &et4000w32p_cardex_revd_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, - { .device = &et4000w32p_diamond_revd_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &gd5424_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &gd5426_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &gd5428_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, @@ -160,6 +153,7 @@ video_cards[] = { { .device = &gd5430_diamond_speedstar_pro_se_a8_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &gd5430_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &gd5434_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, + { .device = &ht216_32_standalone_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &s3_elsa_winner1000_86c928_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &s3_metheus_86c928_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &s3_mirocrystal_8s_805_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, @@ -179,10 +173,20 @@ video_cards[] = { { .device = &s3_spea_mirage_p64_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &s3_diamond_stealth64_968_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &s3_stb_powergraph_64_video_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, - { .device = &ht216_32_standalone_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &tgui9400cxi_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &tgui9440_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, + { .device = &et4000w32_machspeed_vga_gui_2400s_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, + { .device = &et4000w32i_hercules_dynamite_pro_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, + { .device = &et4000w32p_videomagic_revb_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, + { .device = &et4000w32p_cardex_revc_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, + { .device = &et4000w32p_generic_revd_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, + { .device = &et4000w32p_cardex_revd_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, + { .device = &et4000w32p_diamond_revd_vlb_device, .flags = VIDEO_FLAG_TYPE_NONE }, /* PCI */ + { .device = &voodoo_banshee_device, .flags = VIDEO_FLAG_TYPE_NONE }, + { .device = &voodoo_3_1000_device, .flags = VIDEO_FLAG_TYPE_NONE }, + { .device = &voodoo_3_2000_device, .flags = VIDEO_FLAG_TYPE_NONE }, + { .device = &voodoo_3_3000_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &mach32_pci_device, .flags = VIDEO_FLAG_TYPE_8514 }, { .device = &mach64gx_pci_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &mach64ct_device, .flags = VIDEO_FLAG_TYPE_NONE }, @@ -197,10 +201,12 @@ video_cards[] = { { .device = &gd5446_pci_device, .flags = VIDEO_FLAG_TYPE_SECONDARY }, { .device = &gd5446_stb_pci_device, .flags = VIDEO_FLAG_TYPE_SECONDARY }, { .device = &gd5480_pci_device, .flags = VIDEO_FLAG_TYPE_NONE }, - { .device = &et4000w32p_cardex_revc_pci_device, .flags = VIDEO_FLAG_TYPE_NONE }, - { .device = &et4000w32p_generic_revd_pci_device, .flags = VIDEO_FLAG_TYPE_NONE }, - { .device = &et4000w32p_cardex_revd_pci_device, .flags = VIDEO_FLAG_TYPE_NONE }, - { .device = &et4000w32p_diamond_revd_pci_device, .flags = VIDEO_FLAG_TYPE_NONE }, + { .device = &creative_voodoo_banshee_device, .flags = VIDEO_FLAG_TYPE_NONE }, + { .device = &millennium_device, .flags = VIDEO_FLAG_TYPE_NONE }, + { .device = &millennium_ii_device, .flags = VIDEO_FLAG_TYPE_NONE }, + { .device = &mystique_device, .flags = VIDEO_FLAG_TYPE_NONE }, + { .device = &mystique_220_device, .flags = VIDEO_FLAG_TYPE_NONE }, + { .device = &quantum3d_raven_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &s3_elsa_winner1000_86c928_pci_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &s3_spea_mercury_lite_86c928_pci_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &s3_diamond_stealth64_964_pci_device, .flags = VIDEO_FLAG_TYPE_NONE }, @@ -234,26 +240,14 @@ video_cards[] = { { .device = &s3_virge_385_pci_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &s3_virge_357_pci_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &s3_trio3d2x_pci_device, .flags = VIDEO_FLAG_TYPE_NONE }, - { .device = &millennium_device, .flags = VIDEO_FLAG_TYPE_NONE }, - { .device = &millennium_ii_device, .flags = VIDEO_FLAG_TYPE_NONE }, - { .device = &mystique_device, .flags = VIDEO_FLAG_TYPE_NONE }, - { .device = &mystique_220_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &tgui9440_pci_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &tgui9660_pci_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &tgui9680_pci_device, .flags = VIDEO_FLAG_TYPE_NONE }, - { .device = &voodoo_banshee_device, .flags = VIDEO_FLAG_TYPE_NONE }, - { .device = &creative_voodoo_banshee_device, .flags = VIDEO_FLAG_TYPE_NONE }, - { .device = &quantum3d_raven_device, .flags = VIDEO_FLAG_TYPE_NONE }, - { .device = &voodoo_3_1000_device, .flags = VIDEO_FLAG_TYPE_NONE }, - { .device = &voodoo_3_2000_device, .flags = VIDEO_FLAG_TYPE_NONE }, - { .device = &voodoo_3_3000_device, .flags = VIDEO_FLAG_TYPE_NONE }, + { .device = &et4000w32p_cardex_revc_pci_device, .flags = VIDEO_FLAG_TYPE_NONE }, + { .device = &et4000w32p_generic_revd_pci_device, .flags = VIDEO_FLAG_TYPE_NONE }, + { .device = &et4000w32p_cardex_revd_pci_device, .flags = VIDEO_FLAG_TYPE_NONE }, + { .device = &et4000w32p_diamond_revd_pci_device, .flags = VIDEO_FLAG_TYPE_NONE }, /* AGP */ - { .device = &s3_virge_357_agp_device, .flags = VIDEO_FLAG_TYPE_NONE }, - { .device = &s3_diamond_stealth_4000_agp_device, .flags = VIDEO_FLAG_TYPE_NONE }, - { .device = &s3_trio3d2x_agp_device, .flags = VIDEO_FLAG_TYPE_NONE }, -#ifdef USE_G100 - { .device = &productiva_g100_device, .flags = VIDEO_FLAG_TYPE_SPECIAL }, -#endif /*USE_G100 */ { .device = &velocity_100_agp_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &velocity_200_agp_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &voodoo_3_1000_agp_device, .flags = VIDEO_FLAG_TYPE_NONE }, @@ -261,9 +255,15 @@ video_cards[] = { { .device = &voodoo_3_3000_agp_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &voodoo_3_3500_agp_ntsc_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &voodoo_3_3500_agp_pal_device, .flags = VIDEO_FLAG_TYPE_NONE }, + { .device = &voodoo_3_3500_si_agp_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &compaq_voodoo_3_3500_agp_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = &voodoo_3_3500_se_agp_device, .flags = VIDEO_FLAG_TYPE_NONE }, - { .device = &voodoo_3_3500_si_agp_device, .flags = VIDEO_FLAG_TYPE_NONE }, +#ifdef USE_G100 + { .device = &productiva_g100_device, .flags = VIDEO_FLAG_TYPE_SPECIAL }, +#endif /*USE_G100 */ + { .device = &s3_virge_357_agp_device, .flags = VIDEO_FLAG_TYPE_NONE }, + { .device = &s3_diamond_stealth_4000_agp_device, .flags = VIDEO_FLAG_TYPE_NONE }, + { .device = &s3_trio3d2x_agp_device, .flags = VIDEO_FLAG_TYPE_NONE }, { .device = NULL, .flags = VIDEO_FLAG_TYPE_NONE } // clang-format on }; @@ -389,6 +389,10 @@ video_reset(int card) void video_post_reset(void) { + /* Reset the graphics card (or do nothing if it was already done + by the machine's init function). */ + video_reset(gfxcard[0]); + int ibm8514_has_vga = 0; if (gfxcard[0] == VID_INTERNAL) ibm8514_has_vga = (video_get_type_monitor(0) == VIDEO_FLAG_TYPE_8514); @@ -408,9 +412,6 @@ video_post_reset(void) if (da2_standalone_enabled) da2_device_add(); - /* Reset the graphics card (or do nothing if it was already done - by the machine's init function). */ - video_reset(gfxcard[0]); } void diff --git a/src/video/vid_voodoo.c b/src/video/vid_voodoo.c index 960b913a9..6920ece5c 100644 --- a/src/video/vid_voodoo.c +++ b/src/video/vid_voodoo.c @@ -516,6 +516,7 @@ voodoo_writel(uint32_t addr, uint32_t val, void *priv) break; case SST_fbiInit0: if (voodoo->initEnable & 0x01) { + int old_vga_pass = voodoo->fbiInit0 & FBIINIT0_VGA_PASS; voodoo->fbiInit0 = val; thread_wait_mutex(voodoo->force_blit_mutex); voodoo->can_blit = (voodoo->fbiInit0 & FBIINIT0_VGA_PASS) ? 1 : 0; @@ -523,6 +524,10 @@ voodoo_writel(uint32_t addr, uint32_t val, void *priv) voodoo->force_blit_count = 0; thread_release_mutex(voodoo->force_blit_mutex); + /* When VGA pass-through becomes active, mark all lines dirty to force full refresh */ + if (!old_vga_pass && (val & FBIINIT0_VGA_PASS)) + memset(voodoo->dirty_line, 1, sizeof(voodoo->dirty_line)); + if (voodoo->set->nr_cards == 2) svga_set_override(voodoo->svga, (voodoo->set->voodoos[0]->fbiInit0 | voodoo->set->voodoos[1]->fbiInit0) & 1); else diff --git a/src/video/vid_voodoo_blitter.c b/src/video/vid_voodoo_blitter.c index d4b316dc5..87127a949 100644 --- a/src/video/vid_voodoo_blitter.c +++ b/src/video/vid_voodoo_blitter.c @@ -506,6 +506,9 @@ voodoo_fastfill(voodoo_t *voodoo, voodoo_params_t *params) for (int x = params->clipLeft; x < params->clipRight; x++) cbuf[x] = col; } + /* Mark line dirty for single buffer mode */ + if (params->draw_offset == params->front_offset && y < 2048) + voodoo->dirty_line[y] = 1; } } } diff --git a/src/video/vid_voodoo_render.c b/src/video/vid_voodoo_render.c index b25f42507..1c0edf775 100644 --- a/src/video/vid_voodoo_render.c +++ b/src/video/vid_voodoo_render.c @@ -1389,8 +1389,10 @@ skip_pixel: voodoo->texel_count[odd_even] += state->texel_count; voodoo->fbiPixelsIn += state->pixel_count; - if (voodoo->params.draw_offset == voodoo->params.front_offset && (real_y >> 1) < 2048) - voodoo->dirty_line[real_y >> 1] = 1; + if (voodoo->params.draw_offset == voodoo->params.front_offset && !SLI_ENABLED) { + if (real_y < 2048) + voodoo->dirty_line[real_y] = 1; + } next_line: if (SLI_ENABLED) { diff --git a/src/video/video.c b/src/video/video.c index 4c4256a89..c1d096270 100644 --- a/src/video/video.c +++ b/src/video/video.c @@ -376,7 +376,7 @@ video_screenshot_monitor(uint32_t *buf, int start_x, int start_y, int row_len, i video_take_screenshot_monitor((const char *) path, buf, start_x, start_y, row_len, monitor_index); png_destroy_write_struct(&png_ptr[monitor_index], &info_ptr[monitor_index]); - atomic_fetch_sub(&monitors[monitor_index].mon_screenshots, 1); + atomic_fetch_sub(&monitors[monitor_index].mon_screenshots_raw, 1); } void @@ -814,6 +814,9 @@ video_monitor_init(int index) monitors[index].mon_vid_type = VIDEO_FLAG_TYPE_NONE; atomic_init(&doresize_monitors[index], 0); atomic_init(&monitors[index].mon_screenshots, 0); + atomic_init(&monitors[index].mon_screenshots_clipboard, 0); + atomic_init(&monitors[index].mon_screenshots_raw, 0); + atomic_init(&monitors[index].mon_screenshots_raw_clipboard, 0); if (index >= 1) ui_init_monitor(index); monitors[index].mon_blit_data_ptr->blit_thread = thread_create(blit_thread, monitors[index].mon_blit_data_ptr);