diff --git a/AppleWinExpress2019.vcxproj b/AppleWinExpress2019.vcxproj index 301e31e6..573a9e0b 100644 --- a/AppleWinExpress2019.vcxproj +++ b/AppleWinExpress2019.vcxproj @@ -114,6 +114,7 @@ + @@ -222,6 +223,7 @@ + @@ -467,7 +469,7 @@ Windows true - htmlhelp.lib;comctl32.lib;winmm.lib;dsound.lib;dxguid.lib;version.lib;strmiids.lib;dinput8.lib;user32.lib;gdi32.lib;advapi32.lib;shell32.lib;comdlg32.lib;ole32.lib;wsock32.lib;shlwapi.lib;ddraw.lib;%(AdditionalDependencies) + iphlpapi.lib;htmlhelp.lib;comctl32.lib;winmm.lib;dsound.lib;dxguid.lib;version.lib;strmiids.lib;dinput8.lib;user32.lib;gdi32.lib;advapi32.lib;shell32.lib;comdlg32.lib;ole32.lib;wsock32.lib;shlwapi.lib;ddraw.lib;%(AdditionalDependencies) "type='Win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'" 5.01 @@ -495,7 +497,7 @@ Windows true - htmlhelp.lib;comctl32.lib;winmm.lib;dsound.lib;ddraw_lib\x86\dxguid.lib;version.lib;strmiids.lib;dinput8.lib;user32.lib;gdi32.lib;advapi32.lib;shell32.lib;comdlg32.lib;ole32.lib;wsock32.lib;shlwapi.lib;ddraw_lib\x86\ddraw.lib;%(AdditionalDependencies) + iphlpapi.lib;htmlhelp.lib;comctl32.lib;winmm.lib;dsound.lib;ddraw_lib\x86\dxguid.lib;version.lib;strmiids.lib;dinput8.lib;user32.lib;gdi32.lib;advapi32.lib;shell32.lib;comdlg32.lib;ole32.lib;wsock32.lib;shlwapi.lib;ddraw_lib\x86\ddraw.lib;%(AdditionalDependencies) "type='Win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'" 5.01 @@ -522,7 +524,7 @@ Windows true - htmlhelp.lib;comctl32.lib;winmm.lib;dsound.lib;dxguid.lib;version.lib;strmiids.lib;dinput8.lib;user32.lib;gdi32.lib;advapi32.lib;shell32.lib;comdlg32.lib;ole32.lib;wsock32.lib;shlwapi.lib;%(AdditionalDependencies) + iphlpapi.lib;htmlhelp.lib;comctl32.lib;winmm.lib;dsound.lib;dxguid.lib;version.lib;strmiids.lib;dinput8.lib;user32.lib;gdi32.lib;advapi32.lib;shell32.lib;comdlg32.lib;ole32.lib;wsock32.lib;shlwapi.lib;%(AdditionalDependencies) "type='Win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'" 5.01 @@ -553,7 +555,7 @@ true true true - htmlhelp.lib;comctl32.lib;winmm.lib;dsound.lib;dxguid.lib;version.lib;strmiids.lib;dinput8.lib;user32.lib;gdi32.lib;advapi32.lib;shell32.lib;comdlg32.lib;ole32.lib;wsock32.lib;shlwapi.lib;ddraw.lib;%(AdditionalDependencies) + iphlpapi.lib;htmlhelp.lib;comctl32.lib;winmm.lib;dsound.lib;dxguid.lib;version.lib;strmiids.lib;dinput8.lib;user32.lib;gdi32.lib;advapi32.lib;shell32.lib;comdlg32.lib;ole32.lib;wsock32.lib;shlwapi.lib;ddraw.lib;%(AdditionalDependencies) UseLinkTimeCodeGeneration "type='Win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'" 5.01 @@ -586,7 +588,7 @@ true true true - htmlhelp.lib;comctl32.lib;winmm.lib;dsound.lib;ddraw_lib\x86\dxguid.lib;version.lib;strmiids.lib;dinput8.lib;user32.lib;gdi32.lib;advapi32.lib;shell32.lib;comdlg32.lib;ole32.lib;wsock32.lib;shlwapi.lib;ddraw_lib\x86\ddraw.lib;%(AdditionalDependencies) + iphlpapi.lib;htmlhelp.lib;comctl32.lib;winmm.lib;dsound.lib;ddraw_lib\x86\dxguid.lib;version.lib;strmiids.lib;dinput8.lib;user32.lib;gdi32.lib;advapi32.lib;shell32.lib;comdlg32.lib;ole32.lib;wsock32.lib;shlwapi.lib;ddraw_lib\x86\ddraw.lib;%(AdditionalDependencies) UseLinkTimeCodeGeneration "type='Win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'" 5.01 @@ -619,7 +621,7 @@ true true true - htmlhelp.lib;comctl32.lib;winmm.lib;dsound.lib;dxguid.lib;version.lib;strmiids.lib;dinput8.lib;user32.lib;gdi32.lib;advapi32.lib;shell32.lib;comdlg32.lib;ole32.lib;wsock32.lib;shlwapi.lib;%(AdditionalDependencies) + iphlpapi.lib;htmlhelp.lib;comctl32.lib;winmm.lib;dsound.lib;dxguid.lib;version.lib;strmiids.lib;dinput8.lib;user32.lib;gdi32.lib;advapi32.lib;shell32.lib;comdlg32.lib;ole32.lib;wsock32.lib;shlwapi.lib;%(AdditionalDependencies) UseLinkTimeCodeGeneration "type='Win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'" 5.01 diff --git a/AppleWinExpress2019.vcxproj.filters b/AppleWinExpress2019.vcxproj.filters index 617a7d87..c3ce4b15 100644 --- a/AppleWinExpress2019.vcxproj.filters +++ b/AppleWinExpress2019.vcxproj.filters @@ -259,6 +259,9 @@ Source Files\Uthernet + + Source Files\Uthernet + @@ -594,6 +597,9 @@ Source Files\Uthernet + + Source Files\Uthernet + diff --git a/README.md b/README.md index ebfc0b06..43a3486e 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ AppleWin AppleWin is a fully-featured emulator supporting different Apple II models and clones. A variety of peripheral cards and video display modes are supported (eg. NTSC, RGB); and there's an extensive built-in symbolic debugger. -Peripheral cards supported: +Peripheral cards and add-on hardware supported: - Mockingboard, Phasor and SAM sound cards - Disk II interface for floppy disk drives - Hard disk controller @@ -17,6 +17,8 @@ Peripheral cards supported: - CP/M SoftCard - Uthernet I (ethernet card) - Language Card and Saturn 64/128K for Apple II/II+ +- 4Play and SNES MAX joystick cards +- VidHD card (functionality limited to IIgs' Super Hi-Res video modes) - No Slot Clock (NSC) diff --git a/help/acknowledgements.html b/help/acknowledgements.html index 4884f11d..8cd91657 100644 --- a/help/acknowledgements.html +++ b/help/acknowledgements.html @@ -21,7 +21,7 @@

Bob Sander-Cederlof: Applesoft Symbols (http://www.txbobsc.com/scsc/scdocumentor/ S-C DocuMentor: Applesoft)

David Schmidt: Updates to this help file

Mike Harvey, Founder & Editor of Nibble Magazine: For providing us Apple fans the pleasure of eagerly awaiting each next month's issue to learn about the Apple! (http://www.nibblemagazine.com/)

-

Andrea Odetti: working on making the source code more portable

+

Andrea Odetti: working on making the source code more portable & Uthernet II card support

Iván Izaguirre: Taiwanese Copam Base64A Apple II clone

Arnaud C: debugger suggestions and help with 6502/6522/video timing issues

Cyril Lambin: RGB card/monitor rendering, debugger improvements

diff --git a/help/cfg-config.html b/help/cfg-config.html index 69a9d549..deaa397d 100644 --- a/help/cfg-config.html +++ b/help/cfg-config.html @@ -78,7 +78,7 @@ Ethernet Settings...:
This allows to choose which network interface card (NIC) you want to - use with the Uthernet card.
+ use with the Uthernet or Uthernet II card.

Emulation Speed Control:
diff --git a/help/savestate.html b/help/savestate.html index 4b5c35e3..06589606 100644 --- a/help/savestate.html +++ b/help/savestate.html @@ -32,7 +32,7 @@
  • Parallel Printer card
  • Super Serial card
  • No-Slot clock
  • -
  • Uthernet card
  • +
  • Uthernet & Uthernet II cards
  • 4Play & SNES MAX joystick cards
  • VidHD card
  • diff --git a/help/toc.html b/help/toc.html index a09a5e04..91981322 100644 --- a/help/toc.html +++ b/help/toc.html @@ -31,7 +31,7 @@
  • Sound
  • Clock
  • Super Serial card -
  • Uthernet network card +
  • Uthernet network cards
  • AppleWin Configuration
  • Using the Debugger
  • Resources
  • diff --git a/help/uthernet.html b/help/uthernet.html index ad8ece60..ca1373a9 100644 --- a/help/uthernet.html +++ b/help/uthernet.html @@ -1,22 +1,23 @@ - Uthernet network card + Uthernet network cards -

    Uthernet network card

    +

    Uthernet network cards


    Overview:

    -

    The Uthernet network card coupled with the Contiki OS allows you to browse the +

    The Uthernet network cards coupled with the Contiki OS allow you to browse the internet on your Apple.

    Acknowledgment:

    -

    Uthernet (TFE) support in Applewin was made possible by implementing the GPL +

    Uthernet (TFE) support in AppleWin was made possible by implementing the GPL source written by Spiro Trikaliotis for the Vice emulator - http://vice-emu.sourceforge.net/index.html#developers

    +

    Uthernet II support in AppleWin has been contributed by Andrea (audetto) Odetti.

    Details:

    To enable ethernet support in AppleWin you must first download and install @@ -35,15 +36,15 @@

    After AppleWin starts, select the settings icon and then select the ethernet settings button.

    -

    Uthernet will be disabled. Select Uthernet from the list of available ethernet - emulations (currently the only one). +

    Uthernet will be disabled. Select Uthernet or Uthernet II from the list of available ethernet + emulations.

    Select the ethernet interface you want to work with. This must be a physical ethernet interface.

    If you have more than one interface you may need to select them in turn in order to get the text description for each interface vs what Npcap likes to use for - a reference. Select Ok. and then close AppleWin. + a reference.

    Note: Wireless does not work with WinPcap (but see WiFi Workaround). @@ -56,7 +57,7 @@ also grab a copy of the Uthernet/Contiki getting started guide http://www.a2retrosystems.com/a2UtherManual.pdf

    -

    When you run AppleWin again, select the contiki80pri.dsk image. Boot AppleWin. +

    Select the contiki80pri.dsk image. Boot AppleWin.

    Once Contiki is loaded then press Enter to clear the welcome screen and press ESC for a menu. @@ -87,5 +88,14 @@ if you are still having difficulty then you should refer to the VICE network support page for additional information - http://vicekb.trikaliotis.net/13-005.shtml

    - +

    Uthernet II: +

    +

    Most features of the Uthernet II are emulated, with the following caveats: +

      +
    • PPPoE, interrupts and SPI are not implemented
    • +
    • server side is not well tested
    • +
    • after loading a save-state file, TCP and UDP sockets are closed
    • +
    +

    + diff --git a/source/LanguageCard.cpp b/source/LanguageCard.cpp index d44d5082..6d8079b5 100644 --- a/source/LanguageCard.cpp +++ b/source/LanguageCard.cpp @@ -443,7 +443,7 @@ bool Saturn128K::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version) } // "Memory Bankxx" - std::string memName = GetSnapshotMemStructName() + StrFormat("%02X", uBank); + std::string memName = GetSnapshotMemStructName() + ByteToHexStr(uBank); if (!yamlLoadHelper.GetSubMap(memName)) throw std::runtime_error("Memory: Missing map name: " + memName); diff --git a/source/Memory.cpp b/source/Memory.cpp index d7c81bfd..cb173603 100644 --- a/source/Memory.cpp +++ b/source/Memory.cpp @@ -2443,7 +2443,7 @@ static void MemLoadSnapshotAuxCommon(YamlLoadHelper& yamlLoadHelper, const std:: } // "Auxiliary Memory Bankxx" - std::string auxMemName = MemGetSnapshotAuxMemStructName() + StrFormat("%02X", uBank-1); + std::string auxMemName = MemGetSnapshotAuxMemStructName() + ByteToHexStr(uBank-1); if (!yamlLoadHelper.GetSubMap(auxMemName)) throw std::runtime_error("Memory: Missing map name: " + auxMemName); diff --git a/source/SSI263.cpp b/source/SSI263.cpp index 06bc3eac..2945f916 100644 --- a/source/SSI263.cpp +++ b/source/SSI263.cpp @@ -86,7 +86,7 @@ void SSI_Output(void) LogOutput("SSI: "); for (int i = 0; i <= 4; i++) { - std::string r = (ssiRegs[i] >= 0) ? StrFormat("%02X", ssiRegs[i]) : "--"; + std::string r = (ssiRegs[i] >= 0) ? ByteToHexStr(ssiRegs[i]) : "--"; LogOutput("%s ", r.c_str()); ssiRegs[i] = -1; } diff --git a/source/StrFormat.h b/source/StrFormat.h index 1cda84a2..b20ed91c 100644 --- a/source/StrFormat.h +++ b/source/StrFormat.h @@ -6,6 +6,7 @@ #if defined(_MSC_VER) && _MSC_VER < 1600 #include typedef UINT8 uint8_t; +typedef UINT16 uint16_t; #else #include #endif @@ -19,10 +20,59 @@ typedef UINT8 uint8_t; std::string StrFormat(const char* format, ...) ATTRIBUTE_FORMAT_PRINTF(1, 2); std::string StrFormatV(const char* format, va_list va); +namespace { + +const char g_aHexDigits[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', +}; + +// No buffer overflow check or null termination. Use with caution. +inline char* StrBufferAppendByteAsHex(char* cp, uint8_t n) +{ + *cp++ = g_aHexDigits[(n >> 4) & 0x0f]; + *cp++ = g_aHexDigits[(n >> 0) & 0x0f]; + return cp; +} + +// No buffer overflow check or null termination. Use with caution. +inline char* StrBufferAppendWordAsHex(char* cp, uint16_t n) +{ + *cp++ = g_aHexDigits[(n >> 12) & 0x0f]; + *cp++ = g_aHexDigits[(n >> 8) & 0x0f]; + *cp++ = g_aHexDigits[(n >> 4) & 0x0f]; + *cp++ = g_aHexDigits[(n >> 0) & 0x0f]; + return cp; +} + inline std::string& StrAppendByteAsHex(std::string& s, uint8_t n) { - const char szHex[] = "0123456789ABCDEF"; - s += szHex[(n >> 4) & 0x0f]; - s += szHex[n & 0x0f]; + const char hex[2] = { g_aHexDigits[(n >> 4) & 0x0f], + g_aHexDigits[(n >> 0) & 0x0f] }; + return s.append(hex, 2); +} + +inline std::string& StrAppendWordAsHex(std::string& s, uint16_t n) +{ + const char hex[4] = { g_aHexDigits[(n >> 12) & 0x0f], + g_aHexDigits[(n >> 8) & 0x0f], + g_aHexDigits[(n >> 4) & 0x0f], + g_aHexDigits[(n >> 0) & 0x0f] }; + return s.append(hex, 4); +} + +inline std::string ByteToHexStr(uint8_t n) +{ + std::string s; + StrAppendByteAsHex(s, n); return s; } + +inline std::string WordToHexStr(uint16_t n) +{ + std::string s; + StrAppendWordAsHex(s, n); + return s; +} + +} // namespace diff --git a/source/Tfe/IPRaw.cpp b/source/Tfe/IPRaw.cpp new file mode 100644 index 00000000..12a0ac4c --- /dev/null +++ b/source/Tfe/IPRaw.cpp @@ -0,0 +1,162 @@ +/* +AppleWin : An Apple //e emulator for Windows + +Copyright (C) 2022, Andrea Odetti + +AppleWin is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +AppleWin is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with AppleWin; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "StdAfx.h" + +#include "IPRaw.h" + +#ifndef _MSC_VER +#include +#endif + +#define IPV4 0x04 + +namespace +{ + +#pragma pack(push) +#pragma pack(1) // Ensure struct is packed + struct IP4Header + { + uint8_t ihl : 4; + uint8_t version : 4; + uint8_t tos; + uint16_t len; + uint16_t id; + uint16_t flags : 3; + uint16_t fragmentOffset : 13; + uint8_t ttl; + uint8_t proto; + uint16_t checksum; + uint32_t sourceAddress; + uint32_t destinationAddress; + }; + + struct ETH2Frame + { + uint8_t destinationMac[6]; + uint8_t sourceMac[6]; + uint16_t type; + }; +#pragma pack(pop) + + uint32_t sum_every_16bits(const void *addr, int count) + { + uint32_t sum = 0; + const uint16_t *ptr = reinterpret_cast(addr); + + while (count > 1) + { + /* This is the inner loop */ + sum += *ptr++; + count -= 2; + } + + /* Add left-over byte, if any */ + if (count > 0) + sum += *reinterpret_cast(ptr); + + return sum; + } + + uint16_t checksum(const void *addr, int count) + { + /* Compute Internet Checksum for "count" bytes + * beginning at location "addr". + * Taken from https://tools.ietf.org/html/rfc1071 + */ + uint32_t sum = sum_every_16bits(addr, count); + + /* Fold 32-bit sum to 16 bits */ + while (sum >> 16) + sum = (sum & 0xffff) + (sum >> 16); + + return ~sum; + } + + // get the minimum size of a ETH Frame that contains a IP payload + // 34 = 14 bytes for ETH2 + 20 bytes IPv4 (minimum) + int getIPMinimumSize() + { + const int minimumSize = sizeof(ETH2Frame) + sizeof(IP4Header) + 0; // 0 len + return minimumSize; + } + +} + +std::vector createETH2Frame(const std::vector &data, + const MACAddress *sourceMac, const MACAddress *destinationMac, + const uint8_t ttl, const uint8_t tos, const uint8_t protocol, + const uint32_t sourceAddress, const uint32_t destinationAddress) +{ + const size_t total = sizeof(ETH2Frame) + sizeof(IP4Header) + data.size(); + std::vector frame(total); + ETH2Frame *eth2frame = reinterpret_cast(frame.data() + 0); + memcpy(eth2frame->destinationMac, destinationMac, sizeof(eth2frame->destinationMac)); + memcpy(eth2frame->sourceMac, sourceMac, sizeof(eth2frame->destinationMac)); + eth2frame->type = htons(0x0800); + IP4Header *ip4header = reinterpret_cast(frame.data() + sizeof(ETH2Frame)); + + ip4header->version = IPV4; + ip4header->ihl = 0x05; // minimum size = 20 bytes, without any extra option + ip4header->tos = tos; + ip4header->len = htons(static_cast(sizeof(IP4Header) + data.size())); + ip4header->id = 0; + ip4header->fragmentOffset = 0; + ip4header->ttl = ttl; + ip4header->proto = protocol; + ip4header->sourceAddress = sourceAddress; + ip4header->destinationAddress = destinationAddress; + ip4header->checksum = checksum(ip4header, sizeof(IP4Header)); + + memcpy(frame.data() + sizeof(ETH2Frame) + sizeof(IP4Header), data.data(), data.size()); + + return frame; +} + +void getIPPayload(const int lengthOfFrame, const uint8_t *frame, + size_t &lengthOfPayload, const uint8_t *&payload, uint32_t &destination, uint8_t &protocol) +{ + const int minimumSize = getIPMinimumSize(); + if (lengthOfFrame > minimumSize) + { + const ETH2Frame *eth2Frame = reinterpret_cast(frame); + const IP4Header *ip4header = reinterpret_cast(frame + sizeof(ETH2Frame)); + if (eth2Frame->type == htons(0x0800) && ip4header->version == IPV4) + { + const uint16_t ipv4HeaderSize = ip4header->ihl * 4; + const uint16_t ipPacketSize = ntohs(ip4header->len); + const int expectedSize = sizeof(ETH2Frame) + ipPacketSize; + if (ipPacketSize > ipv4HeaderSize && lengthOfFrame >= expectedSize) + { + protocol = ip4header->proto; + payload = frame + sizeof(ETH2Frame) + ipv4HeaderSize; + lengthOfPayload = ipPacketSize - ipv4HeaderSize; + destination = ip4header->destinationAddress; + return; + } + } + } + // not a good packet + protocol = 0xFF; // reserved protocol + payload = nullptr; + lengthOfPayload = 0; + destination = 0; +} diff --git a/source/Tfe/IPRaw.h b/source/Tfe/IPRaw.h new file mode 100644 index 00000000..cd0c534e --- /dev/null +++ b/source/Tfe/IPRaw.h @@ -0,0 +1,11 @@ +#pragma once + +struct MACAddress; + +std::vector createETH2Frame(const std::vector &data, + const MACAddress *sourceMac, const MACAddress *destinationMac, + const uint8_t ttl, const uint8_t tos, const uint8_t protocol, + const uint32_t sourceAddress, const uint32_t destinationAddress); + +void getIPPayload(const int lengthOfFrame, const uint8_t *frame, + size_t &lengthOfPayload, const uint8_t *&payload, uint32_t &destination, uint8_t &protocol); diff --git a/source/Tfe/NetworkBackend.h b/source/Tfe/NetworkBackend.h index e883c9cb..3bfda5dd 100644 --- a/source/Tfe/NetworkBackend.h +++ b/source/Tfe/NetworkBackend.h @@ -6,6 +6,14 @@ #define MAX_RXLENGTH 1518 #define MIN_RXLENGTH 64 +#pragma pack(push) +#pragma pack(1) // Ensure struct is packed +struct MACAddress +{ + uint8_t address[6]; +}; +#pragma pack(pop) + class NetworkBackend { public: @@ -26,6 +34,9 @@ public: // process pending packets virtual void update(const ULONG nExecutedCycles) = 0; + // get MAC for IPRAW (it is only supposed to handle addresses on the local network) + virtual void getMACAddress(const uint32_t address, MACAddress & mac) = 0; + // if the backend is usable virtual bool isValid() = 0; }; diff --git a/source/Tfe/PCapBackend.cpp b/source/Tfe/PCapBackend.cpp index c2f7c5aa..58d349da 100644 --- a/source/Tfe/PCapBackend.cpp +++ b/source/Tfe/PCapBackend.cpp @@ -25,6 +25,10 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "../Common.h" #include "../Registry.h" +#ifdef _MSC_VER +#include +#endif + std::string PCapBackend::tfe_interface; PCapBackend::PCapBackend(const std::string & pcapInterface) @@ -71,6 +75,16 @@ void PCapBackend::update(const ULONG /* nExecutedCycles */) // nothing to do } +void PCapBackend::getMACAddress(const uint32_t address, MACAddress & mac) +{ + // this is only expected to be called for IP addresses on the same network +#ifdef _MSC_VER + const DWORD dwSourceAddress = INADDR_ANY; + ULONG len = sizeof(MACAddress::address); + SendARP(address, dwSourceAddress, mac.address, &len); +#endif +} + int PCapBackend::tfe_enumadapter_open(void) { return tfe_arch_enumadapter_open(); diff --git a/source/Tfe/PCapBackend.h b/source/Tfe/PCapBackend.h index 01952b90..a94023d7 100644 --- a/source/Tfe/PCapBackend.h +++ b/source/Tfe/PCapBackend.h @@ -29,6 +29,9 @@ public: // process pending packets virtual bool isValid(); + // get MAC for IPRAW (it is only supposed to handle addresses on the local network) + virtual void getMACAddress(const uint32_t address, MACAddress & mac); + static void tfe_SetRegistryInterface(UINT slot, const std::string& name); static void get_disabled_state(int * param); diff --git a/source/Uthernet2.cpp b/source/Uthernet2.cpp index 74b32aec..79970f52 100644 --- a/source/Uthernet2.cpp +++ b/source/Uthernet2.cpp @@ -24,6 +24,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "Interface.h" #include "Tfe/NetworkBackend.h" #include "Tfe/PCapBackend.h" +#include "Tfe/IPRaw.h" #include "W5100.h" // Linux uses EINPROGRESS while Windows returns WSAEWOULDBLOCK @@ -73,6 +74,9 @@ typedef int socklen_t; #define SOCK_NONBLOCK O_NONBLOCK #endif +// Dest MAC + Source MAC + Ether Type +#define ETH_MINIMUM_SIZE (6 + 6 + 2) + // #define U2_LOG_VERBOSE // #define U2_LOG_TRAFFIC // #define U2_LOG_STATE @@ -95,6 +99,12 @@ namespace return host; } + uint32_t readAddress(const uint8_t *ptr) + { + const uint32_t address = *reinterpret_cast(ptr); + return address; + } + uint8_t getIByte(const uint16_t value, const size_t shift) { return (value >> shift) & 0xFF; @@ -141,6 +151,13 @@ namespace writeData(socket, memory, data, len); } + void writeDataIPRaw(Socket &socket, std::vector &memory, const uint8_t *data, const size_t len, const uint32_t destination) + { + writeAny(socket, memory, destination); + write16(socket, memory, static_cast(len)); + writeData(socket, memory, data, len); + } + void writeDataForProtocol(Socket &socket, std::vector &memory, const uint8_t *data, const size_t len, const sockaddr_in &source) { if (socket.sn_sr == W5100_SN_SR_SOCK_UDP) @@ -199,31 +216,46 @@ Socket::~Socket() clearFD(); } +bool Socket::isOpen() const +{ + return (myFD != INVALID_SOCKET) && + ((sn_sr == W5100_SN_SR_ESTABLISHED) || (sn_sr == W5100_SN_SR_SOCK_UDP)); +} + void Socket::process() { if (myFD != INVALID_SOCKET && sn_sr == W5100_SN_SR_SOCK_INIT && (myErrno == SOCK_EINPROGRESS || myErrno == SOCK_EWOULDBLOCK)) { #ifdef _MSC_VER - FD_SET writefds; + FD_SET writefds, exceptfds; FD_ZERO(&writefds); + FD_ZERO(&exceptfds); FD_SET(myFD, &writefds); + FD_SET(myFD, &exceptfds); const timeval timeout = {0, 0}; - if (select(0, NULL, &writefds, NULL, &timeout) > 0) + if (select(0, NULL, &writefds, &exceptfds, &timeout) > 0) #else pollfd pfd = {.fd = myFD, .events = POLLOUT}; if (poll(&pfd, 1, 0) > 0) #endif { int err = 0; - socklen_t elen = sizeof err; - getsockopt(myFD, SOL_SOCKET, SO_ERROR, reinterpret_cast(&err), &elen); + socklen_t elen = sizeof(err); + const int res = getsockopt(myFD, SOL_SOCKET, SO_ERROR, reinterpret_cast(&err), &elen); - if (err == 0) + if (res == 0 && err == 0) { myErrno = 0; sn_sr = W5100_SN_SR_ESTABLISHED; #ifdef U2_LOG_STATE LogFileOutput("U2: TCP[]: Connected\n"); +#endif + } + else + { + clearFD(); +#ifdef U2_LOG_STATE + LogFileOutput("U2: TCP[]: Connection error: %d - %" ERROR_FMT "\n", res, STRERROR(err)); #endif } } @@ -266,12 +298,18 @@ bool Socket::LoadSnapshot(YamlLoadHelper &yamlLoadHelper) // transmit and receive sizes are restored from the card common registers - if (sn_sr != W5100_SN_SR_SOCK_MACRAW) + switch (sn_sr) { + case W5100_SN_SR_SOCK_MACRAW: + case W5100_SN_SR_SOCK_IPRAW: + // we can restore RAW sockets + break; + default: // no point in restoring a broken UDP or TCP connection // just reset the socket sn_sr = W5100_SN_SR_CLOSED; // for the same reason there is no point in saving myFD and myErrno + break; } return true; @@ -434,7 +472,7 @@ void Uthernet2::updateRSR(const size_t i) socket.sn_rx_rsr = dataPresent; } -int Uthernet2::receiveForMacAddress(const bool acceptAll, const int size, uint8_t * data) +int Uthernet2::receiveForMacAddress(const bool acceptAll, const int size, uint8_t * data, PacketDestination & packetDestination) { const uint8_t * mac = myMemory.data() + W5100_SHAR0; @@ -442,15 +480,9 @@ int Uthernet2::receiveForMacAddress(const bool acceptAll, const int size, uint8_ int len; while ((len = myNetworkBackend->receive(size, data)) > 0) { - // minimum valid Ethernet frame is actually 64 bytes - // 12 is the minimum to ensure valid MAC Address logging later - if (len >= 12) + // smaller frames are not good anyway + if (len >= ETH_MINIMUM_SIZE) { - if (acceptAll) - { - return len; - } - if (data[0] == mac[0] && data[1] == mac[1] && data[2] == mac[2] && @@ -458,6 +490,7 @@ int Uthernet2::receiveForMacAddress(const bool acceptAll, const int size, uint8_ data[4] == mac[4] && data[5] == mac[5]) { + packetDestination = HOST; return len; } @@ -468,8 +501,16 @@ int Uthernet2::receiveForMacAddress(const bool acceptAll, const int size, uint8_ data[4] == 0xFF && data[5] == 0xFF) { + packetDestination = BROADCAST; return len; } + + if (acceptAll) + { + packetDestination = OTHER; + return len; + } + } // skip this frame and try with another one } @@ -477,34 +518,110 @@ int Uthernet2::receiveForMacAddress(const bool acceptAll, const int size, uint8_ return len; } -void Uthernet2::receiveOnePacketMacRaw(const size_t i) +void Uthernet2::receiveOnePacketRaw() +{ + bool acceptAll = false; + int macRawSocket = -1; // to which IPRaw soccket to send to (can only be 0) + + Socket & socket0 = mySockets[0]; + if (socket0.sn_sr == W5100_SN_SR_SOCK_MACRAW) + { + macRawSocket = 0; // the only MAC Raw socket is open, packet will go there as a fallback + const uint8_t mr = myMemory[socket0.registerAddress + W5100_SN_MR]; + + // see if MAC RAW filters or not + const bool filterMAC = mr & W5100_SN_MR_MF; + if (!filterMAC) + { + acceptAll = true; + } + } + + uint8_t buffer[MAX_RXLENGTH]; + PacketDestination packetDestination; + const int len = receiveForMacAddress(acceptAll, sizeof(buffer), buffer, packetDestination); + if (len > 0) + { + const uint8_t * payload; + size_t lengthOfPayload; + uint32_t destination; + uint8_t packetProtocol; + getIPPayload(len, buffer, lengthOfPayload, payload, destination, packetProtocol); + + // see if there is a IPRAW socket that should accept thi spacket + int ipRawSocket = -1; + if (packetDestination != OTHER) // IPRaw always filters (HOST or BROADCAST, never OTHER) + { + for (size_t i = 0; i < mySockets.size(); ++i) + { + Socket & socket = mySockets[i]; + + if (socket.sn_sr == W5100_SN_SR_SOCK_IPRAW) + { + // IP only accepts by protocol & always filters MAC + const uint8_t socketProtocol = myMemory[socket.registerAddress + W5100_SN_PROTO]; + if (payload && packetProtocol == socketProtocol) + { + ipRawSocket = i; + break; // a valid IPRAW socket has been found + } + } + // we should probably check for UDP & TCP sockets and filter these packets too + } + } + + // priority to IPRAW + if (ipRawSocket >= 0) + { + receiveOnePacketIPRaw(ipRawSocket, lengthOfPayload, payload, destination, packetProtocol, len); + } + // fallback to MACRAW (if open) + else if (macRawSocket >= 0) + { + receiveOnePacketMacRaw(macRawSocket, len, buffer); + } + // else packet is dropped + } +} + +void Uthernet2::receiveOnePacketMacRaw(const size_t i, const int size, uint8_t * data) { Socket &socket = mySockets[i]; - uint8_t buffer[MAX_RXLENGTH]; - - const uint8_t mr = myMemory[socket.registerAddress + W5100_SN_MR]; - const bool filterMAC = mr & W5100_SN_MR_MF; - - const int len = receiveForMacAddress(!filterMAC, sizeof(buffer), buffer); - if (len > 0) + if (socket.isThereRoomFor(size, sizeof(uint16_t))) { - // we know the packet is at least 12 bytes, and logging is ok - if (socket.isThereRoomFor(len, sizeof(uint16_t))) - { - writeDataMacRaw(socket, myMemory, buffer, len); + writeDataMacRaw(socket, myMemory, data, size); #ifdef U2_LOG_TRAFFIC - LogFileOutput("U2: Read MACRAW[%" SIZE_T_FMT "]: " MAC_FMT " -> " MAC_FMT ": +%d -> %d bytes\n", i, MAC_SOURCE(buffer), MAC_DEST(buffer), - len, socket.sn_rx_rsr); + LogFileOutput("U2: Read MACRAW[%" SIZE_T_FMT "]: " MAC_FMT " -> " MAC_FMT ": +%d -> %d bytes\n", i, MAC_SOURCE(data), MAC_DEST(data), + size, socket.sn_rx_rsr); #endif - } - else - { - // drop it + } + else + { + // drop it #ifdef U2_LOG_TRAFFIC - LogFileOutput("U2: Skip MACRAW[%" SIZE_T_FMT "]: %d bytes\n", i, len); + LogFileOutput("U2: Skip MACRAW[%" SIZE_T_FMT "]: %d bytes\n", i, size); +#endif + } +} + +void Uthernet2::receiveOnePacketIPRaw(const size_t i, const size_t lengthOfPayload, const uint8_t * payload, const uint32_t destination, const uint8_t protocol, const int len) +{ + Socket &socket = mySockets[i]; + + if (socket.isThereRoomFor(lengthOfPayload, 4 + sizeof(uint16_t))) + { + writeDataIPRaw(socket, myMemory, payload, lengthOfPayload, destination); +#ifdef U2_LOG_TRAFFIC + LogFileOutput("U2: Read IPRAW[%" SIZE_T_FMT "]: +%" SIZE_T_FMT " (%d) -> %d bytes\n", i, lengthOfPayload, len, socket.sn_rx_rsr); +#endif + } + else + { + // drop it +#ifdef U2_LOG_TRAFFIC + LogFileOutput("U2: Skip IPRAW[%" SIZE_T_FMT "]: %" SIZE_T_FMT " (%d) bytes \n", i, lengthOfPayload, len); #endif - } } } @@ -512,7 +629,7 @@ void Uthernet2::receiveOnePacketMacRaw(const size_t i) void Uthernet2::receiveOnePacketFromSocket(const size_t i) { Socket &socket = mySockets[i]; - if (socket.myFD != INVALID_SOCKET) + if (socket.isOpen()) { const uint16_t freeRoom = socket.getFreeRoom(); if (freeRoom > 32) // avoid meaningless reads @@ -557,7 +674,8 @@ void Uthernet2::receiveOnePacket(const size_t i) switch (socket.sn_sr) { case W5100_SN_SR_SOCK_MACRAW: - receiveOnePacketMacRaw(i); + case W5100_SN_SR_SOCK_IPRAW: + receiveOnePacketRaw(); break; case W5100_SN_SR_ESTABLISHED: case W5100_SN_SR_SOCK_UDP: @@ -572,6 +690,29 @@ void Uthernet2::receiveOnePacket(const size_t i) }; } +void Uthernet2::sendDataIPRaw(const size_t i, std::vector &payload) +{ + const Socket &socket = mySockets[i]; + + const uint8_t ttl = myMemory[socket.registerAddress + W5100_SN_TTL]; + const uint8_t tos = myMemory[socket.registerAddress + W5100_SN_TOS]; + const uint8_t protocol = myMemory[socket.registerAddress + W5100_SN_PROTO]; + const uint32_t source = readAddress(myMemory.data() + W5100_SIPR0); + const uint32_t destination = readAddress(myMemory.data() + socket.registerAddress + W5100_SN_DIPR0); + + const MACAddress * sourceMac = reinterpret_cast(myMemory.data() + W5100_SHAR0); + const MACAddress * destinationMac; + getMACAddress(destination, destinationMac); + + std::vector packet = createETH2Frame(payload, sourceMac, destinationMac, ttl, tos, protocol, source, destination); + +#ifdef U2_LOG_TRAFFIC + LogFileOutput("U2: Send IPRAW[%" SIZE_T_FMT "]: %" SIZE_T_FMT " (%" SIZE_T_FMT ") bytes\n", i, payload.size(), packet.size()); +#endif + + myNetworkBackend->transmit(packet.size(), packet.data()); +} + void Uthernet2::sendDataMacRaw(const size_t i, std::vector &packet) const { #ifdef U2_LOG_TRAFFIC @@ -592,7 +733,7 @@ void Uthernet2::sendDataMacRaw(const size_t i, std::vector &packet) con void Uthernet2::sendDataToSocket(const size_t i, std::vector &data) { Socket &socket = mySockets[i]; - if (socket.myFD != INVALID_SOCKET) + if (socket.isOpen()) { sockaddr_in destination = {}; destination.sin_family = AF_INET; @@ -656,6 +797,9 @@ void Uthernet2::sendData(const size_t i) case W5100_SN_SR_SOCK_MACRAW: sendDataMacRaw(i, data); break; + case W5100_SN_SR_SOCK_IPRAW: + sendDataIPRaw(i, data); + break; case W5100_SN_SR_ESTABLISHED: case W5100_SN_SR_SOCK_UDP: sendDataToSocket(i, data); @@ -680,7 +824,7 @@ void Uthernet2::resetRXTXBuffers(const size_t i) myMemory[socket.registerAddress + W5100_SN_RX_RD1] = 0x00; } -void Uthernet2::openSystemSocket(const size_t i, const int type, const int protocol, const int state) +void Uthernet2::openSystemSocket(const size_t i, const int type, const int protocol, const int status) { Socket &s = mySockets[i]; #ifdef _MSC_VER @@ -691,7 +835,7 @@ void Uthernet2::openSystemSocket(const size_t i, const int type, const int proto if (fd == INVALID_SOCKET) { #ifdef U2_LOG_STATE - const char *proto = state == W5100_SN_SR_SOCK_UDP ? "UDP" : "TCP"; + const char *proto = (status == W5100_SN_SR_SOCK_UDP) ? "UDP" : "TCP"; LogFileOutput("U2: %s[%" SIZE_T_FMT "]: socket error: %" ERROR_FMT "\n", proto, i, STRERROR(sock_error())); #endif s.clearFD(); @@ -702,7 +846,7 @@ void Uthernet2::openSystemSocket(const size_t i, const int type, const int proto u_long on = 1; ioctlsocket(fd, FIONBIO, &on); #endif - s.setFD(fd, state); + s.setFD(fd, status); } } @@ -854,7 +998,7 @@ uint8_t Uthernet2::readSocketRegister(const uint16_t address) break; default: #ifdef U2_LOG_UNKNOWN - LogFileOutput("U2: Get unknown socket register[%" SIZE_T_FMT "]: %04x\n", i, address); + LogFileOutput("U2: Get unknown socket register[%d]: %04x\n", i, address); #endif value = myMemory[address]; break; @@ -988,7 +1132,7 @@ void Uthernet2::writeSocketRegister(const uint16_t address, const uint8_t value) break; #ifdef U2_LOG_UNKNOWN default: - LogFileOutput("U2: Set unknown socket register[%" SIZE_T_FMT "]: %04x\n", i, address); + LogFileOutput("U2: Set unknown socket register[%d]: %04x\n", i, address); break; #endif }; @@ -1073,6 +1217,7 @@ void Uthernet2::Reset(const bool powerCycle) // dataAddress is NOT reset, see page 10 of Uthernet II myDataAddress = 0; myNetworkBackend = GetFrame().CreateNetworkBackend(); + myARPCache.clear(); } mySockets.clear(); @@ -1084,14 +1229,25 @@ void Uthernet2::Reset(const bool powerCycle) { resetRXTXBuffers(i); mySockets[i].clearFD(); - mySockets[i].registerAddress = static_cast(W5100_S0_BASE + (i << 8)); + const uint16_t registerAddress = static_cast(W5100_S0_BASE + (i << 8)); + mySockets[i].registerAddress = registerAddress; + + myMemory[registerAddress + W5100_SN_DHAR0] = 0xFF; + myMemory[registerAddress + W5100_SN_DHAR1] = 0xFF; + myMemory[registerAddress + W5100_SN_DHAR2] = 0xFF; + myMemory[registerAddress + W5100_SN_DHAR3] = 0xFF; + myMemory[registerAddress + W5100_SN_DHAR4] = 0xFF; + myMemory[registerAddress + W5100_SN_DHAR5] = 0xFF; + myMemory[registerAddress + W5100_SN_TTL] = 0x80; } // initial values - myMemory[W5100_RTR0] = 0x07; - myMemory[W5100_RTR1] = 0xD0; + myMemory[W5100_RTR0] = 0x07; + myMemory[W5100_RTR1] = 0xD0; + myMemory[W5100_RCR] = 0x08; setRXSizes(W5100_RMSR, 0x55); setTXSizes(W5100_TMSR, 0x55); + myMemory[W5100_PTIMER] = 0x28; } BYTE Uthernet2::IO_C0(WORD programcounter, WORD address, BYTE write, BYTE value, ULONG nCycles) @@ -1162,6 +1318,46 @@ void Uthernet2::InitializeIO(LPBYTE pCxRomPeripheral) RegisterIoHandler(m_slot, u2_C0, u2_C0, nullptr, nullptr, this, nullptr); } +void Uthernet2::getMACAddress(const uint32_t address, const MACAddress * & mac) +{ + const auto it = myARPCache.find(address); + if (it != myARPCache.end()) + { + mac = &it->second; + } + else + { + MACAddress & macAddr = myARPCache[address]; + const uint32_t source = readAddress(myMemory.data() + W5100_SIPR0); + + if (address == source) + { + const uint8_t * sourceMac = myMemory.data() + W5100_SHAR0; + memcpy(macAddr.address, sourceMac, sizeof(macAddr.address)); + } + else + { + memset(macAddr.address, 0xFF, sizeof(macAddr.address)); // fallback to broadcast + if (address != INADDR_BROADCAST) + { + const uint32_t subnet = readAddress(myMemory.data() + W5100_SUBR0); + if ((address & subnet) == (source & subnet)) + { + // same network: send ARP request + myNetworkBackend->getMACAddress(address, macAddr); + } + else + { + const uint32_t gateway = readAddress(myMemory.data() + W5100_GAR0); + // different network: go via gateway + myNetworkBackend->getMACAddress(gateway, macAddr); + } + } + } + mac = &macAddr; + } +} + void Uthernet2::Update(const ULONG nExecutedCycles) { myNetworkBackend->update(nExecutedCycles); diff --git a/source/Uthernet2.h b/source/Uthernet2.h index 5e7952fa..8b33af21 100644 --- a/source/Uthernet2.h +++ b/source/Uthernet2.h @@ -3,8 +3,10 @@ #include "Card.h" #include +#include class NetworkBackend; +struct MACAddress; struct Socket { @@ -28,6 +30,7 @@ struct Socket socket_t myFD; int myErrno; + bool isOpen() const; void clearFD(); void setFD(const socket_t fd, const int status); void process(); @@ -47,6 +50,7 @@ struct Socket * Documentation from * http://dserver.macgui.com/Uthernet%20II%20manual%2017%20Nov%2018.pdf * https://www.wiznet.io/wp-content/uploads/wiznethome/Chip/W5100/Document/W5100_DS_V128E.pdf +* https://www.wiznet.io/wp-content/uploads/wiznethome/Chip/W5100/Document/3150Aplus_5100_ES_V260E.pdf */ class Uthernet2 : public Card @@ -54,6 +58,8 @@ class Uthernet2 : public Card public: static const std::string& GetSnapshotCardName(); + enum PacketDestination { HOST, BROADCAST, OTHER }; + Uthernet2(UINT slot); virtual void Destroy(void) {} @@ -72,6 +78,13 @@ private: uint16_t myDataAddress; std::shared_ptr myNetworkBackend; + // the real Uthernet II card does not have a ARP Cache + // but in the interest of speeding up the emulator + // we introduce one + std::map myARPCache; + + void getMACAddress(const uint32_t address, const MACAddress * & mac); + void setSocketModeRegister(const size_t i, const uint16_t address, const uint8_t value); void setTXSizes(const uint16_t address, uint8_t value); void setRXSizes(const uint16_t address, uint8_t value); @@ -79,11 +92,14 @@ private: uint8_t getTXFreeSizeRegister(const size_t i, const size_t shift) const; uint8_t getRXDataSizeRegister(const size_t i, const size_t shift) const; - void receiveOnePacketMacRaw(const size_t i); + void receiveOnePacketRaw(); + void receiveOnePacketIPRaw(const size_t i, const size_t lengthOfPayload, const uint8_t * payload, const uint32_t destination, const uint8_t protocol, const int len); + void receiveOnePacketMacRaw(const size_t i, const int size, uint8_t * data); void receiveOnePacketFromSocket(const size_t i); void receiveOnePacket(const size_t i); - int receiveForMacAddress(const bool acceptAll, const int size, uint8_t * data); + int receiveForMacAddress(const bool acceptAll, const int size, uint8_t * data, PacketDestination & packetDestination); + void sendDataIPRaw(const size_t i, std::vector &data); void sendDataMacRaw(const size_t i, std::vector &data) const; void sendDataToSocket(const size_t i, std::vector &data); void sendData(const size_t i); @@ -91,7 +107,7 @@ private: void resetRXTXBuffers(const size_t i); void updateRSR(const size_t i); - void openSystemSocket(const size_t i, const int type, const int protocol, const int state); + void openSystemSocket(const size_t i, const int type, const int protocol, const int status); void openSocket(const size_t i); void closeSocket(const size_t i); void connectSocket(const size_t i); diff --git a/source/W5100.h b/source/W5100.h index c9f35ec0..c07a8502 100644 --- a/source/W5100.h +++ b/source/W5100.h @@ -20,8 +20,10 @@ #define W5100_SIPR3 0x0012 #define W5100_RTR0 0x0017 #define W5100_RTR1 0x0018 +#define W5100_RCR 0x0019 #define W5100_RMSR 0x001A #define W5100_TMSR 0x001B +#define W5100_PTIMER 0x0028 #define W5100_UPORT1 0x002F #define W5100_S0_BASE 0x0400 #define W5100_S3_MAX 0x07FF @@ -58,6 +60,12 @@ #define W5100_SN_SR 0x03 #define W5100_SN_PORT0 0x04 #define W5100_SN_PORT1 0x05 +#define W5100_SN_DHAR0 0x06 +#define W5100_SN_DHAR1 0x07 +#define W5100_SN_DHAR2 0x08 +#define W5100_SN_DHAR3 0x09 +#define W5100_SN_DHAR4 0x0A +#define W5100_SN_DHAR5 0x0B #define W5100_SN_DIPR0 0x0C #define W5100_SN_DIPR1 0x0D #define W5100_SN_DIPR2 0x0E