├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── .lvimrc ├── COPYING.GPL1 ├── COPYING.md ├── METANET-HOWTO.md ├── README.md ├── UDPSETUP-HOWTO.md ├── adapters ├── commit.h ├── fragment.c ├── fragment.h ├── nodemap.c ├── nodemap.h ├── rottcom.h ├── vcommit.c └── vrottcom.c ├── bld └── .gitignore ├── build ├── .gitignore ├── dosbox.conf ├── doscmd.bat ├── doscmd.sh ├── tcbuild.sh └── watbuild.sh ├── ctrl ├── analogjs.c ├── control.c ├── control.h ├── joystick.asm └── replay.c ├── lib ├── bakedin.h ├── dos.c ├── dos.h ├── flag.c ├── flag.h ├── ints.c ├── ints.h ├── inttypes.h ├── log.c ├── log.h └── vsetargs.c ├── makefile.tc ├── makefile.wat ├── mkrelease.sh ├── modemcfg ├── AT&T Data Port 14.4 FaxM..cfg ├── ATI 9600 ETC-E Internal v.32.cfg ├── Acubit 14.4 fax v32.cfg ├── Boca 14.4 fax.cfg ├── Boca 14.4 v32 fax.cfg ├── Cardinal 14.4 v32.cfg ├── Cardinal 9600fax.cfg ├── Creative Labs Modem Blaster.cfg ├── Digicom Scout Plus 14.4 Fax.cfg ├── GENERIC 14.4 MODEM.cfg ├── GVC 14.4 MNP2-5 v42.cfg ├── GVC 14.4 fax.cfg ├── Gateway Telepath 14.4 fax.cfg ├── Gateway Telepath 550.cfg ├── Generic Hayes Compatible.cfg ├── Hayes 28.8k V.FAST Modem.cfg ├── Hayes Optima 14.4.cfg ├── Hayes Optima 28.8.cfg ├── Hayes Ultra 14.4.cfg ├── Identity Internal.cfg ├── Infotel 14.4.cfg ├── Intel 14.4 ext.cfg ├── Intel 14.4 fax.cfg ├── Intel 14.4.cfg ├── Intel 14.4i fax.cfg ├── Intel 14.4i.cfg ├── Linelink 144e.cfg ├── MegaHertz C596FM.cfg ├── Netcomm M7F.cfg ├── No Name (Rockwell) 14.4.cfg ├── Nokia ECM 4896M Trellis V.32.cfg ├── Practical Perip 14.4FX v32.cfg ├── Practical Peripherals 14.4.cfg ├── Practical Peripherals PM14400FXMT v.32bis.cfg ├── Practical Peripherals PM14400FXSA v.32bis.cfg ├── README.md ├── Redicard v.32 bis 14.4.cfg ├── Smart One 1442F.cfg ├── SupraFax 14.4 v32-.cfg ├── SupraFax 14.4 v32.cfg ├── SupraFax 14.4.cfg ├── Telebit Worldblazer 14.4.cfg ├── Telepath 550.cfg ├── Twincomm DFi 14.4.cfg ├── USR Sportster 14.4 v32 fax.cfg ├── USRobotics 14.4 fax.cfg ├── USRobotics 14.4 v32 fax.cfg ├── USRobotics 16.8 HST.cfg ├── USRobotics Sportster 14.4.cfg ├── USRobotics Sportster.cfg ├── USRobotics V.Everything.cfg ├── Viva 14.4.cfg ├── Viva 14.4i fax.cfg ├── VivaFax #3.cfg ├── VivaFax 14.4.cfg ├── ZOOM 14.4 v32 v42 fax.cfg ├── ZOOM 14.4.cfg ├── ZOOM VFP 14.4.cfg ├── ZOOM VFX v32.cfg ├── ZOOM VXP 14.4 v42.cfg ├── Zoltrix model 14 14 VE.cfg ├── Zoomfax VFP v32.cfg ├── ZyXEL U-1496 14.4.cfg └── ZyXel 14.4.cfg ├── net ├── dbserver.c ├── dbserver.h ├── doomnet.c ├── doomnet.h ├── dossock.c ├── dossock.h ├── ipxnet.c ├── ipxnet.h ├── ipxsetup.c ├── llcall.asm ├── llcall.h ├── metanet.c ├── parport.c ├── parport.h ├── parsetup.c ├── passthru.c ├── pktaggr.c ├── pktaggr.h ├── pktstats.c ├── pktstats.h ├── plio.asm ├── serarb.c ├── serarb.h ├── serport.c ├── serport.h ├── sersetup.c ├── sirsetup.c ├── solo-net.c ├── udpipx.c └── ws2patch.c ├── stat ├── statdump.c ├── statprnt.c ├── statprnt.h ├── stats.c └── stats.h ├── test ├── common.sh ├── dosbox.conf ├── exes │ ├── README.md │ ├── hacks.diff │ ├── ipx1.exe │ ├── ipx2.exe │ ├── ipxttl.exe │ ├── ser1.exe │ └── ser2.exe ├── fakedoom.c ├── ipxsetup_test.sh ├── metanet_test.sh ├── modem.cfg ├── runtests.sh ├── sersetup_test.sh └── sirsetup_test.sh ├── vutils.ico └── vutils.png /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | jobs: 6 | watcom_build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v4 11 | with: 12 | submodules: true 13 | - name: Install dependencies 14 | run: | 15 | sudo apt update 16 | sudo apt -q install dosbox dos2unix 17 | - name: build 18 | timeout-minutes: 20 19 | run: | 20 | ./build/watbuild.sh 21 | ./mkrelease.sh 22 | - name: test 23 | run: ./test/runtests.sh 24 | timeout-minutes: 20 25 | - name: Find Git version 26 | id: version 27 | run: | 28 | if git describe --exact-match --tags >/dev/null; then 29 | VERSION=$(git describe --exact-match --tags) 30 | VERSION=${VERSION/#v/} 31 | else 32 | VERSION=$(git rev-parse --short HEAD) 33 | fi 34 | echo "VERSION=$VERSION" >> $GITHUB_OUTPUT 35 | - name: Upload EXEs 36 | uses: actions/upload-artifact@v4 37 | with: 38 | path: "dist" 39 | name: vanilla-utilities-${{steps.version.outputs.VERSION}} 40 | 41 | turboc_build: 42 | runs-on: ubuntu-latest 43 | 44 | steps: 45 | - uses: actions/checkout@v4 46 | with: 47 | submodules: true 48 | - name: Install dependencies 49 | run: | 50 | sudo apt update 51 | sudo apt -q install dosbox 52 | - name: build 53 | timeout-minutes: 20 54 | run: | 55 | ./build/tcbuild.sh 56 | - name: test 57 | run: ./test/runtests.sh 58 | timeout-minutes: 20 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.ERR 2 | *.OBJ 3 | bld 4 | dist 5 | test/FAKEDOOM.EXE 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "dos-compilers"] 2 | path = build/dos-compilers 3 | url = https://github.com/fragglet/dos-compilers.git 4 | -------------------------------------------------------------------------------- /.lvimrc: -------------------------------------------------------------------------------- 1 | " Local vimrc configuration file. Install the localvimrc.vim vim script. 2 | setlocal tabstop=8 softtabstop=4 shiftwidth=4 expandtab cindent 3 | 4 | if &filetype == "make" 5 | setlocal softtabstop=8 shiftwidth=8 noexpandtab nocindent 6 | elseif &filetype == "markdown" 7 | setlocal smartindent nocindent 8 | endif 9 | -------------------------------------------------------------------------------- /METANET-HOWTO.md: -------------------------------------------------------------------------------- 1 | `metanet` is a special Doom network driver that builds a packet forwarding 2 | network out of other Doom network drivers. This document explains how to 3 | use it through several example scenarios. 4 | In the examples, `doom.exe` will be used for the game itself that is being 5 | launched, but this could be any other game or program that uses the Doom 6 | networking API; for example, `heretic.exe` or `hexen.exe`. 7 | 8 | Firstly, some basic principles: 9 | 10 | * `metanet` always runs "on top" of other networking drivers. For example, 11 | `sersetup.exe` is used to invoke `metanet.exe` which then invokes 12 | `doom.exe`. It can use more than one underlying driver. 13 | 14 | * Every player in the game must use `metanet`. 15 | 16 | * A `metanet` network cannot have more than four "hops". That is, if you're 17 | daisy-chaining computers together, there cannot be more than four links in 18 | the chain. 19 | 20 | ## Example 1: Three player serial game 21 | 22 | In this example, three computers (*A*, *B* and *C*) do not have network 23 | cards, but do have serial ports that they can use to communicate with each 24 | other. *A* and *C* are both connected to *B* with null-modem cables; if *A* 25 | wants to send a message to *C*, it must send it to *B*, which will then 26 | forward it on to *C*. 27 | 28 | Setting up `metanet` in this scenario is fairly straightforward. *A* and 29 | *C* are both started with the following command (it is assumed the cable is 30 | connected to *COM1:* on each machine): 31 | ``` 32 | sersetup.exe -com1 metanet.exe doom.exe 33 | ``` 34 | Machine *B* must communicate on two ports (*COM1:* and *COM2:*), so the 35 | command line looks like this: 36 | ``` 37 | sersetup.exe -com1 sersetup.exe -com2 metanet.exe doom.exe 38 | ``` 39 | On startup, the serial link is established by `sersetup.exe` on *COM1:*, 40 | then a second `sersetup.exe` is invoked to establish the link on *COM2:*. 41 | This then invokes `metanet.exe` which discovers all nodes in the network. 42 | Finally, `metanet.exe` on all three machines launches `doom.exe` to start 43 | the game. 44 | 45 | This fairly simple example could be expanded to include a fourth machine 46 | connected to any of the three machines. Note that there are trade-offs: for 47 | example, machine *D* could be connected to *B*, but *B* would need a third 48 | COM port for this to work. Alternatively, *D* could be connected to *C*, 49 | but this would increase the number of hops in the network, potentially 50 | affecting performance (since *A* to *D* would require three hops). 51 | 52 | ### Example 2: Four player mixed IPX/serial game 53 | 54 | In this example, three machines (*A*, *B*, and *C*) have networking cards but a 55 | fourth (*D*) does not. Fortunately *D* can communicate with *C* through a 56 | serial link. `metanet` can be used to establish a four player game. 57 | 58 | The command line for *A* and *B* looks like this: 59 | ``` 60 | ipxsetup.exe -nodes 3 metanet.exe doom.exe 61 | ``` 62 | Note the `-nodes 3` parameter which may be slightly counterintuitive since 63 | we are establishing a four player game. Here the `-nodes` parameter only 64 | controls the number of machines on the IPX link, of which there are three 65 | (*A*, *B*, *C*). 66 | 67 | The command line for *C* looks like this: 68 | ``` 69 | ipxsetup.exe -nodes 3 sersetup.exe -com1 metanet.exe doom.exe 70 | ``` 71 | The command line for *D* looks like this: 72 | ``` 73 | sersetup.exe -com1 metanet.exe doom.exe 74 | ``` 75 | The null-modem cable connecting *C* and *D* is connected to *COM1:* on both 76 | machines. 77 | 78 | ### Example 3: Joining two LAN parties 79 | 80 | Two LAN parties are taking place in different cities in the same evening, 81 | and the players want to set up an eight player Hexen game over a phone link 82 | (in this example, Hexen is being used because it supports up to eight 83 | players). Four players will be participating at each site, and there will 84 | be a dedicated forwarding machine at each site running the phone link. 85 | 86 | This is a fairly complicated example that also introduces `metanet`'s 87 | *forwarding mode*: in forwarding mode, the machine does not participate in 88 | the game itself but merely performs packet forwarding between other 89 | machines. Forwarding mode is useful here to ensure low latency, since 90 | the CPU is not being shared between the game and `metanet`. 91 | 92 | Each player starts the game with the following command line: 93 | ``` 94 | ipxsetup.exe -nodes 5 metanet.exe hexen.exe 95 | ``` 96 | As with the previous example, `-nodes 5` may be counterintuitive. There are 97 | four players at each site, plus one forwarding machine, which makes for 98 | five nodes at each site in total. 99 | 100 | Site A's forwarding machine listens for the phone call from Site B: 101 | ``` 102 | sersetup.exe -answer -com1 ipxsetup.exe -nodes 5 metanet.exe -forward 103 | ``` 104 | The ordering here potentially matters: it listens for the incoming phone 105 | call *first*, before joining the IPX game. If it was the other way round, 106 | there's a chance that the incoming call might come while it was still 107 | establishing the IPX link with other local machines. 108 | 109 | Side B's forwarding machine initiates the phone call to Site A: 110 | ``` 111 | sersetup.exe -com1 -dial 555-1212 ipxsetup.exe -nodes 5 metanet.exe -forward 112 | ``` 113 | Note that neither of the forwarding machines includes `hexen.exe` on the 114 | command line since they do not launch the game. Instead, `-forward` is used 115 | to indicate that `metanet` should simply forward packets once the network 116 | has been constructed. 117 | 118 | ### Debugging 119 | 120 | `metanet` prints some statistics on exit and if a game is not starting up as 121 | expected, it can be worth checking to see if any of these appear: 122 | 123 | * `wrong_magic`: Check that all machines in the network are using `metanet`. 124 | * `too_many_hops`: Only up to four hops are supported in a meta-network. 125 | Try to centralize the network around a smaller number of forwarding 126 | machines. 127 | * `invalid_dest`: Machine is receiving forwarding packets for an unknown 128 | destination. This may indicate packet corruption; check all links are 129 | working okay and consider increasing the baud rate on serial links. 130 | * `unknown_src`: Game packets are being received from an unknown machine on 131 | the meta-network. Check for link corruption, and consider reporting a bug 132 | if no cause can be found. 133 | * `node_limit`: You've hit the limit for the number of nodes in a 134 | meta-network (16). 135 | * `bad_send`: Game is trying to send to an invalid node. If you're using a 136 | source port this may be a bug you should report. 137 | * `unknown_type`: Unknown type of `metanet` packet received. Make sure that 138 | all nodes are using the same `metanet` version. 139 | 140 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![vutils icon](vutils.png) 3 | 4 | # The Vanilla Utilities 5 | 6 | This is a collection of DOS utilities for interacting with Doom's external 7 | driver APIs. Specifically it includes improved versions of Doom's network 8 | drivers (IPXSETUP and SERSETUP) along with APIs for the 9 | [network](https://doomwiki.org/wiki/Doom_networking_component#External_drivers), 10 | [control](https://doomwiki.org/wiki/External_control_driver), and 11 | [statistics](https://doomwiki.org/wiki/Statistics_driver) interfaces. 12 | 13 | Design principles here are: 14 | 15 | * **Composability** - it is possible to combine multiple tools and use them 16 | together. 17 | * **Reusability** - the codebase has clearly-defined APIs for interacting 18 | with Doom's command line interfaces, so that making new tools is 19 | straightforward. 20 | 21 | ## Utilities 22 | 23 | * **udpsetup** - network driver for Internet play over the UDP protocol. 24 | Multiple network stacks are supported including Winsock1, Winsock2 and the 25 | DOS MSClient stack. The protocol is the same as DOSbox's IPXNET protocol, 26 | so it can connect to DOSbox servers and play against emulated machines. 27 | [Example video](https://www.youtube.com/watch?v=1PLXPSP7ZBE); see 28 | [UDPSETUP-HOWTO](UDPSETUP-HOWTO.md) for more information. 29 | * **ipxsetup** - bugfixed and expanded version of the driver originally shipped 30 | with Doom. Supports the extensions from [xttl's version](https://github.com/AXDOOMER/ipxsetup_xttl) 31 | with further enhancements (not all players need specify the -dup or -player 32 | parameters). 33 | * **sersetup** - bugfixed and expanded version of the serial/modem driver 34 | originally included with Doom. Supports background answering, which allows the 35 | game to launch before the incoming call is received. 36 | * **parsetup** - parallel port network driver, derived from 37 | [the version from the idgames archive](https://www.doomworld.com/idgames/utils/serial/psetup11). 38 | Performance has been significantly improved. 39 | * **sirsetup** - driver for running over a half-duplex serial infrared (SIR) 40 | link (aka IrDA), as commonly found on many late '90s laptops. 41 | * **metanet** - networking driver that combines other networking drivers 42 | into a packet forwarding network. This allows you, for example, to build a 43 | a four player game from daisy-chaining null-modem cables. 44 | See [METANET-HOWTO](METANET-HOWTO.md) for more information. 45 | * **solo-net** - null/standalone network driver that starts a network game 46 | without any real connection. Replicates the `-solo-net` parameter found in 47 | many Doom source ports. 48 | * **analogjs** - PC joystick driver with analog control that is more precise 49 | than Doom's built in joystick support. 50 | * **replay** - demo replay tool that uses the external control API, so that 51 | demos can be "continued" by recording a new demo from an old one. 52 | * **statdump** - external statistics driver that can write a text summary. 53 | * **vcommit** - adapter that converts a Doom network driver into a 3D Realms 54 | *COMMIT* driver, as used for *Duke Nukem 3D*, *Blood*, *Shadow Warrior* 55 | and various other games. This means that all of the above network drivers 56 | can also be used with those games. 57 | [Example video](https://youtu.be/4L5wVLp5wVE). 58 | * **vrottcom** - adapter that converts a Doom network driver into a *Rise 59 | of the Triad* ROTTCOM driver. This means that all of the above network 60 | drivers can also be used with ROTT. 61 | -------------------------------------------------------------------------------- /adapters/commit.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | // Types for 3DRealms COMMIT interface, based on the description given 12 | // in "Communicating with 3DRealms Games" by Mark Dochtermann. 13 | 14 | #define COMMIT_MAXPACKETSIZE 2048 15 | 16 | enum 17 | { 18 | COMMIT_CMD_SEND = 1, 19 | COMMIT_CMD_GET = 2, 20 | COMMIT_CMD_SENDTOALL = 3, 21 | COMMIT_CMD_SENDTOALLOTHERS = 4, 22 | COMMIT_CMD_SCORE = 5, 23 | }; 24 | 25 | enum 26 | { 27 | COMMIT_GAME_SERIAL = 1, 28 | COMMIT_GAME_MODEM = 2, 29 | COMMIT_GAME_NETWORK = 3, 30 | }; 31 | 32 | #if __WATCOMC__ 33 | #pragma pack (1) 34 | #endif 35 | 36 | typedef struct 37 | { 38 | int16_t intnum; 39 | 40 | int16_t command; 41 | int16_t remotenode; 42 | int16_t datalength; 43 | 44 | int16_t consoleplayer; 45 | int16_t numplayers; 46 | int16_t gametype; 47 | 48 | int16_t unused; 49 | 50 | uint8_t data[COMMIT_MAXPACKETSIZE]; 51 | } gamecom_t; 52 | 53 | #if __WATCOMC__ 54 | #pragma pack (4) 55 | #endif 56 | 57 | -------------------------------------------------------------------------------- /adapters/fragment.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | // Packet fragmenter and reassembler using a minimalist fragmenting 12 | // protocol to identify and reassemble packets. This is implemented as 13 | // a workaround for the fact that ROTTCOM/COMMIT drivers expect support 14 | // for larger packet sizes that Doom's drivers do not support. 15 | 16 | #include 17 | 18 | #include "lib/dos.h" 19 | #include "net/doomnet.h" 20 | #include "adapters/fragment.h" 21 | #include "adapters/nodemap.h" 22 | 23 | #define FRAGMENT_SIZE 500 24 | #define REASSEMBLY_BUFFERS 8 25 | #define FRAGMENT_HEADER_SIZE (sizeof(struct fragment) - FRAGMENT_SIZE) 26 | 27 | struct fragment 28 | { 29 | uint8_t seq; 30 | uint8_t fragment; 31 | uint8_t payload[FRAGMENT_SIZE]; 32 | }; 33 | 34 | static struct reassembled_packet buffers[REASSEMBLY_BUFFERS]; 35 | static doomcom_t far *driver; 36 | static uint8_t send_seq; 37 | 38 | void InitFragmentReassembly(doomcom_t far *d) 39 | { 40 | driver = d; 41 | } 42 | 43 | static struct reassembled_packet *FindOrAllocateBuffer( 44 | int remotenode, uint8_t seq) 45 | { 46 | int i; 47 | 48 | for (i = 0; i < REASSEMBLY_BUFFERS; ++i) 49 | { 50 | if (buffers[i].remotenode == remotenode && buffers[i].seq == seq) 51 | { 52 | return &buffers[i]; 53 | } 54 | } 55 | 56 | for (i = 0; i < REASSEMBLY_BUFFERS; ++i) 57 | { 58 | if (buffers[i].received == 0) 59 | { 60 | return &buffers[i]; 61 | } 62 | } 63 | // Throw away an existing buffer. 64 | // TODO: better heuristics for throwing away buffers 65 | for (i = 0; i < REASSEMBLY_BUFFERS; ++i) 66 | { 67 | if (buffers[i].remotenode == remotenode) 68 | { 69 | buffers[i].received = 0; 70 | return &buffers[i]; 71 | } 72 | } 73 | buffers[0].received = 0; 74 | return &buffers[0]; 75 | } 76 | 77 | struct reassembled_packet *FragmentGetPacket(void) 78 | { 79 | struct reassembled_packet *result; 80 | struct fragment far *f = (struct fragment far *) driver->data; 81 | int fnum, num_fragments; 82 | 83 | while (NetGetPacket(driver)) 84 | { 85 | // FIXME: Fragment reassembler should not be coupled to node 86 | // discovery code. 87 | if (CheckLateDiscover(driver)) 88 | { 89 | continue; 90 | } 91 | 92 | fnum = f->fragment & 0x0f; 93 | num_fragments = (f->fragment >> 4) & 0x0f; 94 | 95 | result = FindOrAllocateBuffer(driver->remotenode, f->seq); 96 | result->seq = f->seq; 97 | result->remotenode = driver->remotenode; 98 | if (fnum == num_fragments - 1) 99 | { 100 | result->datalength = (num_fragments - 1) * FRAGMENT_SIZE 101 | + driver->datalength - FRAGMENT_HEADER_SIZE; 102 | } 103 | far_memcpy(result->data + fnum * FRAGMENT_SIZE, f->payload, 104 | driver->datalength - FRAGMENT_HEADER_SIZE); 105 | 106 | // If we have received every fragment of this packet then each 107 | // bit will have been set and we can return the reassembled 108 | // packet. Reset received to zero so the buffer can be reused 109 | // for the next call. 110 | result->received |= 1 << fnum; 111 | if (result->received == (1 << num_fragments) - 1) 112 | { 113 | result->received = 0; 114 | return result; 115 | } 116 | } 117 | 118 | // We didn't manage to assemble a complete packet yet. 119 | return NULL; 120 | } 121 | 122 | void FragmentSendPacket(int remotenode, uint8_t *buf, size_t len) 123 | { 124 | struct fragment far *f = (struct fragment far *) driver->data; 125 | int sendbytes; 126 | int num_fragments; 127 | int i; 128 | 129 | driver->remotenode = remotenode; 130 | num_fragments = (len + FRAGMENT_SIZE - 1) / FRAGMENT_SIZE; 131 | 132 | for (i = 0; i < num_fragments; ++i) 133 | { 134 | f->seq = send_seq; 135 | f->fragment = (i & 0xf) | (num_fragments << 4); 136 | sendbytes = len; 137 | if (sendbytes > FRAGMENT_SIZE) 138 | { 139 | sendbytes = FRAGMENT_SIZE; 140 | } 141 | far_memcpy(f->payload, buf, sendbytes); 142 | driver->datalength = sendbytes + FRAGMENT_HEADER_SIZE; 143 | NetSendPacket(driver); 144 | buf += sendbytes; 145 | len -= sendbytes; 146 | } 147 | 148 | ++send_seq; 149 | } 150 | 151 | -------------------------------------------------------------------------------- /adapters/fragment.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | #define MAX_REASSEMBLED_PACKET 2048 12 | 13 | struct reassembled_packet 14 | { 15 | int remotenode; 16 | uint8_t seq; 17 | uint16_t received; 18 | unsigned int datalength; 19 | uint8_t data[MAX_REASSEMBLED_PACKET]; 20 | }; 21 | 22 | void InitFragmentReassembly(doomcom_t far *d); 23 | struct reassembled_packet *FragmentGetPacket(void); 24 | void FragmentSendPacket(int remotenode, uint8_t *buf, size_t len); 25 | 26 | -------------------------------------------------------------------------------- /adapters/nodemap.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | // Mini protocol for discovering node<->player mapping. 12 | // Doom network node numbers have no relation to player numbers, 13 | // and Doom in fact does its own discovery in d_net.c to discover 14 | // which node is which player. However, the ROTTCOM and COMMIT APIs 15 | // are just designed around player numbers. To be able to implement 16 | // these interfaces we therefore need to discover players. 17 | 18 | #include 19 | #include 20 | #include "lib/inttypes.h" 21 | 22 | #include "lib/dos.h" 23 | #include "lib/log.h" 24 | #include "net/doomnet.h" 25 | 26 | #include "adapters/nodemap.h" 27 | 28 | #define MAGIC_STRING "V~UTiLS!" 29 | 30 | struct discover_packet 31 | { 32 | char magic_string[8]; 33 | uint8_t is_reply; 34 | uint8_t player; 35 | }; 36 | 37 | int nodetoplayer[MAXNETNODES]; 38 | int playertonode[MAXPLAYERS]; 39 | 40 | static int HasMagicString(struct discover_packet far *pkt) 41 | { 42 | return far_memcmp(pkt->magic_string, MAGIC_STRING, 43 | sizeof(pkt->magic_string)) == 0; 44 | } 45 | 46 | static void SendDiscover(doomcom_t far *doomcom, int node, int is_reply) 47 | { 48 | struct discover_packet far *pkt = (void far *) doomcom->data; 49 | 50 | far_memmove(pkt->magic_string, MAGIC_STRING, sizeof(pkt->magic_string)); 51 | pkt->player = doomcom->consoleplayer; 52 | pkt->is_reply = is_reply; 53 | 54 | doomcom->remotenode = node; 55 | doomcom->datalength = sizeof(struct discover_packet); 56 | NetSendPacket(doomcom); 57 | } 58 | 59 | static void SendDiscoverToAll(doomcom_t far *doomcom) 60 | { 61 | int i; 62 | 63 | for (i = 1; i < doomcom->numnodes; ++i) 64 | { 65 | SendDiscover(doomcom, i, 0); 66 | } 67 | } 68 | 69 | // Invoked by higher-level code after DiscoverPlayers() has finished, to 70 | // reply to discover packets from other nodes still stuck in their own 71 | // DiscoverPlayers() loop. Returns 1 if the packet should be ignored. 72 | int CheckLateDiscover(doomcom_t far *doomcom) 73 | { 74 | struct discover_packet far *pkt = (void far *) doomcom->data; 75 | 76 | // We may receive a late discover packet from another node still stuck 77 | // in the DiscoverPlayers() loop. If we do, send a reply. 78 | if (doomcom->datalength == sizeof(struct discover_packet) 79 | && HasMagicString(pkt) && !pkt->is_reply) 80 | { 81 | SendDiscover(doomcom, doomcom->remotenode, 1); 82 | return 1; 83 | } 84 | 85 | return 0; 86 | } 87 | 88 | void DiscoverPlayers(doomcom_t far *doomcom) 89 | { 90 | struct discover_packet far *pkt = (void far *) doomcom->data; 91 | clock_t last_send = 0, now; 92 | uint32_t got_nodes = 0; 93 | 94 | LogMessage("Discovering players."); 95 | 96 | nodetoplayer[0] = doomcom->consoleplayer; 97 | playertonode[doomcom->consoleplayer] = 0; 98 | got_nodes = 1 << 0; 99 | 100 | while (got_nodes != (1 << doomcom->numnodes) - 1) 101 | { 102 | CheckAbort("Player discovery"); 103 | now = clock(); 104 | if (now - last_send > CLOCKS_PER_SEC) 105 | { 106 | SendDiscoverToAll(doomcom); 107 | last_send = now; 108 | } 109 | if (!NetGetPacket(doomcom)) 110 | { 111 | continue; 112 | } 113 | 114 | if (doomcom->datalength == sizeof(struct discover_packet) 115 | && HasMagicString(pkt) && pkt->player < MAXPLAYERS) 116 | { 117 | nodetoplayer[doomcom->remotenode] = pkt->player; 118 | playertonode[pkt->player] = doomcom->remotenode; 119 | got_nodes |= 1 << doomcom->remotenode; 120 | } 121 | } 122 | 123 | SendDiscoverToAll(doomcom); 124 | } 125 | 126 | -------------------------------------------------------------------------------- /adapters/nodemap.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | extern int nodetoplayer[MAXNETNODES]; 12 | extern int playertonode[MAXPLAYERS]; 13 | 14 | int CheckLateDiscover(doomcom_t far *doomcom); 15 | void DiscoverPlayers(doomcom_t far *doomcom); 16 | 17 | -------------------------------------------------------------------------------- /adapters/rottcom.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) 1994-1995 Apogee Software, Ltd. 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | #define ROTT_MAXPACKETSIZE 2048 12 | 13 | #if __WATCOMC__ 14 | #pragma pack (1) 15 | #endif 16 | 17 | typedef struct 18 | { 19 | short intnum; // ROTT executes an int to send commands 20 | 21 | // communication between ROTT and the driver 22 | short command; // CMD_SEND or CMD_GET 23 | short remotenode; // dest for send, set by get (-1 = no packet) 24 | short datalength; // bytes in rottdata to be sent / bytes read 25 | 26 | // info specific to this node 27 | short consoleplayer; // 0-3 = player number 28 | short numplayers; // 1-4 29 | short client; // 0 = server 1 = client 30 | short gametype; // 0 = modem 1 = network 31 | short ticstep; // 1 for every tic 2 for every other tic ... 32 | short remoteridicule; // 0 = remote ridicule is off 1= rr is on 33 | 34 | // packet data to be sent 35 | char data[ROTT_MAXPACKETSIZE]; 36 | } rottcom_t; 37 | 38 | #if __WATCOMC__ 39 | #pragma pack (4) 40 | #endif 41 | 42 | #define ROTT_MODEM_GAME 0 43 | #define ROTT_NETWORK_GAME 1 44 | 45 | -------------------------------------------------------------------------------- /adapters/vcommit.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | // Adapter that converts a Doom network driver into a COMMIT driver 12 | // used by various 3D Realms games. 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "lib/inttypes.h" 20 | 21 | #include "lib/dos.h" 22 | #include "lib/flag.h" 23 | #include "lib/ints.h" 24 | #include "lib/log.h" 25 | #include "net/doomnet.h" 26 | 27 | #include "adapters/commit.h" 28 | #include "adapters/fragment.h" 29 | #include "adapters/nodemap.h" 30 | 31 | static struct interrupt_hook net_interrupt; 32 | static doomcom_t far *inner_driver; 33 | static gamecom_t gamecom; 34 | 35 | static void ExecuteCommand(void) 36 | { 37 | struct reassembled_packet *pkt; 38 | int i; 39 | 40 | switch (gamecom.command) 41 | { 42 | case COMMIT_CMD_SEND: 43 | FragmentSendPacket(playertonode[gamecom.remotenode - 1], 44 | gamecom.data, gamecom.datalength); 45 | break; 46 | 47 | case COMMIT_CMD_GET: 48 | pkt = FragmentGetPacket(); 49 | if (pkt == NULL) 50 | { 51 | gamecom.remotenode = -1; 52 | return; 53 | } 54 | gamecom.remotenode = nodetoplayer[pkt->remotenode] + 1; 55 | gamecom.datalength = pkt->datalength; 56 | far_memcpy(gamecom.data, pkt->data, pkt->datalength); 57 | break; 58 | 59 | case COMMIT_CMD_SENDTOALL: 60 | for (i = 0; i < inner_driver->numnodes; ++i) 61 | { 62 | FragmentSendPacket(i, gamecom.data, gamecom.datalength); 63 | } 64 | break; 65 | 66 | case COMMIT_CMD_SENDTOALLOTHERS: 67 | for (i = 1; i < inner_driver->numnodes; ++i) 68 | { 69 | FragmentSendPacket(i, gamecom.data, gamecom.datalength); 70 | } 71 | break; 72 | } 73 | } 74 | 75 | static void interrupt far NetISR(void) 76 | { 77 | SWITCH_ISR_STACK; 78 | ExecuteCommand(); 79 | RESTORE_ISR_STACK; 80 | } 81 | 82 | static void SetDriver(long l) 83 | { 84 | assert(inner_driver == NULL); 85 | inner_driver = NetGetHandle(l); 86 | } 87 | 88 | int main(int argc, char *argv[]) 89 | { 90 | char addrstring[16]; 91 | long flataddr; 92 | char **args; 93 | 94 | SetHelpText("Doom to COMMIT network adapter", 95 | "ipxsetup -nodes 3 %s duke3d.exe"); 96 | 97 | APIPointerFlag("-net", SetDriver); 98 | args = ParseCommandLine(argc, argv); 99 | if (args == NULL) 100 | { 101 | ErrorPrintUsage("No command given to run."); 102 | } 103 | 104 | assert(inner_driver != NULL); 105 | InitFragmentReassembly(inner_driver); 106 | DiscoverPlayers(inner_driver); 107 | 108 | gamecom.consoleplayer = inner_driver->consoleplayer + 1; 109 | gamecom.numplayers = inner_driver->numplayers; 110 | gamecom.gametype = COMMIT_GAME_NETWORK; 111 | 112 | // Prepare to launch game 113 | if (!FindAndHookInterrupt(&net_interrupt, NetISR)) 114 | { 115 | Error("Warning: no free interrupt handlers found. You can specify" 116 | "a vector with the -vector 0x parameter."); 117 | } 118 | 119 | gamecom.intnum = net_interrupt.interrupt_num; 120 | 121 | // Add -net &gamecom 122 | flataddr = (long) FP_SEG(&gamecom) * 16 + FP_OFF(&gamecom); 123 | sprintf(addrstring, "%lu", flataddr); 124 | args = AppendArgs(args, "-net", addrstring, NULL); 125 | 126 | spawnv(P_WAIT, args[0], (void *) args); 127 | 128 | RestoreInterrupt(&net_interrupt); 129 | 130 | return 0; 131 | } 132 | 133 | -------------------------------------------------------------------------------- /adapters/vrottcom.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | // Adapter that converts a Doom network driver into a ROTT rottcom 12 | // driver. 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "lib/inttypes.h" 20 | 21 | #include "lib/dos.h" 22 | #include "lib/flag.h" 23 | #include "lib/ints.h" 24 | #include "lib/log.h" 25 | #include "net/doomnet.h" 26 | 27 | #include "adapters/fragment.h" 28 | #include "adapters/nodemap.h" 29 | #include "adapters/rottcom.h" 30 | 31 | static struct interrupt_hook net_interrupt; 32 | static doomcom_t far *inner_driver; 33 | static rottcom_t rottcom; 34 | 35 | static void interrupt far ExecuteCommand(void) 36 | { 37 | struct reassembled_packet *pkt; 38 | 39 | switch (rottcom.command) 40 | { 41 | case CMD_SEND: 42 | FragmentSendPacket(playertonode[rottcom.remotenode - 1], 43 | rottcom.data, rottcom.datalength); 44 | break; 45 | 46 | case CMD_GET: 47 | pkt = FragmentGetPacket(); 48 | if (pkt == NULL) 49 | { 50 | rottcom.remotenode = -1; 51 | return; 52 | } 53 | rottcom.remotenode = nodetoplayer[pkt->remotenode] + 1; 54 | rottcom.datalength = pkt->datalength; 55 | far_memcpy(rottcom.data, pkt->data, pkt->datalength); 56 | break; 57 | } 58 | } 59 | 60 | static void interrupt far NetISR(void) 61 | { 62 | SWITCH_ISR_STACK; 63 | ExecuteCommand(); 64 | RESTORE_ISR_STACK; 65 | } 66 | 67 | static void SetDriver(long l) 68 | { 69 | assert(inner_driver == NULL); 70 | inner_driver = NetGetHandle(l); 71 | } 72 | 73 | int main(int argc, char *argv[]) 74 | { 75 | char addrstring[16]; 76 | long flataddr; 77 | int remoteridicule = 0; 78 | char **args; 79 | 80 | SetHelpText("Doom to ROTT network adapter", 81 | "ipxsetup -nodes 3 %s rott.exe"); 82 | 83 | APIPointerFlag("-net", SetDriver); 84 | BoolFlag("-remoteridicule", &remoteridicule, "Enable remote ridicule"); 85 | args = ParseCommandLine(argc, argv); 86 | if (args == NULL) 87 | { 88 | ErrorPrintUsage("No command given to run."); 89 | } 90 | 91 | assert(inner_driver != NULL); 92 | InitFragmentReassembly(inner_driver); 93 | DiscoverPlayers(inner_driver); 94 | 95 | rottcom.consoleplayer = inner_driver->consoleplayer + 1; 96 | rottcom.numplayers = inner_driver->numplayers; 97 | rottcom.ticstep = inner_driver->ticdup; 98 | rottcom.client = rottcom.consoleplayer > 1; 99 | rottcom.gametype = ROTT_NETWORK_GAME; 100 | rottcom.remoteridicule = remoteridicule; 101 | 102 | // Prepare to launch game 103 | if (!FindAndHookInterrupt(&net_interrupt, NetISR)) 104 | { 105 | Error("Warning: no free interrupt handlers found. You can specify" 106 | "a vector with the -vector 0x parameter."); 107 | } 108 | 109 | rottcom.intnum = net_interrupt.interrupt_num; 110 | 111 | // Add -net &rottcom 112 | flataddr = (long) FP_SEG(&rottcom) * 16 + FP_OFF(&rottcom); 113 | sprintf(addrstring, "%lu", flataddr); 114 | args = AppendArgs(args, "-net", addrstring, NULL); 115 | 116 | spawnv(P_WAIT, args[0], (void *) args); 117 | 118 | RestoreInterrupt(&net_interrupt); 119 | 120 | return 0; 121 | } 122 | 123 | -------------------------------------------------------------------------------- /bld/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fragglet/vanilla-utilities/d3d1181d50ecf1716012955d0641bd7e66870a35/bld/.gitignore -------------------------------------------------------------------------------- /build/.gitignore: -------------------------------------------------------------------------------- 1 | build.log 2 | result.txt 3 | thecmd.bat 4 | -------------------------------------------------------------------------------- /build/dosbox.conf: -------------------------------------------------------------------------------- 1 | # This is a dosbox configuration that turns off all the features and sets 2 | # up the environment for doing a build. 3 | 4 | [sdl] 5 | usescancodes = false 6 | fullscreen = false 7 | fullresolution = 640 x 480 8 | 9 | [render] 10 | aspect = false 11 | scaler = none 12 | frameskip=10 13 | 14 | [midi] 15 | mpu401 = none 16 | mididevice = none 17 | 18 | [speaker] 19 | pcspeaker = false 20 | tandy = off 21 | disney = false 22 | 23 | [joystick] 24 | joysticktype = none 25 | 26 | [sblaster] 27 | sbtype = none 28 | oplmode = none 29 | 30 | [gus] 31 | gus = false 32 | 33 | [cpu] 34 | cycles = fixed 12000 35 | 36 | [ipx] 37 | ipx = false 38 | 39 | [autoexec] 40 | mount c build/dos-compilers 41 | call c:\watcom\owsetenv.bat 42 | 43 | set PATH=%PATH%;c:\tcpp\bin;c:\tasm\bin 44 | 45 | mount d . 46 | d: 47 | 48 | -------------------------------------------------------------------------------- /build/doscmd.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | rem Wrapper batch file that runs the command in thecmd.bat and saves its 3 | rem exit code to a file so that we can pass it back in the exit code of 4 | rem doscmd.sh. 5 | 6 | call build\thecmd.bat 7 | 8 | if errorlevel 1 goto failure 9 | echo 0 result >> build\result.txt 10 | goto end 11 | 12 | :failure 13 | echo 1 result >> build\result.txt 14 | 15 | :end 16 | exit 17 | 18 | -------------------------------------------------------------------------------- /build/doscmd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Fire up a background dosbox and run a command, eg. 4 | # build/doscmd.sh wmake -f makefile.wat clean 5 | # 6 | # This should be run from the top-level directory. 7 | # 8 | # How this works: 9 | # * a temporary batch file called "thecmd.bat" is generated that gets 10 | # run inside dosbox by a wrapper batch file named doscmd.bat. 11 | # * the DOS stdout of the command (not stderr) gets written to build.log 12 | # and then gets passed through to this script's stdout 13 | # * the exit code is written to a temporary file named result.txt and 14 | # passed back to this script (although only success/failure; not the 15 | # full exit code) 16 | 17 | rm -f build/build.log build/result.txt 18 | touch build/build.log build/result.txt 19 | 20 | echo "$@ >> build\\\\build.log" > build/thecmd.bat 21 | 22 | SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \ 23 | dosbox -conf build/dosbox.conf -c "build\\doscmd.bat" 24 | cat build/build.log 25 | 26 | read result _ < build/result.txt 27 | rm -f build/result.txt build/thecmd.bat 28 | 29 | exit $result 30 | 31 | -------------------------------------------------------------------------------- /build/tcbuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Performs OpenWatcom build inside a background dosbox. 4 | # This should be run from the top-level directory. 5 | 6 | build/doscmd.sh make -f makefile.tc "$@" 7 | result=$? 8 | 9 | exit $result 10 | 11 | -------------------------------------------------------------------------------- /build/watbuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Performs OpenWatcom build inside a background dosbox. 4 | # This should be run from the top-level directory. 5 | 6 | rm -f WMAKE.ERR 7 | build/doscmd.sh wmake -e -f makefile.wat -l wmake.err "$@" 8 | result=$? 9 | 10 | touch NOTHING.ERR 11 | grep "" $(ls -rt *.ERR) 12 | rm -f NOTHING.ERR 13 | 14 | exit $result 15 | 16 | -------------------------------------------------------------------------------- /ctrl/analogjs.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | #include 12 | #include 13 | 14 | #include "lib/inttypes.h" 15 | 16 | #include "ctrl/control.h" 17 | #include "lib/dos.h" 18 | #include "lib/flag.h" 19 | #include "lib/log.h" 20 | 21 | #define JOYSTICK_PORT 0x201 22 | 23 | struct axis 24 | { 25 | int min, max, center; 26 | int deadzone_pct; 27 | }; 28 | 29 | const int forwardmove[2] = {0x19, 0x32}; 30 | const int sidemove[2] = {0x18, 0x28}; 31 | 32 | // From joystick.asm: 33 | extern int joystickx, joysticky; 34 | extern void __stdcall ReadJoystick(void); 35 | 36 | static char *config_file = "analogjs.cfg"; 37 | static int calibrate = 0; 38 | 39 | static struct axis x_axis, y_axis; 40 | 41 | static int always_run = 0; 42 | static int turn_speed = 512; 43 | static int novert = 0; 44 | 45 | static int joyb_fire = 0; 46 | static int joyb_strafe = 1; 47 | static int joyb_use = 3; 48 | static int joyb_speed = 2; 49 | static int joyb_forward = -1; 50 | 51 | static struct 52 | { 53 | const char *name; 54 | int *var; 55 | } config_vars[] = { 56 | {"always_run", &always_run}, 57 | {"turn_speed", &turn_speed}, 58 | {"novert", &novert}, 59 | {"cal_x_min", &x_axis.min}, 60 | {"cal_x_max", &x_axis.max}, 61 | {"cal_x_center", &x_axis.center}, 62 | {"cal_x_deadzone_pct", &x_axis.deadzone_pct}, 63 | {"cal_y_min", &y_axis.min}, 64 | {"cal_y_max", &y_axis.max}, 65 | {"cal_y_center", &y_axis.center}, 66 | {"cal_y_deadzone_pct", &y_axis.deadzone_pct}, 67 | {"joyb_fire", &joyb_fire}, 68 | {"joyb_strafe", &joyb_strafe}, 69 | {"joyb_use", &joyb_use}, 70 | {"joyb_speed", &joyb_speed}, 71 | {"joyb_forward", &joyb_forward}, 72 | }; 73 | 74 | static int ParseConfigFile(char *filename) 75 | { 76 | char config_name[20]; 77 | int value; 78 | int i; 79 | FILE *fs; 80 | 81 | fs = fopen(filename, "r"); 82 | if (fs == NULL) 83 | { 84 | return 0; 85 | } 86 | 87 | while (!feof(fs)) 88 | { 89 | if (fscanf(fs, "%20s %d\n", config_name, &value) != 2) 90 | { 91 | continue; 92 | } 93 | for (i = 0; i < sizeof(config_vars) / sizeof(*config_vars); ++i) 94 | { 95 | if (!strcmp(config_vars[i].name, config_name)) 96 | { 97 | *config_vars[i].var = value; 98 | break; 99 | } 100 | } 101 | } 102 | 103 | fclose(fs); 104 | return 1; 105 | } 106 | 107 | static void WriteConfigFile(char *filename) 108 | { 109 | FILE *fs; 110 | int i; 111 | 112 | fs = fopen(filename, "w"); 113 | if (fs == NULL) 114 | { 115 | Error("Failed to open '%s' for writing.", filename); 116 | } 117 | 118 | for (i = 0; i < sizeof(config_vars) / sizeof(*config_vars); ++i) 119 | { 120 | fprintf(fs, "%-20s%d\n", config_vars[i].name, *config_vars[i].var); 121 | } 122 | 123 | fclose(fs); 124 | } 125 | 126 | static int ReadButtons(void) 127 | { 128 | return (INPUT(JOYSTICK_PORT) >> 4) & 0xf; 129 | } 130 | 131 | static void WaitButtonPress(void) 132 | { 133 | while ((ReadButtons() & 0x01) != 0) 134 | { 135 | CheckAbort("Joystick calibration"); 136 | } 137 | while ((ReadButtons() & 0x01) == 0) 138 | { 139 | CheckAbort("Joystick calibration"); 140 | } 141 | } 142 | 143 | static void CalibrateJoystick(void) 144 | { 145 | LogMessage("CENTER the joystick and press button 1:"); 146 | WaitButtonPress(); 147 | ReadJoystick(); 148 | x_axis.center = joystickx; y_axis.center = joysticky; 149 | 150 | LogMessage("Push the joystick to the UPPER LEFT and press button 1:"); 151 | WaitButtonPress(); 152 | ReadJoystick(); 153 | x_axis.min = joystickx; y_axis.min = joysticky; 154 | 155 | LogMessage("Push the joystick to the LOWER RIGHT and press button 1:"); 156 | WaitButtonPress(); 157 | ReadJoystick(); 158 | x_axis.max = joystickx; y_axis.max = joysticky; 159 | } 160 | 161 | static int16_t AdjustAxisValue(struct axis *a, int v, int speed) 162 | { 163 | long scratch; 164 | int range; 165 | 166 | if (v < a->min) 167 | { 168 | v = a->min; 169 | } 170 | else if (v > a->max) 171 | { 172 | v = a->max; 173 | } 174 | 175 | range = (a->max - a->min) * a->deadzone_pct / 200; 176 | 177 | if (v > a->center - range && v < a->center + range) 178 | { 179 | // In dead zone 180 | return 0; 181 | } 182 | else if (v < a->center) 183 | { 184 | v = a->center - v; 185 | range = a->center - a->min; 186 | scratch = ((long) speed * v) / range; 187 | return (int16_t) scratch; 188 | } 189 | else 190 | { 191 | v = v - a->center; 192 | range = a->max - a->center; 193 | scratch = ((long) -speed * v) / range; 194 | return (int16_t) scratch; 195 | } 196 | } 197 | 198 | #define IS_PRESSED(buttons, b) \ 199 | (b < 0 ? 0 : \ 200 | b > 20 ? 1 : (buttons & (1 << b)) == 0) 201 | 202 | static void far ControlCallback(ticcmd_t *ticcmd) 203 | { 204 | int buttons; 205 | int run; 206 | 207 | ReadJoystick(); 208 | buttons = ReadButtons(); 209 | 210 | run = always_run || IS_PRESSED(buttons, joyb_speed); 211 | 212 | if (IS_PRESSED(buttons, joyb_strafe)) 213 | { 214 | ticcmd->sidemove += AdjustAxisValue( 215 | &x_axis, joystickx, -sidemove[run]); 216 | } 217 | else 218 | { 219 | ticcmd->angleturn += AdjustAxisValue(&x_axis, joystickx, turn_speed); 220 | } 221 | 222 | if (IS_PRESSED(buttons, joyb_forward)) 223 | { 224 | ticcmd->forwardmove += forwardmove[run]; 225 | } 226 | else if (!novert) 227 | { 228 | ticcmd->forwardmove += AdjustAxisValue( 229 | &y_axis, joysticky, forwardmove[run]); 230 | } 231 | 232 | if (IS_PRESSED(buttons, joyb_fire)) 233 | { 234 | ticcmd->buttons |= BT_ATTACK; 235 | } 236 | if (IS_PRESSED(buttons, joyb_use)) 237 | { 238 | ticcmd->buttons |= BT_USE; 239 | } 240 | } 241 | 242 | int main(int argc, char *argv[]) 243 | { 244 | char **args; 245 | 246 | StringFlag("-jscfg", &config_file, "filename", 247 | "Path to config file to use"); 248 | BoolFlag("-calibrate", &calibrate, "Calibrate joystick"); 249 | ControlRegisterFlags(); 250 | 251 | args = ParseCommandLine(argc, argv); 252 | 253 | if (calibrate) 254 | { 255 | ParseConfigFile(config_file); 256 | CalibrateJoystick(); 257 | WriteConfigFile(config_file); 258 | LogMessage("Wrote config file %s", config_file); 259 | return 0; 260 | } 261 | 262 | if (args == NULL) 263 | { 264 | ErrorPrintUsage("No command given to run."); 265 | } 266 | 267 | if (!ParseConfigFile(config_file)) 268 | { 269 | Error("Failed to read config file %s", config_file); 270 | } 271 | 272 | // We take over joystick control. 273 | args = AppendArgs(args, "-nojoy", NULL); 274 | 275 | ControlLaunchDoom(args, ControlCallback); 276 | 277 | return 0; 278 | } 279 | 280 | -------------------------------------------------------------------------------- /ctrl/control.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2011-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | 10 | // Functions for interacting with the Doom -control API. 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "ctrl/control.h" 19 | #include "lib/dos.h" 20 | #include "lib/ints.h" 21 | #include "lib/flag.h" 22 | #include "lib/log.h" 23 | 24 | struct control_handle_s 25 | { 26 | long intnum; 27 | ticcmd_t ticcmd; 28 | }; 29 | 30 | static struct interrupt_hook control_interrupt; 31 | static control_handle_t control_buf; 32 | static control_handle_t far *next_handle; 33 | 34 | static control_callback_t int_callback; 35 | 36 | // Interrupt service routine. 37 | 38 | static void interrupt far ControlISR() 39 | { 40 | SWITCH_ISR_STACK; 41 | 42 | // If we have a next_handle, invoke the next -control driver in the 43 | // chain to populate the ticcmd before we invoke our callback 44 | // function. Otherwise, we just start by zeroing out the ticcmd. 45 | if (next_handle != NULL) 46 | { 47 | ControlInvoke(next_handle, &control_buf.ticcmd); 48 | } 49 | else 50 | { 51 | memset(&control_buf.ticcmd, 0, sizeof(ticcmd_t)); 52 | } 53 | 54 | // The callback may invoke DOS API functions to read/write files, 55 | // so switch back to our PSP for the duration of the callback. 56 | { 57 | unsigned int saved_psp = SwitchPSP(); 58 | int_callback(&control_buf.ticcmd); 59 | RestorePSP(saved_psp); 60 | } 61 | 62 | RESTORE_ISR_STACK; 63 | } 64 | 65 | static void ControlPointerCallback(long l) 66 | { 67 | assert(next_handle == NULL); 68 | next_handle = ControlGetHandle(l); 69 | } 70 | 71 | void ControlRegisterFlags(void) 72 | { 73 | APIPointerFlag("-control", ControlPointerCallback); 74 | IntFlag("-cvector", &control_interrupt.force_vector, "vector", NULL); 75 | } 76 | 77 | // Launch the game. args[0] is the program to invoke. 78 | void ControlLaunchDoom(char **args, control_callback_t callback) 79 | { 80 | char addr_string[32]; 81 | long flataddr; 82 | 83 | if (!FindAndHookInterrupt(&control_interrupt, ControlISR)) 84 | { 85 | Error("Failed to find a free DOS interrupt. Try using -cvector " 86 | "to manually force an interrupt."); 87 | } 88 | 89 | // Initialize the interrupt handler. 90 | 91 | int_callback = callback; 92 | control_buf.intnum = control_interrupt.interrupt_num; 93 | 94 | // Add the -control argument. 95 | 96 | flataddr = (long) FP_SEG(&control_buf) * 16 + FP_OFF(&control_buf); 97 | sprintf(addr_string, "%li", flataddr); 98 | args = AppendArgs(args, "-control", addr_string, NULL); 99 | 100 | SquashToResponseFile(args); 101 | 102 | // Launch Doom: 103 | spawnv(P_WAIT, args[0], (void *) args); 104 | 105 | free(args); 106 | 107 | RestoreInterrupt(&control_interrupt); 108 | } 109 | 110 | // ControlGetHandle takes the given long value read from the command line 111 | // and returns a control_handle_t pointer. 112 | control_handle_t far *ControlGetHandle(long l) 113 | { 114 | control_handle_t far *result = NULL; 115 | unsigned int seg; 116 | 117 | assert(l != 0); 118 | seg = (int) ((l >> 4) & 0xf000L); 119 | result = (void far *) MK_FP(seg, l & 0xffffL); 120 | 121 | return result; 122 | } 123 | 124 | void ControlInvoke(control_handle_t far *handle, ticcmd_t *ticcmd) 125 | { 126 | union REGS regs; 127 | far_memcpy(&handle->ticcmd, ticcmd, sizeof(ticcmd_t)); 128 | int86((int) handle->intnum, ®s, ®s); 129 | far_memcpy(ticcmd, &handle->ticcmd, sizeof(ticcmd_t)); 130 | } 131 | 132 | -------------------------------------------------------------------------------- /ctrl/control.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2011-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | // Functions for interacting with the Doom -control API. 12 | 13 | #ifndef CONTROL_H 14 | #define CONTROL_H 15 | 16 | #include "lib/inttypes.h" 17 | 18 | typedef struct control_handle_s control_handle_t; 19 | 20 | // Press "Fire". 21 | #define BT_ATTACK 1 22 | // Use button, to open doors, activate switches. 23 | #define BT_USE 2 24 | 25 | // Flag: game events, not really buttons. 26 | #define BT_SPECIAL 128 27 | #define BT_SPECIALMASK 3 28 | 29 | // Flag, weapon change pending. 30 | // If true, the next 3 bits hold weapon num. 31 | #define BT_CHANGE 4 32 | // The 3bit weapon mask and shift, convenience. 33 | #define BT_WEAPONMASK (8+16+32) 34 | #define BT_WEAPONSHIFT 3 35 | 36 | // Pause the game. 37 | #define BTS_PAUSE 1 38 | // Save the game at each console. 39 | #define BTS_SAVEGAME 2 40 | 41 | // Savegame slot numbers 42 | // occupy the second byte of buttons. 43 | #define BTS_SAVEMASK (4+8+16) 44 | #define BTS_SAVESHIFT 2 45 | 46 | typedef struct { 47 | signed char forwardmove; // *2048 for move 48 | signed char sidemove; // *2048 for move 49 | int16_t angleturn; // <<16 for angle delta 50 | int16_t consistancy; // netgame check 51 | uint8_t chatchar; 52 | uint8_t buttons; 53 | uint8_t buttons2; 54 | int32_t inventoryitem; 55 | } ticcmd_t; 56 | 57 | typedef void (far *control_callback_t)(ticcmd_t *ticcmd); 58 | 59 | void ControlRegisterFlags(void); 60 | void ControlLaunchDoom(char **args, control_callback_t callback); 61 | control_handle_t far *ControlGetHandle(long l); 62 | void ControlInvoke(control_handle_t far *handle, ticcmd_t *ticcmd); 63 | 64 | #endif /* CONTROL_H */ 65 | -------------------------------------------------------------------------------- /ctrl/joystick.asm: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright(C) 1993-1996 Id Software, Inc. 3 | ; Copyright(C) 1993-2008 Raven Software 4 | ; 5 | ; You can redistribute and/or modify this program under the terms of the 6 | ; GNU General Public License version 2 as published by the Free Software 7 | ; Foundation, or any later version. This program is distributed WITHOUT 8 | ; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | ; FITNESS FOR A PARTICULAR PURPOSE. 10 | ; 11 | ; Joystick routines, from i_ibm_a.asm in the Heretic source 12 | .model small 13 | 14 | .data 15 | 16 | _joystickx dw 0 17 | _joysticky dw 0 18 | saved_si dw 0 19 | saved_di dw 0 20 | saved_bp dw 0 21 | PUBLIC _joystickx, _joysticky 22 | 23 | .code 24 | 25 | public _ReadJoystick 26 | _ReadJoystick: 27 | pushf ; state of interrupt flag 28 | cli 29 | 30 | mov dx, 0201h 31 | in al, dx 32 | out dx, al ; Clear the resistors 33 | 34 | mov ah, 1 ; Get masks into registers 35 | mov ch, 2 36 | 37 | mov saved_si, si 38 | mov saved_di, di 39 | mov saved_bp, bp 40 | xor si, si ; Clear count registers 41 | xor di, di 42 | xor bx, bx ; Clear high byte of bx for later 43 | 44 | mov bp, 10000 ; joystick is disconnected if value is this big 45 | 46 | jloop: 47 | in al, dx ; Get bits indicating whether all are finished 48 | 49 | dec bp ; Check bounding register 50 | jz bad ; We have a silly value - abort 51 | 52 | mov bl, al ; Duplicate the bits 53 | and bl, ah ; 54 | add si, bx ; si += al & 0x01 55 | mov cl, bl 56 | 57 | mov bl, al 58 | and bl, ch 59 | add di, bx ; di += al & 0x02 60 | 61 | add cl, bl 62 | jnz jloop ; If both bits were 0, drop out 63 | 64 | done: 65 | mov _joystickx, si 66 | shr di, 1 ; di is shifted left one bit 67 | mov _joysticky, di 68 | 69 | popf ; restore interrupt flag 70 | mov si, saved_si ; restore saved registers 71 | mov di, saved_di 72 | mov bp, saved_bp 73 | mov ax, 1 ; read was ok 74 | ret 75 | 76 | bad: 77 | popf ; restore interrupt flag 78 | mov si, saved_si ; restore saved registers 79 | mov di, saved_di 80 | mov bp, saved_bp 81 | xor ax, ax ; read was bad 82 | ret 83 | 84 | END 85 | 86 | -------------------------------------------------------------------------------- /ctrl/replay.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2011-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | // Program to use the -control API to play back a recorded .lmp 12 | // demo. The game can be continued when the demo finishes. 13 | 14 | #include 15 | #include 16 | 17 | #include "ctrl/control.h" 18 | #include "lib/flag.h" 19 | #include "lib/log.h" 20 | 21 | static FILE *demo_stream; 22 | static char *demo_filename = NULL; 23 | static int is_strife = 0; 24 | 25 | static void far ReplayCallback(ticcmd_t *ticcmd) 26 | { 27 | unsigned char ticbuf[6]; 28 | int ticbuf_size, nbytes; 29 | 30 | if (is_strife) 31 | { 32 | ticbuf_size = 6; 33 | } 34 | else 35 | { 36 | ticbuf_size = 4; 37 | } 38 | 39 | nbytes = fread(ticbuf, 1, ticbuf_size, demo_stream); 40 | 41 | // EOF? 42 | if (nbytes < ticbuf_size || ticbuf[0] == 0x80) 43 | { 44 | return; 45 | } 46 | 47 | ticcmd->forwardmove = ticbuf[0]; 48 | ticcmd->sidemove = ticbuf[1]; 49 | ticcmd->angleturn = (ticbuf[2] << 8); 50 | ticcmd->buttons = ticbuf[3]; 51 | 52 | if (is_strife) 53 | { 54 | ticcmd->buttons2 = ticbuf[4]; 55 | ticcmd->inventoryitem = ticbuf[5]; 56 | } 57 | } 58 | 59 | static void OpenDemo(char *filename) 60 | { 61 | int header_size; 62 | 63 | if (is_strife) 64 | { 65 | header_size = 16; 66 | } 67 | else 68 | { 69 | header_size = 13; 70 | } 71 | 72 | demo_stream = fopen(filename, "rb"); 73 | 74 | if (demo_stream == NULL) 75 | { 76 | Error("Failed to open %s", filename); 77 | } 78 | 79 | fseek(demo_stream, header_size, SEEK_SET); 80 | } 81 | 82 | int main(int argc, char *argv[]) 83 | { 84 | char **args; 85 | 86 | SetHelpText("Replay demo through Doom control API", 87 | "%s -playdemo old.lmp doom.exe -record new.lmp"); 88 | StringFlag("-playdemo", &demo_filename, 89 | "filename", "play back the specified demo file"); 90 | BoolFlag("-strife", &is_strife, "play back a Strife demo"); 91 | ControlRegisterFlags(); 92 | args = ParseCommandLine(argc, argv); 93 | 94 | if (demo_filename == NULL) 95 | { 96 | ErrorPrintUsage("Demo file not specified."); 97 | } 98 | if (args == NULL) 99 | { 100 | ErrorPrintUsage("No command given to run."); 101 | } 102 | 103 | OpenDemo(demo_filename); 104 | ControlLaunchDoom(args, ReplayCallback); 105 | fclose(demo_stream); 106 | 107 | return 0; 108 | } 109 | -------------------------------------------------------------------------------- /lib/bakedin.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | // Definitions for baked-in command line argument config. 12 | 13 | #define BAKED_IN_MAGIC1 "_-_vUtILS_" 14 | #define BAKED_IN_MAGIC2 "-_aRgS_-_" 15 | #define BAKED_IN_MAX_LEN 1024 16 | 17 | #define HAVE_BAKED_IN_CONFIG(cfg) ((cfg).config[0] != '\0') 18 | 19 | struct baked_in_config { 20 | char magic[20]; 21 | char config[BAKED_IN_MAX_LEN]; 22 | }; 23 | 24 | -------------------------------------------------------------------------------- /lib/dos.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | #include 12 | #include 13 | 14 | #include "lib/dos.h" 15 | #include "lib/inttypes.h" 16 | 17 | int SetKeyboardLEDs(int value) 18 | { 19 | int keyboard_leds = 0xff; 20 | 21 | if (keyboard_leds == value) 22 | { 23 | return 1; 24 | } 25 | 26 | // We write to the keyboard controller on port 60h; ED=set keyboard LEDs 27 | OUTPUT(0x60, 0xed); 28 | OUTPUT(0x60, value); 29 | keyboard_leds = value; 30 | 31 | return INPUT(0x60) == 0xfa; 32 | } 33 | 34 | long GetEntropy(void) 35 | { 36 | long result; 37 | char *entropy = getenv("ENTROPY"); 38 | _bios_timeofday(_TIME_GETCLOCK, &result); 39 | if (entropy != NULL) 40 | { 41 | result ^= atoi(entropy); 42 | } 43 | return result; 44 | } 45 | 46 | void far_memcpy(void far *dest, void far *src, size_t nbytes) 47 | { 48 | uint8_t far *dest_p = (uint8_t far *) dest; 49 | uint8_t far *src_p = (uint8_t far *) src; 50 | int i; 51 | 52 | for (i = 0; i < nbytes; ++i) 53 | { 54 | *dest_p = *src_p; 55 | ++dest_p; ++src_p; 56 | } 57 | } 58 | 59 | int far_memcmp(void far *a, void far *b, size_t nbytes) 60 | { 61 | uint8_t far *a_p = (uint8_t far *) a; 62 | uint8_t far *b_p = (uint8_t far *) b; 63 | int i; 64 | 65 | for (i = 0; i < nbytes; ++i) 66 | { 67 | if (*a_p != *b_p) 68 | { 69 | if (*a_p < *b_p) 70 | { 71 | return -1; 72 | } 73 | else 74 | { 75 | return 1; 76 | } 77 | } 78 | ++a_p; ++b_p; 79 | } 80 | return 0; 81 | } 82 | 83 | void far_memmove(void far *dest, void far *src, size_t nbytes) 84 | { 85 | uint8_t far *dest_p = (uint8_t far *) dest; 86 | uint8_t far *src_p = (uint8_t far *) src; 87 | int i; 88 | 89 | if (dest < src) 90 | { 91 | for (i = 0; i < nbytes; ++i) 92 | { 93 | *dest_p = *src_p; 94 | ++dest_p; ++src_p; 95 | } 96 | } 97 | else 98 | { 99 | dest_p += nbytes - 1; 100 | src_p += nbytes - 1; 101 | for (i = 0; i < nbytes; ++i) 102 | { 103 | *dest_p = *src_p; 104 | --dest_p; --src_p; 105 | } 106 | } 107 | } 108 | 109 | void far_bzero(void far *dest, size_t nbytes) 110 | { 111 | uint8_t far *dest_p = dest; 112 | int i; 113 | 114 | for (i = 0; i < nbytes; ++i) 115 | { 116 | *dest_p = 0; 117 | ++dest_p; 118 | } 119 | } 120 | 121 | -------------------------------------------------------------------------------- /lib/dos.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | #include 12 | #include 13 | 14 | #if defined(__TURBOC__) 15 | #define cmdline_argc _argc 16 | #define cmdline_argv _argv 17 | #define __stdcall 18 | typedef int ssize_t; 19 | #else 20 | #define cmdline_argc __argc 21 | #define cmdline_argv __argv 22 | #endif 23 | 24 | #define strcasecmp stricmp 25 | #define strncasecmp strnicmp 26 | 27 | #define INPUT( port ) inp( port ) 28 | #define OUTPUT( port, data ) (void) outp( port, data ) 29 | 30 | #define LED_SCROLL_LOCK 0x01 31 | #define LED_NUM_LOCK 0x02 32 | #define LED_CAPS_LOCK 0x04 33 | 34 | int SetKeyboardLEDs(int value); 35 | long GetEntropy(void); 36 | 37 | void far_memcpy(void far *dest, void far *src, size_t nbytes); 38 | int far_memcmp(void far *a, void far *b, size_t nbytes); 39 | void far_memmove(void far *dest, void far *src, size_t nbytes); 40 | void far_bzero(void far *dest, size_t nbytes); 41 | 42 | -------------------------------------------------------------------------------- /lib/flag.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | typedef void (*api_pointer_callback_t)(long value); 12 | 13 | void SetHelpText(char *program_description, char *example_cmd); 14 | void BoolFlag(const char *name, int *ptr, const char *help_text); 15 | void IntFlag(const char *name, int *ptr, 16 | const char *param_name, const char *help_text); 17 | void UnsignedIntFlag(const char *name, unsigned int *ptr, 18 | const char *param_name, const char *help_text); 19 | void StringFlag(const char *name, char **ptr, 20 | const char *param_name, const char *help_text); 21 | void APIPointerFlag(const char *name, api_pointer_callback_t callback); 22 | void PrintProgramUsage(FILE *output); 23 | char **ParseCommandLine(int argc, char **argv); 24 | char **AppendArgList(char **args, int argc, char **argv); 25 | char **AppendArgs(char **args, ...); 26 | int ArgListLength(char **args); 27 | void SquashToResponseFile(char **args); 28 | 29 | #define DuplicateArgList(args) \ 30 | AppendArgList(NULL, ArgListLength(args), args) 31 | 32 | -------------------------------------------------------------------------------- /lib/ints.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "lib/dos.h" 18 | #include "lib/ints.h" 19 | #include "lib/log.h" 20 | 21 | // DOS programs can use interrupts in the range 0x60..0x80, although we 22 | // also check to make sure nobody else is using the same interrupt before 23 | // we grab it. Note that the original Doom ipxsetup/sersetup only ever went 24 | // up to 0x66, but we use the full range up to 0x80, which is consistent 25 | // with what DOS packet drivers do. 26 | #define MIN_USER_INTERRUPT 0x60 27 | #define MAX_USER_INTERRUPT 0x80 28 | 29 | unsigned char isr_stack_space[ISR_STACK_SIZE]; 30 | 31 | #ifdef __WATCOMC__ 32 | unsigned int old_stacklow; 33 | #endif 34 | 35 | static int FindFreeInterrupt(void) 36 | { 37 | int i; 38 | 39 | for (i = MIN_USER_INTERRUPT; i <= MAX_USER_INTERRUPT; ++i) 40 | { 41 | if (_dos_getvect(i) == NULL) 42 | { 43 | return i; 44 | } 45 | } 46 | LogMessage("No free interrupt handler found in the 0x%x..0x%x range.", 47 | MIN_USER_INTERRUPT, MAX_USER_INTERRUPT); 48 | return 0; 49 | } 50 | 51 | int FindAndHookInterrupt(struct interrupt_hook *state, 52 | interrupt_handler_t isr) 53 | { 54 | if (state->force_vector != 0) 55 | { 56 | state->interrupt_num = state->force_vector; 57 | } 58 | else 59 | { 60 | state->interrupt_num = FindFreeInterrupt(); 61 | if (state->interrupt_num == 0) 62 | { 63 | return 0; 64 | } 65 | } 66 | 67 | state->old_isr = _dos_getvect(state->interrupt_num); 68 | _dos_setvect(state->interrupt_num, isr); 69 | return 1; 70 | } 71 | 72 | void RestoreInterrupt(struct interrupt_hook *state) 73 | { 74 | if (state->interrupt_num == 0) 75 | { 76 | return; 77 | } 78 | _dos_setvect(state->interrupt_num, state->old_isr); 79 | state->interrupt_num = 0; 80 | } 81 | 82 | static int CheckChainedIRQ(unsigned int irq) 83 | { 84 | char name[14]; 85 | char *value; 86 | 87 | sprintf(name, "CHAIN_IRQ%d", irq); 88 | value = getenv(name); 89 | return value != NULL && !strcmp(value, "1"); 90 | } 91 | 92 | static void SetChainedIRQ(struct irq_hook *state, int enable) 93 | { 94 | sprintf(state->env_string, "CHAIN_IRQ%d=%s", state->irq, 95 | enable ? "1" : ""); 96 | putenv(state->env_string); 97 | } 98 | 99 | void HookIRQ(struct irq_hook *state, interrupt_handler_t isr, 100 | unsigned int irq) 101 | { 102 | assert(irq < 8); 103 | 104 | // Usually, for efficiency, we want the hardware all to ourselves and 105 | // deliberately don't chain to call whatever interrupt handler was 106 | // there before us. However, we want to play nicely with other copies 107 | // of the same binary that are sharing the same interrupt (eg. two 108 | // SERSETUPs on COM1 and COM3). So the first time that we hook the 109 | // interrupt we set a special environment variable. Other instances 110 | // then detect this and set chaining mode. 111 | // Doing this also ensures that we send the EOI message to the PIC only 112 | // once (see END_OF_IRQ() macro). 113 | state->irq = irq; 114 | state->chained = CheckChainedIRQ(irq); 115 | if (!state->chained) 116 | { 117 | SetChainedIRQ(state, 1); 118 | } 119 | 120 | _disable(); 121 | 122 | state->was_masked = (INPUT(PIC_DATA_PORT) & (1 << irq)) != 0; 123 | state->old_isr = _dos_getvect(8 + irq); 124 | _dos_setvect(8 + irq, isr); 125 | 126 | ClearIRQMask(state); 127 | 128 | _enable(); 129 | } 130 | 131 | void RestoreIRQ(struct irq_hook *state) 132 | { 133 | _disable(); 134 | 135 | SetIRQMask(state); 136 | _dos_setvect(8 + state->irq, state->old_isr); 137 | 138 | if (!state->was_masked) 139 | { 140 | ClearIRQMask(state); 141 | } 142 | 143 | _enable(); 144 | 145 | // We can't delete an environment variable but we can set it to zero. 146 | // We only do this if we previously set the variable ourselves. 147 | if (!state->chained) 148 | { 149 | SetChainedIRQ(state, 0); 150 | } 151 | } 152 | 153 | void SetIRQMask(struct irq_hook *irq) 154 | { 155 | OUTPUT(PIC_DATA_PORT, INPUT(PIC_DATA_PORT) | (1 << irq->irq)); 156 | } 157 | 158 | void ClearIRQMask(struct irq_hook *irq) 159 | { 160 | OUTPUT(PIC_DATA_PORT, INPUT(PIC_DATA_PORT) & ~(1 << irq->irq)); 161 | } 162 | 163 | #define DOS_INTERRUPT_API 0x21 164 | #define DOS_API_SET_CURRENT_PROCESS 0x50 165 | #define DOS_API_GET_CURRENT_PROCESS 0x51 166 | 167 | void RestorePSP(unsigned int psp) 168 | { 169 | union REGS regs; 170 | regs.h.ah = DOS_API_SET_CURRENT_PROCESS; 171 | regs.x.bx = psp; 172 | int86(DOS_INTERRUPT_API, ®s, ®s); 173 | } 174 | 175 | unsigned int SwitchPSP(void) 176 | { 177 | union REGS regs; 178 | 179 | regs.h.ah = DOS_API_GET_CURRENT_PROCESS; 180 | int86(DOS_INTERRUPT_API, ®s, ®s); 181 | 182 | // Now we switch back to our own PSP during the lifetime of the ISR. 183 | RestorePSP(_psp); 184 | 185 | return regs.x.bx; 186 | } 187 | 188 | -------------------------------------------------------------------------------- /lib/ints.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | #include 12 | 13 | #define ISR_STACK_SIZE 1024 14 | 15 | /* SwitchStack() should be called at the start of an ISR function to swap 16 | * out the stack pointer to point to the isr_stack_space memory block. 17 | * 18 | * When an interrupt is serviced, CS and DS are restored so that all our 19 | * pointers work as expected, but unless we do something, SS:SP still points 20 | * to the calling program's stack. This means that pointers to data on the 21 | * stack do not resolve correctly, since DS != SS. While it is possible to 22 | * carefully avoid taking pointers to anything on the stack, it tends to be 23 | * fragile and error-prone, with confusing bugs that are hard to track 24 | * down. isr_stack_space is located in our data segment so we can set 25 | * SS = DS while the interrupt is being serviced. 26 | * 27 | * After switching to the new stack we push the old SS, SP and BP register 28 | * values; RestoreStack() will pop them off and restore the old stack. 29 | */ 30 | 31 | #if defined(__TURBOC__) 32 | 33 | #define RESTORE_ISR_STACK \ 34 | do { \ 35 | asm pop bp; \ 36 | asm pop cx; \ 37 | asm pop bx; \ 38 | asm mov ss, bx; \ 39 | asm mov sp, cx; \ 40 | } while(0) 41 | 42 | #define SWITCH_ISR_STACK \ 43 | do { \ 44 | unsigned int nsp = \ 45 | (FP_OFF(isr_stack_space + sizeof(isr_stack_space) - 32)); \ 46 | asm mov ax, nsp; \ 47 | asm mov bx, ss; \ 48 | asm mov cx, sp; \ 49 | asm mov dx, ds; \ 50 | asm mov ss, dx; \ 51 | asm mov sp, ax; \ 52 | asm push bx; \ 53 | asm push cx; \ 54 | asm push bp; \ 55 | asm mov bp, sp; \ 56 | } while(0) 57 | 58 | #elif defined(__WATCOMC__) 59 | 60 | extern void SwitchStack(unsigned int); 61 | #pragma aux SwitchStack = \ 62 | "mov bx, ss" \ 63 | "mov cx, sp" \ 64 | "mov dx, ds" \ 65 | "mov ss, dx" \ 66 | "mov sp, ax" \ 67 | "push bx" \ 68 | "push cx" \ 69 | "push bp" \ 70 | "mov bp, sp" \ 71 | parm [ax] \ 72 | modify [bx cx dx]; 73 | 74 | extern void RestoreStack(void); 75 | #pragma aux RestoreStack = \ 76 | "pop bp" \ 77 | "pop cx" \ 78 | "pop bx" \ 79 | "mov ss, bx" \ 80 | "mov sp, cx" \ 81 | modify [bx cx]; 82 | 83 | extern unsigned int old_stacklow; 84 | extern unsigned int _STACKLOW; // Watcom-internal 85 | 86 | // For Watcom we must override the _STACKLOW variable to point to the bottom 87 | // of the new stack, in order to play nice with Watcom's stack overflow 88 | // detection code that gets included in function headers. 89 | #define SWITCH_ISR_STACK \ 90 | do { \ 91 | SwitchStack(FP_OFF(isr_stack_space + sizeof(isr_stack_space) - 32)); \ 92 | old_stacklow = _STACKLOW; \ 93 | _STACKLOW = FP_OFF(isr_stack_space); \ 94 | } while(0) 95 | #define RESTORE_ISR_STACK \ 96 | do { \ 97 | RestoreStack(); \ 98 | _STACKLOW = old_stacklow; \ 99 | } while(0) 100 | 101 | #else 102 | 103 | #error No stack switching implemented for this compiler! 104 | 105 | #endif 106 | 107 | typedef void (interrupt far *interrupt_handler_t)(); 108 | 109 | struct interrupt_hook 110 | { 111 | int force_vector; 112 | int interrupt_num; 113 | interrupt_handler_t old_isr; 114 | }; 115 | 116 | int FindAndHookInterrupt(struct interrupt_hook *state, 117 | interrupt_handler_t isr); 118 | void RestoreInterrupt(struct interrupt_hook *state); 119 | unsigned int SwitchPSP(void); 120 | void RestorePSP(unsigned int old_psp); 121 | 122 | #define PIC_COMMAND_PORT 0x20 123 | #define PIC_DATA_PORT 0x21 124 | 125 | struct irq_hook 126 | { 127 | unsigned int irq; 128 | interrupt_handler_t old_isr; 129 | char env_string[14]; 130 | unsigned int was_masked :1; 131 | unsigned int chained :1; 132 | }; 133 | 134 | void HookIRQ(struct irq_hook *state, interrupt_handler_t isr, 135 | unsigned int irq); 136 | void RestoreIRQ(struct irq_hook *state); 137 | void SetIRQMask(struct irq_hook *irq); 138 | void ClearIRQMask(struct irq_hook *irq); 139 | 140 | // In chained mode we call the original ISR and it sends the EOI 141 | // to the PIC. Otherwise we send it ourselves. It is important that 142 | // the interrupt is only acknowledged once, otherwise we can end up 143 | // acknowledging the wrong interrupt. 144 | #define END_OF_IRQ(irq_hook) \ 145 | if ((irq_hook).chained) { \ 146 | _chain_intr((irq_hook).old_isr); \ 147 | } else { \ 148 | OUTPUT(PIC_COMMAND_PORT, 0x60 + (irq_hook).irq); \ 149 | } 150 | 151 | extern unsigned char isr_stack_space[ISR_STACK_SIZE]; 152 | 153 | -------------------------------------------------------------------------------- /lib/inttypes.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | #ifndef DOS16_INTTYPES_H 12 | #define DOS16_INTTYPES_H 13 | 14 | #if !defined(__TURBOC__) && !defined(MSDOS) 15 | #include 16 | #else 17 | 18 | // A rough and incomplete version of C99's inttypes.h that can be used 19 | // with old DOS 16-bit compilers like Turbo C. 20 | 21 | typedef signed char int8_t; 22 | typedef unsigned char uint8_t; 23 | typedef signed short int16_t; 24 | typedef unsigned short uint16_t; 25 | typedef signed long int32_t; 26 | typedef unsigned long uint32_t; 27 | 28 | #define INT8_MIN (-128) 29 | #define INT16_MIN (-32767-1) 30 | #define INT32_MIN (-2147483647L-1) 31 | #define INT8_MAX (127) 32 | #define INT16_MAX (32767) 33 | #define INT32_MAX (2147483647L) 34 | #define UINT8_MAX (255) 35 | #define UINT16_MAX (65535) 36 | #define UINT32_MAX (4294967295UL) 37 | 38 | #endif 39 | 40 | #endif /* #ifndef DOS16_INTTYPES_H */ 41 | -------------------------------------------------------------------------------- /lib/log.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "lib/dos.h" 19 | #include "lib/flag.h" 20 | #include "lib/log.h" 21 | 22 | static FILE *log = stdout; 23 | static char progname[9] = "", distinguisher[10] = ""; 24 | 25 | static void SetLogName(void) 26 | { 27 | char *p; 28 | 29 | p = strrchr(cmdline_argv[0], '\\'); 30 | if (p != NULL) 31 | { 32 | ++p; 33 | } 34 | else 35 | { 36 | p = cmdline_argv[0]; 37 | } 38 | strncpy(progname, p, sizeof(progname)); 39 | progname[sizeof(progname) - 1] = '\0'; 40 | 41 | // Cut off file extension 42 | p = strchr(progname, '.'); 43 | if (p != NULL) 44 | { 45 | *p = '\0'; 46 | } 47 | 48 | for (p = progname; *p != '\0'; ++p) 49 | { 50 | *p = tolower(*p); 51 | } 52 | } 53 | 54 | void SetLogDistinguisher(const char *name) 55 | { 56 | strncpy(distinguisher, name, sizeof(distinguisher)); 57 | distinguisher[sizeof(distinguisher) - 1] = '\0'; 58 | } 59 | 60 | static void LogVarargs(const char *fmt, va_list args) 61 | { 62 | if (strlen(progname) == 0) 63 | { 64 | SetLogName(); 65 | } 66 | 67 | fprintf(log, "%s", progname); 68 | if (strlen(distinguisher) > 0) 69 | { 70 | fprintf(log, "[%s]", distinguisher); 71 | } 72 | fprintf(log, ": "); 73 | 74 | vfprintf(log, fmt, args); 75 | 76 | fprintf(log, "\n"); 77 | } 78 | 79 | void LogMessage(const char *fmt, ...) 80 | { 81 | va_list args; 82 | 83 | va_start(args, fmt); 84 | LogVarargs(fmt, args); 85 | va_end(args); 86 | } 87 | 88 | // Aborts the program with an abnormal program termination. 89 | void Error(const char *fmt, ...) 90 | { 91 | va_list args; 92 | 93 | va_start(args, fmt); 94 | LogVarargs(fmt, args); 95 | va_end(args); 96 | 97 | exit(1); 98 | } 99 | 100 | // Aborts the program with an error due to wrong command line arguments. 101 | void ErrorPrintUsage(const char *fmt, ...) 102 | { 103 | va_list args; 104 | 105 | va_start(args, fmt); 106 | LogVarargs(fmt, args); 107 | va_end(args); 108 | 109 | PrintProgramUsage(stderr); 110 | 111 | exit(1); 112 | } 113 | 114 | static void DosIdle(void) 115 | { 116 | static int dos_major_version = 0; 117 | union REGS inregs, outregs; 118 | 119 | // Invoke DOS idle interrupt: 120 | int86(0x28, &inregs, &outregs); 121 | 122 | if (dos_major_version == 0) 123 | { 124 | inregs.h.ah = 0x30; // GET DOS VERSION 125 | inregs.h.al = 0; 126 | outregs.h.al = 0; 127 | int86(0x21, &inregs, &outregs); 128 | dos_major_version = outregs.h.al; 129 | } 130 | 131 | // Release current virtual machine timeslice. 132 | // Note that this is also supported under older DOS & Windows versions, 133 | // but we only call it on Win9x ("DOS 7") because, 134 | // " When called very often without intermediate screen output under MS 135 | // Windows 3.x, the VM will go into an idle-state and will not receive 136 | // the next slice before 8 seconds have elapsed. " 137 | if (dos_major_version >= 7) 138 | { 139 | inregs.x.ax = 0x1680; 140 | int86(0x2f, &inregs, &outregs); 141 | } 142 | } 143 | 144 | void CheckAbort(const char *operation) 145 | { 146 | while (_bios_keybrd(_KEYBRD_READY)) 147 | { 148 | if ((_bios_keybrd(_KEYBRD_READ) & 0xff) == 27) 149 | { 150 | Error("%s aborted.", operation); 151 | } 152 | } 153 | 154 | DosIdle(); 155 | } 156 | 157 | -------------------------------------------------------------------------------- /lib/log.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | void SetLogDistinguisher(const char *name); 12 | void LogMessage(const char *fmt, ...); 13 | void Error(const char *error, ...); 14 | void ErrorPrintUsage(const char *fmt, ...); 15 | void CheckAbort(const char *operation); 16 | 17 | -------------------------------------------------------------------------------- /lib/vsetargs.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "lib/bakedin.h" 16 | 17 | static struct baked_in_config config; 18 | 19 | // Scan the given file to find the magic string that is included at the 20 | // start of struct baked_in_config. Returns zero if not found. 21 | long FindConfigPosition(FILE *fstream) 22 | { 23 | char magic[20]; 24 | char buf[64]; 25 | int magic_len; 26 | int i; 27 | 28 | strcpy(magic, BAKED_IN_MAGIC1); 29 | strcat(magic, BAKED_IN_MAGIC2); 30 | magic_len = strlen(magic); 31 | 32 | rewind(fstream); 33 | memset(buf, 0, sizeof(buf)); 34 | 35 | for (;;) 36 | { 37 | memmove(buf, buf + magic_len, sizeof(buf) - magic_len); 38 | if (fread(&buf[sizeof(buf) - magic_len], 1, magic_len, 39 | fstream) != magic_len) 40 | { 41 | return 0; 42 | } 43 | 44 | for (i = 0; i < magic_len; i++) 45 | { 46 | if (!strncmp(&buf[i], magic, magic_len)) 47 | { 48 | return ftell(fstream) - sizeof(buf) + i; 49 | } 50 | } 51 | } 52 | return 0; 53 | } 54 | 55 | void ReadConfig(FILE *fstream, long pos) 56 | { 57 | if (fseek(fstream, pos, SEEK_SET) != 0) 58 | { 59 | perror("fseek"); 60 | exit(1); 61 | } 62 | if (fread(&config, sizeof(struct baked_in_config), 1, fstream) != 1) 63 | { 64 | perror("fread"); 65 | exit(1); 66 | } 67 | } 68 | 69 | void WriteConfig(FILE *fstream, long pos) 70 | { 71 | if (fseek(fstream, pos, SEEK_SET) != 0) 72 | { 73 | perror("fseek"); 74 | exit(1); 75 | } 76 | if (fwrite(&config, sizeof(struct baked_in_config), 1, fstream) != 1) 77 | { 78 | perror("fwrite"); 79 | exit(1); 80 | } 81 | } 82 | 83 | void PrintConfig(char *filename) 84 | { 85 | char *c; 86 | 87 | if (!HAVE_BAKED_IN_CONFIG(config)) 88 | { 89 | printf("%s has no current baked-in arguments.\n", filename); 90 | return; 91 | } 92 | 93 | printf("Current baked-in arguments for %s:\n", filename); 94 | printf(" "); 95 | for (c = config.config; *c != '\0'; c += strlen(c) + 1) 96 | { 97 | printf("%s ", c); 98 | } 99 | printf("\n "); 100 | for (c = config.config; *c != '\0' || *(c + 1) != '\0'; ++c) 101 | { 102 | putchar(*c == '\0' ? ' ' : 0xdf); 103 | } 104 | printf("\n"); 105 | } 106 | 107 | void SetConfig(char **args, int nargs) 108 | { 109 | char *w; 110 | size_t total_len = 1; 111 | int i; 112 | 113 | // As a special case, a single argument of "-" clears all args. 114 | if (nargs == 1 && !strcmp(args[0], "-")) 115 | { 116 | nargs = 0; 117 | } 118 | 119 | for (i = 0; i < nargs; i++) 120 | { 121 | total_len += strlen(args[i]) + 1; 122 | } 123 | if (total_len > BAKED_IN_MAX_LEN) 124 | { 125 | fprintf(stderr, "Arguments too long to store in executable: %d > %d\n", 126 | total_len, BAKED_IN_MAX_LEN); 127 | exit(1); 128 | } 129 | 130 | w = config.config; 131 | for (i = 0; i < nargs; i++) 132 | { 133 | strcpy(w, args[i]); 134 | w += strlen(args[i]) + 1; 135 | } 136 | *w = '\0'; 137 | } 138 | 139 | void PrintHelpText(char *cmdname) 140 | { 141 | printf( 142 | "Set baked-in command line arguments for Vanilla Utilities binary.\n" 143 | "\n" 144 | "Usage:\n" 145 | " %s filename.exe - Show current baked-in arguments.\n" 146 | " %s filename.exe - Set baked-in arguments.\n" 147 | " %s filename.exe - - Clear baked-in arguments.\n" 148 | "\n" 149 | "Examples:\n" 150 | " %s vcommit.exe duke3d.exe :: Always run duke3d.exe\n" 151 | " %s vrottcom.exe rott.exe :: Always run rott.exe\n" 152 | " %s sersetup.exe doom2.exe :: Always run doom2.exe\n" 153 | "\n" 154 | " :: Make a copy of ipxsetup that always starts a 3-player game:\n" 155 | " COPY ipxsetup.exe 3player.exe\n" 156 | " %s 3player.exe -nodes 3\n" 157 | "\n", 158 | cmdname, cmdname, cmdname, cmdname, cmdname, cmdname, cmdname 159 | ); 160 | } 161 | 162 | enum command { 163 | CMD_PRINT, 164 | CMD_SET, 165 | }; 166 | 167 | int main(int argc, char *argv[]) 168 | { 169 | FILE *fstream; 170 | enum command cmd; 171 | long config_pos; 172 | 173 | switch (argc) 174 | { 175 | case 1: 176 | PrintHelpText(argv[0]); 177 | exit(0); 178 | break; 179 | 180 | case 2: 181 | cmd = CMD_PRINT; 182 | break; 183 | 184 | default: 185 | cmd = CMD_SET; 186 | break; 187 | } 188 | 189 | fstream = fopen(argv[1], cmd == CMD_PRINT ? "rb" : "r+b"); 190 | if (fstream == NULL) 191 | { 192 | perror("fopen"); 193 | exit(1); 194 | } 195 | 196 | config_pos = FindConfigPosition(fstream); 197 | if (config_pos == 0) 198 | { 199 | fprintf(stderr, "Not a vanilla-utilities binary? Failed to find " 200 | "magic string.\n"); 201 | exit(1); 202 | } 203 | 204 | ReadConfig(fstream, config_pos); 205 | 206 | switch (cmd) 207 | { 208 | case CMD_PRINT: 209 | PrintConfig(argv[1]); 210 | break; 211 | 212 | case CMD_SET: 213 | SetConfig(argv + 2, argc - 2); 214 | WriteConfig(fstream, config_pos); 215 | printf("Baked-in arguments updated for %s.\n", argv[1]); 216 | break; 217 | } 218 | 219 | fclose(fstream); 220 | 221 | return 0; 222 | } 223 | 224 | -------------------------------------------------------------------------------- /makefile.tc: -------------------------------------------------------------------------------- 1 | 2 | # makefile for Borland Turbo C make 3 | # To invoke: make -f makefile.tc 4 | 5 | CFLAGS = -O2 -d -w -Dinline= 6 | 7 | REPLAY_OBJS = ctrl\replay.obj ctrl\control.obj bld\common.lib 8 | ANALOGJS_OBJS = ctrl\analogjs.obj ctrl\control.obj ctrl\joystick.obj \ 9 | bld\common.lib 10 | IPXSETUP_OBJS = net\ipxsetup.obj net\doomnet.obj net\ipxnet.obj \ 11 | net\llcall.obj net\pktstats.obj bld\common.lib 12 | SERSETUP_OBJS = net\sersetup.obj net\doomnet.obj net\serport.obj \ 13 | net\serarb.obj bld\common.lib net\pktstats.obj 14 | SIRSETUP_OBJS = net\sirsetup.obj net\doomnet.obj net\serport.obj \ 15 | net\pktaggr.obj bld\common.lib net\pktstats.obj 16 | PARSETUP_OBJS = net\parsetup.obj net\doomnet.obj net\serarb.obj \ 17 | bld\par.lib bld\common.lib 18 | PASSTHRU_OBJS = net\passthru.obj net\doomnet.obj bld\common.lib 19 | SOLO_NET_OBJS = net\solo-net.obj net\doomnet.obj bld\common.lib 20 | METANET_OBJS = net\metanet.obj net\doomnet.obj bld\common.lib \ 21 | net\pktaggr.obj net\pktstats.obj 22 | STATDUMP_OBJS = stat\statdump.obj ctrl\control.obj stat\statprnt.obj \ 23 | stat\stats.obj bld\common.lib 24 | VROTTCOM_OBJS = adapters\vrottcom.obj adapters\fragment.obj net\doomnet.obj \ 25 | adapters\nodemap.obj bld\common.lib 26 | VCOMMIT_OBJS = adapters\vcommit.obj adapters\fragment.obj net\doomnet.obj \ 27 | adapters\nodemap.obj bld\common.lib 28 | VSETARGS_OBJS = lib\vsetargs.obj 29 | UDPSETUP_OBJS = net\ipxsetup.obj net\doomnet.obj net\pktstats.obj \ 30 | bld\common.lib bld\udpipx.lib 31 | FAKEDOOM_OBJS = test\fakedoom.obj net\doomnet.obj ctrl\control.obj \ 32 | stat\stats.obj bld\common.lib 33 | 34 | EXES = bld\analogjs.exe bld\replay.exe bld\statdump.exe bld\metanet.exe \ 35 | bld\ipxsetup.exe bld\sersetup.exe bld\parsetup.exe bld\solo-net.exe \ 36 | bld\vcommit.exe bld\vrottcom.exe bld\vsetargs.exe bld\sirsetup.exe \ 37 | bld\udpsetup.exe 38 | 39 | all: exes tests 40 | exes: $(EXES) 41 | tests: test\fakedoom.exe 42 | 43 | bld\common.lib: lib\flag.obj lib\log.obj lib\dos.obj lib\ints.obj 44 | del $@ 45 | tlib $@ +lib\flag.obj +lib\log.obj +lib\dos.obj +lib\ints.obj 46 | bld\udpipx.lib: net\udpipx.obj net\llcall.obj net\dossock.obj \ 47 | net\dbserver.obj 48 | del $@ 49 | tlib $@ +net\udpipx.obj +net\llcall.obj +net\dossock.obj 50 | tlib $@ +net\dbserver.obj 51 | bld\par.lib: net\parport.obj net\plio.obj net\pktstats.obj 52 | del $@ 53 | tlib $@ +net\parport.obj +net\plio.obj +net\pktstats.obj 54 | bld\replay.exe: $(REPLAY_OBJS) 55 | tcc -e$* -oreplay $(REPLAY_OBJS) 56 | bld\analogjs.exe: $(ANALOGJS_OBJS) 57 | tcc -e$* -oanalogjs $(ANALOGJS_OBJS) 58 | bld\ipxsetup.exe: $(IPXSETUP_OBJS) 59 | tcc -e$* -oipxsetup $(IPXSETUP_OBJS) 60 | bld\sersetup.exe: $(SERSETUP_OBJS) 61 | tcc -e$* -osersetup $(SERSETUP_OBJS) 62 | bld\sirsetup.exe: $(SIRSETUP_OBJS) 63 | tcc -e$* -osirsetup $(SIRSETUP_OBJS) 64 | bld\parsetup.exe: $(PARSETUP_OBJS) 65 | tcc -e$* -oparsetup $(PARSETUP_OBJS) 66 | bld\udpsetup.exe: $(UDPSETUP_OBJS) 67 | tcc -e$* -oudpsetup $(UDPSETUP_OBJS) 68 | bld\metanet.exe: $(METANET_OBJS) 69 | tcc -e$* -ometanet $(METANET_OBJS) 70 | bld\passthru.exe: $(PASSTHRU_OBJS) 71 | tcc -e$* -opassthru $(PASSTHRU_OBJS) 72 | bld\statdump.exe: $(STATDUMP_OBJS) 73 | tcc -e$* -ostatdump $(STATDUMP_OBJS) 74 | bld\solo-net.exe: $(SOLO_NET_OBJS) 75 | tcc -e$* -osolo-net $(SOLO_NET_OBJS) 76 | bld\vcommit.exe: $(VCOMMIT_OBJS) 77 | tcc -e$* -ovcommit $(VCOMMIT_OBJS) 78 | bld\vrottcom.exe: $(VROTTCOM_OBJS) 79 | tcc -e$* -ovrottcom $(VROTTCOM_OBJS) 80 | bld\vsetargs.exe: $(VSETARGS_OBJS) 81 | tcc -e$* -ovsetargs $(VSETARGS_OBJS) 82 | test\fakedoom.exe: $(FAKEDOOM_OBJS) 83 | tcc -e$* -ofakedoom $(FAKEDOOM_OBJS) 84 | 85 | .c.obj: 86 | tcc $(CFLAGS) -c -o$@ $< 87 | .asm.obj: 88 | tasm /ml $< $@ 89 | 90 | clean: 91 | del bld\*.exe 92 | del bld\*.lib 93 | del test\fakedoom.exe 94 | del adapters\*.obj 95 | del ctrl\*.obj 96 | del lib\*.obj 97 | del net\*.obj 98 | del stat\*.obj 99 | del test\*.obj 100 | -------------------------------------------------------------------------------- /makefile.wat: -------------------------------------------------------------------------------- 1 | 2 | # makefile for OpenWatcom wmake 3 | # To invoke: wmake -f makefile.wat 4 | 5 | SOURCE_DIRS = ctrl;lib;net;stat;adapters;test 6 | CFLAGS = -I. -q -onx -w3 7 | LDFLAGS = -q 8 | 9 | REPLAY_OBJS = bld\replay.o bld\control.o bld\common.lib 10 | ANALOGJS_OBJS = bld\analogjs.o bld\control.o bld\joystick.o bld\common.lib 11 | IPXSETUP_OBJS = bld\ipxsetup.o bld\doomnet.o bld\ipxnet.o bld\llcall.o & 12 | bld\pktstats.o bld\common.lib 13 | SERSETUP_OBJS = bld\sersetup.o bld\doomnet.o bld\serport.o bld\serarb.o & 14 | bld\common.lib bld\pktstats.o 15 | SIRSETUP_OBJS = bld\sirsetup.o bld\doomnet.o bld\serport.o bld\pktaggr.o & 16 | bld\common.lib bld\pktstats.o 17 | PARSETUP_OBJS = bld\parsetup.o bld\doomnet.o bld\parport.o bld\plio.o & 18 | bld\serarb.o bld\pktstats.o bld\common.lib 19 | UDPSETUP_OBJS = bld\ipxsetup.o bld\doomnet.o bld\pktstats.o & 20 | bld\udpipx.lib bld\common.lib 21 | PASSTHRU_OBJS = bld\passthru.o bld\doomnet.o bld\common.lib 22 | SOLO_NET_OBJS = bld\solo-net.o bld\doomnet.o bld\common.lib 23 | METANET_OBJS = bld\metanet.o bld\doomnet.o bld\common.lib bld\pktaggr.o & 24 | bld\pktstats.o 25 | STATDUMP_OBJS = bld\statdump.o bld\control.o bld\statprnt.o bld\stats.o & 26 | bld\common.lib 27 | VCOMMIT_OBJS = bld\vcommit.o bld\fragment.o bld\doomnet.o & 28 | bld\nodemap.o bld\common.lib 29 | VROTTCOM_OBJS = bld\vrottcom.o bld\fragment.o bld\doomnet.o & 30 | bld\nodemap.o bld\common.lib 31 | VSETARGS_OBJS = bld\vsetargs.o 32 | FAKEDOOM_OBJS = bld\fakedoom.o bld\doomnet.o bld\control.o & 33 | bld\stats.o bld\common.lib 34 | 35 | EXES = bld\analogjs.exe bld\replay.exe bld\statdump.exe bld\metanet.exe & 36 | bld\ipxsetup.exe bld\sersetup.exe bld\parsetup.exe bld\solo-net.exe & 37 | bld\vcommit.exe bld\vrottcom.exe bld\vsetargs.exe bld\sirsetup.exe & 38 | bld\udpsetup.exe bld\ws2patch.exe 39 | 40 | TESTS = test\fakedoom.exe 41 | 42 | all: $(EXES) $(TESTS) 43 | 44 | bld\common.lib: bld\flag.o bld\log.o bld\dos.o bld\ints.o 45 | wlib -q -n $@ +bld\flag.o +bld\log.o +bld\dos.o +bld\ints.o 46 | bld\udpipx.lib: bld\udpipx.o bld\llcall.o bld\dossock.o bld\dbserver.o 47 | wlib -q -n $@ +bld\udpipx.o +bld\llcall.o +bld\dossock.o & 48 | +bld\dbserver.o 49 | bld\replay.exe: $(REPLAY_OBJS) 50 | wcl -q -fe=$@ $(REPLAY_OBJS) 51 | bld\analogjs.exe: $(ANALOGJS_OBJS) 52 | wcl -q -fe=$@ $(ANALOGJS_OBJS) 53 | bld\ipxsetup.exe: $(IPXSETUP_OBJS) 54 | wcl -q -fe=$@ $(IPXSETUP_OBJS) 55 | bld\sersetup.exe: $(SERSETUP_OBJS) 56 | wcl -q -fe=$@ $(SERSETUP_OBJS) 57 | bld\sirsetup.exe: $(SIRSETUP_OBJS) 58 | wcl -q -fe=$@ $(SIRSETUP_OBJS) 59 | bld\parsetup.exe: $(PARSETUP_OBJS) 60 | wcl -q -fe=$@ $(PARSETUP_OBJS) 61 | bld\udpsetup.exe: $(UDPSETUP_OBJS) 62 | wcl -q -fe=$@ $(UDPSETUP_OBJS) 63 | bld\metanet.exe: $(METANET_OBJS) 64 | wcl -q -fe=$@ $(METANET_OBJS) 65 | bld\passthru.exe: $(PASSTHRU_OBJS) 66 | wcl -q -fe=$@ $(PASSTHRU_OBJS) 67 | bld\statdump.exe: $(STATDUMP_OBJS) 68 | wcl -q -fe=$@ $(STATDUMP_OBJS) 69 | bld\solo-net.exe: $(SOLO_NET_OBJS) 70 | wcl -q -fe=$@ $(SOLO_NET_OBJS) 71 | bld\vcommit.exe: $(VCOMMIT_OBJS) 72 | wcl -q -fe=$@ $(VCOMMIT_OBJS) 73 | bld\vrottcom.exe: $(VROTTCOM_OBJS) 74 | wcl -q -fe=$@ $(VROTTCOM_OBJS) 75 | bld\vsetargs.exe: $(VSETARGS_OBJS) 76 | wcl -q -fe=$@ $(VSETARGS_OBJS) 77 | test\fakedoom.exe: $(FAKEDOOM_OBJS) 78 | wcl -q -fe=$@ $(FAKEDOOM_OBJS) 79 | 80 | # TODO: Not yet included in EXES until we have a working UDP/IP version 81 | # of IPXSETUP. 82 | bld\ws2patch.exe: net\ws2patch.c 83 | wcl386 $(CFLAGS) -fe=$@ $< -l=dos32a 84 | 85 | .EXTENSIONS: 86 | .EXTENSIONS: .exe .o .asm .c 87 | 88 | .c: $(SOURCE_DIRS) 89 | .asm: $(SOURCE_DIRS) 90 | 91 | .c.o: 92 | wcc $(CFLAGS) -fo$@ $< 93 | .asm.o: 94 | wasm -q -fo=$@ $< 95 | 96 | clean: 97 | del bld\*.o 98 | del bld\*.exe 99 | del bld\*.lib 100 | del test\fakedoom.exe 101 | -------------------------------------------------------------------------------- /mkrelease.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -rf dist 4 | mkdir dist 5 | 6 | cp -R bld/*.EXE modemcfg/ vutils.ico dist/ 7 | 8 | cp COPYING.GPL1 dist/COPYING1.txt 9 | cp COPYING.md dist/COPYING.txt 10 | cp METANET-HOWTO.md dist/METANET.txt 11 | cp UDPSETUP-HOWTO.md dist/UDPSETUP.txt 12 | unix2dos dist/COPYING1.txt dist/COPYING.txt dist/METANET.txt \ 13 | dist/UDPSETUP.txt 14 | 15 | cd dist 16 | zip -X -r ../vanilla-utilities-${1:-unknown-version}.zip * 17 | 18 | -------------------------------------------------------------------------------- /modemcfg/AT&T Data Port 14.4 FaxM..cfg: -------------------------------------------------------------------------------- 1 | AT&T Data Port 14.4 FaxM. 2 | AT &F &C1 &D2 S41=3 \N0 %C0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/ATI 9600 ETC-E Internal v.32.cfg: -------------------------------------------------------------------------------- 1 | ATI 9600 ETC-E Internal v.32 2 | AT &F &C1 &D1 &K0 &Q6 S36=3 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Acubit 14.4 fax v32.cfg: -------------------------------------------------------------------------------- 1 | Acubit 14.4 fax v32 2 | AT &F &C1 &D2 %C0 \N1 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Boca 14.4 fax.cfg: -------------------------------------------------------------------------------- 1 | Boca 14.4 fax 2 | AT &F &C1 &D2 S46=0 N0 &D2 &K0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Boca 14.4 v32 fax.cfg: -------------------------------------------------------------------------------- 1 | Boca 14.4 v32 fax 2 | AT &F &C1 &D2 \N0 \G0 &K0 %C0 N0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Cardinal 14.4 v32.cfg: -------------------------------------------------------------------------------- 1 | Cardinal 14.4 v32 2 | AT &F &C1 &D2 N0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Cardinal 9600fax.cfg: -------------------------------------------------------------------------------- 1 | Cardinal 9600fax 2 | AT &F &C1 &D2 &Q6 &K0 %C0 \N0 N0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Creative Labs Modem Blaster.cfg: -------------------------------------------------------------------------------- 1 | Creative Labs Modem Blaster 2 | AT &F &K0 %E0 \G0 \N0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Digicom Scout Plus 14.4 Fax.cfg: -------------------------------------------------------------------------------- 1 | Digicom Scout Plus 14.4 Fax 2 | AT &F &C1 &D2 *M0 *E0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/GENERIC 14.4 MODEM.cfg: -------------------------------------------------------------------------------- 1 | GENERIC 14.4 MODEM 2 | AT &F &C1 &D2 &Q5 &K0 S46=0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/GVC 14.4 MNP2-5 v42.cfg: -------------------------------------------------------------------------------- 1 | GVC 14.4 MNP2-5 v42 2 | AT &F &C1 &D2 %C0 \N0 B8 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/GVC 14.4 fax.cfg: -------------------------------------------------------------------------------- 1 | GVC 14.4 fax 2 | AT &F &C1 &D2 S46=0 N0 &K0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Gateway Telepath 14.4 fax.cfg: -------------------------------------------------------------------------------- 1 | Gateway Telepath 14.4 fax 2 | AT &F &C1 &D2 B0 N0 \N0 %C0 &K0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Gateway Telepath 550.cfg: -------------------------------------------------------------------------------- 1 | Gateway Telepath 550 2 | AT &F &C1 &D2 S27=32 S15=16 S13=64 &B0 &H2 &I1 &K0 &M0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Generic Hayes Compatible.cfg: -------------------------------------------------------------------------------- 1 | Generic Hayes Compatible 2 | AT Z 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Hayes 28.8k V.FAST Modem.cfg: -------------------------------------------------------------------------------- 1 | Hayes 28.8k V.FAST Modem 2 | AT &Q6 &K S37=9 N %C0 \N0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Hayes Optima 14.4.cfg: -------------------------------------------------------------------------------- 1 | Hayes Optima 14.4 2 | AT &F &C1 &D2 S46=0 N0 &K0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Hayes Optima 28.8.cfg: -------------------------------------------------------------------------------- 1 | Hayes Optima 28.8 2 | AT &F &C1 &D2 &K0 &M0 &Q5 S46=136 3 | AT Z H0 4 | 38400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Hayes Ultra 14.4.cfg: -------------------------------------------------------------------------------- 1 | Hayes Ultra 14.4 2 | AT &F &C1 &D2 &Q5 &K0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Identity Internal.cfg: -------------------------------------------------------------------------------- 1 | Identity Internal 2 | AT &F &C1 &D2 B8 %C0 \Q0 \N1 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Infotel 14.4.cfg: -------------------------------------------------------------------------------- 1 | Infotel 14.4 2 | AT &F &C1 &D2 &Q6 %C0 N0 &K0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Intel 14.4 ext.cfg: -------------------------------------------------------------------------------- 1 | Intel 14.4 ext 2 | AT &F &C1 &D2 \N0 /Q0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Intel 14.4 fax.cfg: -------------------------------------------------------------------------------- 1 | Intel 14.4 fax 2 | AT &F N0 &D2 &C1 &Q6 &K0 S0=1 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Intel 14.4.cfg: -------------------------------------------------------------------------------- 1 | Intel 14.4 2 | AT &F &C1 &D2 \N0 \Q0 \J1 \V0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Intel 14.4i fax.cfg: -------------------------------------------------------------------------------- 1 | Intel 14.4i fax 2 | AT &F &C1 &D2 \N0 \Q0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Intel 14.4i.cfg: -------------------------------------------------------------------------------- 1 | Intel 14.4i 2 | AT &F &C1 &D2 B8 %C0 "H0 \N0 \Q0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Linelink 144e.cfg: -------------------------------------------------------------------------------- 1 | Linelink 144e 2 | AT &F &D1 &K0 &Q6 S36=3 S46=136 %C0 3 | AT Z H0 4 | 19200 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/MegaHertz C596FM.cfg: -------------------------------------------------------------------------------- 1 | MegaHertz C596FM 2 | AT &F &C1 &D2 \N1 \J0 %C0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Netcomm M7F.cfg: -------------------------------------------------------------------------------- 1 | Netcomm M7F 2 | AT &E &K0 B0 \V0 X4 &D2 \N1 \Q0 #J0 #Q9 %C0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/No Name (Rockwell) 14.4.cfg: -------------------------------------------------------------------------------- 1 | No Name (Rockwell) 14.4 2 | AT &F &C1 &D2 N0 S46=0 &K0 %C0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Nokia ECM 4896M Trellis V.32.cfg: -------------------------------------------------------------------------------- 1 | Nokia ECM 4896M Trellis V.32 2 | AT Z %C0 /N0 3 | AT Z H0 4 | 14400 5 | -------------------------------------------------------------------------------- /modemcfg/Practical Perip 14.4FX v32.cfg: -------------------------------------------------------------------------------- 1 | Practical Perip 14.4FX v32 2 | AT &F &C1 &D2 &K0 &Q5 S46=0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Practical Peripherals 14.4.cfg: -------------------------------------------------------------------------------- 1 | Practical Peripherals 14.4 2 | AT Z S46=0 &Q0 &D2 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Practical Peripherals PM14400FXMT v.32bis.cfg: -------------------------------------------------------------------------------- 1 | Practical Peripherals PM14400FXMT v.32bis 2 | AT Z S46=0 &Q0 &D2 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Practical Peripherals PM14400FXSA v.32bis.cfg: -------------------------------------------------------------------------------- 1 | Practical Peripherals PM14400FXSA v.32bis 2 | AT Z S46=0 &Q0 &D2 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/README.md: -------------------------------------------------------------------------------- 1 | Modem configuration files for `SERSETUP`. Pick one appropriate for your 2 | modem and rename to `modem.cfg`. 3 | 4 | -------------------------------------------------------------------------------- /modemcfg/Redicard v.32 bis 14.4.cfg: -------------------------------------------------------------------------------- 1 | Redicard v.32 bis 14.4 2 | AT &F &C1 &D2 %C \N 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Smart One 1442F.cfg: -------------------------------------------------------------------------------- 1 | Smart One 1442F 2 | AT &F &C1 &D2 %C0 &K0 S95=44 S46=0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/SupraFax 14.4 v32-.cfg: -------------------------------------------------------------------------------- 1 | SupraFax 14.4 v32- 2 | AT &F &C1 &D2 S46=136 M1 Q0 D2 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/SupraFax 14.4 v32.cfg: -------------------------------------------------------------------------------- 1 | SupraFax 14.4 v32 2 | AT &F &C1 &D2 S46=136 %C0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/SupraFax 14.4.cfg: -------------------------------------------------------------------------------- 1 | SupraFax 14.4 2 | AT &F &C1 &D2 &Q6 &K %C0 \N1 S37=6 S0=1 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Telebit Worldblazer 14.4.cfg: -------------------------------------------------------------------------------- 1 | Telebit Worldblazer 14.4 2 | AT &F &C1 &D2 S51=4 S180=0 S183=8 S190=0 L1 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Telepath 550.cfg: -------------------------------------------------------------------------------- 1 | Telepath 550 2 | AT &F &C1 &D2 &M0 &K0 S0=1 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Twincomm DFi 14.4.cfg: -------------------------------------------------------------------------------- 1 | Twincomm DFi 14.4 2 | AT&F &Q0 %C0 S37=9 &D2 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/USR Sportster 14.4 v32 fax.cfg: -------------------------------------------------------------------------------- 1 | USR Sportster 14.4 v32 fax 2 | AT &F &A0 &B1 &C1 &D2 &M0 &H0 &I0 &K0 S27=48 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/USRobotics 14.4 fax.cfg: -------------------------------------------------------------------------------- 1 | USRobotics 14.4 fax 2 | AT &F &A0 &B1 &C1 &D2 &M0 &H0 &I0 &K0 S27=48 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/USRobotics 14.4 v32 fax.cfg: -------------------------------------------------------------------------------- 1 | USRobotics 14.4 v32 fax 2 | AT &F &A0 &B1 &C1 &D2 &M0 &H0 &I0 &K0 S27=48 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/USRobotics 16.8 HST.cfg: -------------------------------------------------------------------------------- 1 | USRobotics 16.8 HST 2 | AT &F &K0 &H0 &I0 &M0 &D1 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/USRobotics Sportster 14.4.cfg: -------------------------------------------------------------------------------- 1 | USRobotics Sportster 14.4 2 | AT &F &A0 &B1 &C1 &D2 &M0 &H0 &I0 &K0 S27=48 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/USRobotics Sportster.cfg: -------------------------------------------------------------------------------- 1 | USRobotics Sportster 2 | AT &F &K0 &H0 &I0 &M0 &D1 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/USRobotics V.Everything.cfg: -------------------------------------------------------------------------------- 1 | USRobotics V.Everything 2 | AT &F &K0 &H0 &I0 &M0 &D1 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Viva 14.4.cfg: -------------------------------------------------------------------------------- 1 | Viva 14.4 2 | AT &F &C1 &D2 \N0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Viva 14.4i fax.cfg: -------------------------------------------------------------------------------- 1 | Viva 14.4i fax 2 | AT &F &C1 &D2 \N0 \Q0 \J1 \VI 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/VivaFax #3.cfg: -------------------------------------------------------------------------------- 1 | VivaFax #3 2 | AT &F &C1 &D2 N0 \N1 %C0 &K0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/VivaFax 14.4.cfg: -------------------------------------------------------------------------------- 1 | VivaFax 14.4 2 | AT &F &C1 &D2 \Q0 &M0 %C0 B8 \N1 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/ZOOM 14.4 v32 v42 fax.cfg: -------------------------------------------------------------------------------- 1 | ZOOM 14.4 v32 v42 fax 2 | AT &F &C1 &D2 &K0 &Q6 %C0 \G0 \N0 S46=136 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/ZOOM 14.4.cfg: -------------------------------------------------------------------------------- 1 | ZOOM 14.4 2 | AT &F &C1 &D2 &K0 &Q6 %C0 \G0 \N0 S46=136 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/ZOOM VFP 14.4.cfg: -------------------------------------------------------------------------------- 1 | ZOOM VFP 14.4 2 | AT &F &C1 &D2 &K0 &Q6 %C0 \G0 \N0 S46=136 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/ZOOM VFX v32.cfg: -------------------------------------------------------------------------------- 1 | ZOOM VFX v32 2 | AT &F &C1 &D2 &K0 &Q6 %C0 \G0 \N0 S46=136 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/ZOOM VXP 14.4 v42.cfg: -------------------------------------------------------------------------------- 1 | ZOOM VXP 14.4 v42 2 | AT &F &C1 &D2 &K0 &Q6 %C0 \G0 \N0 S46=136 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Zoltrix model 14 14 VE.cfg: -------------------------------------------------------------------------------- 1 | Zoltrix model 14/14 VE 2 | AT S0=Q0 V1 &C1 &D2 W2 &Q0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/Zoomfax VFP v32.cfg: -------------------------------------------------------------------------------- 1 | Zoomfax VFP v32 2 | AT &F &C1 &D2 &K0 &Q6 %C0 \G0 \N0 S46=136 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/ZyXEL U-1496 14.4.cfg: -------------------------------------------------------------------------------- 1 | ZyXEL U-1496 14.4 2 | AT &F &C1 &D2 S46=0 &K0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /modemcfg/ZyXel 14.4.cfg: -------------------------------------------------------------------------------- 1 | ZyXel 14.4 2 | AT &F &C1 &D2 \N0 3 | AT Z H0 4 | 14400 5 | 6 | -------------------------------------------------------------------------------- /net/dbserver.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | // Mini dosbox server implementation 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include "lib/dos.h" 18 | #include "lib/log.h" 19 | #include "net/doomnet.h" 20 | #include "net/dossock.h" 21 | #include "net/ipxnet.h" 22 | #include "net/pktstats.h" 23 | 24 | extern const ipx_addr_t broadcast_addr; 25 | const ipx_addr_t null_addr = {0, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; 26 | 27 | static struct sockaddr_in server_client_addrs[MAXNETNODES]; 28 | static int server_num_clients = 0; 29 | 30 | static packet_t packet; 31 | static SOCKET server_sock = INVALID_SOCKET; 32 | 33 | DECLARE_COUNTER(srv_rx_packets); 34 | DECLARE_COUNTER(srv_rx_errors); 35 | DECLARE_COUNTER(srv_tx_packets); 36 | DECLARE_COUNTER(srv_tx_errors); 37 | 38 | static void RegisterCounters(void) 39 | { 40 | REGISTER_COUNTER(srv_rx_packets); 41 | REGISTER_COUNTER(srv_rx_errors); 42 | REGISTER_COUNTER(srv_tx_packets); 43 | REGISTER_COUNTER(srv_tx_errors); 44 | } 45 | 46 | static ssize_t SendtoOrLog(SOCKET socket, const void far *msg, size_t len, 47 | int flags, const struct sockaddr_in far *to) 48 | { 49 | ssize_t result = sendto(socket, msg, len, flags, to); 50 | 51 | if (result < 0) 52 | { 53 | INCREMENT_COUNTER(srv_tx_errors); 54 | } 55 | else 56 | { 57 | INCREMENT_COUNTER(srv_tx_packets); 58 | } 59 | 60 | return result; 61 | } 62 | 63 | static void SockaddrToIPX(struct sockaddr_in *inaddr, ipx_addr_t *ipxaddr) 64 | { 65 | ipxaddr->Network = 0; 66 | memcpy(&ipxaddr->Node[0], &inaddr->sin_addr, 4); 67 | memcpy(&ipxaddr->Node[4], &inaddr->sin_port, 2); 68 | } 69 | 70 | static void IPXToSockaddr(ipx_addr_t *ipxaddr, struct sockaddr_in *inaddr) 71 | { 72 | inaddr->sin_family = AF_INET; 73 | memcpy(&inaddr->sin_addr, &ipxaddr->Node[0], 4); 74 | memcpy(&inaddr->sin_port, &ipxaddr->Node[4], 2); 75 | } 76 | 77 | static int InClientsList(struct sockaddr_in *addr) 78 | { 79 | int i; 80 | 81 | for (i = 0; i < server_num_clients; i++) 82 | { 83 | if (!memcmp(&server_client_addrs[i], addr, sizeof(struct sockaddr_in))) 84 | { 85 | return 1; 86 | } 87 | } 88 | 89 | return 0; 90 | } 91 | 92 | static void ForwardPacket(struct sockaddr_in *src_addr, int len) 93 | { 94 | struct sockaddr_in dest_addr; 95 | int i; 96 | 97 | if (memcmp(&packet.ipx.Dest, &broadcast_addr, sizeof(ipx_addr_t)) != 0) 98 | { 99 | IPXToSockaddr(&packet.ipx.Dest, &dest_addr); 100 | SendtoOrLog(server_sock, &packet, len, 0, &dest_addr); 101 | return; 102 | } 103 | 104 | for (i = 0; i < server_num_clients; i++) 105 | { 106 | if (memcmp(&server_client_addrs[i], src_addr, 107 | sizeof(struct sockaddr_in)) != 0) 108 | { 109 | SendtoOrLog(server_sock, &packet, len, 0, &server_client_addrs[i]); 110 | } 111 | } 112 | } 113 | 114 | static int IsRegistrationPacket(ipx_header_t *hdr) 115 | { 116 | return !memcmp(&hdr->Dest, &null_addr, sizeof(ipx_addr_t)) 117 | && ntohs(hdr->DestSocket) == 2; 118 | } 119 | 120 | static void NewClient(struct sockaddr_in *addr) 121 | { 122 | ipx_header_t reply; 123 | 124 | if (!InClientsList(addr)) 125 | { 126 | if (server_num_clients >= MAXNETNODES) 127 | { 128 | return; 129 | } 130 | memcpy(&server_client_addrs[server_num_clients], addr, 131 | sizeof(struct sockaddr_in)); 132 | ++server_num_clients; 133 | } 134 | 135 | reply.PacketCheckSum = htons(0xffff); 136 | reply.PacketLength = htons(sizeof(ipx_header_t));; 137 | reply.PacketTransportControl = 0; 138 | reply.PacketType = 0; 139 | 140 | SockaddrToIPX(addr, &reply.Dest); 141 | reply.DestSocket = htons(2); 142 | 143 | memcpy(&reply.Src, &broadcast_addr, sizeof(ipx_addr_t)); 144 | reply.Src.Network = htonl(1); 145 | reply.SrcSocket = htons(2); 146 | 147 | SendtoOrLog(server_sock, &reply, sizeof(ipx_header_t), 0, addr); 148 | } 149 | 150 | static void SendShutdown(void) 151 | { 152 | ipx_header_t msg; 153 | int i, c; 154 | 155 | memset(&msg, 0, sizeof(msg)); 156 | msg.PacketCheckSum = htons(0xffff); 157 | msg.PacketLength = htons(sizeof(msg)); 158 | msg.PacketTransportControl = 0; 159 | msg.PacketType = 0xff; 160 | msg.DestSocket = htons(86); 161 | msg.SrcSocket = htons(86); 162 | 163 | memcpy(&msg.Src, &null_addr, sizeof(ipx_addr_t)); 164 | 165 | for (c = 0; c < server_num_clients; c++) 166 | { 167 | SockaddrToIPX(&server_client_addrs[c], &msg.Dest); 168 | 169 | for (i = 0; i < 3; i++) 170 | { 171 | if (SendtoOrLog(server_sock, &msg, sizeof(ipx_header_t), 0, 172 | &server_client_addrs[c]) < 0) 173 | { 174 | LogMessage("Error sending shutdown packet: errno=%d", 175 | DosSockLastError); 176 | return; 177 | } 178 | } 179 | } 180 | } 181 | 182 | void RunServer(void) 183 | { 184 | struct sockaddr_in addr; 185 | int len; 186 | 187 | if (server_sock == INVALID_SOCKET) 188 | { 189 | return; 190 | } 191 | 192 | for (;;) 193 | { 194 | len = recvfrom(server_sock, &packet, sizeof(packet), 0, &addr); 195 | if (len < 0) 196 | { 197 | // WSAEWOULDBLOCK is expected when there are no more packets to 198 | // process; other errors are not. 199 | if (DosSockLastError != WSAEWOULDBLOCK) 200 | { 201 | INCREMENT_COUNTER(srv_rx_errors); 202 | } 203 | break; 204 | } 205 | INCREMENT_COUNTER(srv_rx_packets); 206 | if (IsRegistrationPacket(&packet.ipx)) 207 | { 208 | NewClient(&addr); 209 | continue; 210 | } 211 | if (InClientsList(&addr)) 212 | { 213 | ForwardPacket(&addr, len); 214 | } 215 | } 216 | } 217 | 218 | void ShutdownServer(void) 219 | { 220 | if (server_sock != INVALID_SOCKET) 221 | { 222 | SendShutdown(); 223 | closesocket(server_sock); 224 | server_sock = INVALID_SOCKET; 225 | } 226 | } 227 | 228 | void StartServer(uint16_t port) 229 | { 230 | unsigned long trueval = 1; 231 | struct sockaddr_in bind_addr = {AF_INET, 0, {INADDR_ANY}}; 232 | 233 | RegisterCounters(); 234 | 235 | server_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 236 | if (server_sock == INVALID_SOCKET) 237 | { 238 | Error("StartServer: failed to create UDP socket: err=%d", 239 | DosSockLastError); 240 | } 241 | 242 | atexit(ShutdownServer); 243 | 244 | if (ioctlsocket(server_sock, FIONBIO, &trueval) < 0) 245 | { 246 | Error("StartServer: setting nonblocking failed, err=%d", 247 | DosSockLastError); 248 | } 249 | 250 | bind_addr.sin_port = htons(port); 251 | if (bind(server_sock, &bind_addr) < 0) 252 | { 253 | Error("bind failed, err=%d", DosSockLastError); 254 | } 255 | 256 | server_num_clients = 0; 257 | } 258 | -------------------------------------------------------------------------------- /net/dbserver.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | 12 | void RunServer(void); 13 | void ShutdownServer(void); 14 | void StartServer(uint16_t port); 15 | 16 | -------------------------------------------------------------------------------- /net/doomnet.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 1993 id Software, Inc. 3 | // Copyright(C) 2019-2023 Simon Howard 4 | // 5 | // You can redistribute and/or modify this program under the terms of the 6 | // GNU General Public License version 2 as published by the Free Software 7 | // Foundation, or any later version. This program is distributed WITHOUT 8 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | // FITNESS FOR A PARTICULAR PURPOSE. 10 | // 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "lib/flag.h" 19 | #include "lib/ints.h" 20 | #include "lib/log.h" 21 | #include "net/doomnet.h" 22 | 23 | static struct interrupt_hook net_interrupt; 24 | static void (*isr_callback)(void); 25 | int doomnet_dup = 1, doomnet_extratics = 0; 26 | 27 | void NetRegisterFlags(void) 28 | { 29 | IntFlag("-dup", &doomnet_dup, "n", 30 | "reduce movement resolution & bandwidth by factor n"); 31 | IntFlag("-extratics", &doomnet_extratics, "n", 32 | "send n extra tics per packet as insurance"); 33 | BoolFlag("-extratic", &doomnet_extratics, NULL); 34 | IntFlag("-vector", &net_interrupt.force_vector, "v", NULL); 35 | } 36 | 37 | static void UnhookDoomVector(void) 38 | { 39 | RestoreInterrupt(&net_interrupt); 40 | } 41 | 42 | static void interrupt far NetISR(void) 43 | { 44 | SWITCH_ISR_STACK; 45 | isr_callback(); 46 | RESTORE_ISR_STACK; 47 | } 48 | 49 | /* 50 | ============= 51 | = 52 | = NetLaunchDoom 53 | = 54 | These fields in doomcom should be filled in before calling: 55 | 56 | short numnodes; // console is always node 0 57 | short ticdup; // 1 = no duplication, 2-5 = dup for slow nets 58 | short extratics; // 1 = send a backup tic in every packet 59 | 60 | short consoleplayer; // 0-3 = player number 61 | short numplayers; // 1-4 62 | short angleoffset; // 1 = left, 0 = center, -1 = right 63 | short drone; // 1 = drone 64 | ============= 65 | */ 66 | 67 | void NetLaunchDoom(doomcom_t far *doomcom, char **args, 68 | void (*callback)(void)) 69 | { 70 | char addrstring[10]; 71 | long flataddr; 72 | 73 | isr_callback = callback; 74 | 75 | if (doomnet_dup != 1) 76 | { 77 | doomcom->ticdup = (short) doomnet_dup; 78 | } 79 | if (doomnet_extratics != 0) 80 | { 81 | doomcom->extratics = (short) doomnet_extratics; 82 | } 83 | 84 | // prepare for DOOM 85 | doomcom->id = DOOMCOM_ID; 86 | 87 | if (!FindAndHookInterrupt(&net_interrupt, NetISR)) 88 | { 89 | Error("Warning: no free interrupt handlers found. You can specify " 90 | "a vector with the -vector 0x parameter."); 91 | } 92 | 93 | doomcom->intnum = net_interrupt.interrupt_num; 94 | 95 | // We unhook the vector anyway after the game exits, but just in case, set 96 | // an atexit handler as well - it will gracefully handle multiple calls. 97 | atexit(UnhookDoomVector); 98 | 99 | // Add -net &doomcom 100 | flataddr = (long) FP_SEG(doomcom) * 16 + FP_OFF(doomcom); 101 | sprintf(addrstring, "%lu", flataddr); 102 | args = DuplicateArgList(args); 103 | args = AppendArgs(args, "-net", addrstring, NULL); 104 | 105 | SquashToResponseFile(args); 106 | spawnv(P_WAIT, args[0], (void *) args); 107 | 108 | UnhookDoomVector(); 109 | free(args); 110 | } 111 | 112 | // NetGetHandle takes the given long value read from the command line 113 | // and returns a doomcom_t pointer, performing appropriate checks. 114 | doomcom_t far *NetGetHandle(long l) 115 | { 116 | doomcom_t far *result = NULL; 117 | unsigned int seg; 118 | 119 | assert(l != 0); 120 | seg = (int) ((l >> 4) & 0xf000L); 121 | result = (void far *) MK_FP(seg, l & 0xffffL); 122 | assert(result->id == DOOMCOM_ID); 123 | 124 | return result; 125 | } 126 | 127 | void NetSendPacket(doomcom_t far *doomcom) 128 | { 129 | union REGS regs; 130 | doomcom->command = CMD_SEND; 131 | int86(doomcom->intnum, ®s, ®s); 132 | } 133 | 134 | int NetGetPacket(doomcom_t far *doomcom) 135 | { 136 | union REGS regs; 137 | doomcom->command = CMD_GET; 138 | int86(doomcom->intnum, ®s, ®s); 139 | return doomcom->remotenode != -1; 140 | } 141 | 142 | 143 | -------------------------------------------------------------------------------- /net/doomnet.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 1993 id Software, Inc. 3 | // Copyright(C) 2019-2023 Simon Howard 4 | // 5 | // You can redistribute and/or modify this program under the terms of the 6 | // GNU General Public License version 2 as published by the Free Software 7 | // Foundation, or any later version. This program is distributed WITHOUT 8 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | // FITNESS FOR A PARTICULAR PURPOSE. 10 | // 11 | 12 | #include "lib/inttypes.h" 13 | 14 | // Original Doom *setup tools only allowed up to 4 players; however, Hexen 15 | // and Strife allowed up to 8 players. Since we have vrottcom/vcommit 16 | // which support even more players, we bump this up to 16. 17 | #define MAXNETNODES 16 18 | #define MAXPLAYERS 16 19 | 20 | #define CMD_SEND 1 21 | #define CMD_GET 2 22 | 23 | #define DOOMCOM_ID 0x12345678l 24 | 25 | typedef struct { 26 | long id; 27 | short intnum; // DOOM executes an int to send commands 28 | 29 | // communication between DOOM and the driver 30 | short command; // CMD_SEND or CMD_GET 31 | short remotenode; // dest for send, set by get (-1 = no packet) 32 | short datalength; // bytes in doomdata to be sent / bytes read 33 | 34 | // info common to all nodes 35 | short numnodes; // console is allways node 0 36 | short ticdup; // 1 = no duplication, 2-5 = dup for slow nets 37 | short extratics; // 1 = send a backup tic in every packet 38 | short deathmatch; // 1 = deathmatch 39 | short savegame; // -1 = new game, 0-5 = load savegame 40 | short episode; // 1-3 41 | short map; // 1-9 42 | short skill; // 1-5 43 | 44 | // info specific to this node 45 | short consoleplayer; // 0-3 = player number 46 | short numplayers; // 1-4 47 | short angleoffset; // 1 = left, 0 = center, -1 = right 48 | short drone; // 1 = drone 49 | 50 | // packet data to be sent 51 | uint8_t data[512]; 52 | } doomcom_t; 53 | 54 | #define BACKUPTICS 12 55 | 56 | #define NCMD_EXIT 0x80000000l 57 | #define NCMD_RETRANSMIT 0x40000000l 58 | #define NCMD_SETUP 0x20000000l 59 | #define NCMD_KILL 0x10000000l // kill game 60 | #define NCMD_CHECKSUM 0x0fffffffl 61 | 62 | // The data sampled per tick (single player) 63 | // and transmitted to other peers (multiplayer). 64 | // Mainly movements/button commands per game tick, 65 | // plus a checksum for internal state consistency. 66 | typedef struct 67 | { 68 | char forwardmove; // *2048 for move 69 | char sidemove; // *2048 for move 70 | short angleturn; // <<16 for angle delta 71 | short consistancy; // checks for net game 72 | unsigned char chatchar; 73 | unsigned char buttons; 74 | } ticcmd_t; 75 | 76 | // 77 | // Network packet data. 78 | // 79 | typedef struct 80 | { 81 | // High bit is retransmit request. 82 | unsigned long checksum; 83 | // Only valid if NCMD_RETRANSMIT. 84 | unsigned char retransmitfrom; 85 | unsigned char starttic; 86 | unsigned char player; 87 | unsigned char numtics; 88 | ticcmd_t cmds[BACKUPTICS]; 89 | } doompacket_t; 90 | 91 | void NetRegisterFlags(void); 92 | void NetLaunchDoom(doomcom_t far *doomcom, char **args, 93 | void (*callback)(void)); 94 | doomcom_t far *NetGetHandle(long l); 95 | void NetSendPacket(doomcom_t far *doomcom); 96 | int NetGetPacket(doomcom_t far *doomcom); 97 | 98 | extern int doomnet_dup, doomnet_extratics; 99 | 100 | -------------------------------------------------------------------------------- /net/dossock.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | // DOS API code for accessing Winsock functions via VxD backdoors. 12 | 13 | #define AF_INET 2 14 | 15 | #define SOCK_STREAM 1 16 | #define SOCK_DGRAM 2 17 | #define SOCK_RAW 3 18 | 19 | #define IPPROTO_IP 0 20 | #define IPPROTO_ICMP 1 21 | #define IPPROTO_TCP 6 22 | #define IPPROTO_UDP 17 23 | 24 | #define INADDR_ANY 0x00000000UL 25 | #define INADDR_LOOPBACK 0x7f000001UL // 127.0.0.1 26 | #define INADDR_BROADCAST 0xffffffffUL 27 | 28 | #define INVALID_SOCKET 0xffffffffUL 29 | 30 | #define WSABASEERR 10000 31 | 32 | #define WSAEINTR (WSABASEERR+4) 33 | #define WSAEBADF (WSABASEERR+9) 34 | #define WSAEACCES (WSABASEERR+13) 35 | #define WSAEFAULT (WSABASEERR+14) 36 | #define WSAEINVAL (WSABASEERR+22) 37 | #define WSAEMFILE (WSABASEERR+24) 38 | #define WSAEWOULDBLOCK (WSABASEERR+35) 39 | #define WSAEINPROGRESS (WSABASEERR+36) 40 | #define WSAEALREADY (WSABASEERR+37) 41 | #define WSAENOTSOCK (WSABASEERR+38) 42 | #define WSAEDESTADDRREQ (WSABASEERR+39) 43 | #define WSAEMSGSIZE (WSABASEERR+40) 44 | #define WSAEPROTOTYPE (WSABASEERR+41) 45 | #define WSAENOPROTOOPT (WSABASEERR+42) 46 | #define WSAEPROTONOSUPPORT (WSABASEERR+43) 47 | #define WSAESOCKTNOSUPPORT (WSABASEERR+44) 48 | #define WSAEOPNOTSUPP (WSABASEERR+45) 49 | #define WSAEPFNOSUPPORT (WSABASEERR+46) 50 | #define WSAEAFNOSUPPORT (WSABASEERR+47) 51 | #define WSAEADDRINUSE (WSABASEERR+48) 52 | #define WSAEADDRNOTAVAIL (WSABASEERR+49) 53 | #define WSAENETDOWN (WSABASEERR+50) 54 | #define WSAENETUNREACH (WSABASEERR+51) 55 | #define WSAENETRESET (WSABASEERR+52) 56 | #define WSAECONNABORTED (WSABASEERR+53) 57 | #define WSAECONNRESET (WSABASEERR+54) 58 | #define WSAENOBUFS (WSABASEERR+55) 59 | #define WSAEISCONN (WSABASEERR+56) 60 | #define WSAENOTCONN (WSABASEERR+57) 61 | #define WSAESHUTDOWN (WSABASEERR+58) 62 | #define WSAETOOMANYREFS (WSABASEERR+59) 63 | #define WSAETIMEDOUT (WSABASEERR+60) 64 | #define WSAECONNREFUSED (WSABASEERR+61) 65 | #define WSAELOOP (WSABASEERR+62) 66 | #define WSAENAMETOOLONG (WSABASEERR+63) 67 | #define WSAEHOSTDOWN (WSABASEERR+64) 68 | #define WSAEHOSTUNREACH (WSABASEERR+65) 69 | #define WSAENOTEMPTY (WSABASEERR+66) 70 | #define WSAEPROCLIM (WSABASEERR+67) 71 | #define WSAEUSERS (WSABASEERR+68) 72 | #define WSAEDQUOT (WSABASEERR+69) 73 | #define WSAESTALE (WSABASEERR+70) 74 | #define WSAEREMOTE (WSABASEERR+71) 75 | 76 | #define IOCPARM_MASK 0x7fUL // parameters must be < 128 bytes 77 | #define IOC_VOID 0x20000000UL // no parameters 78 | #define IOC_OUT 0x40000000UL // copy out parameters 79 | #define IOC_IN 0x80000000UL // copy in parameters 80 | #define IOC_INOUT (IOC_IN|IOC_OUT) 81 | #define _IO(x,y) (IOC_VOID|((x)<<8)|(y)) 82 | #define _IOR(x,y,t) (IOC_OUT|((sizeof(t)&IOCPARM_MASK)<<16)|((x)<<8)|(y)) 83 | #define _IOW(x,y,t) (IOC_IN|((sizeof(t)&IOCPARM_MASK)<<16)|((x)<<8)|(y)) 84 | 85 | // ioctls: 86 | #define FIONREAD _IOR('f', 127, long) // get # bytes to read 87 | #define FIONBIO _IOW('f', 126, long) // set/clear non-blocking i/o 88 | #define SIOCSHIWAT _IOW('s', 0, long) // set high watermark 89 | #define SIOCGHIWAT _IOR('s', 1, long) // get high watermark 90 | #define SIOCSLOWAT _IOW('s', 2, long) // set low watermark 91 | #define SIOCGLOWAT _IOR('s', 3, long) // get low watermark 92 | #define SIOCATMARK _IOR('s', 7, long) // at oob mark? 93 | 94 | #define htonl(x) \ 95 | ((((x) & 0xff000000UL) >> 24) \ 96 | | (((x) & 0x00ff0000UL) >> 8) \ 97 | | (((x) & 0x0000ff00UL) << 8) \ 98 | | (((x) & 0x000000ffUL) << 24)) 99 | 100 | #define htons(x) \ 101 | ((((x) & 0xff00) >> 8) | (((x) & 0x00ff) << 8)) 102 | 103 | #define ntohl(x) htonl(x) 104 | #define ntohs(x) htons(x) 105 | 106 | struct in_addr { 107 | unsigned long s_addr; 108 | }; 109 | 110 | struct sockaddr_in { 111 | unsigned short sin_family; // Set this to AF_INET 112 | unsigned short sin_port; 113 | struct in_addr sin_addr; 114 | char _padding[8]; // msclient/winsock expect sizeof(sockaddr_in) == 16 115 | }; 116 | 117 | // Socket handles are actually pointers rather than file handles like they 118 | // are on most Unix systems; a long can always store a pointer. 119 | typedef long SOCKET; 120 | 121 | void DosSockInit(void); 122 | 123 | SOCKET socket(int domain, int type, int protocol); 124 | int closesocket(SOCKET socket); 125 | 126 | int bind(SOCKET socket, struct sockaddr_in far *addr); 127 | 128 | ssize_t sendto(SOCKET socket, const void far *msg, size_t len, int flags, 129 | const struct sockaddr_in far *to); 130 | ssize_t recvfrom(SOCKET socket, void far *buf, size_t len, int flags, 131 | struct sockaddr_in far *from); 132 | int ioctlsocket(SOCKET socket, unsigned long cmd, void far *value); 133 | 134 | int inet_aton(const char *cp, struct in_addr *inp); 135 | 136 | extern unsigned long DosSockLastError; 137 | -------------------------------------------------------------------------------- /net/ipxnet.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 1993 id Software, Inc. 3 | // Copyright(C) 2019-2023 Simon Howard 4 | // 5 | // You can redistribute and/or modify this program under the terms of the 6 | // GNU General Public License version 2 as published by the Free Software 7 | // Foundation, or any later version. This program is distributed WITHOUT 8 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | // FITNESS FOR A PARTICULAR PURPOSE. 10 | // 11 | 12 | #include "lib/inttypes.h" 13 | 14 | //=========================================================================== 15 | 16 | #define NUMPACKETS 10 // max outstanding packets before loss 17 | 18 | // 0x869c is the official DOOM socket as registered with Novell back in the 19 | // '90s. But the original IPXSETUP used a signed 16-bit integer for the port 20 | // variable, causing an integer overflow. As a result, the actual default 21 | // socket number is one lower. 22 | #define DEFAULT_IPX_SOCKET 0x869b 23 | 24 | typedef struct { 25 | uint32_t Network; /* high-low */ 26 | uint8_t Node[6]; /* high-low */ 27 | } ipx_addr_t; 28 | 29 | typedef struct { 30 | uint16_t PacketCheckSum; /* high-low */ 31 | uint16_t PacketLength; /* high-low */ 32 | uint8_t PacketTransportControl; 33 | uint8_t PacketType; 34 | 35 | ipx_addr_t Dest; 36 | uint16_t DestSocket; /* high-low */ 37 | 38 | ipx_addr_t Src; 39 | uint16_t SrcSocket; /* high-low */ 40 | } ipx_header_t; 41 | 42 | // time is used by the communication driver to sequence packets returned 43 | // to DOOM when more than one is waiting 44 | 45 | typedef struct { 46 | ipx_header_t ipx; 47 | 48 | int32_t time; 49 | uint8_t payload[512]; 50 | } packet_t; 51 | 52 | extern long ipx_localtime; // for time stamp in packets 53 | extern const ipx_addr_t broadcast_addr; 54 | 55 | void IPXRegisterFlags(void); 56 | void InitNetwork(void); 57 | void ShutdownNetwork(void); 58 | void IPXGetLocalAddress(ipx_addr_t *addr); 59 | void IPXSendPacket(const ipx_addr_t *addr, void *data, size_t data_len); 60 | void IPXReleasePacket(packet_t *packet); 61 | packet_t *IPXGetPacket(void); 62 | void IPXStartGame(void); 63 | unsigned short ShortSwap(unsigned short i); 64 | 65 | -------------------------------------------------------------------------------- /net/llcall.asm: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright(C) 2019-2023 Simon Howard 3 | ; 4 | ; You can redistribute and/or modify this program under the terms of the 5 | ; GNU General Public License version 2 as published by the Free Software 6 | ; Foundation, or any later version. This program is distributed WITHOUT 7 | ; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | ; FITNESS FOR A PARTICULAR PURPOSE. 9 | ; 10 | 11 | ; Trampoline functions for calling low-level function calls with particular 12 | ; registers set, and then capturing the resulting register values. Used for 13 | ; calling into the IPX API. 14 | 15 | .model small 16 | 17 | ll_regs struc 18 | llr_ax dw ? 19 | llr_bx dw ? 20 | llr_cx dw ? 21 | llr_dx dw ? 22 | llr_es dw ? 23 | llr_si dw ? 24 | ll_regs ends 25 | 26 | .data 27 | public _ll_regs, _ll_funcptr 28 | _ll_regs ll_regs ? 29 | _ll_funcptr dd 0 30 | 31 | .code 32 | 33 | ; Call the far function pointer set in ll_funcptr with the registers that 34 | ; are set in ll_regs. 35 | ; As used by the "new-style" IPX API. 36 | public _LowLevelCall 37 | _LowLevelCall: 38 | push bp 39 | push bx 40 | push es 41 | push si 42 | push di 43 | mov ax, _ll_regs.llr_ax 44 | mov bx, _ll_regs.llr_bx 45 | mov cx, _ll_regs.llr_cx 46 | mov dx, _ll_regs.llr_dx 47 | mov es, _ll_regs.llr_es 48 | mov si, _ll_regs.llr_si 49 | call ds:_ll_funcptr 50 | mov _ll_regs.llr_ax, ax 51 | mov _ll_regs.llr_bx, bx 52 | mov _ll_regs.llr_cx, cx 53 | mov _ll_regs.llr_dx, dx 54 | mov _ll_regs.llr_es, es 55 | mov _ll_regs.llr_si, si 56 | pop di 57 | pop si 58 | pop es 59 | pop bx 60 | pop bp 61 | ret 62 | 63 | end 64 | 65 | -------------------------------------------------------------------------------- /net/llcall.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | union ll_regs { 12 | struct { 13 | uint16_t ax, bx, cx, dx; 14 | uint16_t es, si; 15 | } x; 16 | struct { 17 | uint8_t al, ah, bl, bh, cl, ch, dl, dh; 18 | } h; 19 | }; 20 | 21 | extern union ll_regs ll_regs; 22 | extern void __stdcall far (*ll_funcptr)(); 23 | 24 | extern void __stdcall LowLevelCall(void); 25 | 26 | -------------------------------------------------------------------------------- /net/parport.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 1994 Scott Coleman, American Society of Reverse Engineers 3 | // 4 | // This program is free software; you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, version 1. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | 14 | // NOTE: Portions of this program were adapted from other freely available 15 | // software, including SERSETUP and the Crynwr PLIP parallel port Internet 16 | // Protocol driver. 17 | 18 | #include 19 | #include 20 | #include 21 | #include "lib/inttypes.h" 22 | 23 | #include "lib/dos.h" 24 | #include "lib/flag.h" 25 | #include "lib/ints.h" 26 | #include "lib/log.h" 27 | #include "net/doomnet.h" 28 | #include "net/parport.h" 29 | #include "net/pktstats.h" 30 | 31 | #define NUM_RX_BUFFERS 16 32 | #define BUFSIZE 512 33 | 34 | struct rx_buffer { 35 | uint8_t buffer[BUFSIZE]; 36 | unsigned int len; 37 | }; 38 | 39 | unsigned int portbase = 0x378; 40 | static int irq = 7; 41 | 42 | static struct irq_hook parport_interrupt; 43 | 44 | static struct rx_buffer rx_buffers[NUM_RX_BUFFERS]; 45 | static unsigned int rx_buffer_head, rx_buffer_tail; 46 | 47 | DECLARE_COUNTER(errors_packet_overwritten); 48 | 49 | unsigned int bufseg = 0; 50 | unsigned int bufofs = 0; 51 | unsigned int recv_count = 0; 52 | 53 | // Flags: 54 | static int lpt2, lpt3; 55 | static int irq_flag = 0, port_flag; 56 | 57 | extern void __stdcall PLIORecvPacket(void); 58 | 59 | // Called by the I/O code when a complete packet has been received. 60 | // When called, the buffer (in the rx_buffer_head'th buffer) has been 61 | // populated, and recv_count is set to the packet length. 62 | void __stdcall PacketReceived(void) 63 | { 64 | struct rx_buffer *buf = &rx_buffers[rx_buffer_head]; 65 | unsigned int next_head; 66 | 67 | if (buf->len != 0) 68 | { 69 | INCREMENT_COUNTER(errors_packet_overwritten); 70 | } 71 | 72 | buf->len = recv_count; 73 | 74 | // Advance head, but we don't overflow the queue. If there are no more 75 | // buffers available, we will end up overwriting a packet. 76 | next_head = (rx_buffer_head + 1) & (NUM_RX_BUFFERS - 1); 77 | if (next_head != rx_buffer_tail) 78 | { 79 | rx_buffer_head = next_head; 80 | } 81 | } 82 | 83 | void interrupt far ReceiveISR(void) 84 | { 85 | bufseg = FP_SEG(&rx_buffers[rx_buffer_head].buffer); 86 | bufofs = FP_OFF(&rx_buffers[rx_buffer_head].buffer); 87 | 88 | PLIORecvPacket(); 89 | 90 | END_OF_IRQ(parport_interrupt); 91 | } 92 | 93 | // Returns zero if there is no packet waiting to be received. 94 | unsigned int NextPacket(uint8_t *result_buf, unsigned int max_len) 95 | { 96 | struct rx_buffer *buf; 97 | unsigned int result; 98 | 99 | while (rx_buffer_head != rx_buffer_tail) 100 | { 101 | buf = &rx_buffers[rx_buffer_tail]; 102 | rx_buffer_tail = (rx_buffer_tail + 1) & (NUM_RX_BUFFERS - 1); 103 | 104 | // We skip over packets if they would overflow result_buf. 105 | if (buf->len <= max_len) 106 | { 107 | memcpy(result_buf, &buf->buffer, buf->len); 108 | result = buf->len; 109 | buf->len = 0; 110 | 111 | return result; 112 | } 113 | } 114 | 115 | return 0; 116 | } 117 | 118 | void ParallelRegisterFlags(void) 119 | { 120 | BoolFlag("-lpt2", &lpt2, "(or -lpt3) use LPTx instead of LPT1"); 121 | BoolFlag("-lpt3", &lpt3, NULL); 122 | IntFlag("-port", &port_flag, "port number", NULL); 123 | IntFlag("-irq", &irq_flag, "irq", NULL); 124 | 125 | REGISTER_COUNTER(errors_packet_overwritten); 126 | } 127 | 128 | void GetPort(void) 129 | { 130 | int portnum = 0; 131 | 132 | if (port_flag != 0) 133 | { 134 | portbase = port_flag; 135 | } 136 | else if (lpt2) 137 | { 138 | SetLogDistinguisher("LPT2"); 139 | if (irq_flag == 0) 140 | { 141 | LogMessage("Assuming IRQ 5 for LPT2; you might want to double " 142 | "check this. Use -irq to specify the right IRQ if " 143 | "this is wrong and you get problems."); 144 | irq = 5; 145 | } 146 | portbase = 0x278; 147 | portnum = 2; 148 | } 149 | else if (lpt3) 150 | { 151 | SetLogDistinguisher("LPT3"); 152 | if (irq_flag == 0) 153 | { 154 | Error("Cowardly refusing to guess IRQ for LPT3 because it's too " 155 | "unusual. Please use the -irq flag to specify the IRQ " 156 | "number for this port."); 157 | } 158 | portbase = 0x3bc; 159 | portnum = 3; 160 | } 161 | else 162 | { 163 | SetLogDistinguisher("LPT1"); 164 | irq = 7; 165 | portnum = 1; 166 | } 167 | 168 | if (portnum > ((_bios_equiplist() >> 14) & 0x3)) 169 | { 170 | LogMessage("Proceeding, but your BIOS says you don't have an LPT%d.", 171 | portnum); 172 | } 173 | 174 | if (irq_flag != 0) 175 | { 176 | irq = irq_flag; 177 | } 178 | 179 | LogMessage("Using parallel port with base address 0x%x and IRQ %u.", 180 | portbase, irq); 181 | } 182 | 183 | void InitPort(void) 184 | { 185 | // find the irq and i/o address of the port 186 | GetPort(); 187 | 188 | HookIRQ(&parport_interrupt, ReceiveISR, irq); 189 | 190 | // enable interrupts from the printer port 191 | OUTPUT(portbase + 2, INPUT(portbase + 2) | 0x10); 192 | } 193 | 194 | void ShutdownPort(void) 195 | { 196 | // disable interrupts from the printer port 197 | OUTPUT(portbase + 2, INPUT(portbase + 2) & ~0x10); 198 | 199 | RestoreIRQ(&parport_interrupt); 200 | } 201 | 202 | -------------------------------------------------------------------------------- /net/parport.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 1994 Scott Coleman, American Society of Reverse Engineers 3 | // 4 | // This program is free software; you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, version 1. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | 14 | // Declarations for the DOOM parallel port driver. 15 | void ParallelRegisterFlags(void); 16 | void InitPort(void); 17 | void ShutdownPort(void); 18 | unsigned int NextPacket(uint8_t *result_buf, unsigned int max_len); 19 | 20 | -------------------------------------------------------------------------------- /net/parsetup.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 1994 Scott Coleman, American Society of Reverse Engineers 3 | // 4 | // This program is free software; you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, version 1. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | 14 | // NOTE: Portions of this program were adapted from other freely available 15 | // software, including SERSETUP and the Crynwr PLIP parallel port Internet 16 | // Protocol driver. 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "lib/inttypes.h" 25 | 26 | #include "lib/dos.h" 27 | #include "lib/flag.h" 28 | #include "lib/log.h" 29 | #include "net/doomnet.h" 30 | #include "net/parport.h" 31 | #include "net/pktstats.h" 32 | #include "net/serarb.h" 33 | 34 | #define MAXPACKET 512 35 | 36 | static doomcom_t doomcom; 37 | 38 | DECLARE_COUNTER(errors_wrong_checksum); 39 | DECLARE_COUNTER(errors_timeout); 40 | 41 | extern int __stdcall PLIOWritePacket(void); 42 | extern unsigned recv_count; 43 | 44 | void __stdcall ErrorWrongChecksum(void) 45 | { 46 | INCREMENT_COUNTER(errors_wrong_checksum); 47 | } 48 | 49 | void __stdcall ErrorTimeout(void) 50 | { 51 | INCREMENT_COUNTER(errors_timeout); 52 | } 53 | 54 | int WritePacket(uint8_t *data, unsigned len) 55 | { 56 | extern int plio_write_seg, plio_write_off, plio_write_len; 57 | 58 | plio_write_seg = FP_SEG(data); 59 | plio_write_off = FP_OFF(data); 60 | plio_write_len = len; 61 | 62 | return PLIOWritePacket(); 63 | } 64 | 65 | static void NetCallback(void) 66 | { 67 | if (doomcom.command == CMD_SEND) 68 | { 69 | WritePacket((char *)doomcom.data, doomcom.datalength); 70 | } 71 | else if (doomcom.command == CMD_GET) 72 | { 73 | doomcom.datalength = NextPacket(doomcom.data, sizeof(doomcom.data)); 74 | if (doomcom.datalength > 0) 75 | { 76 | doomcom.remotenode = 1; 77 | } 78 | else 79 | { 80 | doomcom.remotenode = -1; 81 | } 82 | } 83 | } 84 | 85 | void main(int argc, char *argv[]) 86 | { 87 | int printstats = 0; 88 | char **args; 89 | 90 | srand(GetEntropy()); 91 | 92 | SetHelpText("Doom parallel port network device driver", 93 | "%s doom2.exe -warp 15 -skill 3"); 94 | BoolFlag("-printstats", &printstats, NULL); 95 | RegisterArbitrationFlags(); 96 | ParallelRegisterFlags(); 97 | NetRegisterFlags(); 98 | PacketStatsRegisterFlags(); 99 | args = ParseCommandLine(argc, argv); 100 | if (args == NULL) 101 | { 102 | ErrorPrintUsage("No command given to run."); 103 | } 104 | 105 | REGISTER_COUNTER(errors_wrong_checksum); 106 | REGISTER_COUNTER(errors_timeout); 107 | 108 | // set network characteristics 109 | doomcom.ticdup = 1; 110 | doomcom.extratics = 0; 111 | doomcom.consoleplayer = 0; 112 | doomcom.numnodes = 2; 113 | doomcom.numplayers = 2; 114 | doomcom.drone = 0; 115 | 116 | // establish communications 117 | InitPort(); 118 | atexit(ShutdownPort); 119 | 120 | ArbitratePlayers(&doomcom, NetCallback); 121 | 122 | // launch DOOM 123 | NetLaunchDoom(&doomcom, args, NetCallback); 124 | } 125 | 126 | -------------------------------------------------------------------------------- /net/passthru.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | // Pass-through driver. Minimal example of the networking API. 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include "lib/inttypes.h" 18 | 19 | #include "lib/dos.h" 20 | #include "lib/flag.h" 21 | #include "lib/log.h" 22 | #include "net/doomnet.h" 23 | 24 | static doomcom_t doomcom; 25 | static doomcom_t far *inner_driver; 26 | 27 | static void NetCallback(void) 28 | { 29 | switch (doomcom.command) 30 | { 31 | case CMD_SEND: 32 | inner_driver->remotenode = doomcom.remotenode; 33 | inner_driver->datalength = doomcom.datalength; 34 | far_memcpy(&inner_driver->data, doomcom.data, doomcom.datalength); 35 | NetSendPacket(inner_driver); 36 | break; 37 | 38 | case CMD_GET: 39 | NetGetPacket(inner_driver); 40 | if (inner_driver->remotenode == -1) 41 | { 42 | doomcom.remotenode = -1; 43 | return; 44 | } 45 | doomcom.remotenode = inner_driver->remotenode; 46 | doomcom.datalength = inner_driver->datalength; 47 | far_memcpy(doomcom.data, &inner_driver->data, 48 | inner_driver->datalength); 49 | break; 50 | } 51 | } 52 | 53 | static void SetDriver(long l) 54 | { 55 | assert(inner_driver == NULL); 56 | inner_driver = NetGetHandle(l); 57 | } 58 | 59 | int main(int argc, char *argv[]) 60 | { 61 | char **args; 62 | 63 | APIPointerFlag("-net", SetDriver); 64 | NetRegisterFlags(); 65 | args = ParseCommandLine(argc, argv); 66 | if (args == NULL) 67 | { 68 | ErrorPrintUsage("No command given to run."); 69 | } 70 | 71 | assert(inner_driver != NULL); 72 | 73 | doomcom.numnodes = inner_driver->numnodes; 74 | doomcom.consoleplayer = inner_driver->consoleplayer; 75 | doomcom.numplayers = inner_driver->numplayers; 76 | doomcom.ticdup = inner_driver->ticdup; 77 | doomcom.extratics = inner_driver->extratics; 78 | 79 | NetLaunchDoom(&doomcom, args, NetCallback); 80 | 81 | return 0; 82 | } 83 | 84 | -------------------------------------------------------------------------------- /net/pktaggr.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | // Packet "aggregation" code: this detects when an identical packet is sent to 12 | // every other node in sequence by the caller. Doom does this when it generates 13 | // a new tic, so the majority of transmitted data originates this way. The code 14 | // here identifies such packets and translates them into broadcast sends; this 15 | // is used as an optimization for example in the metanet driver so that the 16 | // rate of packets sent over the forwarding network is O(n) of the number of 17 | // nodes rather than O(n^2). 18 | 19 | #include 20 | #include 21 | 22 | #include "lib/inttypes.h" 23 | #include "net/doomnet.h" 24 | 25 | #define PENDING_BUFFER_LEN 512 26 | 27 | static int aggr_numnodes; 28 | static void (*aggr_sendpkt)(int node, void *data, size_t data_len); 29 | 30 | // When we send packets, they sometimes get placed into the broadcast 31 | // pending buffer to determine if they should be sent as broadcast packets 32 | // to save bandwidth: 33 | static uint8_t pending_buffer[PENDING_BUFFER_LEN]; 34 | static size_t pending_buffer_len; 35 | static int pending_count; 36 | 37 | // FlushPendingPackets sends the currently pending packet to any nodes for 38 | // which it was held back - if any. 39 | void FlushPendingPackets(void) 40 | { 41 | int i; 42 | 43 | for (i = 0; i < pending_count; ++i) 44 | { 45 | aggr_sendpkt(i + 1, pending_buffer, pending_buffer_len); 46 | } 47 | 48 | pending_count = 0; 49 | } 50 | 51 | // TryBroadcastStore tries to stage the packet in aggregation_doomcom and 52 | // returns 1 if the packet should not be sent yet. 53 | static int TryBroadcastStore(int node, void *data, size_t data_len) 54 | { 55 | // The objective here is to try to detect the specific sequence of calls 56 | // from Doom's NetUpdate() function - when a new tic is generated, it 57 | // does a transmit to each node of (usually) the exact same data. 58 | // We count up the number of such identical packets we have received and 59 | // when pending_count == num_nodes - 1, we have successfully detected 60 | // something that can be sent as a broadcast packet. 61 | 62 | // If this looks like the first packet in a sequence, we store the packet 63 | // into inner->data but don't send yet. 64 | if (node == 1) 65 | { 66 | FlushPendingPackets(); 67 | if (data_len > PENDING_BUFFER_LEN) 68 | { 69 | return 0; 70 | } 71 | pending_buffer_len = data_len; 72 | memcpy(pending_buffer, data, data_len); 73 | pending_count = 0; 74 | } 75 | // The packet must exactly match the previous ones, and be in sequence. 76 | else if (node != pending_count + 1 || pending_buffer_len != data_len 77 | || memcmp(pending_buffer, data, data_len) != 0) 78 | { 79 | FlushPendingPackets(); 80 | return 0; 81 | } 82 | 83 | // We got the next in sequence successfully. 84 | ++pending_count; 85 | if (pending_count == aggr_numnodes - 1) 86 | { 87 | // We have a complete broadcast packet. 88 | aggr_sendpkt(MAXNETNODES, pending_buffer, pending_buffer_len); 89 | pending_count = 0; 90 | } 91 | return 1; 92 | } 93 | 94 | void AggregatedSendPacket(int node, void *data, size_t data_len) 95 | { 96 | if (node > 0 && node < aggr_numnodes 97 | && TryBroadcastStore(node, data, data_len)) 98 | { 99 | return; 100 | } 101 | 102 | // No buffering, pass right through and send straight away. 103 | aggr_sendpkt(node, data, data_len); 104 | } 105 | 106 | void InitAggregation(int numnodes, 107 | void (*send)(int node, void *data, size_t data_len)) 108 | { 109 | aggr_numnodes = numnodes; 110 | aggr_sendpkt = send; 111 | } 112 | 113 | -------------------------------------------------------------------------------- /net/pktaggr.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | void FlushPendingPackets(void); 12 | void AggregatedSendPacket(int node, void *data, size_t data_len); 13 | void InitAggregation(int numnodes, 14 | void (*send)(int node, void *data, size_t data_len)); 15 | 16 | -------------------------------------------------------------------------------- /net/pktstats.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2025 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | #include 12 | #include 13 | 14 | #include "lib/flag.h" 15 | #include "lib/log.h" 16 | #include "net/pktstats.h" 17 | 18 | static struct counter *counters = NULL; 19 | static int print_stats_on_exit = 0; 20 | 21 | static void PrintStats(void) 22 | { 23 | struct counter *c; 24 | 25 | if (!print_stats_on_exit) 26 | { 27 | return; 28 | } 29 | 30 | LogMessage("Statistics:"); 31 | for (c = counters; c != NULL; c = c->next) 32 | { 33 | if (c->i != 0) 34 | { 35 | LogMessage("%16s %6ld", c->name, c->i); 36 | } 37 | } 38 | } 39 | 40 | void RegisterCounter(struct counter *ctr) 41 | { 42 | ctr->next = counters; 43 | counters = ctr; 44 | } 45 | 46 | void PacketStatsRegisterFlags(void) 47 | { 48 | BoolFlag("-stats", &print_stats_on_exit, "Print statistics on exit"); 49 | atexit(PrintStats); 50 | } 51 | -------------------------------------------------------------------------------- /net/pktstats.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2025 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | struct counter 12 | { 13 | const char *name; 14 | unsigned long i; 15 | struct counter *next; 16 | }; 17 | 18 | #define DECLARE_COUNTER(name) \ 19 | static struct counter stats_ ## name = { #name, 0, 0 } 20 | #define REGISTER_COUNTER(name) \ 21 | RegisterCounter(&stats_ ## name) 22 | #define INCREMENT_COUNTER(name) \ 23 | ++stats_ ## name.i 24 | 25 | void RegisterCounter(struct counter *ctr); 26 | void PacketStatsRegisterFlags(void); 27 | -------------------------------------------------------------------------------- /net/serarb.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 1993 id Software, Inc. 3 | // Copyright(C) 2019-2023 Simon Howard 4 | // 5 | // You can redistribute and/or modify this program under the terms of the 6 | // GNU General Public License version 2 as published by the Free Software 7 | // Foundation, or any later version. This program is distributed WITHOUT 8 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | // FITNESS FOR A PARTICULAR PURPOSE. 10 | // 11 | 12 | // Player arbitration code for establishing initial connection over a 13 | // point-to-point link (eg. serial/modem), and determining which player 14 | // we are. Different versions of Doom's SERSETUP used two different 15 | // protocols; we support both. 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "lib/inttypes.h" 22 | 23 | #include "lib/flag.h" 24 | #include "lib/log.h" 25 | #include "net/doomnet.h" 26 | #include "net/serarb.h" 27 | 28 | static struct { 29 | doomcom_t *doomcom; 30 | void (*net_cmd)(void); 31 | char localid[7]; 32 | clock_t last_send_time; 33 | int localstage; 34 | int new_protocol; 35 | } arb; 36 | 37 | int force_player1 = 0, force_player2 = 0; 38 | 39 | static int DoGetPacket(void) 40 | { 41 | arb.doomcom->command = CMD_GET; 42 | arb.net_cmd(); 43 | return arb.doomcom->remotenode != -1; 44 | } 45 | 46 | static void DoSendPacket(uint8_t *data, int data_len) 47 | { 48 | arb.doomcom->command = CMD_SEND; 49 | arb.doomcom->remotenode = 1; 50 | arb.doomcom->datalength = data_len; 51 | memcpy(arb.doomcom->data, data, data_len); 52 | arb.net_cmd(); 53 | } 54 | 55 | static void ProcessPackets(void) 56 | { 57 | char remoteid[7]; 58 | int remoteplayer, remotestage; 59 | uint8_t *packet = arb.doomcom->data; 60 | 61 | while (DoGetPacket()) 62 | { 63 | packet[arb.doomcom->datalength] = '\0'; 64 | 65 | if (sscanf(packet, "ID%6c_%d", remoteid, &remotestage) == 2) 66 | { 67 | arb.new_protocol = 1; 68 | arb.doomcom->consoleplayer = 69 | memcmp(arb.localid, remoteid, 6) > 0; 70 | if (!memcmp(arb.localid, remoteid, 6)) 71 | { 72 | // TODO: Don't use Error() here because this can occur inside 73 | // the interrupt handler in background answer mode. 74 | Error("Duplicate ID string received"); 75 | } 76 | } 77 | else if (sscanf(packet, "PLAY%d_%d", &remoteplayer, &remotestage) == 2) 78 | { 79 | arb.new_protocol = 0; 80 | 81 | // The original sersetup code would swap the player number when 82 | // detecting a conflict; however, this is not an algorithm that 83 | // is guaranteed to ever terminate. In our case since we only 84 | // ever use the old protocol when the other side needs to, we 85 | // can use this asymmetry as a way of resolving the deadlock: 86 | // we stick to our guns and do not change player, safe in the 87 | // knowledge that the other side will adapt to us. 88 | if (remoteplayer == arb.doomcom->consoleplayer) 89 | { 90 | remotestage = 0; 91 | } 92 | } 93 | else 94 | { 95 | continue; 96 | } 97 | 98 | // We got a packet successfully. Trigger a response with new state. 99 | arb.localstage = remotestage + 1; 100 | arb.last_send_time = 0; 101 | } 102 | } 103 | 104 | static void MakeLocalID(char *buf) 105 | { 106 | uint32_t id; 107 | 108 | if (force_player1) 109 | { 110 | id = 0; 111 | } 112 | else if (force_player2) 113 | { 114 | id = 999999UL; 115 | } 116 | else 117 | { 118 | id = ((uint32_t) rand() << 16) | rand(); 119 | id = id % 1000000L; 120 | } 121 | sprintf(buf, "%.6ld", id); 122 | } 123 | 124 | static void InitArbitration(void) 125 | { 126 | arb.localstage = 0; 127 | arb.new_protocol = 1; 128 | arb.last_send_time = 0; 129 | 130 | // allow override of automatic player ordering 131 | if (force_player1) 132 | { 133 | arb.doomcom->consoleplayer = 0; 134 | } 135 | else if (force_player2) 136 | { 137 | arb.doomcom->consoleplayer = 1; 138 | } 139 | 140 | MakeLocalID(arb.localid); 141 | } 142 | 143 | void StartArbitratePlayers(doomcom_t *dc, void (*net_cmd)(void)) 144 | { 145 | arb.doomcom = dc; 146 | arb.net_cmd = net_cmd; 147 | InitArbitration(); 148 | 149 | LogMessage("Attempting to connect across link."); 150 | } 151 | 152 | int PollArbitratePlayers(void) 153 | { 154 | clock_t now; 155 | char str[20]; 156 | 157 | if (arb.localstage >= 2) 158 | { 159 | // Once complete, flush out any extras 160 | while (DoGetPacket()) 161 | { 162 | } 163 | 164 | return 1; 165 | } 166 | 167 | ProcessPackets(); 168 | 169 | now = clock(); 170 | if (now - arb.last_send_time >= CLOCKS_PER_SEC) 171 | { 172 | arb.last_send_time = now; 173 | if (arb.new_protocol) 174 | { 175 | sprintf(str, "ID%6s_%d", arb.localid, arb.localstage); 176 | } 177 | else 178 | { 179 | sprintf(str, "PLAY%i_%i", arb.doomcom->consoleplayer, arb.localstage); 180 | } 181 | DoSendPacket(str, strlen(str)); 182 | } 183 | 184 | return 0; 185 | } 186 | 187 | // Figure out who is player 0 and 1 188 | void ArbitratePlayers(doomcom_t *dc, void (*net_cmd)(void)) 189 | { 190 | StartArbitratePlayers(dc, net_cmd); 191 | while (!PollArbitratePlayers()) 192 | { 193 | CheckAbort("Connection"); 194 | } 195 | } 196 | 197 | void RegisterArbitrationFlags(void) 198 | { 199 | BoolFlag("-player1", &force_player1, "(or -player2) force player#"); 200 | BoolFlag("-player2", &force_player2, NULL); 201 | } 202 | 203 | -------------------------------------------------------------------------------- /net/serarb.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | void StartArbitratePlayers(doomcom_t *dc, void (*net_cmd)(void)); 12 | int PollArbitratePlayers(void); 13 | void ArbitratePlayers(doomcom_t *dc, void (*net_cmd)(void)); 14 | void RegisterArbitrationFlags(void); 15 | 16 | extern int force_player1, force_player2; 17 | 18 | -------------------------------------------------------------------------------- /net/serport.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 1993 id Software, Inc. 3 | // Copyright(C) 2019-2023 Simon Howard 4 | // 5 | // You can redistribute and/or modify this program under the terms of the 6 | // GNU General Public License version 2 as published by the Free Software 7 | // Foundation, or any later version. This program is distributed WITHOUT 8 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | // FITNESS FOR A PARTICULAR PURPOSE. 10 | // 11 | 12 | #define TRANSMIT_HOLDING_REGISTER 0x00 13 | #define RECEIVE_BUFFER_REGISTER 0x00 14 | #define INTERRUPT_ENABLE_REGISTER 0x01 15 | #define IER_RX_DATA_READY 0x01 16 | #define IER_TX_HOLDING_REGISTER_EMPTY 0x02 17 | #define IER_LINE_STATUS 0x04 18 | #define IER_MODEM_STATUS 0x08 19 | #define INTERRUPT_ID_REGISTER 0x02 20 | #define IIR_MODEM_STATUS_INTERRUPT 0x00 21 | #define IIR_TX_HOLDING_REGISTER_INTERRUPT 0x02 22 | #define IIR_RX_DATA_READY_INTERRUPT 0x04 23 | #define IIR_LINE_STATUS_INTERRUPT 0x06 24 | #define FIFO_CONTROL_REGISTER 0x02 25 | #define FCR_FIFO_ENABLE 0x01 26 | #define FCR_RCVR_FIFO_RESET 0x02 27 | #define FCR_XMIT_FIFO_RESET 0x04 28 | #define FCR_RCVR_TRIGGER_LSB 0x40 29 | #define FCR_RCVR_TRIGGER_MSB 0x80 30 | #define FCR_TRIGGER_01 0x00 31 | #define FCR_TRIGGER_04 0x40 32 | #define FCR_TRIGGER_08 0x80 33 | #define FCR_TRIGGER_14 0xc0 34 | #define LINE_CONTROL_REGISTER 0x03 35 | #define LCR_WORD_LENGTH_MASK 0x03 36 | #define LCR_WORD_LENGTH_SELECT_0 0x01 37 | #define LCR_WORD_LENGTH_SELECT_1 0x02 38 | #define LCR_STOP_BITS 0x04 39 | #define LCR_PARITY_MASK 0x38 40 | #define LCR_PARITY_ENABLE 0x08 41 | #define LCR_EVEN_PARITY_SELECT 0x10 42 | #define LCR_STICK_PARITY 0x20 43 | #define LCR_SET_BREAK 0x40 44 | #define LCR_DLAB 0x80 45 | #define MODEM_CONTROL_REGISTER 0x04 46 | #define MCR_DTR 0x01 47 | #define MCR_RTS 0x02 48 | #define MCR_OUT1 0x04 49 | #define MCR_OUT2 0x08 50 | #define MCR_LOOPBACK 0x10 51 | #define LINE_STATUS_REGISTER 0x05 52 | #define LSR_DATA_READY 0x01 53 | #define LSR_OVERRUN_ERROR 0x02 54 | #define LSR_PARITY_ERROR 0x04 55 | #define LSR_FRAMING_ERROR 0x08 56 | #define LSR_BREAK_DETECT 0x10 57 | #define LSR_THRE 0x20 58 | #define MODEM_STATUS_REGISTER 0x06 59 | #define MSR_DELTA_CTS 0x01 60 | #define MSR_DELTA_DSR 0x02 61 | #define MSR_TERI 0x04 62 | #define MSR_DELTA_CD 0x08 63 | #define MSR_CTS 0x10 64 | #define MSR_DSR 0x20 65 | #define MSR_RI 0x40 66 | #define MSR_CD 0x80 67 | #define DIVISOR_LATCH_LOW 0x00 68 | #define DIVISOR_LATCH_HIGH 0x01 69 | 70 | void SerialRegisterFlags(void); 71 | void InitPort(long baudrate); 72 | void ShutdownPort(void); 73 | 74 | // SerialByteReceived is called every time a new byte is received from 75 | // the serial port. If it returns zero, no more data will be delivered 76 | // until ResumeReceive is called. This may lead to data being lost, but 77 | // should be done if there is no more buffer space left to store data. 78 | int SerialByteReceived(uint8_t c); 79 | 80 | // SerialMoreTXData fills this buffer. 81 | #define SERIAL_TX_BUFFER_LEN 16 82 | extern uint8_t serial_tx_buffer[SERIAL_TX_BUFFER_LEN]; 83 | 84 | // SerialMoreTXData to refill the transmit buffer when it's empty. 85 | // Returns the number of bytes placed into the buffer, or zero to mean 86 | // "stop transmitting". 87 | unsigned int SerialMoreTXData(void); 88 | 89 | // JumpStart restarts transmit if transmit had previously stopped. 90 | void JumpStart(void); 91 | 92 | // ResumeReceive is called to restart receive, if SerialByteReceived 93 | // previously returned zero. 94 | void ResumeReceive(void); 95 | 96 | void SetDTR(int dtr); 97 | -------------------------------------------------------------------------------- /net/solo-net.c: -------------------------------------------------------------------------------- 1 | // 2 | // Doom solo / minimal netgame driver. 3 | // Copyright (C) 2014-2023 Simon Howard 4 | // 5 | // You can redistribute and/or modify this program under the terms of the 6 | // GNU General Public License version 2 as published by the Free Software 7 | // Foundation, or any later version. This program is distributed WITHOUT 8 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | // FITNESS FOR A PARTICULAR PURPOSE. 10 | // 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include "lib/flag.h" 17 | #include "lib/log.h" 18 | #include "net/doomnet.h" 19 | 20 | #define RECV_QUEUE_LEN 8 21 | 22 | static doomcom_t doomcom; 23 | static doompacket_t *packet; 24 | 25 | // Receive queue. We place packets onto the queue for receiving and 26 | // dequeue them when the game issues CMD_GET. 27 | static doompacket_t recv_queue[RECV_QUEUE_LEN]; 28 | static int recv_queue_head = 0, recv_queue_tail = 0; 29 | 30 | // Copy the given packet and place it onto the receive queue. 31 | static void QueuePacket(doompacket_t *sendpacket) 32 | { 33 | // Don't overflow the queue. Drop packets if necessary. 34 | if (((recv_queue_tail + 1) % RECV_QUEUE_LEN) == recv_queue_head) 35 | { 36 | return; 37 | } 38 | 39 | memcpy(&recv_queue[recv_queue_tail], sendpacket, sizeof(doompacket_t)); 40 | 41 | recv_queue_tail = (recv_queue_tail + 1) % RECV_QUEUE_LEN; 42 | } 43 | 44 | // Calculate the size (in bytes) of the given packet. This is important 45 | // because the game will discard packets of the wrong length. 46 | static int PacketSize(doompacket_t *sendpacket) 47 | { 48 | return 8 + sizeof(ticcmd_t) * sendpacket->numtics; 49 | } 50 | 51 | // Calculate the checksum for the given packet and fill in the checksum 52 | // field. If a packet has the wrong checksum the game will discard it. 53 | static void CalculateChecksum(doompacket_t *sendpacket) 54 | { 55 | unsigned long c; 56 | unsigned long *p; 57 | int i, l; 58 | 59 | c = 0x1234567l; 60 | 61 | // Length in 32-bit words. We do not include the checksum field, 62 | // or there is a bootstrapping problem. 63 | l = (PacketSize(sendpacket) - 4) / 4; 64 | p = ((unsigned long *)sendpacket) + 1; 65 | 66 | for (i = 0; i < l; ++i) 67 | { 68 | c += *p * (i + 1); 69 | ++p; 70 | } 71 | 72 | sendpacket->checksum = c & NCMD_CHECKSUM; 73 | } 74 | 75 | // Invoked when the game wants to send a packet. 76 | static void SendPacket(void) 77 | { 78 | static doompacket_t reply; 79 | int i; 80 | 81 | // The game sent a packet to us with some tics. To keep the game 82 | // runnning we need to send some tics back to it, otherwise it will 83 | // stall. So this is "self clocking": we create packets in a 84 | // tit-for-tat fashion as we receive them. 85 | 86 | memset(&reply, 0, sizeof(reply)); 87 | reply.retransmitfrom = 0; 88 | reply.player = doomcom.remotenode; 89 | 90 | // If the game sent a setup packet, just respond with an "empty" 91 | // packet starting at tic #0 to let it know that we're in the 92 | // game and it can start. The game sent the episode/map encoded in 93 | // starttic so we can't use that. Otherwise, we send a packet back 94 | // with the same starttic as we received. 95 | if ((packet->checksum & NCMD_SETUP) != 0) 96 | { 97 | reply.starttic = 0; 98 | reply.numtics = 0; 99 | } 100 | else 101 | { 102 | reply.starttic = packet->starttic; 103 | reply.numtics = packet->numtics; 104 | 105 | // Fill in your own code for generating ticcmds here... 106 | // But there's not much that can be done. The game will quit 107 | // with a consistancy failure if the consistancy field is not 108 | // what it is expecting (ie. the lower 16 bits of the x 109 | // coordinate of the player). However - we can assume that 110 | // when the level starts it is equal to zero (as all things 111 | // are on a 1 unit boundary/granularity). This works until 112 | // one of the fake players moves - then it's game over. 113 | for (i = 0; i < reply.numtics; ++i) 114 | { 115 | reply.cmds[i].consistancy = 0; 116 | } 117 | } 118 | 119 | // Put the packet on the receive queue so that it will be read 120 | // when the game next asks for packets. 121 | CalculateChecksum(&reply); 122 | QueuePacket(&reply); 123 | } 124 | 125 | // Invoked when the game wants to check if a packet can be received. 126 | static void ReceivePacket(void) 127 | { 128 | // Receive queue empty? 129 | if (recv_queue_head == recv_queue_tail) 130 | { 131 | doomcom.remotenode = -1; 132 | doomcom.datalength = 0; 133 | return; 134 | } 135 | 136 | // Dequeue a packet. 137 | memcpy(packet, &recv_queue[recv_queue_head], sizeof(doompacket_t)); 138 | recv_queue_head = (recv_queue_head + 1) % RECV_QUEUE_LEN; 139 | 140 | // Use the player number inside the packet as the remotenode. 141 | // Kind of hacky. 142 | doomcom.remotenode = packet->player; 143 | doomcom.datalength = PacketSize(packet); 144 | } 145 | 146 | // Our interrupt service routine that is invoked by Doom when it wants 147 | // to send or receive a packet. 148 | // If numnodes=1 then this can just be an empty function. We only bother 149 | // doing anything so that we can simulate extra dummy players. 150 | static void NetCallback(void) 151 | { 152 | if (doomcom.command == CMD_SEND) 153 | { 154 | SendPacket(); 155 | } 156 | else if (doomcom.command == CMD_GET) 157 | { 158 | ReceivePacket(); 159 | } 160 | } 161 | 162 | // Initialize the doomcom structure to default values. 163 | static void InitDoomcom(void) 164 | { 165 | doomcom.numnodes = 1; 166 | doomcom.ticdup = 1; 167 | doomcom.extratics = 0; 168 | 169 | doomcom.consoleplayer = 0; 170 | doomcom.numplayers = 1; 171 | 172 | // The following are ignored by Doom anyway. 173 | doomcom.angleoffset = 0; 174 | doomcom.drone = 0; 175 | 176 | doomcom.deathmatch = 0; 177 | doomcom.savegame = -1; 178 | doomcom.episode = 1; 179 | doomcom.map = 1; 180 | doomcom.skill = 3; 181 | 182 | packet = (doompacket_t *) doomcom.data; 183 | } 184 | 185 | int main(int argc, char *argv[]) 186 | { 187 | char **args; 188 | int nodes = 1; 189 | 190 | SetHelpText("Doom single player network driver", 191 | "%s doom2.exe"); 192 | IntFlag("-nodes", &nodes, "n", 193 | "total number of players (other players are simulated)"); 194 | NetRegisterFlags(); 195 | args = ParseCommandLine(argc, argv); 196 | if (args == NULL) 197 | { 198 | ErrorPrintUsage("No command given to run."); 199 | } 200 | 201 | InitDoomcom(); 202 | doomcom.numplayers = nodes; 203 | doomcom.numnodes = nodes; 204 | 205 | NetLaunchDoom(&doomcom, args, NetCallback); 206 | 207 | return 0; 208 | } 209 | -------------------------------------------------------------------------------- /stat/statdump.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | // 12 | // Implementation of an external statistics driver using Doom's 13 | // -statcopy command line parameter, using the -control API to provide 14 | // an interrupt callback to check the statistics buffer. 15 | // 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include "lib/flag.h" 22 | #include "lib/log.h" 23 | 24 | #include "ctrl/control.h" 25 | #include "stat/stats.h" 26 | #include "stat/statprnt.h" 27 | 28 | // Array of end-of-level statistics that have been captured. 29 | #define MAX_CAPTURES 32 30 | static wbstartstruct_t captured_stats[MAX_CAPTURES]; 31 | static int num_captured_stats = 0; 32 | 33 | // Output file to write statistics to. If NULL, print to stdout. 34 | static char *output_filename = NULL; 35 | 36 | static void StatsCallback(wbstartstruct_t *stats) 37 | { 38 | if (num_captured_stats < MAX_CAPTURES) 39 | { 40 | memcpy(&captured_stats[num_captured_stats], stats, 41 | sizeof(wbstartstruct_t)); 42 | ++num_captured_stats; 43 | } 44 | } 45 | 46 | // Write the statistics to the output file. 47 | static void WriteStats(void) 48 | { 49 | FILE *outfile; 50 | int i; 51 | 52 | // Open the output file for writing. If none is specified, 53 | // write the data to stdout. 54 | 55 | if (output_filename != NULL) 56 | { 57 | outfile = fopen(output_filename, "w"); 58 | 59 | if (outfile == NULL) 60 | { 61 | Error("Failed to open '%s' for write.", output_filename); 62 | return; 63 | } 64 | } 65 | else 66 | { 67 | outfile = stdout; 68 | } 69 | 70 | // Work out if this was Doom 1 or Doom 2. 71 | 72 | DiscoverGamemode(captured_stats, num_captured_stats); 73 | 74 | // Write the statistics for each level to the file. 75 | 76 | for (i = 0; i < num_captured_stats; ++i) 77 | { 78 | PrintStats(outfile, &captured_stats[i]); 79 | } 80 | 81 | // Close the output file 82 | 83 | if (output_filename != NULL) 84 | { 85 | fclose(outfile); 86 | } 87 | } 88 | 89 | int main(int argc, char *argv[]) 90 | { 91 | char **args; 92 | 93 | SetHelpText("Doom statistics driver", 94 | "%s -o stats.txt doom2.exe -skill 4"); 95 | StringFlag("-o", &output_filename, "filename", 96 | "file to write captured statistics"); 97 | ControlRegisterFlags(); 98 | args = ParseCommandLine(argc, argv); 99 | if (args == NULL) 100 | { 101 | ErrorPrintUsage("No command given to run."); 102 | } 103 | 104 | // Launch Doom 105 | StatsLaunchDoom(args, StatsCallback); 106 | 107 | LogMessage("Statistics captured for %i level(s)", num_captured_stats); 108 | 109 | // Write statistics to the output file. 110 | WriteStats(); 111 | 112 | return 0; 113 | } 114 | -------------------------------------------------------------------------------- /stat/statprnt.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2007-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | #ifndef STATPRNT_H 12 | #define STATPRNT_H 13 | 14 | void DiscoverGamemode(wbstartstruct_t *stats, int num_stats); 15 | void PrintStats(FILE *stream, wbstartstruct_t *stats); 16 | 17 | #endif /* #ifndef STATPRNT_H */ 18 | -------------------------------------------------------------------------------- /stat/stats.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // You can redistribute and/or modify this program under the terms of the 5 | // GNU General Public License version 2 as published by the Free Software 6 | // Foundation, or any later version. This program is distributed WITHOUT 7 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 8 | // FITNESS FOR A PARTICULAR PURPOSE. 9 | // 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "lib/dos.h" 17 | #include "lib/flag.h" 18 | #include "ctrl/control.h" 19 | #include "stat/stats.h" 20 | 21 | static stats_callback_t stats_callback; 22 | 23 | // Statistics buffer that Doom writes into. 24 | static wbstartstruct_t stats_buffer; 25 | 26 | // 27 | // This callback function is invoked in interrupt context by the 28 | // control API interrupt. 29 | // 30 | // We check the "maxfrags" variable, to see if the stats 31 | // buffer has been written to. If it has, save the contents of the 32 | // stats buffer into the captured_stats array for later processing. 33 | /// 34 | static void far ControlCallback(ticcmd_t *unused) 35 | { 36 | unused = unused; 37 | 38 | if (stats_buffer.maxfrags == 0) 39 | { 40 | // New data has been written to the statistics buffer. 41 | stats_callback(&stats_buffer); 42 | stats_buffer.maxfrags = 1; 43 | } 44 | } 45 | 46 | void StatsLaunchDoom(char **args, stats_callback_t callback) 47 | { 48 | char bufaddr[20]; 49 | long flataddr; 50 | 51 | stats_callback = callback; 52 | 53 | // Launch Doom 54 | flataddr = (long) FP_SEG(&stats_buffer) * 16 + FP_OFF(&stats_buffer); 55 | sprintf(bufaddr, "%li", flataddr); 56 | args = AppendArgs(args, "-statcopy", bufaddr, NULL); 57 | 58 | stats_buffer.maxfrags = 1; 59 | 60 | ControlLaunchDoom(args, ControlCallback); 61 | } 62 | 63 | // StatsGetHandle takes the given long value read from the command line 64 | // and returns a wbstartstruct_t pointer. 65 | wbstartstruct_t far *StatsGetHandle(long l) 66 | { 67 | wbstartstruct_t far *result = NULL; 68 | unsigned int seg; 69 | 70 | assert(l != 0); 71 | seg = (int) ((l >> 4) & 0xf000L); 72 | result = (void far *) MK_FP(seg, l & 0xffffL); 73 | 74 | return result; 75 | } 76 | 77 | -------------------------------------------------------------------------------- /stat/stats.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) 1993-1996 by id Software, Inc. 3 | // Copyright (C) 2007-2023 Simon Howard 4 | // 5 | // You can redistribute and/or modify this program under the terms of the 6 | // GNU General Public License version 2 as published by the Free Software 7 | // Foundation, or any later version. This program is distributed WITHOUT 8 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 | // FITNESS FOR A PARTICULAR PURPOSE. 10 | // 11 | 12 | // Structure copied by Doom when using the -statcopy option. From the 13 | // Doom source. 14 | 15 | #ifndef STATS_H 16 | #define STATS_H 17 | 18 | #define DOOM_MAXPLAYERS 4 19 | 20 | typedef struct { 21 | long in; /* whether the player is in game */ 22 | 23 | /* Player stats, kills, collected items etc. */ 24 | long skills; 25 | long sitems; 26 | long ssecret; 27 | long stime; 28 | long frags[DOOM_MAXPLAYERS]; 29 | long score; /* current score on entry, modified on return */ 30 | 31 | } wbplayerstruct_t; 32 | 33 | typedef struct { 34 | long epsd; /* episode # (0-2) */ 35 | 36 | /* if true, splash the secret level */ 37 | long didsecret; 38 | 39 | /* previous and next levels, origin 0 */ 40 | long last; 41 | long next; 42 | 43 | long maxkills; 44 | long maxitems; 45 | long maxsecret; 46 | long maxfrags; 47 | 48 | /* the par time */ 49 | long partime; 50 | 51 | /* index of this player in game */ 52 | long pnum; 53 | 54 | wbplayerstruct_t plyr[DOOM_MAXPLAYERS]; 55 | 56 | } wbstartstruct_t; 57 | 58 | typedef void (*stats_callback_t)(wbstartstruct_t *); 59 | 60 | void StatsLaunchDoom(char **args, stats_callback_t callback); 61 | wbstartstruct_t far *StatsGetHandle(long l); 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /test/common.sh: -------------------------------------------------------------------------------- 1 | 2 | TEST_DIR=$(mktemp -d) 3 | TOPLEVEL_DIR=$(dirname $0)/.. 4 | 5 | DOSBOX_COMMON_OPTIONS=$(cat $TOPLEVEL_DIR/test/dosbox.conf) 6 | 7 | # Don't show a window when DOSbox is running; this should be able to 8 | # run entirely headless. 9 | export SDL_VIDEODRIVER=dummy 10 | export SDL_AUDIODRIVER=dummy 11 | 12 | dosbox_index=0 13 | dosbox_pids="" 14 | 15 | dosbox_shutdown() { 16 | if [ "$dosbox_pids" != "" ]; then 17 | kill -HUP $dosbox_pids 18 | fi 19 | rm -rf "$TEST_DIR" 20 | } 21 | 22 | trap dosbox_shutdown INT EXIT 23 | 24 | start_dosbox() { 25 | local dosbox_conf=$TEST_DIR/dosbox-$dosbox_index.conf 26 | local logfile=$TEST_DIR/dosbox-$dosbox_index.log 27 | local batfile=$TEST_DIR/CMDS_$dosbox_index.BAT 28 | 29 | # Batch file contains commands from stdin 30 | cat > $batfile 31 | 32 | # Generate config file 33 | cat >$dosbox_conf <$logfile 2>&1 & 46 | dosbox_pids="$dosbox_pids $!" 47 | } 48 | 49 | wait_dosboxes() { 50 | wait $dosbox_pids 51 | dosbox_pids="" 52 | } 53 | 54 | -------------------------------------------------------------------------------- /test/dosbox.conf: -------------------------------------------------------------------------------- 1 | # Minimal dosbox configuration that disables most features. 2 | 3 | [sdl] 4 | usescancodes = false 5 | fullscreen = false 6 | fullresolution = 640 x 480 7 | 8 | [sblaster] 9 | sbtype = none 10 | oplmode = none 11 | 12 | [render] 13 | aspect = false 14 | scaler = none 15 | frameskip = 10 16 | 17 | [midi] 18 | mpu401 = none 19 | mididevice = none 20 | 21 | [speaker] 22 | pcspeaker = false 23 | tandy = off 24 | disney = false 25 | 26 | [joystick] 27 | joysticktype = none 28 | 29 | [gus] 30 | gus = false 31 | 32 | [cpu] 33 | cycles = fixed 12000 34 | 35 | [ipx] 36 | ipx = false 37 | -------------------------------------------------------------------------------- /test/exes/README.md: -------------------------------------------------------------------------------- 1 | 2 | Compiled network drivers used for compatibility tests: 3 | 4 | * **ipxttl.exe** - xttl version of IPXSETUP. 5 | 6 | * **ipx1.exe** - Original ("Doom 1") version of IPXSETUP, recompiled 7 | with very minor changes to run fakedoom.exe instead of doom.exe. 8 | 9 | * **ipx2.exe** - Original ("Doom 2") version of IPXSETUP, recompiled 10 | with very minor changes to run fakedoom.exe instead of doom2.exe. 11 | This version of IPXSETUP (v1.22) shipped with Doom II v1.7. 12 | 13 | * **ser1.exe** - Original ("Doom 1") version of SERSETUP, recompiled 14 | with very minor changes to run fakedoom.exe instead of doom.exe. 15 | 16 | * **ser2.exe** - Original ("Doom 2") version of SERSETUP, recompiled 17 | with very minor changes to run fakedoom.exe instead of doom2.exe. 18 | This version of SERSETUP (v1.4) shipped with Doom II v1.7. 19 | 20 | * **hacks.diff** - Diff of all changes against the original released source 21 | code to the original network drivers listed above. 22 | 23 | -------------------------------------------------------------------------------- /test/exes/hacks.diff: -------------------------------------------------------------------------------- 1 | diff --color -ur orig/ipx1/doomnet.c hacks/ipx1/doomnet.c 2 | --- orig/ipx1/doomnet.c 1994-02-13 15:03:58.000000000 -0500 3 | +++ hacks/ipx1/doomnet.c 2023-08-28 14:09:32.788160565 -0400 4 | @@ -103,7 +103,7 @@ 5 | newargs[_argc+2] = NULL; 6 | 7 | // spawnv (P_WAIT, "m:\\newdoom\\doom", newargs); 8 | - spawnv (P_WAIT, "doom", newargs); 9 | + spawnv(P_WAIT, "test\\fakedoom", newargs); 10 | 11 | printf ("Returned from DOOM\n"); 12 | 13 | diff --color -ur orig/ipx2/doomnet.c hacks/ipx2/doomnet.c 14 | --- orig/ipx2/doomnet.c 1994-09-20 13:04:08.000000000 -0400 15 | +++ hacks/ipx2/doomnet.c 2023-08-28 14:09:36.136148635 -0400 16 | @@ -8,8 +8,8 @@ 17 | #include 18 | 19 | #include "doomnet.h" 20 | -//#include "ipxstr.h" 21 | -#include "ipx_frch.h" // FRENCH VERSION 22 | +#include "ipxstr.h" 23 | +//#include "ipx_frch.h" // FRENCH VERSION 24 | 25 | doomcom_t doomcom; 26 | int vectorishooked; 27 | @@ -60,10 +60,7 @@ 28 | newargs[_argc+1] = adrstring; 29 | newargs[_argc+2] = NULL; 30 | 31 | - if (!access("doom2.exe",0)) 32 | - spawnv (P_WAIT, "doom2", newargs); 33 | - else 34 | - spawnv (P_WAIT, "doom", newargs); 35 | + spawnv(P_WAIT, "test\\fakedoom", newargs); 36 | 37 | #ifdef DOOM2 38 | printf (STR_RETURNED"\n"); 39 | diff --color -ur orig/ipx2/ipxsetup.c hacks/ipx2/ipxsetup.c 40 | --- orig/ipx2/ipxsetup.c 1994-09-20 13:19:30.000000000 -0400 41 | +++ hacks/ipx2/ipxsetup.c 2023-08-27 23:16:16.247023252 -0400 42 | @@ -12,8 +12,8 @@ 43 | #include 44 | 45 | #include "ipxnet.h" 46 | -//#include "ipxstr.h" 47 | -#include "ipx_frch.h" // FRENCH VERSION 48 | +#include "ipxstr.h" 49 | +//#include "ipx_frch.h" // FRENCH VERSION 50 | 51 | int gameid; 52 | int numnetnodes; 53 | diff --color -ur orig/ser1/doomnet.c hacks/ser1/doomnet.c 54 | --- orig/ser1/doomnet.c 1994-02-13 14:49:28.000000000 -0500 55 | +++ hacks/ser1/doomnet.c 2023-08-28 14:09:44.716117918 -0400 56 | @@ -101,7 +101,7 @@ 57 | newargs[_argc+2] = NULL; 58 | 59 | // spawnv (P_WAIT, "m:\\newdoom\\doom", newargs); 60 | - spawnv (P_WAIT, "doom", newargs); 61 | + spawnv(P_WAIT, "test\\fakedoom", newargs); 62 | 63 | printf ("Returned from DOOM\n"); 64 | 65 | diff --color -ur orig/ser1/sersetup.c hacks/ser1/sersetup.c 66 | --- orig/ser1/sersetup.c 1994-02-13 16:00:24.000000000 -0500 67 | +++ hacks/ser1/sersetup.c 2023-08-27 23:53:25.460068339 -0400 68 | @@ -393,7 +393,7 @@ 69 | int mcr; 70 | FILE *f; 71 | 72 | - f = fopen ("modem.cfg","r"); 73 | + f = fopen ("test\\modem.cfg","r"); 74 | if (!f) 75 | Error ("Couldn't read MODEM.CFG"); 76 | ReadLine (f, startup); 77 | @@ -427,9 +427,11 @@ 78 | 79 | ModemCommand(cmd); 80 | ModemResponse ("CONNECT"); 81 | + /* 82 | if (strncmp (response+8,"9600",4) ) 83 | Error ("The connection MUST be made at 9600 baud, no error correction, no compression!\n" 84 | "Check your modem initialization string!"); 85 | + */ 86 | doomcom.consoleplayer = 1; 87 | } 88 | 89 | diff --color -ur orig/ser2/doomnet.c hacks/ser2/doomnet.c 90 | --- orig/ser2/doomnet.c 1994-09-20 13:21:58.000000000 -0400 91 | +++ hacks/ser2/doomnet.c 2023-08-28 14:09:46.852110238 -0400 92 | @@ -6,8 +6,8 @@ 93 | #include 94 | #include "doomnet.h" 95 | 96 | -//#include "serstr.h" 97 | -#include "ser_frch.h" // FRENCH VERSION 98 | +#include "serstr.h" 99 | +//#include "ser_frch.h" // FRENCH VERSION 100 | 101 | #define DOOM2 102 | 103 | @@ -109,10 +109,7 @@ 104 | newargs[myargc+2] = NULL; 105 | 106 | // spawnv (P_WAIT, "m:\\newdoom\\doom", newargs); 107 | - if (!access("doom2.exe",0)) 108 | - spawnv (P_WAIT, "doom2", newargs); 109 | - else 110 | - spawnv (P_WAIT, "doom", newargs); 111 | + spawnv(P_WAIT, "test\\fakedoom", newargs); 112 | 113 | #ifdef DOOM2 114 | printf (STR_RETURNED"\n"); 115 | diff --color -ur orig/ser2/port.c hacks/ser2/port.c 116 | --- orig/ser2/port.c 1994-09-21 08:21:38.000000000 -0400 117 | +++ hacks/ser2/port.c 2023-08-27 23:21:14.544953655 -0400 118 | @@ -2,8 +2,8 @@ 119 | 120 | #include "doomnet.h" 121 | #include "sersetup.h" 122 | -//#include "serstr.h" 123 | -#include "ser_frch.h" // FRENCH VERSION 124 | +#include "serstr.h" 125 | +//#include "ser_frch.h" // FRENCH VERSION 126 | 127 | 128 | void jump_start( void ); 129 | diff --color -ur orig/ser2/sersetup.c hacks/ser2/sersetup.c 130 | --- orig/ser2/sersetup.c 1994-09-20 13:20:28.000000000 -0400 131 | +++ hacks/ser2/sersetup.c 2023-08-27 23:53:32.820032503 -0400 132 | @@ -1,8 +1,8 @@ 133 | // sersetup.c 134 | #define DOOM2 135 | #include "sersetup.h" 136 | -//#include "serstr.h" 137 | -#include "ser_frch.h" // FRENCH VERSION 138 | +#include "serstr.h" 139 | +//#include "ser_frch.h" // FRENCH VERSION 140 | #include "DoomNet.h" 141 | 142 | extern que_t inque, outque; 143 | @@ -466,7 +466,7 @@ 144 | FILE *f; 145 | unsigned baud; 146 | 147 | - f = fopen ("modem.cfg","r"); 148 | + f = fopen ("test\\modem.cfg","r"); 149 | if (!f) 150 | Error (STR_CANTREAD); 151 | ReadLine (f, startup); 152 | diff --color -ur orig/ser2/serstr.h hacks/ser2/serstr.h 153 | --- orig/ser2/serstr.h 1994-09-21 08:21:58.000000000 -0400 154 | +++ hacks/ser2/serstr.h 2023-08-27 23:20:33.021237047 -0400 155 | @@ -19,3 +19,7 @@ 156 | #define STR_COMM "Communicating with interrupt vector 0x%x" 157 | #define STR_RETURNED "Returned from DOOM II" 158 | #define STR_PORTSET "Setting port to %lu baud" 159 | +#define STR_PORTLOOK "Looking for UART at port" 160 | +#define STR_CLEARPEND "Resetting pending interrupts.\n" 161 | +#define STR_UART8250 "UART = 8250" 162 | +#define STR_UART16550 "UART = 16550" 163 | -------------------------------------------------------------------------------- /test/exes/ipx1.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fragglet/vanilla-utilities/d3d1181d50ecf1716012955d0641bd7e66870a35/test/exes/ipx1.exe -------------------------------------------------------------------------------- /test/exes/ipx2.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fragglet/vanilla-utilities/d3d1181d50ecf1716012955d0641bd7e66870a35/test/exes/ipx2.exe -------------------------------------------------------------------------------- /test/exes/ipxttl.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fragglet/vanilla-utilities/d3d1181d50ecf1716012955d0641bd7e66870a35/test/exes/ipxttl.exe -------------------------------------------------------------------------------- /test/exes/ser1.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fragglet/vanilla-utilities/d3d1181d50ecf1716012955d0641bd7e66870a35/test/exes/ser1.exe -------------------------------------------------------------------------------- /test/exes/ser2.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fragglet/vanilla-utilities/d3d1181d50ecf1716012955d0641bd7e66870a35/test/exes/ser2.exe -------------------------------------------------------------------------------- /test/fakedoom.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 2019-2023 Simon Howard 3 | // 4 | // This program is free software; you can redistribute it and/or 5 | // modify it under the terms of the GNU General Public License 6 | // as published by the Free Software Foundation; either version 2 7 | // of the License, or (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | 15 | // Fake standin for doom.exe that simulates use of some of its interfaces. 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include "lib/inttypes.h" 23 | 24 | #include "lib/flag.h" 25 | #include "lib/log.h" 26 | #include "net/doomnet.h" 27 | 28 | #define MAGIC_NUMBER 0x83b3 29 | 30 | struct test_packet 31 | { 32 | unsigned int magic; 33 | int consoleplayer; 34 | int secret; 35 | }; 36 | 37 | static int myargc; 38 | static char **myargv; 39 | 40 | FILE *log; 41 | int net_secret = 1337; 42 | doomcom_t far *doomcom = NULL; 43 | 44 | static void ReadResponseFile(void) 45 | { 46 | static char responsebuf[256]; 47 | static char *response_args[32]; 48 | char *p, *r; 49 | FILE *fs; 50 | int i, nbytes; 51 | 52 | for (i = 1; i < myargc; i++) 53 | { 54 | if (myargv[i][0] == '@') 55 | { 56 | break; 57 | } 58 | } 59 | if (i >= myargc) 60 | { 61 | return; 62 | } 63 | 64 | memcpy(response_args, myargv, sizeof(char *) * myargc); 65 | 66 | fs = fopen(myargv[i] + 1, "rb"); 67 | assert(fs != NULL); 68 | nbytes = fread(responsebuf, 1, sizeof(responsebuf), fs); 69 | responsebuf[nbytes] = '\0'; 70 | fclose(fs); 71 | 72 | p = responsebuf; 73 | for (;;) 74 | { 75 | r = strchr(p, '\n'); 76 | if (r == NULL) 77 | { 78 | break; 79 | } 80 | 81 | *r = '\0'; 82 | if (*(r - 1) == '\r') 83 | { 84 | *(r - 1) = '\0'; 85 | } 86 | response_args[myargc] = p; 87 | ++myargc; 88 | p = r + 1; 89 | } 90 | 91 | myargv = response_args; 92 | } 93 | 94 | static char **CheckParm(char *name, int nargs) 95 | { 96 | int i; 97 | 98 | for (i = 1; i < myargc; i++) 99 | { 100 | if (strcmp(myargv[i], name) != 0) 101 | { 102 | continue; 103 | } 104 | if (i + nargs >= myargc) 105 | { 106 | Error("%s needs %d arguments", name, nargs); 107 | } 108 | return myargv + i + 1; 109 | } 110 | 111 | return NULL; 112 | } 113 | 114 | static void SendTestPackets(void) 115 | { 116 | struct test_packet far *pkt = (void far *) doomcom->data; 117 | int i; 118 | 119 | for (i = 1; i < doomcom->numnodes; ++i) 120 | { 121 | pkt->magic = MAGIC_NUMBER; 122 | pkt->consoleplayer = doomcom->consoleplayer; 123 | pkt->secret = net_secret; 124 | doomcom->datalength = sizeof(struct test_packet); 125 | doomcom->remotenode = i; 126 | NetSendPacket(doomcom); 127 | } 128 | } 129 | 130 | static void RunNetworkTest(void) 131 | { 132 | struct test_packet far *pkt = (void far *) doomcom->data; 133 | clock_t now, end_time, last_send = 0; 134 | int secrets[MAXPLAYERS]; 135 | int i, got_nodes; 136 | 137 | LogMessage("Running network test with %d nodes", doomcom->numnodes); 138 | 139 | secrets[doomcom->consoleplayer] = net_secret; 140 | got_nodes = 1 << 0; 141 | 142 | end_time = 0; 143 | 144 | do 145 | { 146 | CheckAbort("Network test"); 147 | now = clock(); 148 | if (now - last_send > CLOCKS_PER_SEC / 2) 149 | { 150 | SendTestPackets(); 151 | last_send = now; 152 | } 153 | 154 | if (!NetGetPacket(doomcom)) { 155 | continue; 156 | } 157 | 158 | if (doomcom->datalength != sizeof(struct test_packet)) { 159 | LogMessage("Packet from %d wrong length, %d != %d", 160 | doomcom->remotenode, doomcom->datalength, 161 | sizeof(struct test_packet)); 162 | continue; 163 | } 164 | if (pkt->magic != MAGIC_NUMBER) 165 | { 166 | LogMessage("Packet from %d wrong magic number, %04x != %04x", 167 | doomcom->remotenode, pkt->magic, MAGIC_NUMBER); 168 | continue; 169 | } 170 | secrets[pkt->consoleplayer] = pkt->secret; 171 | got_nodes |= 1 << doomcom->remotenode; 172 | if (got_nodes == (1 << doomcom->numnodes) - 1 && end_time == 0) 173 | { 174 | LogMessage("All nodes found. Waiting before quit."); 175 | end_time = clock() + 5 * CLOCKS_PER_SEC; 176 | } 177 | } while (end_time == 0 || clock() < end_time); 178 | 179 | fprintf(log, "dup=%d extratics=%d\n", doomcom->ticdup, 180 | doomcom->extratics); 181 | for (i = 0; i < doomcom->numplayers; ++i) 182 | { 183 | fprintf(log, "Player %d: secret=%d\n", i + 1, secrets[i]); 184 | } 185 | } 186 | 187 | int main(int argc, char *argv[]) 188 | { 189 | clock_t start; 190 | char **arg; 191 | 192 | myargc = argc; 193 | myargv = argv; 194 | 195 | ReadResponseFile(); 196 | 197 | if (argc < 2) 198 | { 199 | printf("Usage: %s -out [other args]\n\n" 200 | " -secret n Secret number for network test\n", 201 | argv[0]); 202 | exit(1); 203 | } 204 | 205 | arg = CheckParm("-out", 1); 206 | if (arg == NULL) 207 | { 208 | Error("Must supply -out for log"); 209 | } 210 | log = fopen(*arg, "w"); 211 | assert(log != NULL); 212 | 213 | arg = CheckParm("-secret", 1); 214 | if (arg != NULL) 215 | { 216 | net_secret = atoi(*arg); 217 | } 218 | 219 | arg = CheckParm("-net", 1); 220 | if (arg != NULL) 221 | { 222 | doomcom = NetGetHandle(atol(*arg)); 223 | RunNetworkTest(); 224 | } 225 | 226 | fclose(log); 227 | 228 | for (start = clock(); clock() < start + CLOCKS_PER_SEC / 2; ) 229 | { 230 | } 231 | 232 | return 0; 233 | } 234 | 235 | -------------------------------------------------------------------------------- /test/ipxsetup_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TEST_PORT=31234 4 | 5 | set -eu 6 | 7 | . test/common.sh 8 | 9 | check_outputs_match() { 10 | cnt=$1 11 | 12 | # Check the log files look as expected. All players generate an 13 | # identical log file and all secrets are included. 14 | for i in $(seq $cnt); do 15 | diff -u $TEST_DIR/SERVER.TXT $TEST_DIR/CLIENT$i.TXT 16 | done 17 | } 18 | 19 | # Simple test that we can spin up an 8-player netgame. 20 | test_8players() { 21 | start_dosbox < (F) <---- J 13 | # ^ 4006 ^ 4007 14 | # 4004| 4005| 15 | # G H 16 | # 17 | # There is a three-node IPX LAN and the rest of the connections are 18 | # dial-up serial links (the number next to each arrow is the port 19 | # number). 20 | # 21 | # Things that are tested here: 22 | # * This is an eight player game (+ one dedicated forwarding node); 23 | # you could theoretically run a Hexen game over this. 24 | # * Nodes can use at least four underlying drivers (F). 25 | # * There are two nodes running in forwarding mode (B), and they 26 | # quit automatically when there is no more work to be done. 27 | # * There is at least one four-hop route through the network 28 | # (G-D-A-C-F-I). 29 | 30 | set -eu 31 | 32 | . test/common.sh 33 | 34 | # Node A (IPX server, accepts dial-in from D) 35 | start_dosbox <&1 127 | result=1 128 | fi 129 | done 130 | 131 | # Test outputs for all nodes should be identical. 132 | for node in C D E G H I J; do 133 | filename=MNTEST_${node}.TXT 134 | if diff MNTEST_A.TXT $filename 2>&1; then 135 | rm -f $filename 136 | else 137 | result=1 138 | fi 139 | done 140 | 141 | if [ $result -eq 0 ]; then 142 | rm -f MNTEST_A.TXT 143 | fi 144 | 145 | exit $result 146 | 147 | -------------------------------------------------------------------------------- /test/modem.cfg: -------------------------------------------------------------------------------- 1 | ATZ 2 | AT Z H0 3 | 9600 4 | DOSbox modem 5 | 6 | -------------------------------------------------------------------------------- /test/runtests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | tests=" 4 | ipxsetup_test 5 | sersetup_test 6 | sirsetup_test 7 | " 8 | # metanet_test # Just too flaky at the moment 9 | 10 | result=0 11 | 12 | for t in $tests; do 13 | if ./test/${t}.sh; then 14 | echo "PASS: $t" 15 | else 16 | echo "FAIL: $t" 17 | result=1 18 | fi 19 | done 20 | 21 | exit $result 22 | 23 | -------------------------------------------------------------------------------- /test/sersetup_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TEST_PORT1=30328 4 | TEST_PORT2=30329 5 | 6 | set -eu 7 | 8 | . test/common.sh 9 | 10 | start_node1() { 11 | start_dosbox <