Cogwheel on Google Code

Sunday, 24th February 2008

On bakery2k1's suggestion, I took a look at my sprite collision flag code. It only checked for collisions between sprites appearing in the foreground - sprites that were hidden behind a background tile weren't checked, which caused the Fantastic Dizzy bug.

FantasticDizzyFixedSpriteCollision.png

I have decided to give Google's free project hosting a whirl. As usual, they can't get it working in Opera flaming.gif (no big surprises there) and it's horrifically slow, but I can't really complain given that it's free, and the issue tracking is pretty handy.

Gauntlet.png

Another game that has been fixed is the conversion of that arcade classic, Gauntlet. This was partially due to buggy controller port emulation, but a long-standing bug has been the lack of the y-scrolling inhibition flag. Games could set a flag on the VDP that would prevent the rightmost 64 pixels from scrolling vertically - this is useful to create a locked status bar on the right for a vertical scrolling game, for example.

SmsVdpTest1.png SmsVdpTest2.png

I'm slowly getting there with FluBBa's VDP tester...

/INTerminable Interrupt Problems

Friday, 22nd February 2008

WCLBGolf1.png WCLBGolf2.png
World Class Leaderboard Golf

Well, I've just about got it the way it was before with the new interrupt code; most games work again, but the few that have always given problems (such as Desert Speedtrap) still don't work properly. sad.gif I think this is the stage where I have to start trawling through disassemblies to try and work out why they're not working.

CosmicSpacehead.png
Cosmic Spacehead - one of the few games to use the 256×224 mode.

One problem I still haven't got to the bottom of is with the Dizzy games. They either reset when starting a new game or lock up. I'm hoping this is a problem with my implementation of the Codemasters mapper. I guess that The Excellent Dizzy Collection has a simple front-end game selection screen that switches to the requisite ROM page for the selected game, then jumps to the start of that page - in my case this jumps back to the initial Codemasters screen.

I'm not sure where Fantastic Dizzy's problems originate. It looks like an interrupt problem (maybe the palette should be switched when the VDP has finished with the status bar at the top?), but could also be related to the other Codemasters problems.

Interestingly, Micro Machines (1 and 2) and Cosmic Spacehead - both using the Codemasters ROM mapper - seem to work fine.

Interrupts: A Fresh Start

Thursday, 21st February 2008

I gave in and rewrote all of the Z80's interrupt emulation from scratch, finding some rather horrible bugs in the existing implementation as I went.

Some of the highlights included non-maskable interrupts ignoring the state of the IFF1 flag (this flag is automatically cleared when an interrupt is serviced, and is used to prevent the interrupt handler from being called again before it has finished) and the RETN instruction not copying the state of the IFF2 flag back to IFF1. When non-maskable interrupts are serviced, the state of IFF1 is copied to IFF2 before it gets cleared, the idea being that if you use RETN interrupts are automatically re-enabled on exit of the NMI ISR. (Contrast this with maskable interrupts, where both flags are cleared, and you need an explicit EI to re-enable them).

The HALT instruction (executes NOPs until an interrupt is requested or the CPU is reset) was also completely incorrectly (and bizarrely) implemented. The rewrite just sets a Halted property, which prevents the CPU from fetching or executing any instructions. The interrupt-triggering code simply resets this property.

This has fixed numerous bugs (I'm not sure when they were introduced, as it was all working a while back). It's gone from "not working at all" to "just about working", but some games or demos that rely on precise interrupt timing don't work properly.

HicolorDemoCorruption.png DesertSpeedtrapMisaligned.png
Game Gear Hicolor Demo and Desert Speedtrap

Both problems in the above screenshots relate to line-based interrupts from the VDP (Video Display Processor). Some other games simply hang at startup. sad.gif

Loading ROMs

Wednesday, 20th February 2008

OutRunEuropa1.png OutRunEuropa3.png
OutRun Europa

I've been adding a series of new features to Cogwheel. I wrote a basic zip file class a while back (only supporting store and deflate compression - the majority of zip files use deflate), and have added a Utility namespace with methods for help with loading ROMs.

For example, there is a method that is passed a filename by reference, and it'll return an array of bytes of the loaded file. If the passed filename was a zip file, by any chance, it'll search for ROMs inside the zip, and modify the filename (so it might end up as C:\Path\SomeFile.zip\Game.sms).

Once this is done, the CRC-32 checksum of the file is calculated then looked up against a database of known ROM dumps. I'm currently using the SMS Checker .romdata files as the database source. These contain information about known bad dumps, and can be used to strip headers and footers, patch bytes and remove redundant overdump data to end up with a valid ROM image.

Finally, the mapper is detected. Currently, I have three mappers - RAM (which is just 64KB of RAM), Standard (the standard Sega Master System ROM mapper) and Codemasters. I use the RAM mapper for all SG-1000 and SC-3000 games (I'm not sure what they're meant to use, here, but some games - such as The Castle - don't work with the standard SMS mapper).

The Codemasters mapper uses a different method to swap pages, which means that the conventional SMS BIOS checksumming code fails to swap in pages correctly and thus doesn't check the entire cartridge. The games therefore have their own checksumming routines and store their own checksum in a Codemasters-specific header; this makes detecting Codemasters games fairly easy, as all you need to do is check for the extra Codemasters checksum.

As the BIOS doesn't check the checksum, the cartridge provides code to do so itself. You can run it by holding 1 and 2 down as the game boots.

ExcellentDizzyCollectionChecksum.png
The Excellent Dizzy Collection
MicroMachinesChecksum.png
Micro Machines

I seem to have broken interrupts somewhere along the line; most noticable are non-maskable interrupts (as generated by the Pause button) which crash the emulator (it appears that once they fire, interrupts are never re-enabled). I'm not sure what's causing this, but hopefully it won't be too unpleasant to fix!

Game Gear LCD Scaling

Thursday, 14th February 2008

The Game Gear's hardware is very similar indeed to the Master System's - so similar that you can play Master System games on a Game Gear via a special adapter. Some Game Gear games were just the Master System ROMs in a Game Gear cartridge. smile.gif

That said, the Game Gear's LCD is only 160×144, and the Master System has a resolution of 256×192 (or 256×224 or 256×240, but those modes were very rarely used). In Game Gear mode, this resolution is simply cropped. In Master System mode something more interesting has to be done to scale this down to fit the LCD.

I won't bore you with the details here, but will refer you to a post here on the subject. Using the research, I added a mode to the VDP emulator that would mimic this scaling.

pop_comparison.jpg

not_only_words_comparison.jpg

This is old news, of course. I have nothing especially new to report, but I have pretty much entirely rewritten the emulator now (apart from the Z80 emulator, which was fairly well designed). The code was absolutely horrible, and so I've redesigned it to be a lot more flexible and intuitive to use as a library. I've rewritten the standard memory mapper, I/O mapping, and VDP (though I did copy the rasterisation and timing stuff from the old VDP code and cleaned it a little); there's a lot more to do (so far I haven't even reintroduced joypad input) but at least it'll be nicer for me to work with. smile.gif

In the meantime, here are some ugly screenshots of what Master System games look like on the Game Gear LCD.

RoadRashMasterGear.png

BlackBelt.png

PrinceOfPersia.png

RoboCop.png

Necromancy

Friday, 8th February 2008

After seeing Scet and Drilian's work on their respective emulator projects I decided I needed to do something with the stagnating Cogwheel source on my hard disk drive.

The only ROM I have tested where I can't find an explanation for a bug is the Game Gear Garfield: Caught in the Act game. Like many games, when left at the title screen it'll run a demo loop of the game in action. At one point Garfield would walk to the left of the screen, jump over a totem pole, shunt it to the right and use it as a way to jump out of a pit. However, in Cogwheel he would not jump far enough to the left, and not clearing the totem pole he'd just walk back to the right and not have anything to jump out of the pit on.

I remembered a post on MaxCoderz discussing a long-standing tradition of thinking that when a JP <condition>, <address> failed the instruction took a single clock cycle. You can see this misreported here, for example. This document, on the other hand, claims it always takes 10 clock cycles - and most importantly of all, the official user manual backs this up.

Garfield.png

So, Garfield can now get out of his pit. The user interface has changed (again) - I'm now using SlimDX to dump pixels to a Panel, which seems to be the least hassle distribution-wise and doesn't throw LoaderLockExceptions.

PlayStation Controllers

Monday, 4th February 2008

PlayStation controllers are relatively comfortable, and I have a number of them knocking about.

controllers.jpg
From top to bottom - PS2 IR remote control and receiver; Guitar Hero wireless Kramer and receiver; black PS2 DualShock 2 analogue joypad; a pair of grey standard PS1 digital joypads.

As I've learned in the past, a decent gamepad can help with certain games. Of course, what's much more fun than playing the games is trying to work out how these controllers work.

The byte-level protocol is very simple; the PlayStation pulls a select line low (used to grab the attention of the controller) then pulses the clock eight times, writing a bit at a time onto one line and reading another bit at a time from another. This means that the controller and PlayStation end up sending and receiving a byte simultaneously. Finally, the PlayStation checks to see if controller pulls the acknowledge line low to indicate that it received the data; if no acknowledgement is received it assumes that there is no controller on the port it is currently accessing.

All electrical connections are unidirectional, and so a controller can be easily connected to a standard PC's parallel port. There are a number of diagrams floating around the internet using similar pin connections, so I followed one of those.

adapter.jpg

I cut up a pound-shop parallel cable for the PC end and a controller extension cable for the PlayStation end. PlayStation controllers require power; a lot of diagrams I've seen refer to a 9V and 5V supply, some 7.6V and 3.3V. A voltmeter informs me that it's the latter option. Rather than try and draw power from the parallel port, I'm using a generic power supply set to 7.5V. To derive the 3.3V I'm using a 5V regulator followed by two 1A rectifier diodes in series - the diodes provide a voltage drop of 0.7V across each, resulting 3.6V.

I wrote an application in C# that attempted to swap bytes back and forth between the PC and the controller, and was getting good results. I was not, however, having any luck polling the acknowledgement line. It didn't appear to ever go low - my guess was that the program simply couldn't poll the parallel port rapidly enough. Not that this is a slur on C#, of course, but to access the parallel port in the first place I need to use an unmanaged library.

The solution was therefore to write an unmanaged library myself that would handle the PlayStation protocol side of things, which I could then wrap up and add nice functionality to via a C# managed library.

#include "Windows.h"

// inpout32.dll function declarations.
short Inp32(short portAddress);
void Out32(short portAddress, short data);

/// <summary>Gets the state of the data line.</summary>
/// <param name="portAddress">Base address of the parallel port.</param>
/// <returns>The state of the data line.</returns>
extern "C" __declspec(dllexport) bool GetData(short portAddress) {
	return (Inp32(portAddress + 1) & (1 << 6)) != 0;
}

/// <summary>Gets the state of the acknowledge line.</summary>
/// <param name="portAddress">Base address of the parallel port.</param>
/// <returns>The state of the data line.</returns>
extern "C" __declspec(dllexport) bool GetAcknowledge(short portAddress) {
	return (Inp32(portAddress + 1) & (1 << 5)) != 0;
}

/// <summary>Sets the state of the command line.</summary>
/// <param name="portAddress">Base address of the parallel port.</param>
/// <param name="state">The state to set the command line to.</param>
extern "C" __declspec(dllexport) void SetCommand(short portAddress, bool state) {
	Out32(portAddress, (Inp32(portAddress) & ~0x01) | (state ? 0x01 : 0x00));
}

/// <summary>Sets the state of the select line.</summary>
/// <param name="portAddress">Base address of the parallel port.</param>
/// <param name="state">The state to set the select line to.</param>
extern "C" __declspec(dllexport) void SetSelect(short portAddress, bool state) {
	Out32(portAddress, (Inp32(portAddress) & ~0x02) | (state ? 0x02 : 0x00));
}

/// <summary>Sets the state of the clock line.</summary>
/// <param name="portAddress">Base address of the parallel port.</param>
/// <param name="state">The state to set the clock line to.</param>
extern "C" __declspec(dllexport) void SetClock(short portAddress, bool state) {
	Out32(portAddress, (Inp32(portAddress) & ~0x04) | (state ? 0x04 : 0x00));
}

/// <summary>Begins a data transfer by pulling select low.</summary>
/// <param name="portAddress">Base address of the parallel port.</param>
extern "C" __declspec(dllexport) void BeginTransfer(short portAddress) {
	SetSelect(portAddress, false);
}

/// <summary>Ends a data transfer by releasing select high.</summary>
/// <param name="portAddress">Base address of the parallel port.</param>
extern "C" __declspec(dllexport) void EndTransfer(short portAddress) {
	SetSelect(portAddress, true);
}


/// <summary>Exchanges a byte between the PlayStation controller and the PC.</summary>
/// <param name="portAddress">Base address of the parallel port.</param>
/// <param name="data">The data to exchange.</param>
/// <returns>True if the transmission was acknowledged, false if it timed out.</returns>
extern "C" __declspec(dllexport) bool ExchangeByte(short portAddress, unsigned char* data) {

	DWORD TimeoutStart = GetTickCount();

	for (int i = 0; i < 8; ++i) {
		SetClock(portAddress, false);
		SetCommand(portAddress, (*data & (1 << i)) != 0);
		SetClock(portAddress, true);		
		if (GetData(portAddress)) {
			*data |= (1 << i);
		} else {
			*data &= ~(1 << i);
		}
	}

	while (GetAcknowledge(portAddress)) {
		if ((GetTickCount() - TimeoutStart) > 10) return false;
	}

	while (!GetAcknowledge(portAddress)) {
		if ((GetTickCount() - TimeoutStart) > 10) return false;
	}

	return true;

}

/// <summary>Exchanges a block of data between the PlayStation controller and the PC.</summary>
/// <param name="portAddress">Base address of the parallel port.</param>
/// <param name="command">The command byte to send.</param>
/// <param name="data">The data to exchange (input and output).</param>
/// <param name="numberOfElements">The size of data in bytes.</param>
/// <returns>The size of the received packet.</returns>
extern "C" __declspec(dllexport) int SendPacket(short portAddress, unsigned char* command, unsigned char* data, int numberOfElements) {

	// Start by sending 0x01.
	unsigned char ToExchange = 0x01;
	if (!ExchangeByte(portAddress, &ToExchange)) return 0;

	// Next send the command byte.
	// Controller will respond with packet size and mode.
	if (!ExchangeByte(portAddress, command)) return 0;
	
	// Check for end-of-header.
	ToExchange = 0x00;
	if (!ExchangeByte(portAddress, &ToExchange) || ToExchange != 0x5A) return 0;

	// Fix the "numberOfElements" to only try and fetch the number of bytes that are in the packet.
	numberOfElements = min(numberOfElements, (*command & 0xF) * 2);

	for (int i = 0; i < numberOfElements; ++i) {
		if (!ExchangeByte(portAddress, &data[i])) return i + 1;
	}

	return numberOfElements;
}

I'm not much of a C++ programmer, so I hope the above isn't too offensive.

Polling a standard digital joypad or a dual analogue is pretty straightforwards - send 0x42 to the device, and it'll return the status of each button as a bitfield 2 bytes in length. If the controller is in analogue mode, it'll then go on to return a further four bytes; one byte per axis, two axes per joystick.

Standard disclaimer applies; if you're going to hook anything up to your PC, I cannot be held responsible for any damages incurred. Be careful!

I'm still having some problems with data transfer. The controller doesn't always send back enough data (least reliable with a DualShock 2); this could be because I'm running its clock too fast. Introducing delays doesn't seem to help. This is most noticable in the demo program when a DualShock 2 is used in analogue mode; the analogue light flickers on and off.

I also haven't successfully managed to get the DualShock 2 to enter escape mode - this mode is used to access some of the more exotic commands, including commands to control the force feedback motors or to append extra data to the packet sent back when the controller is polled, such as the status of the analogue buttons.

Site update

Friday, 1st February 2008

I intended on making at least one post in January, but looked like I missed the opportunity by a day. I hope you all had pleasant breaks (and here I refer to breaks from my inane ramblings, not any religious festivals).

The lack of updates stem from having precious little to talk about; I've been busily trying to redevelop my website into a single, modular, extensible code base rather than having a large number of insular sub-webs.

Here's a whistle-stop tour of the various iterations of my website; whilst the underlying technology has improved in quality quite significantly it's evident that I couldn't design good looking websites for toffee.

01.jpg

Initially I just used the site to provide links to and information about the various calculator programs I was working on - hence its name, calc83plus. This was hosted for free, and so was just static HTML with each page having different content copied and pasted in.

02.jpg

I decided that this was a bit inefficient, so in the second version used javascript to draw a consistent header on each page. The content was much the same, I just changed the logo and colour scheme.

03.jpg

I eventually ended up with a real webhost, so changed the name and redesigned the site, adding my VB6 projects to it. I evidently decided against the javascript trick and was now using some ghastly frameset solution.

04.jpg

Hang on, this web host supports ASP scripting, and I know VB, so... why not make it server-side script driven? So I did. This version of the site allowed me to log in and post news articles directly with comments, and used server-side includes to insert the body of pages into a template.

05.jpg

I switched web hosts, and had a static placeholder page up for a fairly lengthy period until I decided to use my new PHP knowledge to build something that I could use to provide an image gallery for some of the VB and C projects I'd been working on. The host also supports ASP, but I'd decided that I never had anything interesting to say anyway so a news and comments system wouldn't be of much use. An inextensible system meant that the various new projects I developed ended up scattered all over the place in unrelated sub-webs and news posts resumed with my GDNet+ account.

06.jpg

Which leads us neatly to this; a design that is quite blatently "inspired" by GameDev.net's (and provides news updates by screen-scraping GDNet) but which can be used to host information about pictures, projects and a gallery in one centralised location and can be easily extended with new modules.

FirstNovember 2007March 2008Last RSSSearchBrowse by dateIndexTags