Gravis UltraSound under Qemu

Gravis Ultrasound

Back when the Adlib & Sound Blaster cards were king, there also was the far more expensive, and far better sounding Gravis UltraSound. What really separated the GUS from the Adlib cards was a wavetable approach to midi file playback. The GUS was driven by a ‘soft’ table, meaning programs had to upload samples to the card before it could use them.  This was unlike cards like the SoundBlaster AWE editions which stored their wavetables in ROM.

Qemu has included GUS emulation for some time now, however because of the softset files it is rather involved to get working.

The first thing you will need to do is download the soft files here.  You will need to inject them into your MS-DOS disk image.  I prefer to use the following method:

 -hdb fat:xfer/

Where xfer is the directory where I’ve extracted my files. This will make the directory show up as a second hard disk.  Now the drive is read-only so getting files OUT of the emulator still requires either virtual floppy disks (yuck) or a network client.  The archive contains two versions of the wavetables, one that is stock that I couldn’t get to work, and a ‘patched’ version.  Copy the ULTRASND411 directory to c:\ultrasound  .

Next we need to setup the environment variables.  The Gravis UltraSound in Qemu is setup for IO port 240, Irq 7, and DMA channel 3.  As you can see from gus.c in Qemu source:

static Property gus_properties[] = {
DEFINE_PROP_UINT32 (“freq”, GUSState, freq, 44100),
DEFINE_PROP_HEX32 (“iobase”, GUSState, port, 0x240),
DEFINE_PROP_UINT32 (“irq”, GUSState, emu.gusirq, 7),
DEFINE_PROP_UINT32 (“dma”, GUSState, emu.gusdma, 3),

This means we need to setup the following environment varriables in the autoexec.bat

SET ULTRASND=240,3,3,7,7

 Re-launch Qemu and specify the soundcard emulation something like this:

-soundhw gus,pcspk -parallel none

This will give us both the PC speaker, and the Gravis UltraSound.  Additionally this disables the parallel port which also uses IRQ 7.  We should be able to run games now.  For anyone interested, Maraakate has given me a link to share of his GUS archive.

I found this information on a Qemu patch page (before the GUS support was mainlined), and Vogons support for DOSBox & GUS emulation.

I’ve tested Doom, and Heretic which work fine on Qemu 1.6.1 on OS X.

For anyone who cares, this is what the SoundBlaster/Adlib sounds like, and this is the Gravis UltraSound emulation in Qemu.

Or even better, embedded audio!



Doom IPX revisited

So, since I’ve been starting up my virtual network thing, I got distracted with this thread on reddit

Fun times ahead!

Fun times ahead!

, talking about the good old days of Doom.  Now Doom was revolutionary for the music, the sound effects, the 2.5D environment, the fast pace, the violence, the unlimited lives, dynamic lighting and of course LAN play.  Now what was cool is that it could easily run over IPX/SPX networks.  Although you did have to be on the same network it was very easy to setup, and get going. But as anyone can tell you, the moment you setup a four player deathmatch on a company LAN people would go nuts, as you would without a doubt bring the entire network to a halt.  Back when I used to play this we would just disconnect the 10base2 cable from the lab 486DX2/50 PC’s and just make our own four computer segment.  We did notice that if we had 3 four player games on the same network (afterhours!) that it was unplayable.  I never did have access to a sniffer back then, and when I got a corporate job, being caught with something like Doom was grounds for termination so I never did get to see what the big deal was.

So I figured I’d cook up a super quick lab to check it out.  This is my Dynagen configuration.  Sadly I do need a cisco router to preform the network capture, although I don’t need to configure it to see what is going on.

autostart = True


image = ../C7200-JS.BIN
npe = npe-400
ram = 160
idlepc = 0x60529c84
disk0 = 0
mmap = False
ghostios = True

[[ROUTER corertr1]]
model = 7200
slot1 = PA-8E
F0/0 = coresw1 5

[[ethsw coresw1]]
1 = access 1 NIO_udp:41300:
2 = access 1 NIO_udp:41301:
3 = access 1 NIO_udp:41302:
4 = access 1 NIO_udp:41303:
5 = access 1

As you can see this is very simple, a switch with four ports setup for four virtual computers, and a fifth port for the router.  Next I run four instances of Qemu with a copy of Doom v1.1 shareware, and a IPX/SPX client.  I fire them up like this:

./qemu/qemu-system-i386 -cpu pentium -L ./qemu/pc-bios/ -m 16 -hda doom11-00.img -soundhw sb16,adlib -net nic,model=pcnet,macaddr=00:11:22:33:44:00 -net socket,udp=localhost:41300,localaddr= &
./qemu/qemu-system-i386 -cpu pentium -L ./qemu/pc-bios/ -m 16 -hda doom11-01.img -soundhw sb16,adlib -net nic,model=pcnet,macaddr=00:11:22:33:44:01 -net socket,udp=localhost:41301,localaddr= &
./qemu/qemu-system-i386 -cpu pentium -L ./qemu/pc-bios/ -m 16 -hda doom11-02.img -soundhw sb16,adlib -net nic,model=pcnet,macaddr=00:11:22:33:44:02 -net socket,udp=localhost:41302,localaddr= &
./qemu/qemu-system-i386 -cpu pentium -L ./qemu/pc-bios/ -m 16 -hda doom11-03.img -soundhw sb16,adlib -net nic,model=pcnet,macaddr=00:11:22:33:44:03 -net socket,udp=localhost:41303,localaddr= &

Again all very simple.

From there I configure each of the four VM’s for a four player game, start the game, then connect.

Loneliest deathmatch

Loneliest deathmatch

I only had the game running for a few seconds when I went to my Dynagen console, and setup a capture session:

=> capture corertr1 fa0/0 doom.cap

As you can see very simple.  I walk the guys around a little bit, shoot someone, then just quit all the clients, and turn off the VM’s.  Then I end the capture

=> no capture corertr1 fa0/0

And now I’m left with a 50MB file!

$ ls -alh *cap
-rw-r–r– 1 jsteve staff 50M 25 Oct 14:27 doom.cap

I’m shocked!  I wasn’t even running Doom that long!  A quick peek reveals why Doom was never popular on corporate networks.

14:25:57.464031 IPX 00000000.00:11:22:33:44:00.869c > 00000000.ff:ff:ff:ff:ff:ff.869c: ipx-#869c 444

14:27:28.996489 IPX 00000000.00:11:22:33:44:01.869c > 00000000.ff:ff:ff:ff:ff:ff.869c: ipx-#869c 444
14:27:28.997618 IPX 00000000.00:11:22:33:44:03.869c > 00000000.ff:ff:ff:ff:ff:ff.869c: ipx-#869c 444
14:27:29.019568 IPX 00000000.00:11:22:33:44:01.869c > 00000000.ff:ff:ff:ff:ff:ff.869c: ipx-#869c 444
14:27:29.026000 IPX 00000000.00:11:22:33:44:03.869c > 00000000.ff:ff:ff:ff:ff:ff.869c: ipx-#869c 444
14:27:29.026205 IPX 00000000.00:11:22:33:44:01.869c > 00000000.ff:ff:ff:ff:ff:ff.869c: ipx-#869c 444
14:27:29.054425 IPX 00000000.00:11:22:33:44:01.869c > 00000000.ff:ff:ff:ff:ff:ff.869c: ipx-#869c 444

If you see the destination it is ff:ff:ff:ff:ff:ff, a broadcast packet.  And I have 50MB of this in the span of a minute and a half!  Even worse, opening the packet stream most of it is empty packets!

14:26:22.806014 IPX 00000000.00:11:22:33:44:00.869c > 00000000.ff:ff:ff:ff:ff:ff.869c: ipx-#869c 444
0x0000: e0e0 03ff ff01 da00 0000 0000 00ff ffff …………….
0x0010: ffff ff86 9c00 0000 0000 1122 3344 0086 ………..”3D..
0x0020: 9c00 0000 0000 0000 0001 0000 0001 0000 …………….
0x0030: 0002 0000 0000 0000 0000 0065 0000 0000 ………..e….
0x0040: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0x0050: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0x0060: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0x0070: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0x0080: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0x0090: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0x00a0: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0x00b0: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0x00c0: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0x00d0: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0x00e0: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0x00f0: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0x0100: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0x0110: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0x0120: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0x0130: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0x0140: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0x0150: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0x0160: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0x0170: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0x0180: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0x0190: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0x01a0: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0x01b0: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0x01c0: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0x01d0: 0000 0000 0000 0000 0069 0000 00 ………i…

Needless to say this could have been optimized a little better.. but at the same time back in 1993 this was the coolest thing ever.  Even if it did have a reputation of destroying networks.

I can remember how loudly network people would complain about Doom, but it is kind of interesting to see it first hand how chatty it is. But at the same time, how exhilarating it was, for the first time we were playing against each other, not bots.

And speaking of this era, for those who may not have ever read about the rise & fall of iD, you may want to check out the book ‘Masters of Doom‘. Although the end is pretty much Old Man Murray. There is no mention of the soundcard disaster that was DMX, or anything about how deathmatch could crush a LAN. But I guess everything can’t be 100% for the time.

Some random updates

First I just found out about the KVM Forum 2013, taking place in Edinburgh, Scotland.

You can find all kinds of information and videos of the presentation on the G+ page!

This is an incredible resource for anyone thinking of deploying KVM (Proxmox/VE!) in a serious setting.  Unlike VMWare ESX this is a free solution with no insane license restrictions.  Not to mention that KVM+Qemu is far more flexible than any traditional x86 focused hypervisor will ever be.  And poor Microsoft still doesn’t yet offer x86_64 solution.

I also got a ping back from Linux Lifestyle, about a challenge to find an ancient version of Linux.  Although the real credit goes to the excellent preservation work of .

Personal note, I got the flu (again!) and have been sick.. which is why the lag in the network stuff, but I’ll bang more on it tomorrow.  I’ll finally get to adding remote sites, routing protocols, and all that fun stuff.  Internet/NAT/Firewalls afterwards.  ASA stuff too, as much as I don’t like them.

Configuring IPX/SPX

Much like my prior article on Configuring TCP/IP, the process for configuring IPX/SPX on a cisco router is pretty much the same thing.

The first big ‘gottcha’ in the world of IPX is that it supports multiple frame types.  This winds up leading to all kinds of troubles when various people setup various servers.  It is inevitable that people pick their own and incompatible frame type.  The only to ‘fix’ this is for everyone to be on the same type.  I have heard of people using different frame types for different environments, much like using 802.1q (trunking) to separate various peoples traffic who do not want to see each-others resources.  But back when IPX was prevalent, people were still using HUBS which mirror all traffic, and primitive layer two switching at best.  Not to mention with the advent of Netware 4, you could have virtual NIC’s in the server and workstations, and bind to all possible frame types.

Things got messy, and quick.

But I digress, for this example I’m going to use the ‘default’ frame type of ETHERNET_802.2 or SAP is cisco speak.  Mostly because I don’t feel like fully configuring the MS-DOS client I managed to dig up, and mostly because at this point (2013) I really don’t care what frame type I use.

I’m going to setup the following IPX networks:

WAN C0000001
SERVER C0010001
USER C0010002

The first step is to enable the ipx protocol on the router.  This is done with the ‘ipx routing’ command.

corertr1#config t
Enter configuration commands, one per line. End with CNTL/Z.
corertr1(config)#ipx routing

Now the IPX protocol is enabled.  The next step is to configure the ethernet interfaces.  This is pretty straightfoward, we put in the network numbers, and assign the correct frame types.

corertr1#config t
Enter configuration commands, one per line. End with CNTL/Z.
corertr1(config)#int eth1/0
corertr1(config-if)#ipx encapsulation SAP
corertr1(config-if)#ipx network c0010001
corertr1(config)#int eth1/1
corertr1(config-if)#ipx encapsulation SAP
corertr1(config-if)#ipx network c0010002
corertr1(config)#int fa0/0
corertr1(config-if)#ipx network c0000001
corertr1(config-if)#ipx encapsulation SAP

Now we can quickly check the interfaces that IPX is running on with the ‘show ipx interface brief’ command.  You should get something like this:

corertr1#show ipx interface brief
Interface IPX Network Encapsulation Status IPX State
FastEthernet0/0 C0000001 SAP up [up]
FastEthernet0/1 unassigned not config’d admin down n/a
Ethernet1/0 C0010001 SAP up [up]
Ethernet1/1 C0010002 SAP up [up]
Ethernet1/2 unassigned not config’d admin down n/a
Ethernet1/3 unassigned not config’d admin down n/a
Ethernet1/4 unassigned not config’d admin down n/a
Ethernet1/5 unassigned not config’d admin down n/a
Ethernet1/6 unassigned not config’d admin down n/a
Ethernet1/7 unassigned not config’d admin down n/a

So far, so good.  Now the next question is, does the router see the server?  And what kind of services are available on the network?  This can be found with the ‘sho ipx servers’ command.

corertr1#sho ipx servers
Codes: S – Static, P – Periodic, E – EIGRP, N – NLSP, H – Holddown, + = detail
U – Per-user static
3 Total IPX Servers

Table ordering is based on routing and server info

Type Name Net Address Port Route Hops Itf
P 4 FPNWDC_NW DEAD0001.0000.0000.0001:0451 2/01 1 Et1/0
P 444 VIRTUALLYFUN!FPNWDC DEAD0001.0000.0000.0001:84C8 2/01 1 Et1/0
P 640 FPNWDC DEAD0001.0000.0000.0001:E885 2/01 1 Et1/0

Everything is looking good!  As you can see my virtual Netware server (FPNW on NT 4.0) is type 4, and called FPNWDC_NW.  SAP types 444 and 640 are for Windows NT, with 444 being the NetBIOS Browser/Domain control service.

IPX is a rather chatty protocol, and it is easy for things to go wrong.  Another fun command is ‘show ipx traffic’ which will let you get some idea of what kind of chat is going on.

corertr1#show ipx traffic
System Traffic for 0.0000.0000.0001 System-Name: corertr1
Time since last clear: never
Rcvd: 149 total, 34 format errors, 0 checksum errors, 0 bad hop count,
100 packets pitched, 29 local destination, 0 multicast
Bcast: 126 received, 64 sent
Sent: 79 generated, 18 forwarded
0 encapsulation failed, 2 no route
SAP: 0 Total SAP requests, 0 Total SAP replies, 3 servers
0 SAP general requests received, 4 sent, 0 ignored, 0 replies
0 SAP Get Nearest Server requests, 0 replies
0 SAP Nearest Name requests, 0 replies
0 SAP General Name requests, 0 replies
18 SAP advertisements received, 15 sent, 0 Throttled
4 SAP flash updates sent, 0 SAP format errors
RIP: 2 RIP requests, 0 ignored, 2 RIP replies, 4 routes
9 RIP advertisements received, 30 sent 0 Throttled
7 RIP flash updates sent, 0 atlr sent
4 RIP general requests sent
0 RIP format errors
Echo: Rcvd 0 requests, 0 replies
Sent 10 requests, 0 replies
0 unknown: 0 no socket, 0 filtered, 0 no helper
0 SAPs throttled, freed NDB len 0
0 packets received, 0 replies spoofed
Queue lengths:
IPX input: 0, SAP 0, RIP 0, GNS 0
SAP throttling length: 0/(no limit), 0 nets pending lost route reply
Delayed process creation: 0
EIGRP: Total received 0, sent 0
Updates received 0, sent 0
Queries received 0, sent 0
Replies received 0, sent 0
SAPs received 0, sent 0
Trace: Rcvd 0 requests, 0 replies
Sent 0 requests, 0 replies

In this brief guide, I’m not going to even get into all of this, however it is important to know that the router has sent things, and received things back.

Another thing that is different about IPX vs TCP/IP is that each server has it’s own internal network, where the services originate from.  You can see this with the show ipx route command

corertr1#show ipx route
Codes: C – Connected primary network, c – Connected secondary network
S – Static, F – Floating static, L – Local (internal), W – IPXWAN
R – RIP, E – EIGRP, N – NLSP, X – External, A – Aggregate
s – seconds, u – uses, U – Per-user static/Unknown, H – Hold-down

4 Total IPX routes. Up to 1 parallel paths and 16 hops allowed.

No default route known.

C C0000001 (SAP), Fa0/0
C C0010001 (SAP), Et1/0
C C0010002 (SAP), Et1/1
R DEAD0001 [02/01] via C0010001.5254.0012.3456, 10s, Et1/0

As you can see from the SAP advertisements above, and the route here, the server is configured for it’s internal network to be DEAD0001.  The router picked up the advertisement from C0010001.5254.0012.3456, which breaks down as c0010001 being the network the server is on (the server network), and 5254.0012.3456 being the MAC address of the server.

The real test comes from trying to use a client.

Screen Shot 2013-10-22 at 1.26.35 PM

Netware 3.12 client

As you can see, this MS-DOS client attached to the client vlan, can see and interoperate with the virtual NetWare server.

This pretty much covers the basics of getting IPX/SPX working.  Logically the next thing to do would be to configure routing and get IPX working throughout the WAN.  But I’m going to save that for a later time.

The more ‘advanced’ topics of IPX involve filtering, as it was a common problem in large networks where you could simply have too many servers, and they would be constantly talking among themselves.  Another problem is licensing, where some products are licensed in certain areas, and you want those licenses to stay in a geographic area.  Latency was another issue too, it was insane to say use a SNA server in Japan, to talk to a mainframe in Arizona when you were in Arizona.  Or if you had a 3rd party, and you only wanted them to connect to a print server, and a single file server, you would setup access-lists on your peer router, much in the same way that we setup firewalls in this fine modern age 😉

Qemu 1.6.1 released!

As always, the source code is here.

New updates from the changelog:


This release contains 48 build/bug fixes, including an
important security fix for CVE-2013-4344 involving SCSI disk

Fixed by:

  scsi: Allocate SCSITargetReq r->buf dynamically

Thank you to everyone involved!

62ecc3a: Update VERSION for 1.6.1 release (Michael Roth)
fdcbe7d: scsi: Allocate SCSITargetReq r->buf dynamically (Asias He)
1b5f770: qemu: Add qemu xen logic for Xen HVM S3 resume (Liu, Jinsong)
bc05a48: qemu: Adjust qemu wakeup (Liu, Jinsong)
ba20326: coroutine: add ./configure --disable-coroutine-pool (Stefan Hajnoczi)
ae00a27: piix4: disable io on reset (Michael S. Tsirkin)
61fbeb6: vmdk: fix cluster size check for flat extents (Fam Zheng)
fc06b43: rbd: avoid qemu_rbd_snap_list() memory leaks (Stefan Hajnoczi)
6bbb9d8: tap: Use numbered tap/tun devices on all *BSD OS's (Brad Smith)
b314120: iov: avoid "orig_len may be used unitialized" warning (Michael Tokarev)
dc6fbaa: xhci: emulate intr endpoint intervals correctly (Gerd Hoffmann)
c8adc0d: virtio-blk: do not relay a previous driver's WCE configuration to the 
current (Paolo Bonzini)
aeab582: blockdev: do not default to true (Paolo Bonzini)
5c20c1f: tci: Fix qemu-alpha on 32 bit hosts (wrong assertions) (Stefan Weil)
5d2de77: kvmvapic: Clear also physical ROM address when entering INACTIVE state 
(Jan Kiszka)
7ea8a3c: kvmvapic: Enter inactive state on hardware reset (Jan Kiszka)
50b31e8: kvmvapic: Catch invalid ROM size (Jan Kiszka)
4b5b472: chardev: fix pty_chr_timer (Gerd Hoffmann)
76f6989: pcnet-pci: mark I/O and MMIO as LITTLE_ENDIAN (Aurelien Jarno)
8b4b3a7: Fix enum struct sizes on i686 (Cole Robinson)
41900b0: pc_q35: Initialize Xen. (Anthony PERARD)
755ec4c: pc: Initializing ram_memory under Xen. (Anthony PERARD)
dc0973b: qxl: fix local renderer (Gerd Hoffmann)
b6d163f: ehci: save device pointer in EHCIState (Gerd Hoffmann)
a1991d0: ne2000: mark I/O as LITTLE_ENDIAN (Aurelien Jarno)
1110014: exec: check offset_within_address_space for register subpage (Hu Tao)
2a93d3d: Revert "memory: Return -1 again on reads from unsigned regions" (Jan 
7ab1044: memory: Provide separate handling of unassigned io ports accesses (Jan 
e8601a4: w32: Fix access to host devices (regression) (Stefan Weil)
96b14d0: usb: parallelize usb3 streams (Gerd Hoffmann)
9dbfbb8: xhci: reset port when disabling slot (Gerd Hoffmann)
57ea2d2: exec: always use MADV_DONTFORK (Andrea Arcangeli)
1cd7138: virtio_pci: fix level interrupts with irqfd (Michael S. Tsirkin)
9fab8e1: exec: fix writing to MMIO area with non-power-of-two length (Paolo 
2ffbe03: adlib: sort offsets in portio registration (Hervé Poussineau)
f9fd82e: target-i386: fix disassembly with PAE=1, PG=0 (Paolo Bonzini)
da4e203: block: expect errors from bdrv_co_is_allocated (Paolo Bonzini)
c09a463: Revert "usb-hub: report status changes only once" (Gerd Hoffmann)
c0a5eb8: xhci: fix endpoint interval calculation (Gerd Hoffmann)
358bb0d: virtio: virtqueue_get_avail_bytes: fix desc_pa when loop over the 
indirect descriptor table (yinyin)
3fe494e: pseries: Fix stalls on hypervisor virtual console (Anton Blanchard)
a73c74f: pc: fix regression for 64 bit PCI memory (Michael S. Tsirkin)
964e0d4: scsi: Fix scsi_bus_legacy_add_drive() scsi-generic with serial (Markus 
11b0ab7: usb/dev-hid: Modified usb-tablet category from Misc to Input (Marcel 
d6dcfd6: scripts/ Avoid syntax not supported by Python 2.4 (Peter 
2607906: rdma: silly ipv6 bugfix (Michael R. Hines)
52f99b0: target-ppc: fix bit extraction for FPBF and FPL (Aurelien Jarno)
c0c080c: gdbstub: Fix gdb_register_coprocessor() register counting (Andreas 
670599a: block: ensure bdrv_drain_all() works during bdrv_delete() (Stefan 

I have not done a build as of yet.