├── LICENSE ├── README.TXT ├── README.md ├── bin └── ETHERDFS.EXE └── src ├── CHINT.H ├── CHINT086.ASM ├── DOSSTRUC.H ├── ETHERDFS.C ├── GENMSG.C ├── GLOBALS.H ├── HISTORY.TXT ├── MAKEFILE ├── MEMNOTES.TXT ├── STRLEN.C └── VERSION.H /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Brian Holdsworth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.TXT: -------------------------------------------------------------------------------- 1 | 2 | ETHERDFS - THE ETHERNET DOS FILE SYSTEM 3 | http://etherdfs.sourceforge.net 4 | 5 | 6 | EtherDFS is an 'installable filesystem' TSR for DOS. It maps a drive from a 7 | remote computer (typically Linux-based) to a local drive letter, using raw 8 | ethernet frames to communicate. For years, I was using LapLink to transfer 9 | files between my various "retro" computers. It works, yes, but it's also 10 | annoyingly slow and requires constant attention. One day I thought, "Wouldn't 11 | it be amazing if all my DOS PCs could share a common network drive, similarly 12 | to how NFS operates in the *nix world?". This day EtherDFS was born. I clearly 13 | didn't invent anything - the concept has been around almost as long as the 14 | first IBM PC, and several commercial products addressed that need in the past. 15 | I am not aware, however, of any free and open-source solution. Besides, all 16 | the commercial solutions I know require to set up a pretty complex network 17 | environment first, while EtherDFS doesn't need anything more than just a 18 | packet driver. 19 | 20 | EtherDFS is the "client" part, ie. the TSR running on the client DOS computer. 21 | The client requires an EtherSRV instance to communicate with. Currently, an 22 | implementation of EtherSRV exists only for Linux (ethersrv-linux). 23 | 24 | Since EtherDFS runs over raw ethernet, it doesn't need any TCP/IP stack. It 25 | only requires a working packet driver. The catch of using raw ethernet frames 26 | is that EtherDFS is, by design, unable to communicate outside of a single LAN. 27 | In some context this might actually be a strenght, you don't need to worry 28 | that your EtherDFS transfer will somehow leak outside your LAN - it's simply 29 | not possible. 30 | 31 | Syntax: 32 | etherdfs SRVMAC rdrv1-ldrv1 [rdrv2-ldrv2] [rdrvX-ldrvX] [options] 33 | etherdfs /u [/q] 34 | 35 | where: 36 | SRVMAC is the MAC address of the file server EtherDFS will connect to. You 37 | can also use '::' so EtherDFS will try to auto-discover the server 38 | present in your LAN. 39 | rdrv is the remote drive you want to access on the EtherSRV server. 40 | ldrv is a local drive letter where the remote filesystem will be mapped. 41 | 42 | Available options: 43 | /p=XX use the network packet driver XX (autodetected in the range 60h..80h 44 | if not specified) 45 | /n disable EtherDFS cksum - use only if you are 100% that your network 46 | hardware is working right and you really need to squeeze out some 47 | additional performance (this doesn't disable Ethernet CRC) 48 | /q quiet mode: print nothing on screen if loaded/unloaded successfully 49 | /u unload EtherDFS from memory 50 | 51 | Examples: 52 | etherdfs 6d:4f:4a:4d:49:52 C-F /q 53 | etherdfs :: C-X D-Y E-Z /p=80 54 | 55 | Note: EtherDFS arguments are case-insensitive, and can be passed in any order. 56 | 57 | 58 | ===[ Requirements ]=========================================================== 59 | 60 | EtherDFS hardware/software requirements: 61 | - An 8086/8088 compatible CPU 62 | - MS-DOS 5.0+ or compatible 63 | - 8 KiB of available conventional memory (can be loaded high) 64 | - An Ethernet interface and its packet driver 65 | 66 | ethersrv, on the other hand, requires a reasonably modern Linux system. 67 | Future versions will probably provide a DOS version of ethersrv, too. 68 | 69 | 70 | ===[ Can I use other networking software while EtherDFS is loaded? ]========== 71 | 72 | EtherDFS provides low-level I/O disk connectivity through networking. As such, 73 | it might be adviseable to dedicate a single network interface on your PC 74 | solely for the purpose of handling EtherDFS communications - this would 75 | provide the best possible performance and reliability. However, if your PC has 76 | only one ethernet interface, you still can use EtherDFS simulteanously with 77 | other networking applications - EtherDFS will happily share access to the 78 | packet driver with any other applications, but in such case performance might 79 | not be optimal. Take note that, for such network sharing to be possible, your 80 | other networking software must be written in a way that does not require 81 | exclusive control over the packet driver. 82 | 83 | 84 | ===[ Why Ethernet ]=========================================================== 85 | 86 | This is the first question I get whenever I introduce EtherDFS to someone: 87 | "Why do you use stupid Ethernet instead of IP/UDP/IPv6/IPX/whatever ?" 88 | 89 | I thought about the pros and cons of the multiple technical possibilities, and 90 | the advantages of using raw Ethernet frames vastly outweighted the 91 | constraints. It is to be noted that Ethernet is ubiquitious, has been for the 92 | past 30 years, and is likely to stay here for a while. Here below I paste my 93 | "pros & cons" list, so my reasons should become clear. 94 | 95 | Pros: 96 | - Very simple to parse and process = faster, lower memory footprint, 97 | - No dependencies: doesn't require any TCP/IP stack, only a packet driver, 98 | - Allows EtherDFS to share a single NIC with other DOS networking software, 99 | - IP-agnostic: the network can operate IPv4, IPv6, IPX, or anything else, 100 | - Makes it possible to auto-discover an EtherSRV server on the LAN. 101 | 102 | Cons: 103 | - Unrouteable, meaning that it cannot work outside of a single LAN (running 104 | EtherDFS over a long-distance network would be unadviseable anyway for 105 | reliability and latency reasons). 106 | 107 | 108 | ===[ Is it secure? ]========================================================== 109 | 110 | Shortly said: no. EtherDFS is designed by a hobbyist, for hobbyists. It is not 111 | meant to be used in an environment where security is required. The EtherDFS 112 | protocol transfers all data as plain text, and doesn't have any provisions for 113 | authentication nor access control. It is worth noting that a potentially 114 | desirable side effect of using raw Ethernet for communication is the fact that 115 | EtherDFS data won't ever make it outside your LAN, simply because it is 116 | unrouteable. 117 | 118 | 119 | ===[ But is it still safe for my data? ]====================================== 120 | 121 | Should be. I did my best to ensure that EtherDFS does not lead to any 122 | corruption of data, or any other troubles. You have to keep in mind though, 123 | that EtherDFS is a program that takes root deeply in the (often undocumented) 124 | internals of DOS, and as such, is a quite complex beast. Besides, every 125 | program has bugs, this one is surely not an exception. I can only recommend 126 | that you perform periodic backups of your data, just in case. 127 | 128 | 129 | ===[ Contact the author ]===================================================== 130 | 131 | I'm always happy to get feedback about my software: bug reports, feature 132 | requests, or simply knowing that you use it and it works for you. 133 | You won't get my e-mail address here, but you should find contact pointers to 134 | me on my website: http://mateusz.viste.fr. 135 | 136 | 137 | ===[ License ]================================================================ 138 | 139 | EtherDFS is distributed under the terms of the MIT License, as listed below. 140 | 141 | Copyright (C) 2017, 2018 Mateusz Viste 142 | 143 | Permission is hereby granted, free of charge, to any person obtaining a copy 144 | of this software and associated documentation files (the "Software"), to deal 145 | in the Software without restriction, including without limitation the rights 146 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 147 | copies of the Software, and to permit persons to whom the Software is 148 | furnished to do so, subject to the following conditions: 149 | 150 | The above copyright notice and this permission notice shall be included in all 151 | copies or substantial portions of the Software. 152 | 153 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 154 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 155 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 156 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 157 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 158 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 159 | SOFTWARE. 160 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # etherdfs-client 2 | 3 | Original project migrated from Sourceforge. MIT License. 4 | 5 | _Copyright © 2017, 2018 Mateusz Vistelink_ 6 | 7 | ### EtherDFS - The Ethernet DOS File System 8 | 9 | EtherDFS is a client/server filesystem that allows a modern Linux host (the server) to easily share files with an old PC running DOS. The client in this repository is a __TSR for DOS__. The necessary server code for Linux is also [available on Github](https://github.com/BrianHoldsworth/etherdfs-server). The basic functionality is to map a drive from the server to a local DOS drive letter, using raw Ethernet frames to communicate. It can be used in place of obsolete DOS-to-DOS file sharing applications like [Laplink](https://books.google.com/books?id=kggOZ4-YEKUC&pg=PA92#v=onepage&q&f=false). 10 | 11 | ### Requirements 12 | 13 | EtherDFS hardware/software requirements: 14 | 15 | - An 8086/8088 compatible CPU 16 | - MS-DOS 5.0+ or compatible 17 | - 8 KiB of available conventional memory (can be loaded high) 18 | - An Ethernet interface and its packet driver 19 | 20 | ### Client Build 21 | 22 | The client code for EtherDFS contained in this repository can be built in an MS-DOS environment, such as [Dosbox](https://www.dosbox.com/download.php?main=1), or an actual DOS PC, using the [Open Watcom C compiler V1.9](https://sourceforge.net/projects/openwatcom/files/open-watcom-1.9/open-watcom-c-dos-1.9.exe/download). Build with the included [MAKEFILE](src/MAKEFILE) by simply typing `make` in the cloned Github directory. 23 | 24 | For convenience, a pre-built binary for MS-DOS 5+ is included for [download](bin/ETHERDFS.EXE). 25 | 26 | ### Client Usage 27 | 28 | First, launch the server on your Linux host that is connected to the client via Ethernet LAN. The server will show the MAC address of the interface in use on the command line. You can use this same MAC address when launching the client, or just rely on the auto-discovery feature. 29 | 30 | Next, you will need to locate and launch a suitable packet driver for the NIC installed in your DOS PC. Virtually all legacy NIC's included suitable packet drivers with their installation disks. A good source of [legacy packet drivers](http://www.georgpotthast.de/sioux/packet.htm) can be found online. The ETHERDFS client TSR relies on the packet driver to send and receive all packets to your LAN. Since EtherDFS runs over raw Ethernet, it doesn't need any TCP/IP stack. The limitation of using raw Ethernet frames is that EtherDFS is, by design, unable to communicate outside of a single LAN. 31 | 32 | Finally, launch the client to connect to the server and map the shared directories to appropriate drive letters. 33 | ``` 34 | C:\> ETHERDFS.EXE 0A:0B:0C:0D:0E:0F C-W D-X 35 | ``` 36 | Will connect to the supplied MAC address and map the two shared directories to the W: and X: drives locally. To use the automatic server discovery, just substitute `::` for the MAC address, and your server on the same LAN should be found. 37 | 38 | To summarize the syntax: 39 | 40 | ``` 41 | etherdfs SRVMAC rdrv1-ldrv1 [rdrv2-ldrv2] [rdrvX-ldrvX] [options] 42 | etherdfs /u [/q] 43 | 44 | where: 45 | SRVMAC is the MAC address of the file server EtherDFS will connect to. You 46 | can also use '::' so EtherDFS will try to auto-discover the server 47 | present in your LAN. 48 | rdrv is the remote drive you want to access on the EtherSRV server. 49 | ldrv is a local drive letter where the remote filesystem will be mapped. 50 | 51 | Available options: 52 | /p=XX use the network packet driver XX (autodetected in the range 60h..80h 53 | if not specified) 54 | /n disable EtherDFS cksum - use only if you are 100% that your network 55 | hardware is working right and you really need to squeeze out some 56 | additional performance (this doesn't disable Ethernet CRC) 57 | /q quiet mode: print nothing on screen if loaded/unloaded successfully 58 | /u unload EtherDFS from memory 59 | 60 | Examples: 61 | etherdfs 6d:4f:4a:4d:49:52 C-F /q 62 | etherdfs :: C-X D-Y E-Z /p=80 63 | ``` 64 | 65 | _EtherDFS arguments are case-insensitive, and can be passed in any order._ 66 | 67 | ### Description of EtherDFS Protocol 68 | 69 | The ethernet communication between the client and the server is very simple: 70 | for every INT 2F query, the client (EtherDFS) sends a single ethernet frame to 71 | the server (ethersrv), using the following format: 72 | ``` 73 | DOEEpppVSDLxxx 74 | 75 | where: 76 | 77 | offs|field| description 78 | ----+-----+------------------------------------------------------------------- 79 | 0 | D | destination MAC address 80 | 6 | O | origin (source) MAC address 81 | 12 | EE | EtherType value (0xEDF5) 82 | 14 | ppp | padding: 38 bytes of garbage space. used to make sure every frame 83 | | | respects the minimum ethernet payload length of 46 bytes. could 84 | | | also be used in the future to fill in fake IP/UDP headers for 85 | | | router traversal and such. 86 | 52 | ss | size, in bytes, of the entire frame (optional, can be zero) 87 | 54 | cc | 16-bit BSD checksum, covers payload that follows (if CKS flag set) 88 | 56 | V | the etherdfs protocol version (7 bits) and CKS flag (highest bit) 89 | 57 | S | a single byte with a "sequence" value. Each query is supposed to 90 | | | use a different sequence, to avoid the client getting confused if 91 | | | it receives an answer relating to a different query than it 92 | | | expects. 93 | 58 | D | a single byte representing the numeric value of the destination 94 | | | (server-side) drive (A=0, B=1, C=2, etc) in its 5 lowest bits, 95 | | | and flags in its highest 3 bits (flags are undefined yet). 96 | 59 | L | the AL value of the original INT 2F query, used by the server to 97 | | | identify the exact "subfunction" that is being called. 98 | 60 | xxx | a variable-length payload of the request, it highly depends on the 99 | | | type subfunction being called. 100 | ``` 101 | For each request sent, the client expects to receive exactly one answer. The 102 | client might (and is encouraged to) repeat the query if no valid answer comes 103 | back within a reasonable period of time (several milliseconds at least). 104 | 105 | An EDF5 answer has the following format: 106 | ``` 107 | DOEEpppssccVSAAxxx 108 | 109 | where: 110 | DOEEpppssccVS = same as in query (but with D and O reversed) 111 | AA = the 16-bit value of the AX register (0 for success) 112 | xxx = an optional payload 113 | 114 | Note: All numeric values are transmitted in the native x86 format (that is, 115 | "little endian"), with the obvious exception of the EtherType which 116 | must be transmitted in network byte order (big endian). 117 | 118 | ============================================================================== 119 | RMDIR (0x01), MKDIR (0x03) and CHDIR (0x05) 120 | 121 | Request: SSS... 122 | 123 | SSS... = Variable length, contains the full path of the directory to create, 124 | remove or verify existence (like "\THIS\DIR"). 125 | 126 | Answer: - 127 | 128 | Note: The returned value of AX is 0 on success. 129 | ============================================================================== 130 | CLOSEFILE (0x06) 131 | 132 | Request: SS 133 | 134 | SS = starting sector of the open file (ie. its 16-bit identifier) 135 | 136 | Answer: - 137 | 138 | Note: The returned value of AX is 0 on success. 139 | ============================================================================== 140 | READFILE (0x08) 141 | 142 | Request: OOOOSSLL 143 | 144 | OOOO = offset of the file (where the read must start), 32-bits 145 | SS = starting sector of the open file (ie. its 16-bit identifier) 146 | LL = length of data to read 147 | 148 | Answer: DDD... 149 | 150 | DDD... = binary data of the read file 151 | 152 | Note: AX is set to non-zero on error. Be warned that although LL can be set 153 | as high as 65535, the unerlying Ethernet network is unlikely to be able 154 | to accomodate such amounts of data. 155 | ============================================================================== 156 | WRITEFILE (0x09) 157 | 158 | Request: OOOOSSDDD... 159 | 160 | OOOO = offset of the file (where the read must start), 32-bits 161 | SS = starting sector of the open file (ie. its 16-bit identifier) 162 | DDD... = binary data that has to be written (variable lenght) 163 | 164 | Answer: LL 165 | 166 | LL = amounts of data (in bytes) actually written. 167 | 168 | Note: AX is set to non-zero on error. 169 | ============================================================================== 170 | LOCK/UNLOCK FILE REGION (LOCK = 0x0A, UNLOCK = 0x0B) 171 | 172 | Request: NNSSOOOOZZZZ[OOOOZZZZ]* 173 | 174 | NN = number of lock/unlock regions (16 bit) 175 | SS = starting sector of the open file (ie. its 16-bit identifier) 176 | OOOO = offset of the file where the lock/unlock starts 177 | ZZZZ = size of the lock/unlock region 178 | 179 | Answer: - 180 | 181 | Note: AX is set to non-zero on error. 182 | ============================================================================== 183 | DISKSPACE (0x0C) 184 | 185 | Request: - 186 | 187 | Answer: BBCCDD 188 | BB = BX value 189 | CC = CX value 190 | DD = DX value 191 | 192 | Note: The AX value is already handled in the protocol's header, no need to 193 | transmit it a second time here. 194 | ============================================================================== 195 | SETATTR (0x0E) 196 | 197 | Request: Afff... 198 | A = attributes to set on file 199 | fff... = path/file name 200 | 201 | Answer: - 202 | 203 | Note: AX is set to non-zero on error. 204 | ============================================================================== 205 | GETATTR (0x0F) 206 | 207 | Request: fff... 208 | fff... = path/file name 209 | 210 | Answer: ttddssssA 211 | tt = time of file (word) 212 | dd = date of file (word) 213 | ssss = file size (dword) 214 | A = single byte with the attributes of the file 215 | 216 | Note: AX is set to non-zero on error. 217 | ============================================================================== 218 | RENAME (0x11) 219 | 220 | Request: LSSS...DDD... 221 | L = length of the source file name, in bytes 222 | SSS... = source file name and path 223 | DDD... = destination file name and path 224 | 225 | Answer: - 226 | 227 | Note: AX is set to non-zero on error. 228 | ============================================================================== 229 | DELETE (0x13) 230 | 231 | Request: fff... 232 | fff... = path/file name (may contain wildcards) 233 | 234 | Answer: - (AX = 0 on success) 235 | ============================================================================== 236 | OPEN (0x16) and CREATE (0x17) and SPOPNFIL (0x2E) 237 | 238 | Request: SSCCMMfff... 239 | SS = word from the stack (attributes for created/truncated file, see RBIL) 240 | CC = "action code" (see RBIL for details) - only relevant for SPOPNFIL 241 | MM = "open mode" (see RBIL for details) - only relevant for SPOPNFIL 242 | fff... = path/file name 243 | 244 | Answer: AfffffffffffttddssssCCRRo (25 bytes) 245 | A = single byte with the attributes of the file 246 | fff... = filename in FCB format (always 11 bytes, "FILE0000TXT") 247 | tt = time of file (word) 248 | dd = date of file (word) 249 | ssss = file size (dword) 250 | CC = start cluster of the file (16 bits) 251 | RR = CX result: 1=opened, 2=created, 3=truncated (used with SPOPNFIL only) 252 | o = access and open mode, as defined by INT 21h/AH=3Dh 253 | 254 | Note: Returns AX != 0 on error. 255 | ============================================================================== 256 | FINDFIRST (0x1B) 257 | 258 | Request: Affffffff... 259 | A = single byte with attributes we look for 260 | ffff... = path/file mask (eg. X:\DIR\FILE????.???), variable length (up to 261 | the end of the ethernet frame) 262 | 263 | Answer: AfffffffffffttddssssCCpp (24 bytes) 264 | A = single byte with the attributes we look for 265 | fff... = filename in FCB format (always 11 bytes, "FILE0000TXT") 266 | tt = time of file (word) 267 | dd = date of file (word) 268 | ssss = file size (dword) 269 | CC = "cluster" of the directory (its 16-bit identifier) 270 | pp = position of the file within the directory 271 | ============================================================================== 272 | FINDNEXT (0x1C) 273 | 274 | Request: CCppAfffffffffff 275 | CC = "cluster" of the searched directory (its 16-bit identifier) 276 | pp = the position of the last file within the directory 277 | A = a single byte with attributes we look for 278 | ffff... = an 11-bytes file search template (eg. FILE????.???) 279 | 280 | Answer: exactly the same as for FindFirst 281 | ============================================================================== 282 | SEEKFROMEND (0x21) 283 | 284 | The EDF5 protocol doesn't really need any 'seek' function. This is rather used 285 | by applications to detect changes of file sizes, by translating a 'seek from 286 | end' offset into a 'seek from start' offset. 287 | 288 | Request: ooooSS 289 | oooo = offset (in bytes) from end of file 290 | SS = the 'starting sector' (or 16-bit id) of the open file 291 | 292 | Answer: oooo 293 | oooo = offset (in bytes) from start of file 294 | ============================================================================== 295 | SETFILETIMESTAMP (0x24) 296 | 297 | Request: ttddSS 298 | tt = new time to be set on the file (FAT format, 16 bits) 299 | dd = new date to be set on the file (FAT format, 16 bits) 300 | SS = the 'starting sector' (or 16-bit id) of the open file 301 | 302 | Answer: - (AX zero on success, non-zero otherwise) 303 | 304 | Note: The INT 2Fh interface provides no method to set a file's timestamp. This 305 | call is supported by the EDF5 protocol, but client application must get 306 | creative if such support is required. This would typically involve 307 | catching INT 21h,AX=5701h queries. 308 | ============================================================================== 309 | ``` 310 | 311 | ### Known Issues 312 | 313 | #### Displayed "total" and "available" disk space information is not correct. 314 | 315 | You may notice that EtherDFS shows "total" and "available" disk space 316 | information that is not exactly the same as what the drive on your server 317 | shows. This is expected, and it is due to two causes: 318 | 319 | 1. EtherDFS limits the maximum disk space to slightly under 2 GiB, otherwise 320 | most versions of MS-DOS would be confused. 321 | 322 | 2. Even below 2 GiB, the amount of disk space won't always be exactly 323 | reported, because the method of disk space computation is likely to be 324 | different between DOS and your remote host's system. EtherDFS translates 325 | one into another, but there still will be minor differences where EtherDFS 326 | will show a few kilobytes less than expected. 327 | 328 | In both cases, the limitation is purely cosmetic and doesn't have any impact 329 | on the amount of data that can effectively be read from or written to the 330 | remote drive. 331 | 332 | #### Copied files loose their timestamps. 333 | 334 | When you copy a file to an EtherDFS drive, this file will loose its original 335 | timestamp and appear as being created "now". This is not really an EtherDFS 336 | bug, but rather a limitation of MS-DOS. I might work around this in some 337 | future versions. 338 | 339 | #### The game "Heretic" doesn't start from an EtherDFS drive with RTSPKT 340 | 341 | When the RTSPKT packet driver is used with a Realtek 8139 network card, the 342 | game "Heretic" is unable to start when launched from within an EtherDFS drive. 343 | Not sure whether this is due to a bug in EtherDFS, the RTSPKT driver or both. 344 | -------------------------------------------------------------------------------- /bin/ETHERDFS.EXE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BrianHoldsworth/etherdfs-client/36eb9e4320dfc50eb6779899cdccb39920db17c9/bin/ETHERDFS.EXE -------------------------------------------------------------------------------- /src/CHINT.H: -------------------------------------------------------------------------------- 1 | /* this is a copy of the _chain_intr() function, borrowed as-is from the 2 | * source code of OpenWatcom 1.9 (bld/clib/intel/a/chint086.asm) 3 | * the reason I'm doing this is to have it inside my own code segment, so I 4 | * can use it from within the TSR after dropping all libc */ 5 | 6 | /* original declaration: 7 | _WCRTLINK extern void _chain_intr( void 8 | (_WCINTERRUPT _DOSFAR *__handler)() ); 9 | */ 10 | 11 | #ifndef chint_h_sentinel 12 | #define chint_h_sentinel 13 | 14 | _WCRTLINK extern void _mvchain_intr(void far *__handler); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /src/CHINT086.ASM: -------------------------------------------------------------------------------- 1 | ; this is a copy of the _chain_intr() function, borrowed as-is from the 2 | ; source code of OpenWatcom 1.9 (bld/clib/intel/a/chint086.asm). I only 3 | ; changed the name of the procedure to _mvchain_intr() to avoid confusion. 4 | ; the reason I'm doing this is to have it inside my own code segment, so I 5 | ; can use it from within the TSR after dropping all libc */ 6 | 7 | 8 | ;***************************************************************************** 9 | ;* 10 | ;* Open Watcom Project 11 | ;* 12 | ;* Portions Copyright (c) 1983-2002 Sybase, Inc. All Rights Reserved. 13 | ;* 14 | ;* ======================================================================== 15 | ;* 16 | ;* This file contains Original Code and/or Modifications of Original 17 | ;* Code as defined in and that are subject to the Sybase Open Watcom 18 | ;* Public License version 1.0 (the 'License'). You may not use this file 19 | ;* except in compliance with the License. BY USING THIS FILE YOU AGREE TO 20 | ;* ALL TERMS AND CONDITIONS OF THE LICENSE. A copy of the License is 21 | ;* provided with the Original Code and Modifications, and is also 22 | ;* available at www.sybase.com/developer/opensource. 23 | ;* 24 | ;* The Original Code and all software distributed under the License are 25 | ;* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 26 | ;* EXPRESS OR IMPLIED, AND SYBASE AND ALL CONTRIBUTORS HEREBY DISCLAIM 27 | ;* ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF 28 | ;* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR 29 | ;* NON-INFRINGEMENT. Please see the License for the specific language 30 | ;* governing rights and limitations under the License. 31 | ;* 32 | ;* ======================================================================== 33 | ;* 34 | ;* Description: 16-bit chain interrupt handler function. 35 | ;* 36 | ;***************************************************************************** 37 | 38 | 39 | ;include mdef.inc 40 | ;include struct.inc 41 | 42 | name chainint 43 | 44 | BEGTEXT segment word public 'CODE' 45 | BEGTEXT ends 46 | 47 | 48 | BEGTEXT segment 49 | 50 | _mvchain_intr proc far 51 | public "C",_mvchain_intr 52 | mov cx,ax ; get offset 53 | mov ax,dx ; get segment 54 | mov sp,bp ; reset SP to point to saved registers 55 | xchg cx,20[bp] ; restore cx, & put in offset 56 | xchg ax,22[bp] ; restore ax, & put in segment 57 | mov bx,28[bp] ; restore flags 58 | and bx,0FCFFh ; except for IF and TF 59 | push bx ; : 60 | popf ; : 61 | pop dx ; pop off dummy fs entry 62 | pop dx ; pop off dummy gs entry 63 | pop es ; restore segment registers 64 | pop ds ; 65 | pop di ; . . . 66 | pop si ; . . . 67 | pop bp ; . . . 68 | pop bx ; restore registers 69 | pop bx ; . . . 70 | pop dx ; . . . 71 | ret ; return to previous interrupt handler 72 | _mvchain_intr endp 73 | 74 | BEGTEXT ends 75 | end 76 | -------------------------------------------------------------------------------- /src/DOSSTRUC.H: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the etherdfs project. 3 | * http://etherdfs.sourceforge.net 4 | * 5 | * Copyright (C) 2017 Mateusz Viste 6 | * 7 | * Contains definitions of DOS structures used by etherdfs. 8 | */ 9 | 10 | #ifndef DOSSTRUCTS_SENTINEL 11 | #define DOSSTRUCTS_SENTINEL 12 | 13 | 14 | /* make sure structs are packed tightly (required since that's how DOS packs its CDS) */ 15 | #pragma pack(1) 16 | 17 | 18 | /* CDS (current directory structure), as used by DOS 4+ */ 19 | #define CDSFLAG_SUB 0x1000u /* SUBST drive */ 20 | #define CDSFLAG_JOI 0x2000u /* JOINed drive */ 21 | #define CDSFLAG_PHY 0x4000u /* Physical drive */ 22 | #define CDSFLAG_NET 0x8000u /* Network drive */ 23 | struct cdsstruct { 24 | unsigned char current_path[67]; /* current path */ 25 | unsigned short flags; /* indicates whether the drive is physical, networked, substed or joined*/ 26 | unsigned char far *dpb; /* a pointer to the Drive Parameter Block */ 27 | union { 28 | struct { /* used for local disks */ 29 | unsigned short start_cluster; 30 | unsigned long unknown; 31 | } LOCAL; 32 | struct { /* used for network disks */ 33 | unsigned long redirifs_record_ptr; 34 | unsigned short parameter; 35 | } NET; 36 | } u; 37 | unsigned short backslash_offset; /* offset in current_path of '\' (always 2, unless it's a SUBST drive) */ 38 | /* DOS 4 and newer have 7 extra bytes here */ 39 | unsigned char f2[7]; 40 | }; /* 88 bytes total */ 41 | 42 | 43 | /* called 'srchrec' in phantom.c */ 44 | struct sdbstruct { 45 | unsigned char drv_lett; 46 | unsigned char srch_tmpl[11]; 47 | unsigned char srch_attr; 48 | unsigned short dir_entry; 49 | unsigned short par_clstr; 50 | unsigned char f1[4]; 51 | }; 52 | 53 | 54 | struct foundfilestruct { 55 | unsigned char fname[11]; 56 | unsigned char fattr; /* (1=RO 2=HID 4=SYS 8=VOL 16=DIR 32=ARCH 64=DEVICE) */ 57 | unsigned char f1[10]; 58 | unsigned short time_lstupd; /* 16 bits: hhhhhmmm mmmsssss */ 59 | unsigned short date_lstupd; /* 16 bits: YYYYYYYM MMMDDDDD */ 60 | unsigned short start_clstr; /* (optional) */ 61 | unsigned long fsize; 62 | }; 63 | 64 | 65 | /* 66 | * Pointers to SDA fields. Layout: 67 | * DOS4+ DOS 3, DR-DOS 68 | * DTA ptr 0Ch 0Ch 69 | * First filename buffer 9Eh 92h 70 | * Search data block (SDB) 19Eh 192h 71 | * Dir entry for found file 1B3h 1A7h 72 | * Search attributes 24Dh 23Ah 73 | * File access/sharing mode 24Eh 23Bh 74 | * Ptr to current CDS 282h 26Ch 75 | * Extended open mode 2E1h Not supported 76 | * 77 | * The struct below is matching FreeDOS and MSDOS 4+ 78 | */ 79 | struct sdastruct { 80 | unsigned char f0[12]; 81 | unsigned char far *curr_dta; 82 | unsigned char f1[32]; 83 | unsigned char dd; 84 | unsigned char mm; 85 | unsigned short yy_1980; 86 | unsigned char f2[106]; 87 | unsigned char fn1[128]; 88 | unsigned char fn2[128]; 89 | struct sdbstruct sdb; 90 | struct foundfilestruct found_file; 91 | struct cdsstruct drive_cdscopy; /* 88 bytes total */ 92 | unsigned char fcb_fn1[11]; 93 | unsigned char f3; 94 | unsigned char fcb_fn2[11]; 95 | unsigned char f4[11]; 96 | unsigned char srch_attr; 97 | unsigned char open_mode; 98 | unsigned char f5[51]; 99 | unsigned char far *drive_cdsptr; 100 | unsigned char f6[12]; 101 | unsigned short fn1_csofs; 102 | unsigned short fn2_csofs; 103 | unsigned char f7[71]; 104 | unsigned short spop_act; 105 | unsigned short spop_attr; 106 | unsigned short spop_mode; 107 | unsigned char f8[29]; 108 | struct { 109 | unsigned char drv_lett; 110 | unsigned char srch_tmpl[11]; 111 | unsigned char srch_attr; 112 | unsigned short dir_entry; 113 | unsigned short par_clstr; 114 | unsigned char f1[4]; 115 | } ren_srcfile; 116 | struct { 117 | unsigned char fname[11]; 118 | unsigned char fattr; /* (1=RO 2=HID 4=SYS 8=VOL 16=DIR 32=ARCH 64=DEVICE) */ 119 | unsigned char f1[10]; 120 | unsigned short time_lstupd; /* 16 bits: hhhhhmmm mmmsssss */ 121 | unsigned short date_lstupd; /* 16 bits: YYYYYYYM MMMDDDDD */ 122 | unsigned short start_clstr; /* (optional) */ 123 | unsigned long fsize; 124 | } ren_file; 125 | }; 126 | 127 | 128 | /* DOS System File Table entry - ALL DOS VERSIONS 129 | * Some of the fields below are defined by the redirector, and differ 130 | * from the SFT normally found under DOS */ 131 | struct sftstruct { 132 | unsigned int handle_count; /* count of handles referring to this file */ 133 | unsigned int open_mode; /* open mode, bit 15 set if opened via FCB */ 134 | unsigned char file_attr; /* file attributes */ 135 | unsigned int dev_info_word; /* device info word */ 136 | unsigned char far *dev_drvr_ptr; /* ??? */ 137 | unsigned int start_sector; /* starting cluster of file */ 138 | unsigned long file_time; /* file date and time */ 139 | unsigned long file_size; /* file length */ 140 | unsigned long file_pos; /* current file position */ 141 | unsigned int rel_sector; 142 | unsigned int abs_sector; 143 | unsigned int dir_sector; 144 | unsigned char dir_entry_no; 145 | char file_name[11]; 146 | }; 147 | 148 | #endif 149 | -------------------------------------------------------------------------------- /src/ETHERDFS.C: -------------------------------------------------------------------------------- 1 | /* 2 | * EtherDFS - a network drive for DOS running over raw ethernet 3 | * http://etherdfs.sourceforge.net 4 | * 5 | * Copyright (C) 2017, 2018 Mateusz Viste 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a 8 | * copy of this software and associated documentation files (the "Software"), 9 | * to deal in the Software without restriction, including without limitation 10 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | * and/or sell copies of the Software, and to permit persons to whom the 12 | * Software is furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | * DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #include /* union INTPACK */ 27 | #include "chint.h" /* _mvchain_intr() */ 28 | #include "version.h" /* program & protocol version */ 29 | 30 | /* set DEBUGLEVEL to 0, 1 or 2 to turn on debug mode with desired verbosity */ 31 | #define DEBUGLEVEL 0 32 | 33 | /* define the maximum size of a frame, as sent or received by etherdfs. 34 | * example: value 1084 accomodates payloads up to 1024 bytes +all headers */ 35 | #define FRAMESIZE 1090 36 | 37 | #include "dosstruc.h" /* definitions of structures used by DOS */ 38 | #include "globals.h" /* global variables used by etherdfs */ 39 | 40 | /* define NULL, for readability of the code */ 41 | #ifndef NULL 42 | #define NULL (void *)0 43 | #endif 44 | 45 | /* all the resident code goes to segment 'BEGTEXT' */ 46 | #pragma code_seg(BEGTEXT, CODE) 47 | 48 | 49 | /* copies l bytes from *s to *d */ 50 | static void copybytes(void far *d, void far *s, unsigned int l) { 51 | while (l != 0) { 52 | l--; 53 | *(unsigned char far *)d = *(unsigned char far *)s; 54 | d = (unsigned char far *)d + 1; 55 | s = (unsigned char far *)s + 1; 56 | } 57 | } 58 | 59 | static unsigned short mystrlen(void far *s) { 60 | unsigned short res = 0; 61 | while (*(unsigned char far *)s != 0) { 62 | res++; 63 | s = ((unsigned char far *)s) + 1; 64 | } 65 | return(res); 66 | } 67 | 68 | /* returns -1 if the NULL-terminated s string contains any wildcard (?, *) 69 | * character. otherwise returns the length of the string. */ 70 | static int len_if_no_wildcards(char far *s) { 71 | int r = 0; 72 | for (;;) { 73 | switch (*s) { 74 | case 0: return(r); 75 | case '?': 76 | case '*': return(-1); 77 | } 78 | r++; 79 | s++; 80 | } 81 | } 82 | 83 | /* computes a BSD checksum of l bytes at dataptr location */ 84 | static unsigned short bsdsum(unsigned char *dataptr, unsigned short l) { 85 | unsigned short cksum = 0; 86 | _asm { 87 | cld /* clear direction flag */ 88 | xor bx, bx /* bx will hold the result */ 89 | xor ax, ax 90 | mov cx, l 91 | mov si, dataptr 92 | iterate: 93 | lodsb /* load a byte from DS:SI into AL and INC SI */ 94 | ror bx, 1 95 | add bx, ax 96 | dec cx /* DEC CX + JNZ could be replaced by a single LOOP */ 97 | jnz iterate /* instruction, but DEC+JNZ is 3x faster (on 8086) */ 98 | mov cksum, bx 99 | } 100 | return(cksum); 101 | } 102 | 103 | /* this function is called two times by the packet driver. One time for 104 | * telling that a packet is incoming, and how big it is, so the application 105 | * can prepare a buffer for it and hand it back to the packet driver. the 106 | * second call is just to let know that the frame has been copied into the 107 | * buffer. This is a naked function - I don't need the compiler to get into 108 | * the way when dealing with packet driver callbacks. 109 | * IMPORTANT: this function must take care to modify ONLY the registers 110 | * ES and DI - packet drivers can be easily confused should anything else 111 | * be modified. */ 112 | void __declspec(naked) far pktdrv_recv(void) { 113 | _asm { 114 | jmp skip 115 | SIG db 'p','k','t','r' 116 | skip: 117 | /* save DS and flags to stack */ 118 | push ds /* save old ds (I will change it) */ 119 | push bx /* save bx (I use it as a temporary register) */ 120 | pushf /* save flags */ 121 | /* set my custom DS (not 0, it has been patched at runtime already) */ 122 | mov bx, 0 123 | mov ds, bx 124 | /* handle the call */ 125 | cmp ax, 0 126 | jne secondcall /* if ax != 0, then packet driver just filled my buffer */ 127 | /* first call: the packet driver needs a buffer of CX bytes */ 128 | cmp cx, FRAMESIZE /* is cx > FRAMESIZE ? (unsigned) */ 129 | ja nobufferavail /* it is too small (that's what she said!) */ 130 | /* see if buffer not filled already... */ 131 | cmp glob_pktdrv_recvbufflen, 0 /* is bufflen > 0 ? (signed) */ 132 | jg nobufferavail /* if signed > 0, then we are busy already */ 133 | 134 | /* buffer is available, set its seg:off in es:di */ 135 | push ds /* set es:di to recvbuff */ 136 | pop es 137 | mov di, offset glob_pktdrv_recvbuff 138 | /* set bufferlen to expected len and switch it to neg until data comes */ 139 | mov glob_pktdrv_recvbufflen, cx 140 | neg glob_pktdrv_recvbufflen 141 | /* restore flags, bx and ds, then return */ 142 | jmp restoreandret 143 | 144 | nobufferavail: /* no buffer available, or it's too small -> fail */ 145 | xor bx,bx /* set bx to zero... */ 146 | push bx /* and push it to the stack... */ 147 | push bx /* twice */ 148 | pop es /* zero out es and di - this tells the */ 149 | pop di /* packet driver 'sorry no can do' */ 150 | /* restore flags, bx and ds, then return */ 151 | jmp restoreandret 152 | 153 | secondcall: /* second call: I've just got data in buff */ 154 | /* I switch back bufflen to positive so the app can see that something is there now */ 155 | neg glob_pktdrv_recvbufflen 156 | /* restore flags, bx and ds, then return */ 157 | restoreandret: 158 | popf /* restore flags */ 159 | pop bx /* restore bx */ 160 | pop ds /* restore ds */ 161 | retf 162 | } 163 | } 164 | 165 | 166 | /* translates a drive letter (either upper- or lower-case) into a number (A=0, 167 | * B=1, C=2, etc) */ 168 | #define DRIVETONUM(x) (((x) >= 'a') && ((x) <= 'z')?x-'a':x-'A') 169 | 170 | 171 | /* all the calls I support are in the range AL=0..2Eh - the list below serves 172 | * as a convenience to compare AL (subfunction) values */ 173 | enum AL_SUBFUNCTIONS { 174 | AL_INSTALLCHK = 0x00, 175 | AL_RMDIR = 0x01, 176 | AL_MKDIR = 0x03, 177 | AL_CHDIR = 0x05, 178 | AL_CLSFIL = 0x06, 179 | AL_CMMTFIL = 0x07, 180 | AL_READFIL = 0x08, 181 | AL_WRITEFIL = 0x09, 182 | AL_LOCKFIL = 0x0A, 183 | AL_UNLOCKFIL = 0x0B, 184 | AL_DISKSPACE = 0x0C, 185 | AL_SETATTR = 0x0E, 186 | AL_GETATTR = 0x0F, 187 | AL_RENAME = 0x11, 188 | AL_DELETE = 0x13, 189 | AL_OPEN = 0x16, 190 | AL_CREATE = 0x17, 191 | AL_FINDFIRST = 0x1B, 192 | AL_FINDNEXT = 0x1C, 193 | AL_SKFMEND = 0x21, 194 | AL_UNKNOWN_2D = 0x2D, 195 | AL_SPOPNFIL = 0x2E, 196 | AL_UNKNOWN = 0xFF 197 | }; 198 | 199 | /* this table makes it easy to figure out if I want a subfunction or not */ 200 | static unsigned char supportedfunctions[0x2F] = { 201 | AL_INSTALLCHK, /* 0x00 */ 202 | AL_RMDIR, /* 0x01 */ 203 | AL_UNKNOWN, /* 0x02 */ 204 | AL_MKDIR, /* 0x03 */ 205 | AL_UNKNOWN, /* 0x04 */ 206 | AL_CHDIR, /* 0x05 */ 207 | AL_CLSFIL, /* 0x06 */ 208 | AL_CMMTFIL, /* 0x07 */ 209 | AL_READFIL, /* 0x08 */ 210 | AL_WRITEFIL, /* 0x09 */ 211 | AL_LOCKFIL, /* 0x0A */ 212 | AL_UNLOCKFIL, /* 0x0B */ 213 | AL_DISKSPACE, /* 0x0C */ 214 | AL_UNKNOWN, /* 0x0D */ 215 | AL_SETATTR, /* 0x0E */ 216 | AL_GETATTR, /* 0x0F */ 217 | AL_UNKNOWN, /* 0x10 */ 218 | AL_RENAME, /* 0x11 */ 219 | AL_UNKNOWN, /* 0x12 */ 220 | AL_DELETE, /* 0x13 */ 221 | AL_UNKNOWN, /* 0x14 */ 222 | AL_UNKNOWN, /* 0x15 */ 223 | AL_OPEN, /* 0x16 */ 224 | AL_CREATE, /* 0x17 */ 225 | AL_UNKNOWN, /* 0x18 */ 226 | AL_UNKNOWN, /* 0x19 */ 227 | AL_UNKNOWN, /* 0x1A */ 228 | AL_FINDFIRST, /* 0x1B */ 229 | AL_FINDNEXT, /* 0x1C */ 230 | AL_UNKNOWN, /* 0x1D */ 231 | AL_UNKNOWN, /* 0x1E */ 232 | AL_UNKNOWN, /* 0x1F */ 233 | AL_UNKNOWN, /* 0x20 */ 234 | AL_SKFMEND, /* 0x21 */ 235 | AL_UNKNOWN, /* 0x22 */ 236 | AL_UNKNOWN, /* 0x23 */ 237 | AL_UNKNOWN, /* 0x24 */ 238 | AL_UNKNOWN, /* 0x25 */ 239 | AL_UNKNOWN, /* 0x26 */ 240 | AL_UNKNOWN, /* 0x27 */ 241 | AL_UNKNOWN, /* 0x28 */ 242 | AL_UNKNOWN, /* 0x29 */ 243 | AL_UNKNOWN, /* 0x2A */ 244 | AL_UNKNOWN, /* 0x2B */ 245 | AL_UNKNOWN, /* 0x2C */ 246 | AL_UNKNOWN_2D, /* 0x2D */ 247 | AL_SPOPNFIL /* 0x2E */ 248 | }; 249 | 250 | /* 251 | an INTPACK struct contains following items: 252 | regs.w.gs 253 | regs.w.fs 254 | regs.w.es 255 | regs.w.ds 256 | regs.w.di 257 | regs.w.si 258 | regs.w.bp 259 | regs.w.sp 260 | regs.w.bx 261 | regs.w.dx 262 | regs.w.cx 263 | regs.w.ax 264 | regs.w.ip 265 | regs.w.cs 266 | regs.w.flags (AND with INTR_CF to fetch the CF flag - INTR_CF is defined as 0x0001) 267 | 268 | regs.h.bl 269 | regs.h.bh 270 | regs.h.dl 271 | regs.h.dh 272 | regs.h.cl 273 | regs.h.ch 274 | regs.h.al 275 | regs.h.ah 276 | */ 277 | 278 | 279 | /* sends query out, as found in glob_pktdrv_sndbuff, and awaits for an answer. 280 | * this function returns the length of replyptr, or 0xFFFF on error. */ 281 | static unsigned short sendquery(unsigned char query, unsigned char drive, unsigned short bufflen, unsigned char **replyptr, unsigned short **replyax, unsigned int updatermac) { 282 | static unsigned char seq; 283 | unsigned short count; 284 | unsigned char t; 285 | unsigned char volatile far *rtc = (unsigned char far *)0x46C; /* this points to a char, while the rtc timer is a word - but I care only about the lowest 8 bits. Be warned that this location won't increment while interrupts are disabled! */ 286 | 287 | /* resolve remote drive - no need to validate it, it has been validated 288 | * already by inthandler() */ 289 | drive = glob_data.ldrv[drive]; 290 | 291 | /* bufflen provides payload's lenght, but I prefer knowing the frame's len */ 292 | bufflen += 60; 293 | 294 | /* if query too long then quit */ 295 | if (bufflen > sizeof(glob_pktdrv_sndbuff)) return(0); 296 | /* inc seq */ 297 | seq++; 298 | /* I do not fill in ethernet headers (src mac, dst mac, ethertype), nor 299 | * PROTOVER, since all these have been inited already at transient time */ 300 | /* padding (38 bytes) */ 301 | ((unsigned short *)glob_pktdrv_sndbuff)[26] = bufflen; /* total frame len */ 302 | glob_pktdrv_sndbuff[57] = seq; /* seq number */ 303 | glob_pktdrv_sndbuff[58] = drive; 304 | glob_pktdrv_sndbuff[59] = query; /* AL value (query) */ 305 | if (glob_pktdrv_sndbuff[56] & 128) { /* if CKSUM enabled, compute it */ 306 | /* fill in the BSD checksum at offset 54 */ 307 | ((unsigned short *)glob_pktdrv_sndbuff)[27] = bsdsum(glob_pktdrv_sndbuff + 56, bufflen - 56); 308 | } 309 | /* I do not copy anything more into glob_pktdrv_sndbuff - the caller is 310 | * expected to have already copied all relevant data into glob_pktdrv_sndbuff+60 311 | * copybytes((unsigned char far *)glob_pktdrv_sndbuff + 60, (unsigned char far *)buff, bufflen); 312 | */ 313 | 314 | /* send the query frame and wait for an answer for about 100ms. then, resend 315 | * the query again and again, up to 5 times. the RTC clock at 0x46C is used 316 | * as a timing reference. */ 317 | glob_pktdrv_recvbufflen = 0; /* mark the receiving buffer empty */ 318 | for (count = 5; count != 0; count--) { /* faster than count=0; count<5; count++ */ 319 | /* send the query frame out */ 320 | _asm { 321 | /* save registers */ 322 | push ax 323 | push cx 324 | push dx /* may be changed by the packet driver (set to errno) */ 325 | push si 326 | pushf /* must be last register pushed (expected by 'call') */ 327 | /* */ 328 | mov ah, 4h /* SendPkt */ 329 | mov cx, bufflen 330 | mov si, offset glob_pktdrv_sndbuff /* DS:SI points to buff, I do not 331 | modify DS because the buffer should already 332 | be in my data segment (small memory model) */ 333 | /* int to variable vector is a mess, so I have fetched its vector myself 334 | * and pushf + cli + call far it now to simulate a regular int */ 335 | /* pushf -- already on the stack */ 336 | cli 337 | call dword ptr glob_pktdrv_pktcall 338 | /* restore registers (but not pushf, already restored by call) */ 339 | pop si 340 | pop dx 341 | pop cx 342 | pop ax 343 | } 344 | 345 | /* wait for (and validate) the answer frame */ 346 | t = *rtc; 347 | for (;;) { 348 | int i; 349 | if ((t != *rtc) && (t+1 != *rtc) && (*rtc != 0)) break; /* timeout, retry */ 350 | if (glob_pktdrv_recvbufflen < 1) continue; 351 | /* I've got something! */ 352 | /* is the frame long enough for me to care? */ 353 | if (glob_pktdrv_recvbufflen < 60) goto ignoreframe; 354 | /* is it for me? (correct src mac & dst mac) */ 355 | for (i = 0; i < 6; i++) { 356 | if (glob_pktdrv_recvbuff[i] != GLOB_LMAC[i]) goto ignoreframe; 357 | if ((updatermac == 0) && (glob_pktdrv_recvbuff[i+6] != GLOB_RMAC[i])) goto ignoreframe; 358 | } 359 | /* is the ethertype and seq what I expect? */ 360 | if ((((unsigned short *)glob_pktdrv_recvbuff)[6] != 0xF5EDu) || (glob_pktdrv_recvbuff[57] != seq)) goto ignoreframe; 361 | 362 | /* validate frame length (if provided) */ 363 | if (((unsigned short *)glob_pktdrv_recvbuff)[26] > glob_pktdrv_recvbufflen) { 364 | /* frame appears to be truncated */ 365 | goto ignoreframe; 366 | } 367 | if (((unsigned short *)glob_pktdrv_recvbuff)[26] < 60) { 368 | /* malformed frame */ 369 | goto ignoreframe; 370 | } 371 | glob_pktdrv_recvbufflen = ((unsigned short *)glob_pktdrv_recvbuff)[26]; 372 | 373 | /* if CKSUM enabled, check it on received frame */ 374 | if (glob_pktdrv_sndbuff[56] & 128) { 375 | /* is the cksum ok? */ 376 | if (bsdsum(glob_pktdrv_recvbuff + 56, glob_pktdrv_recvbufflen - 56) != (((unsigned short *)glob_pktdrv_recvbuff)[27])) { 377 | /* DEBUG - prints a '!' on screen on cksum error */ /*{ 378 | unsigned short far *v = (unsigned short far *)0xB8000000l; 379 | v[0] = 0x4000 | '!'; 380 | }*/ 381 | goto ignoreframe; 382 | } 383 | } 384 | 385 | /* return buffer (without headers and seq) */ 386 | *replyptr = glob_pktdrv_recvbuff + 60; 387 | *replyax = (unsigned short *)(glob_pktdrv_recvbuff + 58); 388 | /* update glob_rmac if needed, then return */ 389 | if (updatermac != 0) copybytes(GLOB_RMAC, glob_pktdrv_recvbuff + 6, 6); 390 | return(glob_pktdrv_recvbufflen - 60); 391 | ignoreframe: /* ignore this frame and wait for the next one */ 392 | glob_pktdrv_recvbufflen = 0; /* mark the buffer empty */ 393 | } 394 | } 395 | return(0xFFFFu); /* return error */ 396 | } 397 | 398 | 399 | /* reset CF (set on error only) and AX (expected to contain the error code, 400 | * I might set it later) - I assume a success */ 401 | #define SUCCESSFLAG glob_intregs.w.ax = 0; glob_intregs.w.flags &= ~(INTR_CF); 402 | #define FAILFLAG(x) {glob_intregs.w.ax = x; glob_intregs.w.flags |= INTR_CF;} 403 | 404 | /* this function contains the logic behind INT 2F processing */ 405 | void process2f(void) { 406 | #if DEBUGLEVEL > 0 407 | char far *dbg_msg = NULL; 408 | #endif 409 | short i; 410 | unsigned char *answer; 411 | unsigned char *buff; /* pointer to the "query arguments" part of glob_pktdrv_sndbuff */ 412 | unsigned char subfunction; 413 | unsigned short *ax; /* used to collect the resulting value of AX */ 414 | buff = glob_pktdrv_sndbuff + 60; 415 | 416 | /* DEBUG output (RED) */ 417 | #if DEBUGLEVEL > 0 418 | dbg_xpos &= 511; 419 | dbg_VGA[dbg_startoffset + dbg_xpos++] = 0x4e00 | ' '; 420 | dbg_VGA[dbg_startoffset + dbg_xpos++] = 0x4e00 | (dbg_hexc[(glob_intregs.h.al >> 4) & 0xf]); 421 | dbg_VGA[dbg_startoffset + dbg_xpos++] = 0x4e00 | (dbg_hexc[glob_intregs.h.al & 0xf]); 422 | dbg_VGA[dbg_startoffset + dbg_xpos++] = 0x4e00 | ' '; 423 | #endif 424 | 425 | /* remember the AL register (0x2F subfunction id) */ 426 | subfunction = glob_intregs.h.al; 427 | 428 | /* if we got here, then the call is definitely for us. set AX and CF to */ 429 | /* 'success' (being a natural optimist I assume success) */ 430 | SUCCESSFLAG; 431 | 432 | /* look what function is called exactly and process it */ 433 | switch (subfunction) { 434 | case AL_RMDIR: /*** 01h: RMDIR ******************************************/ 435 | /* RMDIR is like MKDIR, but I need to check if dir is not current first */ 436 | for (i = 0; glob_sdaptr->fn1[i] != 0; i++) { 437 | if (glob_sdaptr->fn1[i] != glob_sdaptr->drive_cdsptr[i]) goto proceedasmkdir; 438 | } 439 | FAILFLAG(16); /* err 16 = "attempted to remove current directory" */ 440 | break; 441 | proceedasmkdir: 442 | case AL_MKDIR: /*** 03h: MKDIR ******************************************/ 443 | i = mystrlen(glob_sdaptr->fn1); 444 | /* fn1 must be at least 2 bytes long */ 445 | if (i < 2) { 446 | FAILFLAG(3); /* "path not found" */ 447 | break; 448 | } 449 | /* copy fn1 to buff (but skip drive part) */ 450 | i -= 2; 451 | copybytes(buff, glob_sdaptr->fn1 + 2, i); 452 | /* send query providing fn1 */ 453 | if (sendquery(subfunction, glob_reqdrv, i, &answer, &ax, 0) == 0) { 454 | glob_intregs.w.ax = *ax; 455 | if (*ax != 0) glob_intregs.w.flags |= INTR_CF; 456 | } else { 457 | FAILFLAG(2); 458 | } 459 | break; 460 | case AL_CHDIR: /*** 05h: CHDIR ******************************************/ 461 | /* The INT 2Fh/1105h redirector callback is executed by DOS when 462 | * changing directories. The Phantom authors (and RBIL contributors) 463 | * clearly thought that it was the redirector's job to update the CDS, 464 | * but in fact the callback is only meant to validate that the target 465 | * directory exists; DOS subsequently updates the CDS. */ 466 | /* fn1 must be at least 2 bytes long */ 467 | i = mystrlen(glob_sdaptr->fn1); 468 | if (i < 2) { 469 | FAILFLAG(3); /* "path not found" */ 470 | break; 471 | } 472 | /* copy fn1 to buff (but skip the drive: part) */ 473 | i -= 2; 474 | copybytes(buff, glob_sdaptr->fn1 + 2, i); 475 | /* send query providing fn1 */ 476 | if (sendquery(AL_CHDIR, glob_reqdrv, i, &answer, &ax, 0) == 0) { 477 | glob_intregs.w.ax = *ax; 478 | if (*ax != 0) glob_intregs.w.flags |= INTR_CF; 479 | } else { 480 | FAILFLAG(3); /* "path not found" */ 481 | } 482 | break; 483 | case AL_CLSFIL: /*** 06h: CLSFIL ****************************************/ 484 | /* my only job is to decrement the SFT's handle count (which I didn't 485 | * have to increment during OPENFILE since DOS does it... talk about 486 | * consistency. I also inform the server about this, just so it knows */ 487 | /* ES:DI points to the SFT */ 488 | { 489 | struct sftstruct far *sftptr = MK_FP(glob_intregs.x.es, glob_intregs.x.di); 490 | if (sftptr->handle_count > 0) sftptr->handle_count--; 491 | ((unsigned short *)buff)[0] = sftptr->start_sector; 492 | if (sendquery(AL_CLSFIL, glob_reqdrv, 2, &answer, &ax, 0) == 0) { 493 | if (*ax != 0) FAILFLAG(*ax); 494 | } 495 | } 496 | break; 497 | case AL_CMMTFIL: /*** 07h: CMMTFIL **************************************/ 498 | /* I have nothing to do here */ 499 | break; 500 | case AL_READFIL: /*** 08h: READFIL **************************************/ 501 | { /* ES:DI points to the SFT (whose file_pos needs to be updated) */ 502 | /* CX = number of bytes to read (to be updated with number of bytes actually read) */ 503 | /* SDA DTA = read buffer */ 504 | struct sftstruct far *sftptr = MK_FP(glob_intregs.x.es, glob_intregs.x.di); 505 | unsigned short totreadlen; 506 | /* is the file open for write-only? */ 507 | if (sftptr->open_mode & 1) { 508 | FAILFLAG(5); /* "access denied" */ 509 | break; 510 | } 511 | /* return immediately if the caller wants to read 0 bytes */ 512 | if (glob_intregs.x.cx == 0) break; 513 | /* do multiple read operations so chunks can fit in my eth frames */ 514 | totreadlen = 0; 515 | for (;;) { 516 | int chunklen, len; 517 | if ((glob_intregs.x.cx - totreadlen) < (FRAMESIZE - 60)) { 518 | chunklen = glob_intregs.x.cx - totreadlen; 519 | } else { 520 | chunklen = FRAMESIZE - 60; 521 | } 522 | /* query is OOOOSSLL (offset, start sector, lenght to read) */ 523 | ((unsigned long *)buff)[0] = sftptr->file_pos + totreadlen; 524 | ((unsigned short *)buff)[2] = sftptr->start_sector; 525 | ((unsigned short *)buff)[3] = chunklen; 526 | len = sendquery(AL_READFIL, glob_reqdrv, 8, &answer, &ax, 0); 527 | if (len == 0xFFFFu) { /* network error */ 528 | FAILFLAG(2); 529 | break; 530 | } else if (*ax != 0) { /* backend error */ 531 | FAILFLAG(*ax); 532 | break; 533 | } else { /* success */ 534 | copybytes(glob_sdaptr->curr_dta + totreadlen, answer, len); 535 | totreadlen += len; 536 | if ((len < chunklen) || (totreadlen == glob_intregs.x.cx)) { /* EOF - update SFT and break out */ 537 | sftptr->file_pos += totreadlen; 538 | glob_intregs.x.cx = totreadlen; 539 | break; 540 | } 541 | } 542 | } 543 | } 544 | break; 545 | case AL_WRITEFIL: /*** 09h: WRITEFIL ************************************/ 546 | { /* ES:DI points to the SFT (whose file_pos needs to be updated) */ 547 | /* CX = number of bytes to write (to be updated with number of bytes actually written) */ 548 | /* SDA DTA = read buffer */ 549 | struct sftstruct far *sftptr = MK_FP(glob_intregs.x.es, glob_intregs.x.di); 550 | unsigned short bytesleft, chunklen, written = 0; 551 | /* is the file open for read-only? */ 552 | if ((sftptr->open_mode & 3) == 0) { 553 | FAILFLAG(5); /* "access denied" */ 554 | break; 555 | } 556 | /* TODO FIXME I should update the file's time in the SFT here */ 557 | /* do multiple write operations so chunks can fit in my eth frames */ 558 | bytesleft = glob_intregs.x.cx; 559 | 560 | while (bytesleft > 0) { 561 | unsigned short len; 562 | chunklen = bytesleft; 563 | if (chunklen > FRAMESIZE - 66) chunklen = FRAMESIZE - 66; 564 | /* query is OOOOSS (file offset, start sector/fileid) */ 565 | ((unsigned long *)buff)[0] = sftptr->file_pos; 566 | ((unsigned short *)buff)[2] = sftptr->start_sector; 567 | copybytes(buff + 6, glob_sdaptr->curr_dta + written, chunklen); 568 | len = sendquery(AL_WRITEFIL, glob_reqdrv, chunklen + 6, &answer, &ax, 0); 569 | if (len == 0xFFFFu) { /* network error */ 570 | FAILFLAG(2); 571 | break; 572 | } else if ((*ax != 0) || (len != 2)) { /* backend error */ 573 | FAILFLAG(*ax); 574 | break; 575 | } else { /* success - write amount of bytes written into CX and update SFT */ 576 | len = ((unsigned short *)answer)[0]; 577 | written += len; 578 | bytesleft -= len; 579 | glob_intregs.x.cx = written; 580 | sftptr->file_pos += len; 581 | if (sftptr->file_pos > sftptr->file_size) sftptr->file_size = sftptr->file_pos; 582 | if (len != chunklen) break; /* something bad happened on the other side */ 583 | } 584 | } 585 | } 586 | break; 587 | case AL_LOCKFIL: /*** 0Ah: LOCKFIL **************************************/ 588 | { 589 | struct sftstruct far *sftptr = MK_FP(glob_intregs.x.es, glob_intregs.x.di); 590 | ((unsigned short *)buff)[0] = glob_intregs.x.cx; 591 | ((unsigned short *)buff)[1] = sftptr->start_sector; 592 | if (glob_intregs.h.bl > 1) FAILFLAG(2); /* BL should be either 0 (lock) or 1 (unlock) */ 593 | /* copy 8*CX bytes from DS:DX to buff+4 (parameters block) */ 594 | copybytes(buff + 4, MK_FP(glob_intregs.x.ds, glob_intregs.x.dx), glob_intregs.x.cx << 3); 595 | if (sendquery(AL_LOCKFIL + glob_intregs.h.bl, glob_reqdrv, (glob_intregs.x.cx << 3) + 4, &answer, &ax, 0) != 0) { 596 | FAILFLAG(2); 597 | } 598 | } 599 | break; 600 | case AL_UNLOCKFIL: /*** 0Bh: UNLOCKFIL **********************************/ 601 | /* Nothing here - this isn't supposed to be used by DOS 4+ */ 602 | FAILFLAG(2); 603 | break; 604 | case AL_DISKSPACE: /*** 0Ch: get disk information ***********************/ 605 | if (sendquery(AL_DISKSPACE, glob_reqdrv, 0, &answer, &ax, 0) == 6) { 606 | glob_intregs.w.ax = *ax; /* sectors per cluster */ 607 | glob_intregs.w.bx = ((unsigned short *)answer)[0]; /* total clusters */ 608 | glob_intregs.w.cx = ((unsigned short *)answer)[1]; /* bytes per sector */ 609 | glob_intregs.w.dx = ((unsigned short *)answer)[2]; /* num of available clusters */ 610 | } else { 611 | FAILFLAG(2); 612 | } 613 | break; 614 | case AL_SETATTR: /*** 0Eh: SETATTR **************************************/ 615 | /* sdaptr->fn1 -> file to set attributes for 616 | stack word -> new attributes (stack must not be changed!) */ 617 | /* fn1 must be at least 2 characters long */ 618 | i = mystrlen(glob_sdaptr->fn1); 619 | if (i < 2) { 620 | FAILFLAG(2); 621 | break; 622 | } 623 | /* */ 624 | buff[0] = glob_reqstkword; 625 | /* copy fn1 to buff (but without the drive part) */ 626 | copybytes(buff + 1, glob_sdaptr->fn1 + 2, i - 2); 627 | #if DEBUGLEVEL > 0 628 | dbg_VGA[dbg_startoffset + dbg_xpos++] = 0x1000 | dbg_hexc[(glob_reqstkword >> 4) & 15]; 629 | dbg_VGA[dbg_startoffset + dbg_xpos++] = 0x1000 | dbg_hexc[glob_reqstkword & 15]; 630 | #endif 631 | i = sendquery(AL_SETATTR, glob_reqdrv, i - 1, &answer, &ax, 0); 632 | if (i != 0) { 633 | FAILFLAG(2); 634 | } else if (*ax != 0) { 635 | FAILFLAG(*ax); 636 | } 637 | break; 638 | case AL_GETATTR: /*** 0Fh: GETATTR **************************************/ 639 | i = mystrlen(glob_sdaptr->fn1); 640 | if (i < 2) { 641 | FAILFLAG(2); 642 | break; 643 | } 644 | i -= 2; 645 | copybytes(buff, glob_sdaptr->fn1 + 2, i); 646 | i = sendquery(AL_GETATTR, glob_reqdrv, i, &answer, &ax, 0); 647 | if ((unsigned short)i == 0xffffu) { 648 | FAILFLAG(2); 649 | } else if ((i != 9) || (*ax != 0)) { 650 | FAILFLAG(*ax); 651 | } else { /* all good */ 652 | /* CX = timestamp 653 | * DX = datestamp 654 | * BX:DI = fsize 655 | * AX = attr 656 | * NOTE: Undocumented DOS talks only about setting AX, no fsize, time 657 | * and date, these are documented in RBIL and used by SHSUCDX */ 658 | glob_intregs.w.cx = ((unsigned short *)answer)[0]; /* time */ 659 | glob_intregs.w.dx = ((unsigned short *)answer)[1]; /* date */ 660 | glob_intregs.w.bx = ((unsigned short *)answer)[3]; /* fsize hi word */ 661 | glob_intregs.w.di = ((unsigned short *)answer)[2]; /* fsize lo word */ 662 | glob_intregs.w.ax = answer[8]; /* file attribs */ 663 | } 664 | break; 665 | case AL_RENAME: /*** 11h: RENAME ****************************************/ 666 | /* sdaptr->fn1 = old name 667 | * sdaptr->fn2 = new name */ 668 | /* is the operation for the SAME drive? */ 669 | if (glob_sdaptr->fn1[0] != glob_sdaptr->fn2[0]) { 670 | FAILFLAG(2); 671 | break; 672 | } 673 | /* prepare the query (LSSS...DDD...) */ 674 | i = mystrlen(glob_sdaptr->fn1); 675 | if (i < 2) { 676 | FAILFLAG(2); 677 | break; 678 | } 679 | i -= 2; /* trim out the drive: part (C:\FILE --> \FILE) */ 680 | buff[0] = i; 681 | copybytes(buff + 1, glob_sdaptr->fn1 + 2, i); 682 | i = len_if_no_wildcards(glob_sdaptr->fn2); 683 | if (i < 2) { 684 | FAILFLAG(3); 685 | break; 686 | } 687 | i -= 2; /* trim out the drive: part (C:\FILE --> \FILE) */ 688 | copybytes(buff + 1 + buff[0], glob_sdaptr->fn2 + 2, i); 689 | /* send the query out */ 690 | i = sendquery(AL_RENAME, glob_reqdrv, 1 + buff[0] + i, &answer, &ax, 0); 691 | if (i != 0) { 692 | FAILFLAG(2); 693 | } else if (*ax != 0) { 694 | FAILFLAG(*ax); 695 | } 696 | break; 697 | case AL_DELETE: /*** 13h: DELETE ****************************************/ 698 | #if DEBUGLEVEL > 0 699 | dbg_msg = glob_sdaptr->fn1; 700 | #endif 701 | /* compute length of fn1 and copy it to buff (w/o the 'drive:' part) */ 702 | i = mystrlen(glob_sdaptr->fn1); 703 | if (i < 2) { 704 | FAILFLAG(2); 705 | break; 706 | } 707 | i -= 2; 708 | copybytes(buff, glob_sdaptr->fn1 + 2, i); 709 | /* send query */ 710 | i = sendquery(AL_DELETE, glob_reqdrv, i, &answer, &ax, 0); 711 | if ((unsigned short)i == 0xffffu) { 712 | FAILFLAG(2); 713 | } else if ((i != 0) || (*ax != 0)) { 714 | FAILFLAG(*ax); 715 | } 716 | break; 717 | case AL_OPEN: /*** 16h: OPEN ********************************************/ 718 | case AL_CREATE: /*** 17h: CREATE ****************************************/ 719 | case AL_SPOPNFIL: /*** 2Eh: SPOPNFIL ************************************/ 720 | #if DEBUGLEVEL > 0 721 | dbg_msg = glob_sdaptr->fn1; 722 | #endif 723 | /* fail if fn1 contains any wildcard, otherwise get len of fn1 */ 724 | i = len_if_no_wildcards(glob_sdaptr->fn1); 725 | if (i < 2) { 726 | FAILFLAG(3); 727 | break; 728 | } 729 | i -= 2; 730 | /* prepare and send query (SSCCMMfff...) */ 731 | ((unsigned short *)buff)[0] = glob_reqstkword; /* WORD from the stack */ 732 | ((unsigned short *)buff)[1] = glob_sdaptr->spop_act; /* action code (SPOP only) */ 733 | ((unsigned short *)buff)[2] = glob_sdaptr->spop_mode; /* open mode (SPOP only) */ 734 | copybytes(buff + 6, glob_sdaptr->fn1 + 2, i); 735 | i = sendquery(subfunction, glob_reqdrv, i + 6, &answer, &ax, 0); 736 | if ((unsigned short)i == 0xffffu) { 737 | FAILFLAG(2); 738 | } else if ((i != 25) || (*ax != 0)) { 739 | FAILFLAG(*ax); 740 | } else { 741 | /* ES:DI contains an uninitialized SFT */ 742 | struct sftstruct far *sftptr = MK_FP(glob_intregs.x.es, glob_intregs.x.di); 743 | /* special treatment for SPOP, (set open_mode and return CX, too) */ 744 | if (subfunction == AL_SPOPNFIL) { 745 | glob_intregs.w.cx = ((unsigned short *)answer)[11]; 746 | } 747 | if (sftptr->open_mode & 0x8000) { /* if bit 15 is set, then it's a "FCB open", and requires the internal DOS "Set FCB Owner" function to be called */ 748 | /* TODO FIXME set_sft_owner() */ 749 | #if DEBUGLEVEL > 0 750 | dbg_VGA[25*80] = 0x1700 | '$'; 751 | #endif 752 | } 753 | sftptr->file_attr = answer[0]; 754 | sftptr->dev_info_word = 0x8040 | glob_reqdrv; /* mark device as network & unwritten drive */ 755 | sftptr->dev_drvr_ptr = NULL; 756 | sftptr->start_sector = ((unsigned short *)answer)[10]; 757 | sftptr->file_time = ((unsigned long *)answer)[3]; 758 | sftptr->file_size = ((unsigned long *)answer)[4]; 759 | sftptr->file_pos = 0; 760 | sftptr->open_mode &= 0xff00u; 761 | sftptr->open_mode |= answer[24]; 762 | sftptr->rel_sector = 0xffff; 763 | sftptr->abs_sector = 0xffff; 764 | sftptr->dir_sector = 0; 765 | sftptr->dir_entry_no = 0xff; /* why such value? no idea, PHANTOM.C uses that, too */ 766 | copybytes(sftptr->file_name, answer + 1, 11); 767 | } 768 | break; 769 | case AL_FINDFIRST: /*** 1Bh: FINDFIRST **********************************/ 770 | case AL_FINDNEXT: /*** 1Ch: FINDNEXT ***********************************/ 771 | { 772 | /* AX = 111Bh 773 | SS = DS = DOS DS 774 | [DTA] = uninitialized 21-byte findfirst search data 775 | (see #01626 at INT 21/AH=4Eh) 776 | SDA first filename pointer (FN1, 9Eh) -> fully-qualified search template 777 | SDA CDS pointer -> current directory structure for drive with file 778 | SDA search attribute = attribute mask for search 779 | 780 | Return: 781 | CF set on error 782 | AX = DOS error code (see #01680 at INT 21/AH=59h/BX=0000h) 783 | -> http://www.ctyme.com/intr/rb-3012.htm 784 | CF clear if successful 785 | [DTA] = updated findfirst search data 786 | (bit 7 of first byte must be set) 787 | [DTA+15h] = standard directory entry for file (see #01352) 788 | 789 | FindNext is the same, but only DTA should be used to fetch search params 790 | */ 791 | struct sdbstruct far *dta; 792 | 793 | #if DEBUGLEVEL > 0 794 | dbg_msg = glob_sdaptr->fn1; 795 | #endif 796 | /* prepare the query buffer (i must provide query's length) */ 797 | if (subfunction == AL_FINDFIRST) { 798 | dta = (struct sdbstruct far *)(glob_sdaptr->curr_dta); 799 | /* FindFirst needs to fetch search arguments from SDA */ 800 | buff[0] = glob_sdaptr->srch_attr; /* file attributes to look for */ 801 | /* copy fn1 (w/o drive) to buff */ 802 | for (i = 2; glob_sdaptr->fn1[i] != 0; i++) buff[i-1] = glob_sdaptr->fn1[i]; 803 | i--; /* adjust i because its one too much otherwise */ 804 | } else { /* FindNext needs to fetch search arguments from DTA (es:di) */ 805 | dta = MK_FP(glob_intregs.x.es, glob_intregs.x.di); 806 | ((unsigned short *)buff)[0] = dta->par_clstr; 807 | ((unsigned short *)buff)[1] = dta->dir_entry; 808 | buff[4] = dta->srch_attr; 809 | /* copy search template to buff */ 810 | for (i = 0; i < 11; i++) buff[i+5] = dta->srch_tmpl[i]; 811 | i += 5; /* i must provide the exact query's length */ 812 | } 813 | /* send query to remote peer and wait for answer */ 814 | i = sendquery(subfunction, glob_reqdrv, i, &answer, &ax, 0); 815 | if (i == 0xffffu) { 816 | if (subfunction == AL_FINDFIRST) { 817 | FAILFLAG(2); /* a failed findfirst returns error 2 (file not found) */ 818 | } else { 819 | FAILFLAG(18); /* a failed findnext returns error 18 (no more files) */ 820 | } 821 | break; 822 | } else if ((*ax != 0) || (i != 24)) { 823 | FAILFLAG(*ax); 824 | break; 825 | } 826 | /* fill in the directory entry 'found_file' (32 bytes) 827 | * 00h unsigned char fname[11] 828 | * 0Bh unsigned char fattr (1=RO 2=HID 4=SYS 8=VOL 16=DIR 32=ARCH 64=DEV) 829 | * 0Ch unsigned char f1[10] 830 | * 16h unsigned short time_lstupd 831 | * 18h unsigned short date_lstupd 832 | * 1Ah unsigned short start_clstr *optional* 833 | * 1Ch unsigned long fsize 834 | */ 835 | copybytes(glob_sdaptr->found_file.fname, answer+1, 11); /* found file name */ 836 | glob_sdaptr->found_file.fattr = answer[0]; /* found file attributes */ 837 | glob_sdaptr->found_file.time_lstupd = ((unsigned short *)answer)[6]; /* time (word) */ 838 | glob_sdaptr->found_file.date_lstupd = ((unsigned short *)answer)[7]; /* date (word) */ 839 | glob_sdaptr->found_file.start_clstr = 0; /* start cluster (I don't care) */ 840 | glob_sdaptr->found_file.fsize = ((unsigned long *)answer)[4]; /* fsize (word) */ 841 | 842 | /* put things into DTA so I can understand where I left should FindNext 843 | * be called - this shall be a valid FindFirst structure (21 bytes): 844 | * 00h unsigned char drive letter (7bits, MSB must be set for remote drives) 845 | * 01h unsigned char search_tmpl[11] 846 | * 0Ch unsigned char search_attr (1=RO 2=HID 4=SYS 8=VOL 16=DIR 32=ARCH 64=DEV) 847 | * 0Dh unsigned short entry_count_within_directory 848 | * 0Fh unsigned short cluster number of start of parent directory 849 | * 11h unsigned char reserved[4] 850 | * -- RBIL says: [DTA+15h] = standard directory entry for file 851 | * 15h 11-bytes (FCB-style) filename+ext ("FILE0000TXT") 852 | * 20h unsigned char attr. of file found (1=RO 2=HID 4=SYS 8=VOL 16=DIR 32=ARCH 64=DEV) 853 | * 21h 10-bytes reserved 854 | * 2Bh unsigned short file time 855 | * 2Dh unsigned short file date 856 | * 2Fh unsigned short cluster 857 | * 31h unsigned long file size 858 | */ 859 | /* init some stuff only on FindFirst (FindNext contains valid values already) */ 860 | if (subfunction == AL_FINDFIRST) { 861 | dta->drv_lett = glob_reqdrv | 128; /* bit 7 set means 'network drive' */ 862 | copybytes(dta->srch_tmpl, glob_sdaptr->fcb_fn1, 11); 863 | dta->srch_attr = glob_sdaptr->srch_attr; 864 | } 865 | dta->par_clstr = ((unsigned short *)answer)[10]; 866 | dta->dir_entry = ((unsigned short *)answer)[11]; 867 | /* then 32 bytes as in the found_file record */ 868 | copybytes(dta + 0x15, &(glob_sdaptr->found_file), 32); 869 | } 870 | break; 871 | case AL_SKFMEND: /*** 21h: SKFMEND **************************************/ 872 | { 873 | struct sftstruct far *sftptr = MK_FP(glob_intregs.x.es, glob_intregs.x.di); 874 | ((unsigned short *)buff)[0] = glob_intregs.x.dx; 875 | ((unsigned short *)buff)[1] = glob_intregs.x.cx; 876 | ((unsigned short *)buff)[2] = sftptr->start_sector; 877 | /* send query to remote peer and wait for answer */ 878 | i = sendquery(AL_SKFMEND, glob_reqdrv, 6, &answer, &ax, 0); 879 | if (i == 0xffffu) { 880 | FAILFLAG(2); 881 | } else if ((*ax != 0) || (i != 4)) { 882 | FAILFLAG(*ax); 883 | } else { /* put new position into DX:AX */ 884 | glob_intregs.w.ax = ((unsigned short *)answer)[0]; 885 | glob_intregs.w.dx = ((unsigned short *)answer)[1]; 886 | } 887 | break; 888 | } 889 | case AL_UNKNOWN_2D: /*** 2Dh: UNKNOWN_2D ********************************/ 890 | /* this is only called in MS-DOS v4.01, its purpose is unknown. MSCDEX 891 | * returns AX=2 there, and so do I. */ 892 | glob_intregs.w.ax = 2; 893 | break; 894 | } 895 | 896 | /* DEBUG */ 897 | #if DEBUGLEVEL > 0 898 | while ((dbg_msg != NULL) && (*dbg_msg != 0)) dbg_VGA[dbg_startoffset + dbg_xpos++] = 0x4f00 | *(dbg_msg++); 899 | #endif 900 | } 901 | 902 | /* this function is hooked on INT 2Fh */ 903 | void __interrupt __far inthandler(union INTPACK r) { 904 | /* insert a static code signature so I can reliably patch myself later, 905 | * this will also contain the DS segment to use and actually set it */ 906 | _asm { 907 | jmp SKIPTSRSIG 908 | TSRSIG DB 'M','V','e','t' 909 | SKIPTSRSIG: 910 | /* save AX */ 911 | push ax 912 | /* switch to new (patched) DS */ 913 | mov ax, 0 914 | mov ds, ax 915 | /* save one word from the stack (might be used by SETATTR later) 916 | * The original stack should be at SS:BP+30 */ 917 | mov ax, ss:[BP+30] 918 | mov glob_reqstkword, ax 919 | 920 | /* uncomment the debug code below to insert a stack's dump into snd eth 921 | * frame - debugging ONLY! */ 922 | /* 923 | mov ax, ss:[BP+20] 924 | mov word ptr [glob_pktdrv_sndbuff+16], ax 925 | mov ax, ss:[BP+22] 926 | mov word ptr [glob_pktdrv_sndbuff+18], ax 927 | mov ax, ss:[BP+24] 928 | mov word ptr [glob_pktdrv_sndbuff+20], ax 929 | mov ax, ss:[BP+26] 930 | mov word ptr [glob_pktdrv_sndbuff+22], ax 931 | mov ax, ss:[BP+28] 932 | mov word ptr [glob_pktdrv_sndbuff+24], ax 933 | mov ax, ss:[BP+30] 934 | mov word ptr [glob_pktdrv_sndbuff+26], ax 935 | mov ax, ss:[BP+32] 936 | mov word ptr [glob_pktdrv_sndbuff+28], ax 937 | mov ax, ss:[BP+34] 938 | mov word ptr [glob_pktdrv_sndbuff+30], ax 939 | mov ax, ss:[BP+36] 940 | mov word ptr [glob_pktdrv_sndbuff+32], ax 941 | mov ax, ss:[BP+38] 942 | mov word ptr [glob_pktdrv_sndbuff+34], ax 943 | mov ax, ss:[BP+40] 944 | mov word ptr [glob_pktdrv_sndbuff+36], ax 945 | */ 946 | /* restore AX */ 947 | pop ax 948 | } 949 | 950 | /* DEBUG output (BLUE) */ 951 | #if DEBUGLEVEL > 1 952 | dbg_VGA[dbg_startoffset + dbg_xpos++] = 0x1e00 | (dbg_hexc[(r.h.ah >> 4) & 0xf]); 953 | dbg_VGA[dbg_startoffset + dbg_xpos++] = 0x1e00 | (dbg_hexc[r.h.ah & 0xf]); 954 | dbg_VGA[dbg_startoffset + dbg_xpos++] = 0x1e00 | (dbg_hexc[(r.h.al >> 4) & 0xf]); 955 | dbg_VGA[dbg_startoffset + dbg_xpos++] = 0x1e00 | (dbg_hexc[r.h.al & 0xf]); 956 | dbg_VGA[dbg_startoffset + dbg_xpos++] = 0; 957 | #endif 958 | 959 | /* is it a multiplex call for me? */ 960 | if (r.h.ah == glob_multiplexid) { 961 | if (r.h.al == 0) { /* install check */ 962 | r.h.al = 0xff; /* 'installed' */ 963 | r.w.bx = 0x4d86; /* MV */ 964 | r.w.cx = 0x7e1; /* 2017 */ 965 | return; 966 | } 967 | if ((r.h.al == 1) && (r.x.cx == 0x4d86)) { /* get shared data ptr (AX=0, ptr under BX:CX) */ 968 | _asm { 969 | push ds 970 | pop glob_reqstkword 971 | } 972 | r.w.ax = 0; /* zero out AX */ 973 | r.w.bx = glob_reqstkword; /* ptr returned at BX:CX */ 974 | r.w.cx = FP_OFF(&glob_data); 975 | return; 976 | } 977 | } 978 | 979 | /* if not related to a redirector function (AH=11h), or the function is 980 | * an 'install check' (0), or the function is over our scope (2Eh), or it's 981 | * an otherwise unsupported function (as pointed out by supportedfunctions), 982 | * then call the previous INT 2F handler immediately */ 983 | if ((r.h.ah != 0x11) || (r.h.al == AL_INSTALLCHK) || (r.h.al > 0x2E) || (supportedfunctions[r.h.al] == AL_UNKNOWN)) goto CHAINTOPREVHANDLER; 984 | 985 | /* DEBUG output (GREEN) */ 986 | #if DEBUGLEVEL > 0 987 | dbg_VGA[dbg_startoffset + dbg_xpos++] = 0x2e00 | (dbg_hexc[(r.h.al >> 4) & 0xf]); 988 | dbg_VGA[dbg_startoffset + dbg_xpos++] = 0x2e00 | (dbg_hexc[r.h.al & 0xf]); 989 | dbg_VGA[dbg_startoffset + dbg_xpos++] = 0; 990 | #endif 991 | 992 | /* determine whether or not the query is meant for a drive I control, 993 | * and if not - chain to the previous INT 2F handler */ 994 | if (((r.h.al >= AL_CLSFIL) && (r.h.al <= AL_UNLOCKFIL)) || (r.h.al == AL_SKFMEND) || (r.h.al == AL_UNKNOWN_2D)) { 995 | /* ES:DI points to the SFT: if the bottom 6 bits of the device information 996 | * word in the SFT are > last drive, then it relates to files not associated 997 | * with drives, such as LAN Manager named pipes. */ 998 | struct sftstruct far *sft = MK_FP(r.w.es, r.w.di); 999 | glob_reqdrv = sft->dev_info_word & 0x3F; 1000 | } else { 1001 | switch (r.h.al) { 1002 | case AL_FINDNEXT: 1003 | glob_reqdrv = glob_sdaptr->sdb.drv_lett & 0x1F; 1004 | break; 1005 | case AL_SETATTR: 1006 | case AL_GETATTR: 1007 | case AL_DELETE: 1008 | case AL_OPEN: 1009 | case AL_CREATE: 1010 | case AL_SPOPNFIL: 1011 | case AL_MKDIR: 1012 | case AL_RMDIR: 1013 | case AL_CHDIR: 1014 | case AL_RENAME: /* check sda.fn1 for drive */ 1015 | glob_reqdrv = DRIVETONUM(glob_sdaptr->fn1[0]); 1016 | break; 1017 | default: /* otherwise check out the CDS (at ES:DI) */ 1018 | { 1019 | struct cdsstruct far *cds = MK_FP(r.w.es, r.w.di); 1020 | glob_reqdrv = DRIVETONUM(cds->current_path[0]); 1021 | #if DEBUGLEVEL > 0 /* DEBUG output (ORANGE) */ 1022 | dbg_VGA[dbg_startoffset + dbg_xpos++] = 0x6e00 | ('A' + glob_reqdrv); 1023 | dbg_VGA[dbg_startoffset + dbg_xpos++] = 0x6e00 | ':'; 1024 | #endif 1025 | } 1026 | break; 1027 | } 1028 | } 1029 | /* validate drive */ 1030 | if ((glob_reqdrv > 25) || (glob_data.ldrv[glob_reqdrv] == 0xff)) { 1031 | goto CHAINTOPREVHANDLER; 1032 | } 1033 | 1034 | /* This should not be necessary. DOS usually generates an FCB-style name in 1035 | * the appropriate SDA area. However, in the case of user input such as 1036 | * 'CD ..' or 'DIR ..' it leaves the fcb area all spaces, hence the need to 1037 | * normalize the fcb area every time. */ 1038 | if (r.h.al != AL_DISKSPACE) { 1039 | unsigned short i; 1040 | unsigned char far *path = glob_sdaptr->fn1; 1041 | 1042 | /* fast forward 'path' to first character of the filename */ 1043 | for (i = 0;; i++) { 1044 | if (glob_sdaptr->fn1[i] == '\\') path = glob_sdaptr->fn1 + i + 1; 1045 | if (glob_sdaptr->fn1[i] == 0) break; 1046 | } 1047 | 1048 | /* clear out fcb_fn1 by filling it with spaces */ 1049 | for (i = 0; i < 11; i++) glob_sdaptr->fcb_fn1[i] = ' '; 1050 | 1051 | /* copy 'path' into fcb_name using the fcb syntax ("FILE TXT") */ 1052 | for (i = 0; *path != 0; path++) { 1053 | if (*path == '.') { 1054 | i = 8; 1055 | } else { 1056 | glob_sdaptr->fcb_fn1[i++] = *path; 1057 | } 1058 | } 1059 | } 1060 | 1061 | /* copy interrupt registers into glob_intregs so the int handler can access them without using any stack */ 1062 | copybytes(&glob_intregs, &r, sizeof(union INTPACK)); 1063 | /* set stack to my custom memory */ 1064 | _asm { 1065 | cli /* make sure to disable interrupts, so nobody gets in the way while I'm fiddling with the stack */ 1066 | mov glob_oldstack_seg, SS 1067 | mov glob_oldstack_off, SP 1068 | /* set SS to DS */ 1069 | mov ax, ds 1070 | mov ss, ax 1071 | /* set SP to the end of my DATASEGSZ (-2) */ 1072 | mov sp, DATASEGSZ-2 1073 | sti 1074 | } 1075 | /* call the actual INT 2F processing function */ 1076 | process2f(); 1077 | /* switch stack back */ 1078 | _asm { 1079 | cli 1080 | mov SS, glob_oldstack_seg 1081 | mov SP, glob_oldstack_off 1082 | sti 1083 | } 1084 | /* copy all registers back so watcom will set them as required 'for real' */ 1085 | copybytes(&r, &glob_intregs, sizeof(union INTPACK)); 1086 | return; 1087 | 1088 | /* hand control to the previous INT 2F handler */ 1089 | CHAINTOPREVHANDLER: 1090 | _mvchain_intr(MK_FP(glob_data.prev_2f_handler_seg, glob_data.prev_2f_handler_off)); 1091 | } 1092 | 1093 | 1094 | /*********************** HERE ENDS THE RESIDENT PART ***********************/ 1095 | 1096 | #pragma code_seg("_TEXT", "CODE"); 1097 | 1098 | /* this function obviously does nothing - but I need it because it is a 1099 | * 'low-water' mark for the end of my resident code (so I know how much memory 1100 | * exactly I can trim when going TSR) */ 1101 | void begtextend(void) { 1102 | } 1103 | 1104 | /* registers a packet driver handle to use on subsequent calls */ 1105 | static int pktdrv_accesstype(void) { 1106 | unsigned char cflag = 0; 1107 | 1108 | _asm { 1109 | mov ax, 201h /* AH=subfunction access_type(), AL=if_class=1(eth) */ 1110 | mov bx, 0ffffh /* if_type = 0xffff means 'all' */ 1111 | mov dl, 0 /* if_number: 0 (first interface) */ 1112 | /* DS:SI should point to the ethertype value in network byte order */ 1113 | mov si, offset glob_pktdrv_sndbuff + 12 /* I don't set DS, it's good already */ 1114 | mov cx, 2 /* typelen (ethertype is 16 bits) */ 1115 | /* ES:DI points to the receiving routine */ 1116 | push cs /* write segment of pktdrv_recv into es */ 1117 | pop es 1118 | mov di, offset pktdrv_recv 1119 | mov cflag, 1 /* pre-set the cflag variable to failure */ 1120 | /* int to variable vector is a mess, so I have fetched its vector myself 1121 | * and pushf + cli + call far it now to simulate a regular int */ 1122 | pushf 1123 | cli 1124 | call dword ptr glob_pktdrv_pktcall 1125 | /* get CF state - reset cflag if CF clear, and get pkthandle from AX */ 1126 | jc badluck /* Jump if Carry */ 1127 | mov word ptr [glob_data + GLOB_DATOFF_PKTHANDLE], ax /* Pkt handle should be in AX */ 1128 | mov cflag, 0 1129 | badluck: 1130 | } 1131 | 1132 | if (cflag != 0) return(-1); 1133 | return(0); 1134 | } 1135 | 1136 | /* get my own MAC addr. target MUST point to a space of at least 6 chars */ 1137 | static void pktdrv_getaddr(unsigned char *dst) { 1138 | _asm { 1139 | mov ah, 6 /* subfunction: get_addr() */ 1140 | mov bx, word ptr [glob_data + GLOB_DATOFF_PKTHANDLE]; /* handle */ 1141 | push ds /* write segment of dst into es */ 1142 | pop es 1143 | mov di, dst /* offset of dst (in small mem model dst IS an offset) */ 1144 | mov cx, 6 /* expected length (ethernet = 6 bytes) */ 1145 | /* int to variable vector is a mess, so I have fetched its vector myself 1146 | * and pushf + cli + call far it now to simulate a regular int */ 1147 | pushf 1148 | cli 1149 | call dword ptr glob_pktdrv_pktcall 1150 | } 1151 | } 1152 | 1153 | 1154 | static int pktdrv_init(unsigned short pktintparam, int nocksum) { 1155 | unsigned short far *intvect = (unsigned short far *)MK_FP(0, pktintparam << 2); 1156 | unsigned short pktdrvfuncoffs = *intvect; 1157 | unsigned short pktdrvfuncseg = *(intvect+1); 1158 | unsigned short rseg = 0, roff = 0; 1159 | char far *pktdrvfunc = (char far *)MK_FP(pktdrvfuncseg, pktdrvfuncoffs); 1160 | int i; 1161 | char sig[8]; 1162 | /* preload sig with "PKT DRVR" -- I could it just as well with 1163 | * char sig[] = "PKT DRVR", but I want to avoid this landing in 1164 | * my DATA segment so it doesn't pollute the TSR memory space. */ 1165 | sig[0] = 'P'; 1166 | sig[1] = 'K'; 1167 | sig[2] = 'T'; 1168 | sig[3] = ' '; 1169 | sig[4] = 'D'; 1170 | sig[5] = 'R'; 1171 | sig[6] = 'V'; 1172 | sig[7] = 'R'; 1173 | 1174 | /* set my ethertype to 0xF5ED (EDF5 in network byte order) */ 1175 | glob_pktdrv_sndbuff[12] = 0xED; 1176 | glob_pktdrv_sndbuff[13] = 0xF5; 1177 | /* set protover and CKSUM flag in send buffer (I won't touch it again) */ 1178 | if (nocksum == 0) { 1179 | glob_pktdrv_sndbuff[56] = PROTOVER | 128; /* protocol version */ 1180 | } else { 1181 | glob_pktdrv_sndbuff[56] = PROTOVER; /* protocol version */ 1182 | } 1183 | 1184 | pktdrvfunc += 3; /* skip three bytes of executable code */ 1185 | for (i = 0; i < 8; i++) if (sig[i] != pktdrvfunc[i]) return(-1); 1186 | 1187 | glob_data.pktint = pktintparam; 1188 | 1189 | /* fetch the vector of the pktdrv interrupt and save it for later */ 1190 | _asm { 1191 | mov ah, 35h /* AH=GetVect */ 1192 | mov al, byte ptr [glob_data] + GLOB_DATOFF_PKTINT; /* AL=int number */ 1193 | push es /* save ES and BX (will be overwritten) */ 1194 | push bx 1195 | int 21h 1196 | mov rseg, es 1197 | mov roff, bx 1198 | pop bx 1199 | pop es 1200 | } 1201 | glob_pktdrv_pktcall = rseg; 1202 | glob_pktdrv_pktcall <<= 16; 1203 | glob_pktdrv_pktcall |= roff; 1204 | 1205 | return(pktdrv_accesstype()); 1206 | } 1207 | 1208 | 1209 | static void pktdrv_free(unsigned long pktcall) { 1210 | _asm { 1211 | mov ah, 3 1212 | mov bx, word ptr [glob_data + GLOB_DATOFF_PKTHANDLE] 1213 | /* int to variable vector is a mess, so I have fetched its vector myself 1214 | * and pushf + cli + call far it now to simulate a regular int */ 1215 | pushf 1216 | cli 1217 | call dword ptr glob_pktdrv_pktcall 1218 | } 1219 | /* if (regs.x.cflag != 0) return(-1); 1220 | return(0);*/ 1221 | } 1222 | 1223 | static struct sdastruct far *getsda(void) { 1224 | /* DOS 3.0+ - GET ADDRESS OF SDA (Swappable Data Area) 1225 | * AX = 5D06h 1226 | * 1227 | * CF set on error (AX=error code) 1228 | * DS:SI -> sda pointer 1229 | */ 1230 | unsigned short rds = 0, rsi = 0; 1231 | _asm { 1232 | mov ax, 5d06h 1233 | push ds 1234 | push si 1235 | int 21h 1236 | mov bx, ds 1237 | mov cx, si 1238 | pop si 1239 | pop ds 1240 | mov rds, bx 1241 | mov rsi, cx 1242 | } 1243 | return(MK_FP(rds, rsi)); 1244 | } 1245 | 1246 | /* returns the CDS struct for drive. requires DOS 4+ */ 1247 | static struct cdsstruct far *getcds(unsigned int drive) { 1248 | /* static to preserve state: only do init once */ 1249 | static unsigned char far *dir; 1250 | static int ok = -1; 1251 | static unsigned char lastdrv; 1252 | /* init of never inited yet */ 1253 | if (ok == -1) { 1254 | /* DOS 3.x+ required - no CDS in earlier versions */ 1255 | ok = 1; 1256 | /* offsets of CDS and lastdrv in the List of Lists depends on the DOS version: 1257 | * DOS < 3 no CDS at all 1258 | * DOS 3.0 lastdrv at 1Bh, CDS pointer at 17h 1259 | * DOS 3.1+ lastdrv at 21h, CDS pointer at 16h */ 1260 | /* fetch lastdrv and CDS through a little bit of inline assembly */ 1261 | _asm { 1262 | push si /* SI needs to be preserved */ 1263 | /* get the List of Lists into ES:BX */ 1264 | mov ah, 52h 1265 | int 21h 1266 | /* get the LASTDRIVE value */ 1267 | mov si, 21h /* 21h for DOS 3.1+, 1Bh on DOS 3.0 */ 1268 | mov ah, byte ptr es:[bx+si] 1269 | mov lastdrv, ah 1270 | /* get the CDS */ 1271 | mov si, 16h /* 16h for DOS 3.1+, 17h on DOS 3.0 */ 1272 | les bx, es:[bx+si] 1273 | mov word ptr dir+2, es 1274 | mov word ptr dir, bx 1275 | /* restore the original SI value*/ 1276 | pop si 1277 | } 1278 | /* some OSes (at least OS/2) set the CDS pointer to FFFF:FFFF */ 1279 | if (dir == (unsigned char far *) -1l) ok = 0; 1280 | } /* end of static initialization */ 1281 | if (ok == 0) return(NULL); 1282 | if (drive > lastdrv) return(NULL); 1283 | /* return the CDS array entry for drive - note that currdir_size depends on 1284 | * DOS version: 0x51 on DOS 3.x, and 0x58 on DOS 4+ */ 1285 | return((struct cdsstruct __far *)((unsigned char __far *)dir + (drive * 0x58 /*currdir_size*/))); 1286 | } 1287 | /******* end of CDS-related stuff *******/ 1288 | 1289 | /* primitive message output used instead of printf() to limit memory usage 1290 | * and binary size */ 1291 | static void outmsg(char *s) { 1292 | _asm { 1293 | mov ah, 9h /* DOS 1+ - WRITE STRING TO STANDARD OUTPUT */ 1294 | mov dx, s /* small memory model: no need to set DS, 's' is an offset */ 1295 | int 21h 1296 | } 1297 | } 1298 | 1299 | /* zero out an object of l bytes */ 1300 | static void zerobytes(void *obj, unsigned short l) { 1301 | unsigned char *o = obj; 1302 | while (l-- != 0) { 1303 | *o = 0; 1304 | o++; 1305 | } 1306 | } 1307 | 1308 | /* expects a hex string of exactly two chars "XX" and returns its value, or -1 1309 | * if invalid */ 1310 | static int hexpair2int(char *hx) { 1311 | unsigned char h[2]; 1312 | unsigned short i; 1313 | /* translate hx[] to numeric values and validate */ 1314 | for (i = 0; i < 2; i++) { 1315 | if ((hx[i] >= 'A') && (hx[i] <= 'F')) { 1316 | h[i] = hx[i] - ('A' - 10); 1317 | } else if ((hx[i] >= 'a') && (hx[i] <= 'f')) { 1318 | h[i] = hx[i] - ('a' - 10); 1319 | } else if ((hx[i] >= '0') && (hx[i] <= '9')) { 1320 | h[i] = hx[i] - '0'; 1321 | } else { /* invalid */ 1322 | return(-1); 1323 | } 1324 | } 1325 | /* compute the end result and return it */ 1326 | i = h[0]; 1327 | i <<= 4; 1328 | i |= h[1]; 1329 | return(i); 1330 | } 1331 | 1332 | /* translates an ASCII MAC address into a 6-bytes binary string */ 1333 | static int string2mac(unsigned char *d, char *mac) { 1334 | int i, v; 1335 | /* is it exactly 17 chars long? */ 1336 | for (i = 0; mac[i] != 0; i++); 1337 | if (i != 17) return(-1); 1338 | /* are nibble pairs separated by colons? */ 1339 | for (i = 2; i < 16; i += 3) if (mac[i] != ':') return(-1); 1340 | /* translate each byte to its numeric value */ 1341 | for (i = 0; i < 16; i += 3) { 1342 | v = hexpair2int(mac + i); 1343 | if (v < 0) return(-1); 1344 | *d = v; 1345 | d++; 1346 | } 1347 | return(0); 1348 | } 1349 | 1350 | 1351 | #define ARGFL_QUIET 1 1352 | #define ARGFL_AUTO 2 1353 | #define ARGFL_UNLOAD 4 1354 | #define ARGFL_NOCKSUM 8 1355 | 1356 | /* a structure used to pass and decode arguments between main() and parseargv() */ 1357 | struct argstruct { 1358 | int argc; /* original argc */ 1359 | char **argv; /* original argv */ 1360 | unsigned short pktint; /* custom packet driver interrupt */ 1361 | unsigned char flags; /* ARGFL_QUIET, ARGFL_AUTO, ARGFL_UNLOAD, ARGFL_CKSUM */ 1362 | }; 1363 | 1364 | 1365 | /* parses (and applies) command-line arguments. returns 0 on success, 1366 | * non-zero otherwise */ 1367 | static int parseargv(struct argstruct *args) { 1368 | int i, drivemapflag = 0, gotmac = 0; 1369 | 1370 | /* iterate through arguments, if any */ 1371 | for (i = 1; i < args->argc; i++) { 1372 | char opt; 1373 | char *arg; 1374 | /* is it a drive mapping, like "c-x"? */ 1375 | if ((args->argv[i][0] >= 'A') && (args->argv[i][1] == '-') && (args->argv[i][2] >= 'A') && (args->argv[i][3] == 0)) { 1376 | unsigned char ldrv, rdrv; 1377 | rdrv = DRIVETONUM(args->argv[i][0]); 1378 | ldrv = DRIVETONUM(args->argv[i][2]); 1379 | if ((ldrv > 25) || (rdrv > 25)) return(-2); 1380 | if (glob_data.ldrv[ldrv] != 0xff) return(-2); 1381 | glob_data.ldrv[ldrv] = rdrv; 1382 | drivemapflag = 1; 1383 | continue; 1384 | } 1385 | /* not a drive mapping -> is it an option? */ 1386 | if (args->argv[i][0] == '/') { 1387 | if (args->argv[i][1] == 0) return(-3); 1388 | opt = args->argv[i][1]; 1389 | /* fetch option's argument, if any */ 1390 | if (args->argv[i][2] == 0) { /* single option */ 1391 | arg = NULL; 1392 | } else if (args->argv[i][2] == '=') { /* trailing argument */ 1393 | arg = args->argv[i] + 3; 1394 | } else { 1395 | return(-3); 1396 | } 1397 | /* normalize the option char to lower case */ 1398 | if ((opt >= 'A') && (opt <= 'Z')) opt += ('a' - 'A'); 1399 | /* what is the option about? */ 1400 | switch (opt) { 1401 | case 'q': 1402 | if (arg != NULL) return(-4); 1403 | args->flags |= ARGFL_QUIET; 1404 | break; 1405 | case 'p': 1406 | if (arg == NULL) return(-4); 1407 | /* I expect an exactly 2-characters string */ 1408 | if ((arg[0] == 0) || (arg[1] == 0) || (arg[2] != 0)) return(-1); 1409 | if ((args->pktint = hexpair2int(arg)) < 1) return(-4); 1410 | break; 1411 | case 'n': /* disable CKSUM */ 1412 | if (arg != NULL) return(-4); 1413 | args->flags |= ARGFL_NOCKSUM; 1414 | break; 1415 | case 'u': /* unload EtherDFS */ 1416 | if (arg != NULL) return(-4); 1417 | args->flags |= ARGFL_UNLOAD; 1418 | break; 1419 | default: /* invalid parameter */ 1420 | return(-5); 1421 | } 1422 | continue; 1423 | } 1424 | /* not a drive mapping nor an option -> so it's a MAC addr perhaps? */ 1425 | if (gotmac != 0) return(-1); /* fail if got a MAC already */ 1426 | /* read the srv mac address, unless it's "::" (auto) */ 1427 | if ((args->argv[i][0] == ':') && (args->argv[i][1] == ':') && (args->argv[i][2] == 0)) { 1428 | args->flags |= ARGFL_AUTO; 1429 | } else { 1430 | if (string2mac(GLOB_RMAC, args->argv[i]) != 0) return(-1); 1431 | } 1432 | gotmac = 1; 1433 | } 1434 | 1435 | /* fail if MAC+unload or mapping+unload */ 1436 | if (args->flags & ARGFL_UNLOAD) { 1437 | if ((gotmac != 0) || (drivemapflag != 0)) return(-1); 1438 | return(0); 1439 | } 1440 | 1441 | /* did I get at least one drive mapping? and a MAC? */ 1442 | if ((drivemapflag == 0) || (gotmac == 0)) return(-6); 1443 | 1444 | return(0); 1445 | } 1446 | 1447 | /* translates an unsigned byte into a 2-characters string containing its hex 1448 | * representation. s needs to be at least 3 bytes long. */ 1449 | static void byte2hex(char *s, unsigned char b) { 1450 | char h[16]; 1451 | unsigned short i; 1452 | /* pre-compute h[] with a string 0..F -- I could do the same thing easily 1453 | * with h[] = "0123456789ABCDEF", but then this would land inside the DATA 1454 | * segment, while I want to keep it in stack to avoid polluting the TSR's 1455 | * memory space */ 1456 | for (i = 0; i < 10; i++) h[i] = '0' + i; 1457 | for (; i < 16; i++) h[i] = ('A' - 10) + i; 1458 | /* */ 1459 | s[0] = h[b >> 4]; 1460 | s[1] = h[b & 15]; 1461 | s[2] = 0; 1462 | } 1463 | 1464 | /* allocates sz bytes of memory and returns the segment to allocated memory or 1465 | * 0 on error. the allocation strategy is 'highest possible' (last fit) to 1466 | * avoid memory fragmentation */ 1467 | static unsigned short allocseg(unsigned short sz) { 1468 | unsigned short volatile res = 0; 1469 | /* sz should contains number of 16-byte paragraphs instead of bytes */ 1470 | sz += 15; /* make sure to allocate enough paragraphs */ 1471 | sz >>= 4; 1472 | /* ask DOS for memory */ 1473 | _asm { 1474 | push cx /* save cx */ 1475 | /* set strategy to 'last fit' */ 1476 | mov ah, 58h 1477 | xor al, al /* al = 0 means 'get strategy' */ 1478 | int 21h /* now current strategy is in ax */ 1479 | mov cx, ax /* copy current strategy to cx */ 1480 | mov ah, 58h 1481 | mov al, 1 /* al = 1 means 'set strategy' */ 1482 | mov bl, 2 /* 2 or greater means 'last fit' */ 1483 | int 21h 1484 | /* do the allocation now */ 1485 | mov ah, 48h /* alloc memory (DOS 2+) */ 1486 | mov bx, sz /* number of paragraphs to allocate */ 1487 | mov res, 0 /* pre-set res to failure (0) */ 1488 | int 21h /* returns allocated segment in AX */ 1489 | /* check CF */ 1490 | jc failed 1491 | mov res, ax /* set res to actual result */ 1492 | failed: 1493 | /* set strategy back to its initial setting */ 1494 | mov ah, 58h 1495 | mov al, 1 1496 | mov bx, cx 1497 | int 21h 1498 | pop cx /* restore cx */ 1499 | } 1500 | return(res); 1501 | } 1502 | 1503 | /* free segment previously allocated through allocseg() */ 1504 | static void freeseg(unsigned short segm) { 1505 | _asm { 1506 | mov ah, 49h /* free memory (DOS 2+) */ 1507 | mov es, segm /* put segment to free into ES */ 1508 | int 21h 1509 | } 1510 | } 1511 | 1512 | /* patch the TSR routine and packet driver handler so they use my new DS. 1513 | * return 0 on success, non-zero otherwise */ 1514 | static int updatetsrds(void) { 1515 | unsigned short newds; 1516 | unsigned char far *ptr; 1517 | unsigned short far *sptr; 1518 | newds = 0; 1519 | _asm { 1520 | push ds 1521 | pop newds 1522 | } 1523 | 1524 | /* first patch the TSR routine */ 1525 | ptr = (unsigned char far *)inthandler + 24; /* the interrupt handler's signature appears at offset 23 (this might change at each source code modification and/or optimization settings) */ 1526 | /*{ 1527 | int x; 1528 | unsigned short far *VGA = (unsigned short far *)(0xB8000000l); 1529 | for (x = 0; x < 128; x++) VGA[80*12 + ((x >> 6) * 80) + (x & 63)] = 0x1f00 | ptr[x]; 1530 | }*/ 1531 | sptr = (unsigned short far *)ptr; 1532 | /* check for the routine's signature first ("MVet") */ 1533 | if ((ptr[0] != 'M') || (ptr[1] != 'V') || (ptr[2] != 'e') || (ptr[3] != 't')) return(-1); 1534 | sptr[3] = newds; 1535 | /* now patch the pktdrv_recv() routine */ 1536 | ptr = (unsigned char far *)pktdrv_recv + 3; 1537 | sptr = (unsigned short far *)ptr; 1538 | /*{ 1539 | int x; 1540 | unsigned short far *VGA = (unsigned short far *)(0xB8000000l); 1541 | for (x = 0; x < 128; x++) VGA[80*12 + ((x >> 6) * 80) + (x & 63)] = 0x1f00 | ptr[x]; 1542 | }*/ 1543 | /* check for the routine's signature first */ 1544 | if ((ptr[0] != 'p') || (ptr[1] != 'k') || (ptr[2] != 't') || (ptr[3] != 'r')) return(-1); 1545 | sptr[4] = newds; 1546 | /*{ 1547 | int x; 1548 | unsigned short far *VGA = (unsigned short far *)(0xB8000000l); 1549 | for (x = 0; x < 128; x++) VGA[80*20 + ((x >> 6) * 80) + (x & 63)] = 0x1f00 | ptr[x]; 1550 | }*/ 1551 | return(0); 1552 | } 1553 | 1554 | /* scans the 2Fh interrupt for some available 'multiplex id' in the range 1555 | * C0..FF. also checks for EtherDFS presence at the same time. returns: 1556 | * - the available id if found 1557 | * - the id of the already-present etherdfs instance 1558 | * - 0 if no available id found 1559 | * presentflag set to 0 if no etherdfs found loaded, non-zero otherwise. */ 1560 | static unsigned char findfreemultiplex(unsigned char *presentflag) { 1561 | unsigned char id = 0, freeid = 0, pflag = 0; 1562 | _asm { 1563 | mov id, 0C0h /* start scanning at C0h */ 1564 | checkid: 1565 | xor al, al /* subfunction is 'installation check' (00h) */ 1566 | mov ah, id 1567 | int 2Fh 1568 | /* is it free? (AL == 0) */ 1569 | test al, al 1570 | jnz notfree /* not free - is it me perhaps? */ 1571 | mov freeid, ah /* it's free - remember it, I may use it myself soon */ 1572 | jmp checknextid 1573 | notfree: 1574 | /* is it me? (AL=FF + BX=4D86 CX=7E1 [MV 2017]) */ 1575 | cmp al, 0ffh 1576 | jne checknextid 1577 | cmp bx, 4d86h 1578 | jne checknextid 1579 | cmp cx, 7e1h 1580 | jne checknextid 1581 | /* if here, then it's me... */ 1582 | mov ah, id 1583 | mov freeid, ah 1584 | mov pflag, 1 1585 | jmp gameover 1586 | checknextid: 1587 | /* if not me, then check next id */ 1588 | inc id 1589 | jnz checkid /* if id is zero, then all range has been covered (C0..FF) */ 1590 | gameover: 1591 | } 1592 | *presentflag = pflag; 1593 | return(freeid); 1594 | } 1595 | 1596 | int main(int argc, char **argv) { 1597 | struct argstruct args; 1598 | struct cdsstruct far *cds; 1599 | unsigned char tmpflag = 0; 1600 | int i; 1601 | unsigned short volatile newdataseg; /* 'volatile' just in case the compiler would try to optimize it out, since I set it through in-line assembly */ 1602 | 1603 | /* set all drive mappings as 'unused' */ 1604 | for (i = 0; i < 26; i++) glob_data.ldrv[i] = 0xff; 1605 | 1606 | /* parse command-line arguments */ 1607 | zerobytes(&args, sizeof(args)); 1608 | args.argc = argc; 1609 | args.argv = argv; 1610 | if (parseargv(&args) != 0) { 1611 | #include "msg/help.c" 1612 | return(1); 1613 | } 1614 | 1615 | /* check DOS version - I require DOS 5.0+ */ 1616 | _asm { 1617 | mov ax, 3306h 1618 | int 21h 1619 | mov tmpflag, bl 1620 | inc al /* if AL was 0xFF ("unsupported function"), it is 0 now */ 1621 | jnz done 1622 | mov tmpflag, 0 /* if AL is 0 (hence was 0xFF), set dosver to 0 */ 1623 | done: 1624 | } 1625 | if (tmpflag < 5) { /* tmpflag contains DOS version or 0 for 'unknown' */ 1626 | #include "msg\\unsupdos.c" 1627 | return(1); 1628 | } 1629 | 1630 | /* look whether or not it's ok to install a network redirector at int 2F */ 1631 | _asm { 1632 | mov tmpflag, 0 1633 | mov ax, 1100h 1634 | int 2Fh 1635 | dec ax /* if AX was set to 1 (ie. "not ok to install"), it's zero now */ 1636 | jnz goodtogo 1637 | mov tmpflag, 1 1638 | goodtogo: 1639 | } 1640 | if (tmpflag != 0) { 1641 | #include "msg\\noredir.c" 1642 | return(1); 1643 | } 1644 | 1645 | /* is it all about unloading myself? */ 1646 | if ((args.flags & ARGFL_UNLOAD) != 0) { 1647 | unsigned char etherdfsid, pktint; 1648 | unsigned short myseg, myoff, myhandle, mydataseg; 1649 | unsigned long pktdrvcall; 1650 | struct tsrshareddata far *tsrdata; 1651 | unsigned char far *int2fptr; 1652 | 1653 | /* am I loaded at all? */ 1654 | etherdfsid = findfreemultiplex(&tmpflag); 1655 | if (tmpflag == 0) { /* not loaded, cannot unload */ 1656 | #include "msg\\notload.c" 1657 | return(1); 1658 | } 1659 | /* am I still at the top of the int 2Fh chain? */ 1660 | _asm { 1661 | /* save AX, BX and ES */ 1662 | push ax 1663 | push bx 1664 | push es 1665 | /* fetch int vector */ 1666 | mov ax, 352Fh /* AH=35h 'GetVect' for int 2Fh */ 1667 | int 21h 1668 | mov myseg, es 1669 | mov myoff, bx 1670 | /* restore AX, BX and ES */ 1671 | pop es 1672 | pop bx 1673 | pop ax 1674 | } 1675 | int2fptr = (unsigned char far *)MK_FP(myseg, myoff) + 24; /* the interrupt handler's signature appears at offset 24 (this might change at each source code modification) */ 1676 | /* look for the "MVet" signature */ 1677 | if ((int2fptr[0] != 'M') || (int2fptr[1] != 'V') || (int2fptr[2] != 'e') || (int2fptr[3] != 't')) { 1678 | #include "msg\\othertsr.c"; 1679 | return(1); 1680 | } 1681 | /* get the ptr to TSR's data */ 1682 | _asm { 1683 | push ax 1684 | push bx 1685 | push cx 1686 | pushf 1687 | mov ah, etherdfsid 1688 | mov al, 1 1689 | mov cx, 4d86h 1690 | mov myseg, 0ffffh 1691 | int 2Fh /* AX should be 0, and BX:CX contains the address */ 1692 | test ax, ax 1693 | jnz fail 1694 | mov myseg, bx 1695 | mov myoff, cx 1696 | mov mydataseg, dx 1697 | fail: 1698 | popf 1699 | pop cx 1700 | pop bx 1701 | pop ax 1702 | } 1703 | if (myseg == 0xffffu) { 1704 | #include "msg\\tsrcomfa.c" 1705 | return(1); 1706 | } 1707 | tsrdata = MK_FP(myseg, myoff); 1708 | mydataseg = myseg; 1709 | /* restore previous int 2f handler (under DS:DX, AH=25h, INT 21h)*/ 1710 | myseg = tsrdata->prev_2f_handler_seg; 1711 | myoff = tsrdata->prev_2f_handler_off; 1712 | _asm { 1713 | /* save AX, DS and DX */ 1714 | push ax 1715 | push ds 1716 | push dx 1717 | /* set DS:DX */ 1718 | mov ax, myseg 1719 | push ax 1720 | pop ds 1721 | mov dx, myoff 1722 | /* call INT 21h,25h for int 2Fh */ 1723 | mov ax, 252Fh 1724 | int 21h 1725 | /* restore AX, DS and DX */ 1726 | pop dx 1727 | pop ds 1728 | pop ax 1729 | } 1730 | /* get the address of the packet driver routine */ 1731 | pktint = tsrdata->pktint; 1732 | _asm { 1733 | /* save AX, BX and ES */ 1734 | push ax 1735 | push bx 1736 | push es 1737 | /* fetch int vector */ 1738 | mov ah, 35h /* AH=35h 'GetVect' */ 1739 | mov al, pktint /* interrupt */ 1740 | int 21h 1741 | mov myseg, es 1742 | mov myoff, bx 1743 | /* restore AX, BX and ES */ 1744 | pop es 1745 | pop bx 1746 | pop ax 1747 | } 1748 | pktdrvcall = myseg; 1749 | pktdrvcall <<= 16; 1750 | pktdrvcall |= myoff; 1751 | /* unregister packet driver */ 1752 | myhandle = tsrdata->pkthandle; 1753 | _asm { 1754 | /* save AX and BX */ 1755 | push ax 1756 | push bx 1757 | /* prepare the release_type() call */ 1758 | mov ah, 3 /* release_type() */ 1759 | mov bx, myhandle 1760 | /* call the pktdrv int */ 1761 | /* int to variable vector is a mess, so I have fetched its vector myself 1762 | * and pushf + cli + call far it now to simulate a regular int */ 1763 | pushf 1764 | cli 1765 | call dword ptr pktdrvcall 1766 | /* restore AX and BX */ 1767 | pop bx 1768 | pop ax 1769 | } 1770 | /* set all mapped drives as 'not available' */ 1771 | for (i = 0; i < 26; i++) { 1772 | if (tsrdata->ldrv[i] == 0xff) continue; 1773 | cds = getcds(i); 1774 | if (cds != NULL) cds->flags = 0; 1775 | } 1776 | /* free TSR's data/stack seg and its PSP */ 1777 | freeseg(mydataseg); 1778 | freeseg(tsrdata->pspseg); 1779 | /* all done */ 1780 | if ((args.flags & ARGFL_QUIET) == 0) { 1781 | #include "msg\\unloaded.c" 1782 | } 1783 | return(0); 1784 | } 1785 | 1786 | /* remember current int 2f handler, we might over-write it soon (also I 1787 | * use it to see if I'm already loaded) */ 1788 | _asm { 1789 | mov ax, 352fh; /* AH=GetVect AL=2F */ 1790 | push es /* save ES and BX (will be overwritten) */ 1791 | push bx 1792 | int 21h 1793 | mov word ptr [glob_data + GLOB_DATOFF_PREV2FHANDLERSEG], es 1794 | mov word ptr [glob_data + GLOB_DATOFF_PREV2FHANDLEROFF], bx 1795 | pop bx 1796 | pop es 1797 | } 1798 | 1799 | /* is the TSR installed already? */ 1800 | glob_multiplexid = findfreemultiplex(&tmpflag); 1801 | if (tmpflag != 0) { /* already loaded */ 1802 | #include "msg\\alrload.c" 1803 | return(1); 1804 | } else if (glob_multiplexid == 0) { /* no free multiplex id found */ 1805 | #include "msg\\nomultpx.c" 1806 | return(1); 1807 | } 1808 | 1809 | /* if any of the to-be-mapped drives is already active, fail */ 1810 | for (i = 0; i < 26; i++) { 1811 | if (glob_data.ldrv[i] == 0xff) continue; 1812 | cds = getcds(i); 1813 | if (cds == NULL) { 1814 | #include "msg\\mapfail.c" 1815 | return(1); 1816 | } 1817 | if (cds->flags != 0) { 1818 | #include "msg\\drvactiv.c" 1819 | return(1); 1820 | } 1821 | } 1822 | 1823 | /* allocate a new segment for all my internal needs, and use it right away 1824 | * as DS */ 1825 | newdataseg = allocseg(DATASEGSZ); 1826 | if (newdataseg == 0) { 1827 | #include "msg\\memfail.c" 1828 | return(1); 1829 | } 1830 | 1831 | /* copy current DS into the new segment and switch to new DS/SS */ 1832 | _asm { 1833 | /* save registers on the stack */ 1834 | push es 1835 | push cx 1836 | push si 1837 | push di 1838 | pushf 1839 | /* copy the memory block */ 1840 | mov cx, DATASEGSZ /* copy cx bytes */ 1841 | xor si, si /* si = 0*/ 1842 | xor di, di /* di = 0 */ 1843 | cld /* clear direction flag (increment si/di) */ 1844 | mov es, newdataseg /* load es with newdataseg */ 1845 | rep movsb /* execute copy DS:SI -> ES:DI */ 1846 | /* restore registers (but NOT es, instead save it into AX for now) */ 1847 | popf 1848 | pop di 1849 | pop si 1850 | pop cx 1851 | pop ax 1852 | /* switch to the new DS _AND_ SS now */ 1853 | push es 1854 | push es 1855 | pop ds 1856 | pop ss 1857 | /* restore ES */ 1858 | push ax 1859 | pop es 1860 | } 1861 | 1862 | /* patch the TSR and pktdrv_recv() so they use my new DS */ 1863 | if (updatetsrds() != 0) { 1864 | #include "msg\\relfail.c" 1865 | freeseg(newdataseg); 1866 | return(1); 1867 | } 1868 | 1869 | /* remember the SDA address (will be useful later) */ 1870 | glob_sdaptr = getsda(); 1871 | 1872 | /* init the packet driver interface */ 1873 | glob_data.pktint = 0; 1874 | if (args.pktint == 0) { /* detect first packet driver within int 60h..80h */ 1875 | for (i = 0x60; i <= 0x80; i++) { 1876 | if (pktdrv_init(i, args.flags & ARGFL_NOCKSUM) == 0) break; 1877 | } 1878 | } else { /* use the pktdrvr interrupt passed through command line */ 1879 | pktdrv_init(args.pktint, args.flags & ARGFL_NOCKSUM); 1880 | } 1881 | /* has it succeeded? */ 1882 | if (glob_data.pktint == 0) { 1883 | #include "msg\\pktdfail.c" 1884 | freeseg(newdataseg); 1885 | return(1); 1886 | } 1887 | pktdrv_getaddr(GLOB_LMAC); 1888 | 1889 | /* should I auto-discover the server? */ 1890 | if ((args.flags & ARGFL_AUTO) != 0) { 1891 | unsigned short *ax; 1892 | unsigned char *answer; 1893 | /* set (temporarily) glob_rmac to broadcast */ 1894 | for (i = 0; i < 6; i++) GLOB_RMAC[i] = 0xff; 1895 | for (i = 0; glob_data.ldrv[i] == 0xff; i++); /* find first mapped disk */ 1896 | /* send a discovery frame that will update glob_rmac */ 1897 | if (sendquery(AL_DISKSPACE, i, 0, &answer, &ax, 1) != 6) { 1898 | #include "msg\\nosrvfnd.c" 1899 | pktdrv_free(glob_pktdrv_pktcall); /* free the pkt drv and quit */ 1900 | freeseg(newdataseg); 1901 | return(1); 1902 | } 1903 | } 1904 | 1905 | /* set all drives as being 'network' drives (also add the PHYSICAL bit, 1906 | * otherwise MS-DOS 6.0 will ignore the drive) */ 1907 | for (i = 0; i < 26; i++) { 1908 | if (glob_data.ldrv[i] == 0xff) continue; 1909 | cds = getcds(i); 1910 | cds->flags = CDSFLAG_NET | CDSFLAG_PHY; 1911 | /* set 'current path' to root, to avoid inheriting any garbage */ 1912 | cds->current_path[0] = 'A' + i; 1913 | cds->current_path[1] = ':'; 1914 | cds->current_path[2] = '\\'; 1915 | cds->current_path[3] = 0; 1916 | } 1917 | 1918 | if ((args.flags & ARGFL_QUIET) == 0) { 1919 | char buff[20]; 1920 | #include "msg\\instlled.c" 1921 | for (i = 0; i < 6; i++) { 1922 | byte2hex(buff + i + i + i, GLOB_LMAC[i]); 1923 | } 1924 | for (i = 2; i < 16; i += 3) buff[i] = ':'; 1925 | buff[17] = '$'; 1926 | outmsg(buff); 1927 | #include "msg\\pktdrvat.c" 1928 | byte2hex(buff, glob_data.pktint); 1929 | buff[2] = ')'; 1930 | buff[3] = '\r'; 1931 | buff[4] = '\n'; 1932 | buff[5] = '$'; 1933 | outmsg(buff); 1934 | for (i = 0; i < 26; i++) { 1935 | int z; 1936 | if (glob_data.ldrv[i] == 0xff) continue; 1937 | buff[0] = ' '; 1938 | buff[1] = 'A' + i; 1939 | buff[2] = ':'; 1940 | buff[3] = ' '; 1941 | buff[4] = '-'; 1942 | buff[5] = '>'; 1943 | buff[6] = ' '; 1944 | buff[7] = '['; 1945 | buff[8] = 'A' + glob_data.ldrv[i]; 1946 | buff[9] = ':'; 1947 | buff[10] = ']'; 1948 | buff[11] = ' '; 1949 | buff[12] = 'o'; 1950 | buff[13] = 'n'; 1951 | buff[14] = ' '; 1952 | buff[15] = '$'; 1953 | outmsg(buff); 1954 | for (z = 0; z < 6; z++) { 1955 | byte2hex(buff + z + z + z, GLOB_RMAC[z]); 1956 | } 1957 | for (z = 2; z < 16; z += 3) buff[z] = ':'; 1958 | buff[17] = '\r'; 1959 | buff[18] = '\n'; 1960 | buff[19] = '$'; 1961 | outmsg(buff); 1962 | } 1963 | } 1964 | 1965 | /* get the segment of the PSP (might come handy later) */ 1966 | _asm { 1967 | mov ah, 62h /* get current PSP address */ 1968 | int 21h /* returns the segment of PSP in BX */ 1969 | mov word ptr [glob_data + GLOB_DATOFF_PSPSEG], bx /* copy PSP segment to glob_pspseg */ 1970 | } 1971 | 1972 | /* free the environment (env segment is at offset 2C of the PSP) */ 1973 | _asm { 1974 | mov es, word ptr [glob_data + GLOB_DATOFF_PSPSEG] /* load ES with PSP's segment */ 1975 | mov es, es:[2Ch] /* get segment of the env block */ 1976 | mov ah, 49h /* free memory (DOS 2+) */ 1977 | int 21h 1978 | } 1979 | 1980 | /* set up the TSR (INT 2F catching) */ 1981 | _asm { 1982 | cli 1983 | mov ax, 252fh /* AH=set interrupt vector AL=2F */ 1984 | push ds /* preserve DS and DX */ 1985 | push dx 1986 | push cs /* set DS to current CS, that is provide the */ 1987 | pop ds /* int handler's segment */ 1988 | mov dx, offset inthandler /* int handler's offset */ 1989 | int 21h 1990 | pop dx /* restore DS and DX to previous values */ 1991 | pop ds 1992 | sti 1993 | } 1994 | 1995 | /* Turn self into a TSR and free memory I won't need any more. That is, I 1996 | * free all the libc startup code and my init functions by passing the 1997 | * number of paragraphs to keep resident to INT 21h, AH=31h. How to compute 1998 | * the number of paragraphs? Simple: look at the memory map and note down 1999 | * the size of the BEGTEXT segment (that's where I store all TSR routines). 2000 | * then: (sizeof(BEGTEXT) + sizeof(PSP) + 15) / 16 2001 | * PSP is 256 bytes of course. And +15 is needed to avoid truncating the 2002 | * last (partially used) paragraph. */ 2003 | _asm { 2004 | mov ax, 3100h /* AH=31 'terminate+stay resident', AL=0 exit code */ 2005 | mov dx, offset begtextend /* DX = offset of resident code end */ 2006 | add dx, 256 /* add size of PSP (256 bytes) */ 2007 | add dx, 15 /* add 15 to avoid truncating last paragraph */ 2008 | shr dx, 1 /* convert bytes to number of 16-bytes paragraphs */ 2009 | shr dx, 1 /* the 8086/8088 CPU supports only a 1-bit version */ 2010 | shr dx, 1 /* of SHR, so I have to repeat it as many times as */ 2011 | shr dx, 1 /* many bits I need to shift. */ 2012 | int 21h 2013 | } 2014 | 2015 | return(0); /* never reached, but compiler complains if not present */ 2016 | } 2017 | -------------------------------------------------------------------------------- /src/GENMSG.C: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the EtherDFS project. 3 | * http://etherdfs.sourceforge.net 4 | * 5 | * Copyright (C) 2017 Mateusz Viste 6 | * 7 | * genmsg generates C files that contain assembly for outputting to screen 8 | * every string that etherdfs might need to output. 9 | * 10 | * The assembly part is based on this model: 11 | * 12 | * push ds ; save all to-be-modified registers on the stack 13 | * push dx 14 | * push ax 15 | * call getip ; skip the binary content below (it's my string!) 16 | * S000 db 84,104,101,32,114,101,113,117,101,115,116,101,100,32,100,114 17 | * S001 db 105,118,101,32,108,101,116,116,101,114,32,105,115,32,97,'$' 18 | * getip: 19 | * pop dx ; "read" the address following the CALL from the stack 20 | * push cs ; load DS with the value of CS (that's where my data is) 21 | * pop ds 22 | * mov ah,9h ; set AH=9 (DOS function "print string") 23 | * int 21h 24 | * pop ax ; restore registers to their previous values 25 | * pop dx 26 | * pop ds 27 | */ 28 | 29 | #include 30 | #include "version.h" 31 | 32 | void genmsg(char *fname, char *msg) { 33 | unsigned short i; 34 | FILE *fd; 35 | fd = fopen(fname, "wb"); 36 | fprintf(fd, "/* %s: THIS FILE IS AUTO-GENERATED BY GENMSG.C -- DO NOT MODIFY! */\r\n", fname); 37 | fprintf(fd, "_asm {\r\n"); 38 | fprintf(fd, " push ds\r\n"); 39 | fprintf(fd, " push dx\r\n"); 40 | fprintf(fd, " push ax\r\n"); 41 | fprintf(fd, " call getip"); 42 | /* */ 43 | for (i = 0; msg[i] != 0; i++) { 44 | if ((i & 15) == 0) { 45 | fprintf(fd, "\r\n S%03X db ", i >> 4); 46 | } else { 47 | fprintf(fd, ","); 48 | } 49 | fprintf(fd, "%u", msg[i]); 50 | } 51 | fprintf(fd, ",'$'\r\n"); 52 | /* close definition */ 53 | fprintf(fd, " getip:\r\n"); 54 | fprintf(fd, " pop dx\r\n"); 55 | fprintf(fd, " push cs\r\n"); 56 | fprintf(fd, " pop ds\r\n"); 57 | fprintf(fd, " mov ah,9h\r\n"); 58 | fprintf(fd, " int 21h\r\n"); 59 | fprintf(fd, " pop ax\r\n"); 60 | fprintf(fd, " pop dx\r\n"); 61 | fprintf(fd, " pop ds\r\n"); 62 | fprintf(fd, "};\r\n"); 63 | fclose(fd); 64 | } 65 | 66 | int main(void) { 67 | 68 | genmsg("msg\\help.c", 69 | "EtherDFS v" PVER " / Copyright (C) " PDATE " Mateusz Viste\r\n" 70 | "A network drive for DOS, running over raw ethernet\r\n" 71 | "\r\n" 72 | "Usage: etherdfs SRVMAC rdrv-ldrv [rdrv2-ldrv2 ...] [options]\r\n" 73 | " etherdfs /u\r\n" 74 | "\r\n" 75 | "Options:\r\n" 76 | " /p=XX use packet driver at interrupt XX (autodetect otherwise)\r\n" 77 | " /n disable EtherDFS checksums\r\n" 78 | " /q quiet mode (print nothing if loaded/unloaded successfully)\r\n" 79 | " /u unload EtherDFS from memory\r\n" 80 | "\r\n" 81 | "Use '::' as SRVMAC for server auto-discovery.\r\n" 82 | "\r\n" 83 | "Examples: etherdfs 6d:4f:4a:4d:49:52 C-F /q\r\n" 84 | " etherdfs :: C-X D-Y E-Z /p=6F\r\n" 85 | ); 86 | 87 | genmsg("msg\\unsupdos.c", "Unsupported DOS version! EtherDFS requires MS-DOS 5+.\r\n"); 88 | 89 | genmsg("msg\\noredir.c", "Redirector installation has been forbidden either by DOS or another process.\r\n"); 90 | 91 | genmsg("msg\\alrload.c", "EtherDFS is already installed and cannot be loaded twice.\r\n"); 92 | 93 | genmsg("msg\\notload.c", "EtherDFS is not loaded, so it cannot be unloaded.\r\n"); 94 | 95 | genmsg("msg\\tsrcomfa.c", "Communication with the TSR failed.\r\n"); 96 | 97 | genmsg("msg\\nomultpx.c", "Failed to find an available INT 2F multiplex id.\r\nYou may have loaded too many TSRs already.\r\n"); 98 | 99 | genmsg("msg\\othertsr.c", "EtherDFS cannot be unloaded because another TSR hooked its interrupt handler.\r\n"); 100 | 101 | genmsg("msg\\unloaded.c", "EtherDFS unloaded successfully.\r\n"); 102 | 103 | genmsg("msg\\mapfail.c", 104 | "Unable to activate the local drive mapping. You are either using an\r\n" 105 | "unsupported operating system, or your LASTDRIVE directive does not permit\r\n" 106 | "to define the requested drive letter (try LASTDRIVE=Z in your CONFIG.SYS).\r\n" 107 | ); 108 | 109 | genmsg("msg\\drvactiv.c", 110 | "The requested local drive letter is already in use. Please choose another\r\n" 111 | "drive letter.\r\n" 112 | ); 113 | 114 | genmsg("msg\\memfail.c", "Memory alloc error!\r\n"); 115 | 116 | genmsg("msg\\relfail.c", "DS/SS relocation failed.\r\n"); 117 | 118 | genmsg("msg\\pktdfail.c", "Packet driver initialization failed.\r\n"); 119 | 120 | genmsg("msg\\nosrvfnd.c", "No EtherSRV server found on the LAN (not for requested drive at least).\r\n"); 121 | 122 | genmsg("msg\\instlled.c", "EtherDFS v" PVER " installed (local MAC "); 123 | 124 | genmsg("msg\\pktdrvat.c", ", pktdrvr at INT "); 125 | 126 | return(0); 127 | } 128 | -------------------------------------------------------------------------------- /src/GLOBALS.H: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the etherdfs project. 3 | * http://etherdfs.sourceforge.net 4 | * 5 | * Copyright (C) 2017 Mateusz Viste 6 | * 7 | * Contains all global variables used by etherdfs. 8 | */ 9 | 10 | #ifndef GLOBALS_SENTINEL 11 | #define GLOBALS_SENTINEL 12 | 13 | /* required size (in bytes) of the data segment - this must be bigh enough as 14 | * to accomodate all "DATA" segments AND the stack, which will be located at 15 | * the very end of the data segment. packet drivers tend to require a stack 16 | * of several hundreds bytes at least - 1K should be safe... It is important 17 | * that DATASEGSZ can contain a stack of AT LEAST the size of the stack used 18 | * by the transient code, since the transient part of the program will switch 19 | * to it and expects the stack to not become corrupted in the process */ 20 | #define DATASEGSZ 3500 21 | 22 | /* a few globals useful only for debug messages */ 23 | #if DEBUGLEVEL > 0 24 | static unsigned short dbg_xpos = 0; 25 | static unsigned short far *dbg_VGA = (unsigned short far *)(0xB8000000l); 26 | static unsigned char dbg_hexc[16] = "0123456789ABCDEF"; 27 | #define dbg_startoffset 80*16 28 | #endif 29 | 30 | /* whenever the tsrshareddata structure changes, offsets below MUST be 31 | * adjusted (these are required by assembly routines) */ 32 | #define GLOB_DATOFF_PREV2FHANDLERSEG 0 33 | #define GLOB_DATOFF_PREV2FHANDLEROFF 2 34 | #define GLOB_DATOFF_PSPSEG 4 35 | #define GLOB_DATOFF_PKTHANDLE 6 36 | #define GLOB_DATOFF_PKTINT 8 37 | static struct tsrshareddata { 38 | /*offs*/ 39 | /* 0 */ unsigned short prev_2f_handler_seg; /* seg:off of the previous 2F handler */ 40 | /* 2 */ unsigned short prev_2f_handler_off; /* (so I can call it for all queries */ 41 | /* that do not relate to my drive */ 42 | /* 4 */ unsigned short pspseg; /* segment of the program's PSP block */ 43 | /* 6 */ unsigned short pkthandle; /* handler returned by the packet driver */ 44 | /* 8 */ unsigned char pktint; /* software interrupt of the packet driver */ 45 | 46 | unsigned char ldrv[26]; /* local to remote drives mappings (0=A:, 1=B, etc */ 47 | } glob_data; 48 | 49 | /* global variables related to packet driver management and handling frames */ 50 | static unsigned char glob_pktdrv_recvbuff[FRAMESIZE]; 51 | static signed short volatile glob_pktdrv_recvbufflen; /* length of the frame in buffer, 0 means "free", and neg value means "awaiting" */ 52 | static unsigned char glob_pktdrv_sndbuff[FRAMESIZE]; /* this not only is my send-frame buffer, but I also use it to store permanently lmac, rmac, ethertype and PROTOVER at proper places */ 53 | static unsigned long glob_pktdrv_pktcall; /* vector address of the pktdrv interrupt */ 54 | 55 | /* a few definitions for data that points to my sending buffer */ 56 | #define GLOB_LMAC (glob_pktdrv_sndbuff + 6) /* local MAC address */ 57 | #define GLOB_RMAC (glob_pktdrv_sndbuff) /* remote MAC address */ 58 | 59 | static unsigned char glob_reqdrv; /* the requested drive, set by the INT 2F * 60 | * handler and read by process2f() */ 61 | 62 | static unsigned short glob_reqstkword; /* WORD saved from the stack (used by SETATTR) */ 63 | static struct sdastruct far *glob_sdaptr; /* pointer to DOS SDA (set by main() at * 64 | * startup, used later by process2f() */ 65 | 66 | /* seg:off addresses of the old (DOS) stack */ 67 | static unsigned short glob_oldstack_seg; 68 | static unsigned short glob_oldstack_off; 69 | 70 | /* the INT 2F "multiplex id" registerd by EtherDFS */ 71 | static unsigned char glob_multiplexid; 72 | 73 | /* an INTPACK structure used to store registers as set when INT2F is called */ 74 | static union INTPACK glob_intregs; 75 | 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /src/HISTORY.TXT: -------------------------------------------------------------------------------- 1 | EtherDFS changelog history 2 | 3 | v0.8.2 [2018-02-03]: 4 | - support for lock/unlock operations (INT 2Fh,110Ah). 5 | 6 | v0.8.1 [2017-04-16]: 7 | - EtherDFS frames are validated by a cksum (can be disabled with /n), 8 | - EDF5 frames announce their length to avoid troubles with Ethernet padding, 9 | - arguments can be passed in any order, 10 | - /q and /u can be used together. 11 | 12 | v0.8 [2017-03-04]: 13 | - improved self-detection to avoid loading EtherDFS twice, 14 | - added unloading support (/u), 15 | - fixed a FindFirst regression (fixes usage under 4DOS), 16 | - fixed SETATTR action when using a non-FreeDOS attrib command, 17 | - implemented the 'Seek From End' call, 18 | - minor memory optimizations, 19 | - makes sure the redirector API is available before installing, 20 | - support for multiple drive mappings. 21 | 22 | v0.7 [2017-02-11]: 23 | - MS-DOS compat: flagging newly mapped drive so MS-DOS doesn't ignore it, 24 | - fixed FindNext behavior so it's compatible with ATTRIB from MS-DOS, 25 | - implemented the "Special Open" call (used by COPY in MSDOS 5.0 and 6.x), 26 | - increased timeout retries from 3 to 5 (more reliable on lossy networks), 27 | - fixed parsing of the MAC address provided on command-line, 28 | - minor speed optimizations. 29 | 30 | v0.6 [2017-02-05]: 31 | - significantly reduced EtherDFS resident footprint (16K -> 7K), 32 | - fixed GETATTR (was returning garbage), 33 | - added support for 'CLOSE FILE', 'RENAME FILE' and 'SET ATTRIBS' actions. 34 | 35 | v0.5 [2017-01-31]: 36 | - first public release. 37 | -------------------------------------------------------------------------------- /src/MAKEFILE: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile for etherdfs, requires Open Watcom v1.9 3 | # Copyright (C) 2017 Mateusz Viste 4 | # 5 | # http://etherdfs.sourceforge.net 6 | # 7 | 8 | all: etherdfs.exe 9 | 10 | genmsg.exe: genmsg.c version.h 11 | wcl -y -0 -s -d0 -lr -ms -we -wx -os genmsg.c -fe=genmsg.exe 12 | 13 | chint.obj: chint086.asm 14 | wasm -0 chint086.asm -fo=chint.obj -ms 15 | 16 | etherdfs.exe: genmsg.exe etherdfs.c chint.obj dosstruc.h globals.h version.h 17 | genmsg.exe 18 | wcl -y -0 -s -d0 -lr -ms -we -wx -k1024 -fm=etherdfs.map -os chint.obj etherdfs.c -fe=etherdfs.exe 19 | upx -9 --8086 etherdfs.exe 20 | 21 | # -y ignore the WCL env. variable, if any 22 | # -0 generate code for 8086 23 | # -s disable stack overflow checks 24 | # -d0 don't include debugging information 25 | # -lr compile to a DOS real-mode application 26 | # -ms small memory model 27 | # -we treat all warnings as errors 28 | # -wx set warning level to max 29 | # -k1024 set stack size to 1024 bytes (for the non-resident part) 30 | # -fm= generate a map file 31 | # -os optimize for size 32 | # -fe set output file name 33 | 34 | clean: .symbolic 35 | if exist etherdfs.exe del etherdfs.exe 36 | if exist genmsg.exe del genmsg.exe 37 | del *.obj 38 | 39 | pkg: .symbolic etherdfs.exe 40 | if exist etherdfs.zip del etherdfs.zip 41 | zip -9 -k etherdfs.zip etherdfs.exe etherdfs.txt history.txt 42 | if exist ethersrc.zip del ethersrc.zip 43 | zip -9 -k ethersrc.zip *.h *.c *.asm *.txt makefile 44 | -------------------------------------------------------------------------------- /src/MEMNOTES.TXT: -------------------------------------------------------------------------------- 1 | 2 | === EtherDFS programming oddities === 3 | Mateusz Viste 4 | 5 | EtherDFS is a TSR program... written in C. This alone might be considered an 6 | odd thing. While trying to limit as much as possible EtherDFS' memory 7 | footprint, I had to use a few *nasty* tricks that are listed below. I write 8 | this file mostly for myself so I can keep track of how that monster works. 9 | 10 | When going TSR, an application uses the INT 21h,31h DOS call. This call not 11 | only makes the application resident, but it also allows to trim any excess 12 | memory that won't be needed by the application any more. Problem is, that the 13 | memory can only be trimmed from the application's base address (PSP) upward. 14 | This is a problem because the order in which compilers lay out the memory is 15 | not compatible with such use. To take a specific example, here's how a typical 16 | OpenWatcom memory map looks like: 17 | 18 | STACK (application's stack) 19 | _BSS (global and static variables, unless pre-initialized) 20 | ... (some other stuff) 21 | CONST (literal constants, strings) 22 | _DATA (all variables and structures that are pre-initialized) 23 | _TEXT (application's code) 24 | BEGTEXT (mostly empty) 25 | PSP 26 | 27 | Looking at the above layout it becomes clear that trimming the code in any 28 | place would lead to a program that would be dysfunctional at best. Why: 29 | - all libc routines will end up dispersed inside _TEXT, hence calling them 30 | will result in undefined behavior if _TEXT is not kept entirely, 31 | - your TSR's routines might very well end up at the end of the _TEXT segment, 32 | hence making it impossible to trim the program at all. 33 | - trimming the code would make us loose all the DATA segment as well, on 34 | which the TSR surely relies in one way or another. 35 | 36 | It is not a hopeless situation, though! 37 | 38 | *** Forbid libc calls in your resident code *** 39 | 40 | First problem can be easily eliminated by simply avoiding using any libc 41 | functions in your TSR code. Forget about conveniences like sprintf(), 42 | memcpy(), int86(), chain_intr(), etc etc. You will have to figure out how to 43 | replace every such call by your own code - at least in the resident part of 44 | your code. Be warned that your compiler might also emit "hidden" calls that 45 | you don't know about. This is commonly done by compilers to detect stack 46 | overflow situations - the stack overflow detection routine is usually part of 47 | the compiler's libc, and as such resides at a more or less random place in 48 | memory. Most compilers allow to disable stack overflow checks through a simple 49 | command-line switch ("-s" for OpenWatcom). 50 | 51 | *** Make sure the resident code comes first *** 52 | 53 | The second problem might or might not actually be a problem, depending on 54 | your compiler and optimization settings. If the compiler generates machine 55 | code in the same order than it appears in the source file, then all is well. 56 | You will simply have to put all resident routines at the top of your source 57 | code. OpenWatcom seems to do it that way - but I found a simple method to make 58 | sure that all my resident code ends up at the top of the program: I instruct 59 | OpenWatcom to put my resident routines in the _BEGTEXT segment, which is 60 | almost unused by default, and it works! To achieve that, OpenWatcom provides 61 | clever "pragma" instructions: 62 | #pragma code_seg(BEGTEXT, CODE) 63 | all code here will end up in BEGTEXT 64 | #pragma code_seg(_TEXT, CODE) 65 | all code here will be put in the default _TEXT segment 66 | 67 | *** Custom DATA & STACK segment *** 68 | 69 | Finally, the third problem is the most tricky to solve. One could think that 70 | it is enough to simply instruct the linker to reorder the layout as to put all 71 | DATA-related segments before code, but most compilers do not provide such 72 | control, and these that do, do not always end up with working code (OpenWatcom 73 | makes it possible to reorder segments at link time, but unfortunately it 74 | doesn't seem to adjust actual addresses of variables in the code, so it's not 75 | so useful). 76 | 77 | The solution I ended up with (not before experimenting with many other 78 | approaches, most of which ended up horribly) is to NOT use the DATA and STACK 79 | segment provided by the compiler. Instead, I allocate my own segment through 80 | an INT 21h,48h call, and I force EtherDFS to use THAT as its DATA & STACK 81 | segment. Easier said than done! 82 | 83 | *** Allocating memory: right amount, right place *** 84 | 85 | So I need to allocate memory to replace the DATA+STACK segment used by my 86 | program. Okay, but how much exactly? To figure this out, the compiler's 87 | memory map is of tremendous help. Simply add the size of all your DATA 88 | segments, add the amount of stack you'd like your TSR to have, and voila. 89 | 90 | Not only you need to allocate the right amount of memory, but you also need to 91 | allocate it at a "good" place. By default, your allocation might end up just 92 | above your program. What difference does it make, you ask? Well, once your 93 | program will be trimmed, it will leave some free space in memory. If this free 94 | space is not contiguous with the rest of your system's free memory, DOS will 95 | be unable to use it to load programs into it... That's called memory 96 | fragmentation. To avoid that, it is possible to force DOS to allocate new 97 | memory as high as possible by temporarily overriding its memory allocation 98 | strategy - see INT 21h/AH=58h. 99 | 100 | *** Self-modifying code *** 101 | 102 | Allocating memory is easy - but how to tell my TSR to use it? The problem here 103 | is that I allocate said memory in my transient code, but I need it to be used 104 | also by the TSR routines (meaning all my interrupt handlers). The solution 105 | I applied to this problem is to patch my TSR routines during runtime. Sounds 106 | crazy, but it works well once mastered. When the transient code allocates 107 | the memory for my new segment, it inserts machine code at the top of every 108 | interrupt handler used by the TSR. The machine code loads DS and SS with the 109 | new segment, as obtained from the memory allocation function. 110 | 111 | *** Where do I draw the line? *** 112 | 113 | Once all is ready, you can at last go TSR and drop the transient part of the 114 | application's memory, along with the old DATA & STACK segment. The "go TSR" 115 | call (also known by its little name "INT 21h/AH=31h") expects to obtain the 116 | amount of memory that needs to be kept, in 16-bytes units (paragraphs). But 117 | how do I know how many paragraphs my resident code really takes? Two solutions 118 | here: either check this out in your compiler's memory map, and report the size 119 | in your source code, or use a little trick to figure it out at compile time... 120 | The trick is as simple as inserting a code symbol (in my case I used an empty 121 | function) at the end of your resident code. The offset of this symbol will 122 | tell you exactly how big the resident code is. 123 | 124 | Important: once you know how big your resident code is, add 256 to that! This 125 | is the size of the PSP. Other compilers might or might not account for that, 126 | but OpenWatcom definitely does not (not in the small model with EXE output at 127 | least, I didn't check other combinations). 128 | 129 | *** Squeeze out some extra bytes by eliminating literal strings *** 130 | 131 | The transient part of your program may use pre-initialized arrays, or pointers 132 | to strings: 133 | char hello[] = "Hello"; 134 | char *wowsig = "wow"; 135 | 136 | In both cases, chances are that your compiler will push these strings into the 137 | DATA segment of the program, hence - again - unnecessarily bloating the memory 138 | area used by the TSR. For simple cases, it's trivial to change such 139 | declarations into something like this, to enforce stack usage: 140 | char hello[6]; 141 | hello[0]='H'; hello[1]='e'; hello[2]='l'; hello[3]='l'; hello[4]='o'; 142 | 143 | But what if the amount of text you wish to output is much larger? It might 144 | become a real pain to handle very long strings, not mentionning the fact that 145 | you might want to keep your stack usage at a reasonable level as well. 146 | 147 | I am aware of no easy solution for this. But since I was motivated, I ended up 148 | taking the hard road: embedding the strings as machine code. It sounds scary 149 | (and it should!), but with proper build automation it can be very manageable. 150 | I have a separate program called "genmsg" that generates assembly functions 151 | responsible for printing out each of my strings. Every such 'function' lands 152 | in a separate C file in the 'msg' subdirectory, so I include the needed file 153 | whenever I want to output a string on the screen. 154 | 155 | 156 | [EOF] 157 | -------------------------------------------------------------------------------- /src/STRLEN.C: -------------------------------------------------------------------------------- 1 | /* 2 | * Here I experimented by trying to create a suposedly more efficient strlen() 3 | * function in assembly. While my asm version is certainly faster, the 4 | * _asm overhead makes it also a lot bigger. 5 | * 6 | * how to build: 7 | * wcl -y -0 -d0 -lr -ms -we -wx -fm=strlen.map -os strlen.c -fe=strlen.exe 8 | */ 9 | 10 | 11 | #include 12 | #include 13 | 14 | 15 | unsigned short mystrlen(void far *s) { 16 | unsigned short res = 0; 17 | while (*(unsigned char far *)s != 0) { 18 | res++; 19 | s = ((unsigned char far *)s) + 1; 20 | } 21 | return(res); 22 | } 23 | 24 | 25 | unsigned short mystrlenasm(void far *s) { 26 | unsigned short res = 0, sseg, soff; 27 | sseg = FP_SEG(s); 28 | soff = FP_OFF(s); 29 | _asm { 30 | xor al, al 31 | mov es, sseg 32 | mov di, soff 33 | mov cx, 0ffffh 34 | cld /* clear the direction flag so repne proceeds forward */ 35 | repne scasb 36 | inc cx 37 | not cx 38 | mov res, cx 39 | } 40 | return(res); 41 | } 42 | 43 | 44 | int main(void) { 45 | char *s[] = {"Mateusz", "", "123", "Hello, World!", NULL}; 46 | int i; 47 | int r1, r2; 48 | 49 | for (i = 0; s[i] != NULL; i++) { 50 | r1 = mystrlen(s[i]); 51 | r2 = mystrlenasm(s[i]); 52 | 53 | printf("'%s' [r1] = %d\n'%s' [r2] = %d\n", s[i], r1, s[i], r2); 54 | } 55 | return(0); 56 | } 57 | -------------------------------------------------------------------------------- /src/VERSION.H: -------------------------------------------------------------------------------- 1 | /* program version and date */ 2 | #define PVER "0.8.2" 3 | #define PDATE "2017,2018" 4 | 5 | /* protocol version */ 6 | #define PROTOVER 2 7 | --------------------------------------------------------------------------------