mirror of
https://github.com/kanjitalk755/macemu.git
synced 2024-12-22 13:30:07 +00:00
- Implemented AppleTalk-over-UDP tunnelling, activated by setting "udptunnel"
to "true". This uses the BSD socket API, so it's fairly portable (currently only imeplemented under Unix, though). This works by sending raw Ethernet packets as UDP packets to a fixed port number ("udpport", default is 6066), using IP broadcasts to simulate Ethernet broad- and multicasts. Currently only tested with AppleTalk.
This commit is contained in:
parent
90c7198b75
commit
6c35c2a9e8
@ -10,6 +10,10 @@ V1.0 (snapshot) - <date>
|
||||
- ADBInterrupt() is no longer called from the 60Hz interrupt but has
|
||||
its own interrupt flag, potentially increasing the smoothness of
|
||||
mouse movement
|
||||
- ether.cpp: implemented relatively platform-independant "AppleTalk
|
||||
over UDP" mode that doesn't require any special kernel modules or
|
||||
network drivers but can only interconnect instances of Basilisk II;
|
||||
this is enabled by setting "udptunnel" to true
|
||||
- Unix: windowed display mode supports different resolutions and color
|
||||
depths, which can be switched on-the-fly
|
||||
- Unix: Ctrl-F5 grabs mouse in windowed mode (enhanced compatibility
|
||||
|
@ -394,6 +394,25 @@ ether <ethernet card description>
|
||||
not an Ethernet device, Basilisk II will display a warning message and
|
||||
disable Ethernet networking.
|
||||
|
||||
See the next item for an alternative way to do networking with Basilisk II.
|
||||
|
||||
udptunnel <"true" or "false">
|
||||
|
||||
Setting this to "true" enables a special network mode in which all network
|
||||
packets sent by MacOS are tunnelled over UDP using the host operating
|
||||
system's native TCP/IP stack. This only works with AppleTalk and can only
|
||||
be used to connect computers running Basilisk II (and not, for example, for
|
||||
connecting to an AppleShare server running on a real Mac), but it is
|
||||
probably the easiest way to set up a network between two instances of
|
||||
Basilisk II because the UDP tunnelling doesn't require any special kernel
|
||||
modules or network add-ons. It relies on IP broadcasting, however, so
|
||||
its range is limited.
|
||||
|
||||
udpport <IP port number>
|
||||
|
||||
This item specifies the IP port number to use for the "AppleTalk over UDP"
|
||||
tunnel mode. The default is 6066.
|
||||
|
||||
rom <ROM file path>
|
||||
|
||||
This item specifies the file name of the Mac ROM file to be used by
|
||||
|
@ -116,11 +116,11 @@ static int16 send_to_proc(uint32 what, uint32 pointer = 0, uint16 type = 0)
|
||||
* Initialization
|
||||
*/
|
||||
|
||||
void EtherInit(void)
|
||||
bool ether_init(void)
|
||||
{
|
||||
// Do nothing if no Ethernet device specified
|
||||
if (PrefsFindString("ether") == NULL)
|
||||
return;
|
||||
return false;
|
||||
|
||||
// Initialize protocol list
|
||||
NewList(&prot_list);
|
||||
@ -151,8 +151,7 @@ void EtherInit(void)
|
||||
goto open_error;
|
||||
|
||||
// Everything OK
|
||||
net_open = true;
|
||||
return;
|
||||
return true;
|
||||
|
||||
open_error:
|
||||
net_proc = NULL;
|
||||
@ -160,6 +159,7 @@ open_error:
|
||||
DeleteMsgPort(reply_port);
|
||||
reply_port = NULL;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -167,7 +167,7 @@ open_error:
|
||||
* Deinitialization
|
||||
*/
|
||||
|
||||
void EtherExit(void)
|
||||
void ether_exit(void)
|
||||
{
|
||||
// Stop process
|
||||
if (net_proc) {
|
||||
@ -191,7 +191,7 @@ void EtherExit(void)
|
||||
void EtherReset(void)
|
||||
{
|
||||
// Remove all protocols
|
||||
if (net_open)
|
||||
if (net_proc)
|
||||
send_to_proc(MSG_CLEANUP);
|
||||
}
|
||||
|
||||
|
@ -105,11 +105,11 @@ static void remove_all_protocols(void)
|
||||
* Initialization
|
||||
*/
|
||||
|
||||
void EtherInit(void)
|
||||
bool ether_init(void)
|
||||
{
|
||||
// Do nothing if no Ethernet device specified
|
||||
if (PrefsFindString("ether") == NULL)
|
||||
return;
|
||||
return false;
|
||||
|
||||
// Find net_server team
|
||||
i_wanna_try_that_again:
|
||||
@ -148,12 +148,12 @@ i_wanna_try_that_again:
|
||||
// It was found, so something else must be wrong
|
||||
if (sheep_net_found) {
|
||||
WarningAlert(GetString(STR_NO_NET_ADDON_WARN));
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Not found, inform the user
|
||||
if (!ChoiceAlert(GetString(STR_NET_CONFIG_MODIFY_WARN), GetString(STR_OK_BUTTON), GetString(STR_CANCEL_BUTTON)))
|
||||
return;
|
||||
return false;
|
||||
|
||||
// Change the network config file and restart the network
|
||||
fin = fopen("/boot/home/config/settings/network", "r");
|
||||
@ -208,16 +208,16 @@ i_wanna_try_that_again:
|
||||
area_id handler_buffer;
|
||||
if ((handler_buffer = find_area("packet buffer")) < B_NO_ERROR) {
|
||||
WarningAlert(GetString(STR_NET_ADDON_INIT_FAILED));
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if ((buffer_area = clone_area("local packet buffer", (void **)&net_buffer_ptr, B_ANY_ADDRESS, B_READ_AREA | B_WRITE_AREA, handler_buffer)) < B_NO_ERROR) {
|
||||
D(bug("EtherInit: couldn't clone packet area\n"));
|
||||
WarningAlert(GetString(STR_NET_ADDON_CLONE_FAILED));
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if ((read_sem = create_sem(0, "ether read")) < B_NO_ERROR) {
|
||||
printf("FATAL: can't create Ethernet semaphore\n");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
net_buffer_ptr->read_sem = read_sem;
|
||||
write_sem = net_buffer_ptr->write_sem;
|
||||
@ -233,7 +233,7 @@ i_wanna_try_that_again:
|
||||
D(bug("Ethernet address %02x %02x %02x %02x %02x %02x\n", ether_addr[0], ether_addr[1], ether_addr[2], ether_addr[3], ether_addr[4], ether_addr[5]));
|
||||
|
||||
// Everything OK
|
||||
net_open = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -241,27 +241,24 @@ i_wanna_try_that_again:
|
||||
* Deinitialization
|
||||
*/
|
||||
|
||||
void EtherExit(void)
|
||||
void ether_exit(void)
|
||||
{
|
||||
if (net_open) {
|
||||
// Close communications with add-on
|
||||
for (int i=0; i<WRITE_PACKET_COUNT; i++)
|
||||
net_buffer_ptr->write[i].cmd = IN_USE | (DEACTIVATE_SHEEP_NET << 8);
|
||||
release_sem(write_sem);
|
||||
|
||||
// Close communications with add-on
|
||||
for (int i=0; i<WRITE_PACKET_COUNT; i++)
|
||||
net_buffer_ptr->write[i].cmd = IN_USE | (DEACTIVATE_SHEEP_NET << 8);
|
||||
release_sem(write_sem);
|
||||
// Quit reception thread
|
||||
ether_thread_active = false;
|
||||
status_t result;
|
||||
release_sem(read_sem);
|
||||
wait_for_thread(read_thread, &result);
|
||||
|
||||
// Quit reception thread
|
||||
ether_thread_active = false;
|
||||
status_t result;
|
||||
release_sem(read_sem);
|
||||
wait_for_thread(read_thread, &result);
|
||||
delete_sem(read_sem);
|
||||
delete_area(buffer_area);
|
||||
|
||||
delete_sem(read_sem);
|
||||
delete_area(buffer_area);
|
||||
|
||||
// Remove all protocols
|
||||
remove_all_protocols();
|
||||
}
|
||||
// Remove all protocols
|
||||
remove_all_protocols();
|
||||
}
|
||||
|
||||
|
||||
@ -364,30 +361,21 @@ int16 ether_write(uint32 wds)
|
||||
} else {
|
||||
|
||||
// Copy packet to buffer
|
||||
uint8 *start;
|
||||
uint8 *bp = start = p->data;
|
||||
for (;;) {
|
||||
int len = ReadMacInt16(wds);
|
||||
if (len == 0)
|
||||
break;
|
||||
Mac2Host_memcpy(bp, ReadMacInt32(wds + 2), len);
|
||||
bp += len;
|
||||
wds += 6;
|
||||
}
|
||||
int len = ether_wds_to_buffer(wds, p->data);
|
||||
|
||||
// Set source address
|
||||
memcpy(start + 6, ether_addr, 6);
|
||||
memcpy(p->data + 6, ether_addr, 6);
|
||||
|
||||
#if MONITOR
|
||||
bug("Sending Ethernet packet:\n");
|
||||
for (int i=0; i<(uint32)(bp-start); i++) {
|
||||
bug("%02x ", start[i]);
|
||||
for (int i=0; i<len; i++) {
|
||||
bug("%02x ", p->data[i]);
|
||||
}
|
||||
bug("\n");
|
||||
#endif
|
||||
|
||||
// Notify add-on
|
||||
p->length = (uint32)(bp - start);
|
||||
p->length = len;
|
||||
p->cmd = IN_USE | (SHEEP_PACKET << 8);
|
||||
wr_pos = (wr_pos + 1) % WRITE_PACKET_COUNT;
|
||||
release_sem(write_sem);
|
||||
|
@ -27,8 +27,10 @@
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#if defined(__FreeBSD__)
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#if defined(__FreeBSD__)
|
||||
#include <net/if.h>
|
||||
#endif
|
||||
|
||||
@ -63,6 +65,7 @@ static pthread_attr_t ether_thread_attr; // Packet reception thread attributes
|
||||
static bool thread_active = false; // Flag: Packet reception thread installed
|
||||
static sem_t int_ack; // Interrupt acknowledge semaphore
|
||||
static bool is_ethertap; // Flag: Ethernet device is ethertap
|
||||
static bool udp_tunnel; // Flag: UDP tunnelling active, fd is the socket descriptor
|
||||
|
||||
// Prototypes
|
||||
static void *receive_func(void *arg);
|
||||
@ -106,11 +109,58 @@ static void remove_all_protocols(void)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Start packet reception thread
|
||||
*/
|
||||
|
||||
static bool start_thread(void)
|
||||
{
|
||||
if (sem_init(&int_ack, 0, 0) < 0) {
|
||||
printf("WARNING: Cannot init semaphore");
|
||||
return false;
|
||||
}
|
||||
|
||||
pthread_attr_init(ðer_thread_attr);
|
||||
#if defined(_POSIX_THREAD_PRIORITY_SCHEDULING)
|
||||
if (geteuid() == 0) {
|
||||
pthread_attr_setinheritsched(ðer_thread_attr, PTHREAD_EXPLICIT_SCHED);
|
||||
pthread_attr_setschedpolicy(ðer_thread_attr, SCHED_FIFO);
|
||||
struct sched_param fifo_param;
|
||||
fifo_param.sched_priority = (sched_get_priority_min(SCHED_FIFO) + sched_get_priority_max(SCHED_FIFO)) / 2 + 1;
|
||||
pthread_attr_setschedparam(ðer_thread_attr, &fifo_param);
|
||||
}
|
||||
#endif
|
||||
|
||||
thread_active = (pthread_create(ðer_thread, ðer_thread_attr, receive_func, NULL) == 0);
|
||||
if (!thread_active) {
|
||||
printf("WARNING: Cannot start Ethernet thread");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Stop packet reception thread
|
||||
*/
|
||||
|
||||
static void stop_thread(void)
|
||||
{
|
||||
if (thread_active) {
|
||||
pthread_cancel(ether_thread);
|
||||
pthread_join(ether_thread, NULL);
|
||||
sem_destroy(&int_ack);
|
||||
thread_active = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Initialization
|
||||
*/
|
||||
|
||||
void EtherInit(void)
|
||||
bool ether_init(void)
|
||||
{
|
||||
int nonblock = 1;
|
||||
char str[256];
|
||||
@ -118,7 +168,7 @@ void EtherInit(void)
|
||||
// Do nothing if no Ethernet device specified
|
||||
const char *name = PrefsFindString("ether");
|
||||
if (name == NULL)
|
||||
return;
|
||||
return false;
|
||||
|
||||
// Is it Ethertap?
|
||||
is_ethertap = (strncmp(name, "tap", 3) == 0);
|
||||
@ -162,41 +212,20 @@ void EtherInit(void)
|
||||
D(bug("Ethernet address %02x %02x %02x %02x %02x %02x\n", ether_addr[0], ether_addr[1], ether_addr[2], ether_addr[3], ether_addr[4], ether_addr[5]));
|
||||
|
||||
// Start packet reception thread
|
||||
if (sem_init(&int_ack, 0, 0) < 0) {
|
||||
printf("WARNING: Cannot init semaphore");
|
||||
if (!start_thread())
|
||||
goto open_error;
|
||||
}
|
||||
pthread_attr_init(ðer_thread_attr);
|
||||
#if defined(_POSIX_THREAD_PRIORITY_SCHEDULING)
|
||||
if (geteuid() == 0) {
|
||||
pthread_attr_setinheritsched(ðer_thread_attr, PTHREAD_EXPLICIT_SCHED);
|
||||
pthread_attr_setschedpolicy(ðer_thread_attr, SCHED_FIFO);
|
||||
struct sched_param fifo_param;
|
||||
fifo_param.sched_priority = (sched_get_priority_min(SCHED_FIFO) + sched_get_priority_max(SCHED_FIFO)) / 2 + 1;
|
||||
pthread_attr_setschedparam(ðer_thread_attr, &fifo_param);
|
||||
}
|
||||
#endif
|
||||
thread_active = (pthread_create(ðer_thread, ðer_thread_attr, receive_func, NULL) == 0);
|
||||
if (!thread_active) {
|
||||
printf("WARNING: Cannot start Ethernet thread");
|
||||
goto open_error;
|
||||
}
|
||||
|
||||
// Everything OK
|
||||
net_open = true;
|
||||
return;
|
||||
return true;
|
||||
|
||||
open_error:
|
||||
if (thread_active) {
|
||||
pthread_cancel(ether_thread);
|
||||
pthread_join(ether_thread, NULL);
|
||||
sem_destroy(&int_ack);
|
||||
thread_active = false;
|
||||
}
|
||||
stop_thread();
|
||||
|
||||
if (fd > 0) {
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -204,7 +233,7 @@ open_error:
|
||||
* Deinitialization
|
||||
*/
|
||||
|
||||
void EtherExit(void)
|
||||
void ether_exit(void)
|
||||
{
|
||||
// Stop reception thread
|
||||
if (thread_active) {
|
||||
@ -334,15 +363,7 @@ int16 ether_write(uint32 wds)
|
||||
len += 2;
|
||||
}
|
||||
#endif
|
||||
for (;;) {
|
||||
int w = ReadMacInt16(wds);
|
||||
if (w == 0)
|
||||
break;
|
||||
Mac2Host_memcpy(p, ReadMacInt32(wds + 2), w);
|
||||
len += w;
|
||||
p += w;
|
||||
wds += 6;
|
||||
}
|
||||
len += ether_wds_to_buffer(wds, p);
|
||||
|
||||
#if MONITOR
|
||||
bug("Sending Ethernet packet:\n");
|
||||
@ -361,6 +382,29 @@ int16 ether_write(uint32 wds)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Start UDP packet reception thread
|
||||
*/
|
||||
|
||||
bool ether_start_udp_thread(int socket_fd)
|
||||
{
|
||||
fd = socket_fd;
|
||||
udp_tunnel = true;
|
||||
return start_thread();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Stop UDP packet reception thread
|
||||
*/
|
||||
|
||||
void ether_stop_udp_thread(void)
|
||||
{
|
||||
stop_thread();
|
||||
fd = -1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Packet reception thread
|
||||
*/
|
||||
@ -397,59 +441,73 @@ void EtherInterrupt(void)
|
||||
|
||||
// Call protocol handler for received packets
|
||||
uint8 packet[1516];
|
||||
ssize_t length;
|
||||
for (;;) {
|
||||
|
||||
// Read packet from sheep_net device
|
||||
if (udp_tunnel) {
|
||||
|
||||
// Read packet from socket
|
||||
struct sockaddr_in from;
|
||||
socklen_t from_len = sizeof(from);
|
||||
length = recvfrom(fd, packet, 1514, 0, (struct sockaddr *)&from, &from_len);
|
||||
if (length < 14)
|
||||
break;
|
||||
ether_udp_read(packet, length, &from);
|
||||
|
||||
} else {
|
||||
|
||||
// Read packet from sheep_net device
|
||||
#if defined(__linux__)
|
||||
ssize_t length = read(fd, packet, is_ethertap ? 1516 : 1514);
|
||||
length = read(fd, packet, is_ethertap ? 1516 : 1514);
|
||||
#else
|
||||
ssize_t length = read(fd, packet, 1514);
|
||||
length = read(fd, packet, 1514);
|
||||
#endif
|
||||
if (length < 14)
|
||||
break;
|
||||
if (length < 14)
|
||||
break;
|
||||
|
||||
#if MONITOR
|
||||
bug("Receiving Ethernet packet:\n");
|
||||
for (int i=0; i<length; i++) {
|
||||
bug("%02x ", packet[i]);
|
||||
}
|
||||
bug("\n");
|
||||
bug("Receiving Ethernet packet:\n");
|
||||
for (int i=0; i<length; i++) {
|
||||
bug("%02x ", packet[i]);
|
||||
}
|
||||
bug("\n");
|
||||
#endif
|
||||
|
||||
// Pointer to packet data (Ethernet header)
|
||||
uint8 *p = packet;
|
||||
// Pointer to packet data (Ethernet header)
|
||||
uint8 *p = packet;
|
||||
#if defined(__linux__)
|
||||
if (is_ethertap) {
|
||||
p += 2; // Linux ethertap has two random bytes before the packet
|
||||
length -= 2;
|
||||
}
|
||||
if (is_ethertap) {
|
||||
p += 2; // Linux ethertap has two random bytes before the packet
|
||||
length -= 2;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Get packet type
|
||||
uint16 type = ntohs(*(uint16 *)(p + 12));
|
||||
// Get packet type
|
||||
uint16 type = (p[12] << 8) | p[13];
|
||||
|
||||
// Look for protocol
|
||||
NetProtocol *prot = find_protocol(type);
|
||||
if (prot == NULL)
|
||||
continue;
|
||||
// Look for protocol
|
||||
NetProtocol *prot = find_protocol(type);
|
||||
if (prot == NULL)
|
||||
continue;
|
||||
|
||||
// No default handler
|
||||
if (prot->handler == 0)
|
||||
continue;
|
||||
// No default handler
|
||||
if (prot->handler == 0)
|
||||
continue;
|
||||
|
||||
// Copy header to RHA
|
||||
Host2Mac_memcpy(ether_data + ed_RHA, p, 14);
|
||||
D(bug(" header %08lx%04lx %08lx%04lx %04lx\n", ReadMacInt32(ether_data + ed_RHA), ReadMacInt16(ether_data + ed_RHA + 4), ReadMacInt32(ether_data + ed_RHA + 6), ReadMacInt16(ether_data + ed_RHA + 10), ReadMacInt16(ether_data + ed_RHA + 12)));
|
||||
// Copy header to RHA
|
||||
Host2Mac_memcpy(ether_data + ed_RHA, p, 14);
|
||||
D(bug(" header %08x%04x %08x%04x %04x\n", ReadMacInt32(ether_data + ed_RHA), ReadMacInt16(ether_data + ed_RHA + 4), ReadMacInt32(ether_data + ed_RHA + 6), ReadMacInt16(ether_data + ed_RHA + 10), ReadMacInt16(ether_data + ed_RHA + 12)));
|
||||
|
||||
// Call protocol handler
|
||||
M68kRegisters r;
|
||||
r.d[0] = type; // Packet type
|
||||
r.d[1] = length - 14; // Remaining packet length (without header, for ReadPacket)
|
||||
r.a[0] = (uint32)p + 14; // Pointer to packet (host address, for ReadPacket)
|
||||
r.a[3] = ether_data + ed_RHA + 14; // Pointer behind header in RHA
|
||||
r.a[4] = ether_data + ed_ReadPacket; // Pointer to ReadPacket/ReadRest routines
|
||||
D(bug(" calling protocol handler %08lx, type %08lx, length %08lx, data %08lx, rha %08lx, read_packet %08lx\n", prot->handler, r.d[0], r.d[1], r.a[0], r.a[3], r.a[4]));
|
||||
Execute68k(prot->handler, &r);
|
||||
// Call protocol handler
|
||||
M68kRegisters r;
|
||||
r.d[0] = type; // Packet type
|
||||
r.d[1] = length - 14; // Remaining packet length (without header, for ReadPacket)
|
||||
r.a[0] = (uint32)p + 14; // Pointer to packet (host address, for ReadPacket)
|
||||
r.a[3] = ether_data + ed_RHA + 14; // Pointer behind header in RHA
|
||||
r.a[4] = ether_data + ed_ReadPacket; // Pointer to ReadPacket/ReadRest routines
|
||||
D(bug(" calling protocol handler %08x, type %08x, length %08x, data %08x, rha %08x, read_packet %08x\n", prot->handler, r.d[0], r.d[1], r.a[0], r.a[3], r.a[4]));
|
||||
Execute68k(prot->handler, &r);
|
||||
}
|
||||
}
|
||||
|
||||
// Acknowledge interrupt to reception thread
|
||||
|
@ -853,7 +853,23 @@ static void create_input_pane(GtkWidget *top)
|
||||
* "Serial/Network" pane
|
||||
*/
|
||||
|
||||
static GtkWidget *w_seriala, *w_serialb, *w_ether;
|
||||
static GtkWidget *w_seriala, *w_serialb, *w_ether, *w_udp_port;
|
||||
|
||||
// Set sensitivity of widgets
|
||||
static void set_serial_sensitive(void)
|
||||
{
|
||||
#if SUPPORTS_UDP_TUNNEL
|
||||
gtk_widget_set_sensitive(w_ether, !PrefsFindBool("udptunnel"));
|
||||
gtk_widget_set_sensitive(w_udp_port, PrefsFindBool("udptunnel"));
|
||||
#endif
|
||||
}
|
||||
|
||||
// "Tunnel AppleTalk over IP" button toggled
|
||||
static void tb_udptunnel(GtkWidget *widget)
|
||||
{
|
||||
PrefsReplaceBool("udptunnel", GTK_TOGGLE_BUTTON(widget)->active);
|
||||
set_serial_sensitive();
|
||||
}
|
||||
|
||||
// Read settings from widgets and set preferences
|
||||
static void read_serial_settings(void)
|
||||
@ -871,6 +887,10 @@ static void read_serial_settings(void)
|
||||
PrefsReplaceString("ether", str);
|
||||
else
|
||||
PrefsRemoveItem("ether");
|
||||
|
||||
#if SUPPORTS_UDP_TUNNEL
|
||||
PrefsReplaceInt32("udpport", gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(w_udp_port)));
|
||||
#endif
|
||||
}
|
||||
|
||||
// Add names of serial devices
|
||||
@ -954,8 +974,8 @@ static GList *add_ether_names(void)
|
||||
// Create "Serial/Network" pane
|
||||
static void create_serial_pane(GtkWidget *top)
|
||||
{
|
||||
GtkWidget *box, *table, *label, *combo, *sep;
|
||||
GList *glist = add_serial_names();
|
||||
GtkWidget *box, *hbox, *table, *label, *combo, *sep;
|
||||
GtkObject *adj;
|
||||
|
||||
box = make_pane(top, STR_SERIAL_NETWORK_PANE_TITLE);
|
||||
table = make_table(box, 2, 4);
|
||||
@ -964,6 +984,7 @@ static void create_serial_pane(GtkWidget *top)
|
||||
gtk_widget_show(label);
|
||||
gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, (GtkAttachOptions)0, (GtkAttachOptions)0, 4, 4);
|
||||
|
||||
GList *glist = add_serial_names();
|
||||
combo = gtk_combo_new();
|
||||
gtk_widget_show(combo);
|
||||
gtk_combo_set_popdown_strings(GTK_COMBO(combo), glist);
|
||||
@ -1006,6 +1027,25 @@ static void create_serial_pane(GtkWidget *top)
|
||||
gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(combo)->entry), str);
|
||||
gtk_table_attach(GTK_TABLE(table), combo, 1, 2, 3, 4, (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), (GtkAttachOptions)0, 4, 4);
|
||||
w_ether = GTK_COMBO(combo)->entry;
|
||||
|
||||
#if SUPPORTS_UDP_TUNNEL
|
||||
make_checkbox(box, STR_UDPTUNNEL_CTRL, "udptunnel", GTK_SIGNAL_FUNC(tb_udptunnel));
|
||||
|
||||
hbox = gtk_hbox_new(FALSE, 4);
|
||||
gtk_widget_show(hbox);
|
||||
gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
|
||||
|
||||
label = gtk_label_new(GetString(STR_UDPPORT_CTRL));
|
||||
gtk_widget_show(label);
|
||||
gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
|
||||
|
||||
adj = gtk_adjustment_new(PrefsFindInt32("udpport"), 1, 65535, 1, 5, 0);
|
||||
w_udp_port = gtk_spin_button_new(GTK_ADJUSTMENT(adj), 0.0, 0);
|
||||
gtk_widget_show(w_udp_port);
|
||||
gtk_box_pack_start(GTK_BOX(hbox), w_udp_port, FALSE, FALSE, 0);
|
||||
#endif
|
||||
|
||||
set_serial_sensitive();
|
||||
}
|
||||
|
||||
|
||||
|
@ -103,6 +103,9 @@
|
||||
/* ExtFS is supported */
|
||||
#define SUPPORTS_EXTFS 1
|
||||
|
||||
/* BSD socket API supported */
|
||||
#define SUPPORTS_UDP_TUNNEL 1
|
||||
|
||||
|
||||
/* Data types */
|
||||
typedef unsigned char uint8;
|
||||
|
@ -32,18 +32,153 @@
|
||||
#include "main.h"
|
||||
#include "macos_util.h"
|
||||
#include "emul_op.h"
|
||||
#include "prefs.h"
|
||||
#include "ether.h"
|
||||
#include "ether_defs.h"
|
||||
|
||||
#if SUPPORTS_UDP_TUNNEL
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <netdb.h>
|
||||
#include <errno.h>
|
||||
#endif
|
||||
|
||||
#include <map>
|
||||
|
||||
#ifndef NO_STD_NAMESPACE
|
||||
using std::map;
|
||||
#endif
|
||||
|
||||
#define DEBUG 0
|
||||
#include "debug.h"
|
||||
|
||||
#define MONITOR 0
|
||||
|
||||
|
||||
// Global variables
|
||||
uint8 ether_addr[6]; // Ethernet address (set by EtherInit())
|
||||
bool net_open = false; // Flag: initialization succeeded, network device open (set by EtherInit())
|
||||
uint8 ether_addr[6]; // Ethernet address (set by ether_init())
|
||||
static bool net_open = false; // Flag: initialization succeeded, network device open (set by EtherInit())
|
||||
|
||||
uint32 ether_data = 0; // Mac address of driver data in MacOS RAM
|
||||
static bool udp_tunnel = false; // Flag: tunnelling AppleTalk over UDP using BSD socket API
|
||||
static uint16 udp_port;
|
||||
static int udp_socket = -1;
|
||||
|
||||
// Mac address of driver data in MacOS RAM
|
||||
uint32 ether_data = 0;
|
||||
|
||||
// Attached network protocols for UDP tunneling, maps protocol type to MacOS handler address
|
||||
static map<uint16, uint32> udp_protocols;
|
||||
|
||||
|
||||
/*
|
||||
* Initialization
|
||||
*/
|
||||
|
||||
void EtherInit(void)
|
||||
{
|
||||
net_open = false;
|
||||
udp_tunnel = false;
|
||||
|
||||
#if SUPPORTS_UDP_TUNNEL
|
||||
// UDP tunnelling requested?
|
||||
if (PrefsFindBool("udptunnel")) {
|
||||
udp_tunnel = true;
|
||||
udp_port = PrefsFindInt32("udpport");
|
||||
|
||||
// Open UDP socket
|
||||
udp_socket = socket(PF_INET, SOCK_DGRAM, 0);
|
||||
if (udp_socket < 0) {
|
||||
perror("socket");
|
||||
return;
|
||||
}
|
||||
|
||||
// Bind to specified address and port
|
||||
struct sockaddr_in sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_addr.s_addr = INADDR_ANY;
|
||||
sa.sin_port = htons(udp_port);
|
||||
if (bind(udp_socket, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
|
||||
perror("bind");
|
||||
close(udp_socket);
|
||||
udp_socket = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve local IP address (or at least one of them)
|
||||
socklen_t sa_len = sizeof(sa);
|
||||
getsockname(udp_socket, (struct sockaddr *)&sa, &sa_len);
|
||||
uint32 udp_ip = sa.sin_addr.s_addr;
|
||||
if (udp_ip == INADDR_ANY || udp_ip == INADDR_LOOPBACK) {
|
||||
char name[256];
|
||||
gethostname(name, sizeof(name));
|
||||
struct hostent *local = gethostbyname(name);
|
||||
if (local)
|
||||
udp_ip = *(uint32 *)local->h_addr_list[0];
|
||||
}
|
||||
udp_ip = ntohl(udp_ip);
|
||||
|
||||
// Construct dummy Ethernet address from local IP address
|
||||
ether_addr[0] = 'B';
|
||||
ether_addr[1] = '2';
|
||||
ether_addr[2] = udp_ip >> 24;
|
||||
ether_addr[3] = udp_ip >> 16;
|
||||
ether_addr[4] = udp_ip >> 8;
|
||||
ether_addr[5] = udp_ip;
|
||||
D(bug("Ethernet address %02x %02x %02x %02x %02x %02x\n", ether_addr[0], ether_addr[1], ether_addr[2], ether_addr[3], ether_addr[4], ether_addr[5]));
|
||||
|
||||
// Set socket options
|
||||
int on = 1;
|
||||
setsockopt(udp_socket, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
|
||||
ioctl(udp_socket, FIONBIO, &on);
|
||||
|
||||
// Start thread for packet reception
|
||||
if (!ether_start_udp_thread(udp_socket)) {
|
||||
close(udp_socket);
|
||||
udp_socket = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
net_open = true;
|
||||
} else
|
||||
#endif
|
||||
if (ether_init())
|
||||
net_open = true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Deinitialization
|
||||
*/
|
||||
|
||||
void EtherExit(void)
|
||||
{
|
||||
if (net_open) {
|
||||
#if SUPPORTS_UDP_TUNNEL
|
||||
if (udp_tunnel) {
|
||||
if (udp_socket >= 0) {
|
||||
ether_stop_udp_thread();
|
||||
close(udp_socket);
|
||||
udp_socket = -1;
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
ether_exit();
|
||||
net_open = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Check whether Ethernet address is AppleTalk broadcast address
|
||||
*/
|
||||
|
||||
static inline bool is_apple_talk_broadcast(uint8 *p)
|
||||
{
|
||||
return p[0] == 0x09 && p[1] == 0x00 && p[2] == 0x07
|
||||
&& p[3] == 0xff && p[4] == 0xff && p[5] == 0xff;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
@ -61,7 +196,7 @@ int16 EtherOpen(uint32 pb, uint32 dce)
|
||||
if (r.a[0] == 0)
|
||||
return openErr;
|
||||
ether_data = r.a[0];
|
||||
D(bug(" data %08lx\n", ether_data));
|
||||
D(bug(" data %08x\n", ether_data));
|
||||
|
||||
WriteMacInt16(ether_data + ed_DeferredTask + qType, dtQType);
|
||||
WriteMacInt32(ether_data + ed_DeferredTask + dtAddr, ether_data + ed_Code);
|
||||
@ -99,48 +234,98 @@ int16 EtherControl(uint32 pb, uint32 dce)
|
||||
uint16 code = ReadMacInt16(pb + csCode);
|
||||
D(bug("EtherControl %d\n", code));
|
||||
switch (code) {
|
||||
case 1: // KillIO
|
||||
case 1: // KillIO
|
||||
return -1;
|
||||
|
||||
case kENetAddMulti: // Add multicast address
|
||||
D(bug("AddMulti %08lx%04x\n", ReadMacInt32(pb + eMultiAddr), ReadMacInt16(pb + eMultiAddr + 4)));
|
||||
if (net_open)
|
||||
D(bug(" AddMulti %08x%04x\n", ReadMacInt32(pb + eMultiAddr), ReadMacInt16(pb + eMultiAddr + 4)));
|
||||
if (net_open && !udp_tunnel)
|
||||
return ether_add_multicast(pb);
|
||||
else
|
||||
return noErr;
|
||||
return noErr;
|
||||
|
||||
case kENetDelMulti: // Delete multicast address
|
||||
D(bug("DelMulti %08lx%04x\n", ReadMacInt32(pb + eMultiAddr), ReadMacInt16(pb + eMultiAddr + 4)));
|
||||
if (net_open)
|
||||
D(bug(" DelMulti %08x%04x\n", ReadMacInt32(pb + eMultiAddr), ReadMacInt16(pb + eMultiAddr + 4)));
|
||||
if (net_open && !udp_tunnel)
|
||||
return ether_del_multicast(pb);
|
||||
else
|
||||
return noErr;
|
||||
return noErr;
|
||||
|
||||
case kENetAttachPH: // Attach protocol handler
|
||||
D(bug("AttachPH prot %04x, handler %08lx\n", ReadMacInt16(pb + eProtType), ReadMacInt32(pb + ePointer)));
|
||||
if (net_open)
|
||||
return ether_attach_ph(ReadMacInt16(pb + eProtType), ReadMacInt32(pb + ePointer));
|
||||
else
|
||||
return noErr;
|
||||
case kENetAttachPH: { // Attach protocol handler
|
||||
uint16 type = ReadMacInt16(pb + eProtType);
|
||||
uint32 handler = ReadMacInt32(pb + ePointer);
|
||||
D(bug(" AttachPH prot %04x, handler %08x\n", type, handler));
|
||||
if (net_open) {
|
||||
if (udp_tunnel) {
|
||||
if (udp_protocols.find(type) != udp_protocols.end())
|
||||
return lapProtErr;
|
||||
udp_protocols[type] = handler;
|
||||
} else
|
||||
return ether_attach_ph(type, handler);
|
||||
}
|
||||
return noErr;
|
||||
}
|
||||
|
||||
case kENetDetachPH: // Detach protocol handler
|
||||
D(bug("DetachPH prot %04x\n", ReadMacInt16(pb + eProtType)));
|
||||
if (net_open)
|
||||
return ether_detach_ph(ReadMacInt16(pb + eProtType));
|
||||
else
|
||||
return noErr;
|
||||
case kENetDetachPH: { // Detach protocol handler
|
||||
uint16 type = ReadMacInt16(pb + eProtType);
|
||||
D(bug(" DetachPH prot %04x\n", type));
|
||||
if (net_open) {
|
||||
if (udp_tunnel) {
|
||||
if (udp_protocols.erase(type) == 0)
|
||||
return lapProtErr;
|
||||
} else
|
||||
return ether_detach_ph(type);
|
||||
}
|
||||
return noErr;
|
||||
}
|
||||
|
||||
case kENetWrite: // Transmit raw Ethernet packet
|
||||
D(bug("EtherWrite\n"));
|
||||
if (ReadMacInt16(ReadMacInt32(pb + ePointer)) < 14)
|
||||
case kENetWrite: { // Transmit raw Ethernet packet
|
||||
uint32 wds = ReadMacInt32(pb + ePointer);
|
||||
D(bug(" EtherWrite\n"));
|
||||
if (ReadMacInt16(wds) < 14)
|
||||
return eLenErr; // Header incomplete
|
||||
if (net_open)
|
||||
return ether_write(ReadMacInt32(pb + ePointer));
|
||||
else
|
||||
return noErr;
|
||||
if (net_open) {
|
||||
#if SUPPORTS_UDP_TUNNEL
|
||||
if (udp_tunnel) {
|
||||
|
||||
// Copy packet to buffer
|
||||
uint8 packet[1514];
|
||||
int len = ether_wds_to_buffer(wds, packet);
|
||||
|
||||
// Set source address and extract destination address
|
||||
memcpy(packet + 6, ether_addr, 6);
|
||||
uint32 dest_ip;
|
||||
if (packet[0] == 'B' && packet[1] == '2')
|
||||
dest_ip = (packet[2] << 24) | (packet[3] << 16) | (packet[4] << 8) | packet[5];
|
||||
else if (is_apple_talk_broadcast(packet))
|
||||
dest_ip = INADDR_BROADCAST;
|
||||
else
|
||||
return eMultiErr;
|
||||
|
||||
#if MONITOR
|
||||
bug("Sending Ethernet packet:\n");
|
||||
for (int i=0; i<len; i++) {
|
||||
bug("%02x ", packet[i]);
|
||||
}
|
||||
bug("\n");
|
||||
#endif
|
||||
|
||||
// Send packet
|
||||
struct sockaddr_in sa;
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_addr.s_addr = htonl(dest_ip);
|
||||
sa.sin_port = htons(udp_port);
|
||||
if (sendto(udp_socket, packet, len, 0, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
|
||||
D(bug("WARNING: Couldn't transmit packet\n"));
|
||||
return excessCollsns;
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
return ether_write(wds);
|
||||
}
|
||||
return noErr;
|
||||
}
|
||||
|
||||
case kENetGetInfo: { // Get device information/statistics
|
||||
D(bug("GetInfo buf %08lx, size %d\n", ReadMacInt32(pb + ePointer), ReadMacInt16(pb + eBuffSize)));
|
||||
D(bug(" GetInfo buf %08x, size %d\n", ReadMacInt32(pb + ePointer), ReadMacInt16(pb + eBuffSize)));
|
||||
|
||||
// Collect info (only ethernet address)
|
||||
uint8 buf[18];
|
||||
@ -157,7 +342,7 @@ int16 EtherControl(uint32 pb, uint32 dce)
|
||||
}
|
||||
|
||||
case kENetSetGeneral: // Set general mode (always in general mode)
|
||||
D(bug("SetGeneral\n"));
|
||||
D(bug(" SetGeneral\n"));
|
||||
return noErr;
|
||||
|
||||
default:
|
||||
@ -173,7 +358,7 @@ int16 EtherControl(uint32 pb, uint32 dce)
|
||||
|
||||
void EtherReadPacket(uint8 **src, uint32 &dest, uint32 &len, uint32 &remaining)
|
||||
{
|
||||
D(bug("EtherReadPacket src %08lx, dest %08lx, len %08lx, remaining %08lx\n", *src, dest, len, remaining));
|
||||
D(bug("EtherReadPacket src %p, dest %08x, len %08x, remaining %08x\n", *src, dest, len, remaining));
|
||||
uint32 todo = len > remaining ? remaining : len;
|
||||
Host2Mac_memcpy(dest, *src, todo);
|
||||
*src += todo;
|
||||
@ -181,3 +366,50 @@ void EtherReadPacket(uint8 **src, uint32 &dest, uint32 &len, uint32 &remaining)
|
||||
len -= todo;
|
||||
remaining -= todo;
|
||||
}
|
||||
|
||||
|
||||
#if SUPPORTS_UDP_TUNNEL
|
||||
/*
|
||||
* Read packet from UDP socket
|
||||
*/
|
||||
|
||||
void ether_udp_read(uint8 *packet, int length, struct sockaddr_in *from)
|
||||
{
|
||||
// Drop packets sent by us
|
||||
if (memcmp(packet + 6, ether_addr, 6) == 0)
|
||||
return;
|
||||
|
||||
#if MONITOR
|
||||
bug("Receiving Ethernet packet:\n");
|
||||
for (int i=0; i<length; i++) {
|
||||
bug("%02x ", packet[i]);
|
||||
}
|
||||
bug("\n");
|
||||
#endif
|
||||
|
||||
// Get packet type
|
||||
uint16 type = (packet[12] << 8) | packet[13];
|
||||
|
||||
// Look for protocol
|
||||
uint16 search_type = (type <= 1500 ? 0 : type);
|
||||
if (udp_protocols.find(search_type) == udp_protocols.end())
|
||||
return;
|
||||
uint32 handler = udp_protocols[search_type];
|
||||
if (handler == 0)
|
||||
return;
|
||||
|
||||
// Copy header to RHA
|
||||
Host2Mac_memcpy(ether_data + ed_RHA, packet, 14);
|
||||
D(bug(" header %08x%04x %08x%04x %04x\n", ReadMacInt32(ether_data + ed_RHA), ReadMacInt16(ether_data + ed_RHA + 4), ReadMacInt32(ether_data + ed_RHA + 6), ReadMacInt16(ether_data + ed_RHA + 10), ReadMacInt16(ether_data + ed_RHA + 12)));
|
||||
|
||||
// Call protocol handler
|
||||
M68kRegisters r;
|
||||
r.d[0] = type; // Packet type
|
||||
r.d[1] = length - 14; // Remaining packet length (without header, for ReadPacket)
|
||||
r.a[0] = (uint32)packet + 14; // Pointer to packet (host address, for ReadPacket)
|
||||
r.a[3] = ether_data + ed_RHA + 14; // Pointer behind header in RHA
|
||||
r.a[4] = ether_data + ed_ReadPacket; // Pointer to ReadPacket/ReadRest routines
|
||||
D(bug(" calling protocol handler %08x, type %08x, length %08x, data %08x, rha %08x, read_packet %08x\n", handler, r.d[0], r.d[1], r.a[0], r.a[3], r.a[4]));
|
||||
Execute68k(handler, &r);
|
||||
}
|
||||
#endif
|
||||
|
@ -21,24 +21,31 @@
|
||||
#ifndef ETHER_H
|
||||
#define ETHER_H
|
||||
|
||||
struct sockaddr_in;
|
||||
|
||||
extern void EtherInit(void);
|
||||
extern void EtherExit(void);
|
||||
|
||||
extern int16 EtherOpen(uint32 pb, uint32 dce);
|
||||
extern int16 EtherControl(uint32 pb, uint32 dce);
|
||||
extern void EtherReadPacket(uint8 **src, uint32 &dest, uint32 &len, uint32 &remaining);
|
||||
|
||||
// System specific and internal functions/data
|
||||
extern void EtherInit(void);
|
||||
extern void EtherExit(void);
|
||||
extern void EtherReset(void);
|
||||
extern void EtherInterrupt(void);
|
||||
|
||||
extern bool ether_init(void);
|
||||
extern void ether_exit(void);
|
||||
extern int16 ether_add_multicast(uint32 pb);
|
||||
extern int16 ether_del_multicast(uint32 pb);
|
||||
extern int16 ether_attach_ph(uint16 type, uint32 handler);
|
||||
extern int16 ether_detach_ph(uint16 type);
|
||||
extern int16 ether_write(uint32 wds);
|
||||
extern bool ether_start_udp_thread(int socket_fd);
|
||||
extern void ether_stop_udp_thread(void);
|
||||
extern void ether_udp_read(uint8 *packet, int length, struct sockaddr_in *from);
|
||||
|
||||
extern uint8 ether_addr[6]; // Ethernet address (set by EtherInit())
|
||||
extern bool net_open; // Flag: initialization succeeded, network device open (set by EtherInit())
|
||||
extern uint8 ether_addr[6]; // Ethernet address (set by ether_init())
|
||||
|
||||
// Ethernet driver data in MacOS RAM
|
||||
enum {
|
||||
@ -53,4 +60,21 @@ enum {
|
||||
|
||||
extern uint32 ether_data; // Mac address of driver data in MacOS RAM
|
||||
|
||||
// Copy packet data from WDS to linear buffer (must hold at least 1514 bytes),
|
||||
// returns packet length
|
||||
static inline int ether_wds_to_buffer(uint32 wds, uint8 *p)
|
||||
{
|
||||
int len = 0;
|
||||
while (len < 1514) {
|
||||
int w = ReadMacInt16(wds);
|
||||
if (w == 0)
|
||||
break;
|
||||
Mac2Host_memcpy(p, ReadMacInt32(wds + 2), w);
|
||||
len += w;
|
||||
p += w;
|
||||
wds += 6;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -169,6 +169,8 @@ enum {
|
||||
STR_ISPAR_CTRL,
|
||||
STR_ETHER_ENABLE_CTRL,
|
||||
STR_ETHERNET_IF_CTRL,
|
||||
STR_UDPTUNNEL_CTRL,
|
||||
STR_UDPPORT_CTRL,
|
||||
|
||||
STR_MEMORY_MISC_PANE_TITLE = 3600, // Memory/Misc pane
|
||||
STR_RAMSIZE_SLIDER,
|
||||
|
@ -43,6 +43,8 @@ prefs_desc common_prefs_items[] = {
|
||||
{"seriala", TYPE_STRING, false, "device name of Mac serial port A"},
|
||||
{"serialb", TYPE_STRING, false, "device name of Mac serial port B"},
|
||||
{"ether", TYPE_STRING, false, "device name of Mac ethernet adapter"},
|
||||
{"udptunnel", TYPE_BOOLEAN, false, "tunnel all network packets over UDP"},
|
||||
{"udpport", TYPE_INT32, false, "IP port number for tunneling"},
|
||||
{"rom", TYPE_STRING, false, "path of ROM file"},
|
||||
{"bootdrive", TYPE_INT32, false, "boot drive number"},
|
||||
{"bootdriver", TYPE_INT32, false, "boot driver number"},
|
||||
@ -66,6 +68,8 @@ prefs_desc common_prefs_items[] = {
|
||||
void AddPrefsDefaults(void)
|
||||
{
|
||||
SysAddSerialPrefs();
|
||||
PrefsAddBool("udptunnel", false);
|
||||
PrefsAddInt32("udpport", 6066);
|
||||
PrefsAddInt32("bootdriver", 0);
|
||||
PrefsAddInt32("bootdrive", 0);
|
||||
PrefsAddInt32("ramsize", 8 * 1024 * 1024);
|
||||
|
@ -184,6 +184,8 @@ user_string_def common_strings[] = {
|
||||
{STR_ISPAR_CTRL, "Parallel Device"},
|
||||
{STR_ETHER_ENABLE_CTRL, "Enable Ethernet"},
|
||||
{STR_ETHERNET_IF_CTRL, "Ethernet Interface"},
|
||||
{STR_UDPTUNNEL_CTRL, "Tunnel AppleTalk over UDP"},
|
||||
{STR_UDPPORT_CTRL, "UDP Port Number"},
|
||||
|
||||
{STR_MEMORY_MISC_PANE_TITLE, "Memory/Misc"},
|
||||
{STR_RAMSIZE_SLIDER, "MacOS RAM Size:"},
|
||||
|
Loading…
Reference in New Issue
Block a user