Merge remote-tracking branch 'origin/master' into feature/recompiler_improvements

This commit is contained in:
OBattler
2026-01-10 02:12:49 +01:00
139 changed files with 7451 additions and 7196 deletions

View File

@@ -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

30
CODE_OF_CONDUCT.md Normal file
View File

@@ -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).

View File

@@ -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.

View File

@@ -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);

View File

@@ -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)

View File

@@ -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();
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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)
{

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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 */

View File

@@ -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

View File

@@ -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)

View File

@@ -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)
{

999
src/disk/hdd_audio.c Normal file
View File

@@ -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, <riikonen.toni@gmail.com>
*
* Copyright 2026 Toni Riikonen.
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#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);
}
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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);

View File

@@ -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];

View File

@@ -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);

View File

@@ -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, <riikonen.toni@gmail.com>
*
* Copyright 2026 Toni Riikonen.
*/
#ifndef EMU_HDD_AUDIO_H
#define EMU_HDD_AUDIO_H
#include <stdint.h>
#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 */

View File

@@ -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);

View File

@@ -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 */

View File

@@ -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;

View File

@@ -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);

View File

@@ -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*/

View File

@@ -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;

View File

@@ -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;

View File

@@ -0,0 +1,28 @@
#ifndef SOUND_UTIL_H
#define SOUND_UTIL_H
#include <stdint.h>
/* 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 */

View File

@@ -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. */

View File

@@ -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)
{

View File

@@ -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)

View File

@@ -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();

View File

@@ -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

View File

@@ -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")

View File

@@ -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;
}

View File

@@ -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 <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>
#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# include <winsock2.h>
#else
# include <poll.h>
#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,
};

551
src/network/net_switch.c Normal file
View File

@@ -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, <richardg867@gmail.com>
*
* Copyright 2026 RichardG.
*/
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include <wchar.h>
#include <fcntl.h>
#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
# include <winsock2.h>
# include <ws2tcpip.h>
# include <windows.h>
# include <iphlpapi.h>
# define IFF_POINTOPOINT IFF_POINTTOPOINT
#else
# include <unistd.h>
# include <poll.h>
# include <sys/types.h>
# include <sys/socket.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <ifaddrs.h>
# include <net/if.h>
#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
};

View File

@@ -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];

View File

@@ -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 <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <wchar.h>
#include <stdbool.h>
#include <errno.h>
#include <fcntl.h>
#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# include <winsock2.h>
#else
# include <poll.h>
# include <netdb.h>
#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 <sys/time.h>
#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;
}

View File

@@ -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 <Winsock2.h> // before Windows.h, else Winsock 1 conflict
#include <Ws2tcpip.h> // needed for ip_mreq definition for multicast
#include <Windows.h>
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#endif
#include <unistd.h>
#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

View File

@@ -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);

View File

@@ -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)

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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 <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include <limits.h>
#ifdef PB_ENABLE_MALLOC
#include <stdlib.h>
#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 <avr/pgmspace.h>
#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<<bits))
#define PB_FIELDINFO_ASSERT_1(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag,6) && PB_FITS(data_offset,8) && PB_FITS(size_offset,4) && PB_FITS(data_size,4) && PB_FITS(array_size,1), FIELDINFO_DOES_NOT_FIT_width1_field ## tag)
#define PB_FIELDINFO_ASSERT_2(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag,10) && PB_FITS(data_offset,16) && PB_FITS(size_offset,4) && PB_FITS(data_size,12) && PB_FITS(array_size,12), FIELDINFO_DOES_NOT_FIT_width2_field ## tag)
#ifndef PB_FIELD_32BIT
/* Maximum field sizes are still 16-bit if pb_size_t is 16-bit */
#define PB_FIELDINFO_ASSERT_4(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag,16) && PB_FITS(data_offset,16) && PB_FITS((int_least8_t)size_offset,8) && PB_FITS(data_size,16) && 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,16) && PB_FITS(data_offset,16) && PB_FITS((int_least8_t)size_offset,8) && PB_FITS(data_size,16) && PB_FITS(array_size,16), FIELDINFO_DOES_NOT_FIT_width8_field ## tag)
#else
/* Up to 32-bit fields supported.
* Note that the checks are against 31 bits to avoid compiler warnings about shift wider than type in the test.
* I expect that there is no reasonable use for >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 <typename GenMessageT> struct MessageDescriptor;
} // namespace nanopb
}
#endif /* __cplusplus */
#endif

View File

@@ -1,388 +0,0 @@
/* pb_common.c: Common support functions for pb_encode.c and pb_decode.c.
*
* 2014 Petteri Aimonen <jpa@kapsi.fi>
*/
#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 <http://www.cl.cam.ac.uk/~mgk25/> 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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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()

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -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 ""

View File

@@ -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 ""

View File

@@ -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 <hrdlickadavid@outlook.com>\n"
"Language-Team: Czech <https://weblate.86box.net/projects/86box/86box/cs/>\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 ""

View File

@@ -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 ""

3070
src/qt/languages/el-GR.po Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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 "<b>An update to 86Box is available!</b>"
msgstr "<b>¡Una actualización para 86Box está disponible!</b>"
@@ -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 "<b>Version %1</b> is now available."
msgstr "<b>La versión %1</b> está ahora disponible."
msgid "You are currently running build <b>%1</b>."
msgstr "Actualmente está a ejecutar la compialación <b>%1</b>."
msgstr "Actualmente está a ejecutar la compilación <b>%1</b>."
msgid "<b>Build %1</b> is now available."
msgstr "<b>La compilación %1</b> está ahora disponible."
@@ -2999,3 +3008,6 @@ msgstr "&Forzar interpretación"
msgid "&Allow recompilation"
msgstr "&Permitir recompilación"
msgid "&Fast forward"
msgstr ""

View File

@@ -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 <daniel@gurney.dev>\n"
"Language-Team: Finnish <https://weblate.86box.net/projects/86box/86box/fi/>\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 ""

View File

@@ -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 <b>%1</b>."
msgstr "Vous exécutez actuellement le build <b>%1</b>."
msgid "<b>Build %1</b> is now available."
msgstr "<b>Le Build %1</b> est désormais disponible."
msgstr "<b>Le build %1</b> 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 ""

View File

@@ -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 ""

View File

@@ -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 ""

View File

@@ -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 ""

View File

@@ -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 ""

View File

@@ -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 ""

View File

@@ -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 ""

View File

@@ -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 ""

View File

@@ -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 ""

View File

@@ -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 ""

View File

@@ -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 <lemondrops358@gmail.com>\n"
"Language-Team: Russian <https://weblate.86box.net/projects/86box/86box/ru/>\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 ""

View File

@@ -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 ""

View File

@@ -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 ""

View File

@@ -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 ""

View File

@@ -1,8 +1,14 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2026-01-01 20:06+0000\n"
"Last-Translator: Umut Çağan Uçanok <ucucanok@outlook.com.tr>\n"
"Language-Team: Turkish <https://weblate.86box.net/projects/86box/86box/tr/>\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 ""

View File

@@ -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 ""

View File

@@ -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 ""

View File

@@ -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 ""

View File

@@ -1,8 +1,15 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2026-01-02 21:56+0000\n"
"Last-Translator: Alexander Babikov <lemondrops358@gmail.com>\n"
"Language-Team: Chinese (Traditional Han script) <https://weblate.86box.net/"
"projects/86box/86box/zh_Hant/>\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 "<b>An update to 86Box is available!</b>"
msgstr "<b>86Box 的更新可用!</b>"
msgstr "<b>86Box 的更新可用!</b>"
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 ""

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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 <locale.h>
@@ -452,7 +453,7 @@ main_thread_fn()
#endif
drawits += static_cast<int>(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);
}

View File

@@ -65,6 +65,7 @@ extern int qt_nvr_save(void);
#endif
extern bool cpu_thread_running;
extern bool fast_forward;
};
#include <QGuiApplication>
@@ -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()
{

View File

@@ -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);

View File

@@ -70,8 +70,6 @@
</widget>
<addaction name="menuTablet_tool"/>
<addaction name="separator"/>
<addaction name="actionForce_interpretation"/>
<addaction name="separator"/>
<addaction name="actionKeyboard_requires_capture"/>
<addaction name="actionRight_CTRL_is_left_ALT"/>
<addaction name="separator"/>
@@ -80,6 +78,10 @@
<addaction name="actionAuto_pause"/>
<addaction name="actionPause"/>
<addaction name="separator"/>
<addaction name="actionFast_forward"/>
<addaction name="separator"/>
<addaction name="actionForce_interpretation"/>
<addaction name="separator"/>
<addaction name="actionHard_Reset"/>
<addaction name="actionCtrl_Alt_Del"/>
<addaction name="separator"/>
@@ -106,6 +108,10 @@
<addaction name="actionEnable_Discord_integration"/>
<addaction name="separator"/>
<addaction name="actionTake_screenshot"/>
<addaction name="actionTake_raw_screenshot"/>
<addaction name="actionCopy_screenshot"/>
<addaction name="actionCopy_raw_screenshot"/>
<addaction name="separator"/>
<addaction name="menuSound"/>
<addaction name="separator"/>
<addaction name="actionPreferences"/>
@@ -295,26 +301,24 @@
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionForce_interpretation"/>
<addaction name="separator"/>
<addaction name="actionPause"/>
<addaction name="actionFast_forward"/>
<addaction name="actionHard_Reset"/>
<addaction name="separator"/>
<addaction name="actionACPI_Shutdown"/>
<addaction name="separator"/>
<addaction name="actionCtrl_Alt_Del"/>
<addaction name="actionCtrl_Alt_Esc"/>
<addaction name="separator"/>
<addaction name="actionSettings"/>
<addaction name="separator"/>
<addaction name="actionTake_screenshot"/>
<addaction name="actionCopy_screenshot"/>
</widget>
<action name="actionForce_interpretation">
<property name="icon">
<iconset resource="../qt_resources.qrc">
<normaloff>:/menuicons/qt/icons/interpreter.ico</normaloff>:/menuicons/qt/icons/interpreter.ico</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Force interpretation</string>
</property>
@@ -374,9 +378,6 @@
<property name="toolTip">
<string>Press Ctrl+Alt+Del</string>
</property>
<property name="shortcut">
<string>Ctrl+F12</string>
</property>
</action>
<action name="actionCtrl_Alt_Esc">
<property name="icon">
@@ -426,9 +427,6 @@
<property name="text">
<string>&amp;Fullscreen</string>
</property>
<property name="shortcut">
<string>Ctrl+Alt+PgUp</string>
</property>
</action>
<action name="actionSoftware_Renderer">
<property name="checkable">
@@ -746,8 +744,36 @@
<property name="text">
<string>Take s&amp;creenshot</string>
</property>
<property name="shortcut">
<string>Ctrl+F11</string>
<property name="icon">
<iconset resource="../qt_resources.qrc">
<normaloff>:/menuicons/qt/icons/take_screenshot.ico</normaloff>:/menuicons/qt/icons/take_screenshot.ico</iconset>
</property>
</action>
<action name="actionTake_raw_screenshot">
<property name="text">
<string>Take &amp;raw screenshot</string>
</property>
<property name="icon">
<iconset resource="../qt_resources.qrc">
<normaloff>:/menuicons/qt/icons/take_raw_screenshot.ico</normaloff>:/menuicons/qt/icons/take_raw_screenshot.ico</iconset>
</property>
</action>
<action name="actionCopy_screenshot">
<property name="text">
<string>C&amp;opy screenshot</string>
</property>
<property name="icon">
<iconset resource="../qt_resources.qrc">
<normaloff>:/menuicons/qt/icons/copy_screenshot.ico</normaloff>:/menuicons/qt/icons/copy_screenshot.ico</iconset>
</property>
</action>
<action name="actionCopy_raw_screenshot">
<property name="text">
<string>Copy r&amp;aw screenshot</string>
</property>
<property name="icon">
<iconset resource="../qt_resources.qrc">
<normaloff>:/menuicons/qt/icons/copy_raw_screenshot.ico</normaloff>:/menuicons/qt/icons/copy_raw_screenshot.ico</iconset>
</property>
</action>
<action name="actionMute_Unmute">
@@ -1045,6 +1071,18 @@
<string>&amp;8x</string>
</property>
</action>
<action name="actionFast_forward">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="../qt_resources.qrc">
<normaloff>:/settings/qt/icons/fast_forward.ico</normaloff>:/settings/qt/icons/fast_forward.ico</iconset>
</property>
<property name="text">
<string>&amp;Fast forward</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@@ -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;
}

View File

@@ -26,6 +26,7 @@ extern MainWindow *main_window;
#include <QCoreApplication>
#include <QMessageBox>
#include <QWindow>
#include <QClipboard>
#include <QPainter>
#include <QWidget>
#include <QEvent>
@@ -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<std::tuple<uint8_t *, std::atomic_flag *>>
@@ -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);

Some files were not shown because too many files have changed in this diff Show More