Friday, 25th November 2016
I recently picked up a Sega Saturn and a copy of the technical marvel that is Quake for it.
The Saturn is not renowned for being a particularly capable 3D machine and so the fact that Quake runs at all is quite remarkable, let alone as well as it does in Lobotomy Software's version. Rather than port the Quake engine to the Saturn the game uses the SlaveDriver engine, and includes conversions of 28 of the original 32 levels with some minor tweaks to improve performance. It certainly captures the atmosphere of Quake far more faithfully than most console ports of DOOM did to that game, leaving the sound and music intact and retaining the gritty aesthetic of Quake's software renderer.
Unfortunately, I'm not very good at it. Even though I could probably complete the PC version's first level in my sleep these days it took me three shameful attempts on the easiest difficulty level to get through it on the Saturn. The controls are somewhat awkward (for example, to aim up you need to hold X and press down on the d-pad) and so I thought that a cheat code or two might help me along until I'd got to grips with the game's controls.
I found a list of cheats on a newsgroup from the game's developer but most of them did not work with my copy of the game, and the few codes that did do something ended up performing the function of a different cheat. For example, invicibility ("Paul Mode") is toggled by highlighting "Customize Controls" then entering RLXYZRLXYZ, but on my copy of the game that toggled "Jevons-Control Mode" instead. These codes matched the ones on various cheat database sites across the Internet, so I was a bit puzzled until I found a forum post with a couple of codes that did work. This is an incomplete set, and it's clear that the PAL version of the game has different cheats to the NTSC-U version. Other sites either mentioned that the PAL version doesn't have cheats at all, or is missing most of them due to being an older version of the engine.
One thing stuck out to me, though - all NTSC-U cheat codes follow the same basic formula of highlighting a particular menu item under "Options", entering a ten button sequence using only the X, Y, Z, R and L buttons, and then seeing a confirmation message on the screen. I assumed that the PAL version would do something similar, and that some table of cheat codes and messages could be found in the executable. I popped the game CD into my PC CD drive and copied the executable file to the hard disk so I could examine it in a hex editor.
The first thing I did was search for "Jevons-Control" which identified it at the top of a list of other cheat-related messages such as "All weapons added" or "Nail Tracers On" which made me hopeful that the other cheat codes were present in the PAL version of the game. Some cheats in the NTSC-U version (such as those relating to rain or cluster bombs) didn't seem to have an equivalent message in the list of strings here so these were presumably missing in the PAL version, but at least I knew I was on the right track.
At this point, I had two button sequences that I knew worked - RLXYZRLXYZ and LRLLZYXZYX. I hoped that these codes might be found in the game's executable, but of course didn't know how they'd be represented. At first I assumed each code would be a ten byte sequence, with one value for 'R', one value for 'L', another for 'X' and so on and so forth. As the two known cheats repeated buttons (for example, LRLLZYXZYX has L three times) it would be possible to see if a particular sequence of bytes followed the same pattern as the cheat code (for example, with LRLLZYXZYX the first, third and fourth bytes would need to all have the same value to represent 'L', and that value could not appear anywhere else in the ten byte sequence). With that in mind I wrote a program that scanned through the entire binary from start to finish, checking to see if either of the two codes could be found. Neither could, so I changed the program to instead assume that each button's value would be stored as a sixteen-bit word. Still no luck, but as the Saturn is a 32-bit system I again increased the size of each button in the sequence to try 32-bit integers and found two matches for the two codes.
Not only were both sequences found, but both used the same values for the buttons (e.g. L is 0x00000008 in both sequences) and both were near each other in the binary. This seemed like the place to look, so I put the lower address value into the hex editor to see if there were other sequences nearby.
Now that I knew where the cheat codes were and how to map each value to a button name I could work through the binary, check to see if each sequence of ten 32-bit integers all matched known button values and if so output the sequence. This gave me three additional sequences for a total of five.
Now I could take those five sequences, try them in the game and match them to the known NTSC-U sequences. Most of the cheat codes sequences are used more than once, changing behaviour depending on which menu item was highlighted when they were entered. Comparing the effect of certain codes in the PAL version against what the NTSC-U version was known to do produces the following table:
I'm not sure why the PAL version uses different cheat code sequences, but it is an earlier version of the game and they are also quite a bit easier to enter on the console so maybe it was decided that players needed to work harder to take advantage of their cheat codes. There are a few other NTSC-U cheat code sequences that don't match up with the PAL version, but these are for cheats that seem to be missing equivalent strings (such as the previously mentioned rain or cluster bomb cheats) so I reckon they were not yet added to the PAL version.
For the sake of completeness, here is a list of cheats that work in the PAL version of Quake. All need to be entered by pausing the game, highlighting a particular item in the Options menu and then entering the cheat code as quickly as you can. Some cheats also require you to stand in a particular place in a map or to have collected certain items first; these are noted where appropriate.
|Paul Mode (Invincibility)||Customize Controls||LRLLZYXZYX|
|All Weapons||Customize Controls||XYZLRXYZRL|
|Full Health||Customize Controls||YYZYYZLRLR|
Stand in the area where the first rune can be picked up in "The House of Cthon".
|Jevons-Control Mode (3D Control Pad)||Customize Controls||RLXYZRLXYZ|
You must either have all four runes or be standing on the right hand side of the flat part of the bridge over the lava in the "Entrance" level.
|Reset to Defaults||RLXYZRLXYZ|
|Restart Level||Reset to Defaults||LRLLZYXZYX|
|Normal Difficulty||Music Volume||RLXYZRLXYZ|
|Easy Difficulty||Music Volume||LRLLZYXZYX|
|Hard Difficulty||Music Volume||XYZLRXYZRL|
Stand on the right hand side of the bridge under the round stained glass window in "Castle of the Damned".
Show Special Credits
Stand in the secret underwater cave containing the Megahealth and Nails in "Gloom Keep".
Stand either at the Quad Damage in the secret area opened by jumping into the overhead light in "The Sewage System" or in the suspended cage half way through "The Tower of Despair".
|Monster Attack||Auto Targeting||RLXYZRLXYZ|
|Nail Tracers||Auto Targeting||LRLLZYXZYX|
If there are still people out there struggling through the PAL version of Quake on the Sega Saturn, maybe these cheat codes will come in handy!
Saturday, 20th April 2013
I recently purchased an inexpensive PlayStation controller USB adaptor for my PC. Several reviews confirmed that it was compatible with the controller's analogue joysticks so I thought it would be what I was after. Life is rarely that easy with cheap electronics, unfortunately!
When it arrived I plugged it in and Windows installed the appropriate HID drivers for it automatically, but as much as I waggled the joysticks on a connected DualShock 2 controller the axis preview in Control Panel remained resolutely in the zero position. PlayStation controllers have an "Analog" button that can be pressed to toggle between digital and analogue modes, but any attempts to press this resulted in the "Analog" light briefly flashing before immediately switching off again.
Thinking it may be a driver issue I tried to install the drivers from the mini CD that had been included with the adaptor. My PC could not read the disc (it appeared to be scratched, and was not very well protected in postage) so I hunted around online until I found a package that worked using the device's USB ID (VID_0810&PID_0001). This enabled the controller's rumble/vibration feature, but I still couldn't get analogue input to work. Thinking that if one driver package could add vibration support, another might add analogue support I contacted the Amazon seller to ask them if they could send me a copy of the correct drivers - they instead chose to send me a whole other unit in the post.
In the meantime, I experimented with another controller plugged into the adaptor. I was surprised to find that with two controllers plugged in at once I could enable analogue mode on one of the controllers. This made me think there could be a power issue - the second controller increased the capacitance across the power supply, which would make it more resilient to voltage spikes and reduce ripple that could be causing the controller to reset out of analogue mode. This was further confirmed by plugging the adaptor with a single controller into a powered USB hub - in this scenario the controller would only leave analogue mode when vibrating. I checked the power supply pins on the controller ports and was very surprised to see that there was apparently nothing connected to pin 5, which is supposed to deliver +5V to the controllers. At this point I decided to dismantle the adaptor to see what was going on.
On the inside of the adaptor I could see that several components had been omitted. This could be to blame on cost-cutting measures (e.g. the LEDs D1 and D2 which are purely cosmetic) but the removal of D3 puzzled me the most - this diode is connected between USB VCC and the controller port pin 5, and is presumably responsible for providing power to the connected controller. I put this down to an oversight at the factory, and soldered a 1N4001 rectifier diode in the marked place.
The above image shows a close-up of the place the missing diode should appear - D3 is indicated by a silk-screened diode symbol. Unsurprisingly the 1N4001 silicon diode has far superior characteristics to the silk-screen diode it replaced.
With the diode in place both controller ports started working flawlessly, even allowing me to use a wireless Guitar Hero controller receiver (though not the whammy bar - Guitar Hero controllers lack the "Analog" button to manually enable the analogue mode and instead rely on the PlayStation to enable it via software). Whilst I had the soldering iron out I thought I should add the missing LEDs, once again using the existing markings to establish the correct polarity:
If the markings are unclear, the anode (+) is always to the left when viewing the bottom of the circuit board when the other markings are upright.
As the enclosure is blue and I seem to remember some fuss being made of the PlayStation 2's blue LED when it first came out I opted to use two blue LEDs with 1K5 resistors. I do not have any surface-mount resistors but through-hole ones fit quite easily though they can be a little fiddly to solder down.
When the replacement adaptor arrived in the post I was surprised to see that (once again) the diode D3 was missing and it demonstrated the same problems as the other one I'd fixed. I find it unlikely that the same mistake could be made twice, so this seems to be a genuine cost-cutting measure. Microcontroller I/O pins often have an internal protection diode between them and the positive power supply, which is how I assume the circuit works at all when the controllers are left unpowered - a small amount of current flows from the I/O (data) pins to the positive rail via these protection diodes, which is just enough to let the controller work in digital mode but once they draw more current (e.g. when sampling analogue inputs or driving the vibration motors) the voltage droops far enough for the controller to reset and leave analogue mode.
With these fixes in place I now have two working PlayStation USB adaptors for the price of one (and two 1N4001 diodes). I'm still rather perplexed by why there's such a blatent flaw in the hardware, but it is at least an easy fix which is why I've written it up. In summary: if your cheap PlayStation to USB adaptor ("Twin USB Vibration Gamepad", "Twin USB Joystick") is not working correctly, unscrew it and see if D3 is missing. If it is, solder a 1N4001 or similar diode between the two holes left for that purpose.
Sunday, 23rd October 2011
It's been a long time since I posted about any of my projects for the simple reason that I haven't had any real time to work on them this year. Work commitments have not been particularly kind to my free time and there has been no progress on my 3D engine for TI calculators or any new electronics projects.
I did, however, replace my ailing Zen Xtra digital audio player with a Zen X-Fi 2 earlier in the year. The X-Fi 2 supports simple application development in Lua, a language I had no experience with, so I spent a few days in April knocking together a game as a learning project. I've always been fond of Kevin Ng's Laserstrike and it seemed a good fit for a device with a touch screen.
I used the smaller levels from Badga's Laser Mayhem as it let me use larger tiles, otherwise it would be tricky to tap the correct block on the X-Fi 2's 3" screen.
Having not used Lua before the code is far from brilliant (for some reason I chose to represent the level as a string rather than an array, by way of example) but it works well enough and has occasionally kept me occupied on bus and train journeys. Rather than let the game stagnate on my hard disk drive I added a final bit of polish and have released it on my website. If you'd like to try the game but do not own an X-Fi 2 (which would be almost everyone reading this) you can play it in the Zen X-Fi 2 Application Development Kit (extract the game to C:\Creative\ZEN X-Fi2\Applications) but be warned that the simulator is a little buggy (it doesn't detect touch input in the 16 rows and columns at the top and left edges of the screen for starters).
Fingers crossed I can get more time for what I enjoy doing in 2012. I have plenty of fun ideas, but little time to put them into practice!
Monday, 29th November 2010
I've made a few attempts to boost the performance of the 3D engine for the TI-83+ I'm working on with little success. I had previously failed to get any improvement by adding bounding boxes around each BSP node (the idea being that if a node falls outside the view you can discard it and, by extension, all of its children) but the act of transforming the bounding box to determine whether it was inside or outside the view was more CPU intensive than blindly handling the nodes whether they were inside the view or not.
A simpler test, I reckoned, would be to use bounding circles. These only have one point to transform, and determining whether they are in the view is one comparison to ensure that they're in front of the camera followed by one multiplication (by the constant √2) and two more comparisons to determine whether they are to the left or right of the camera's view; far simpler than a bounding box!
The bounding circles did cut down the number of BSP nodes that were handled each frame but the additional checks made the engine slightly slower in general than it had been before. In some circumstances it was slightly faster, but not enough to make a noticeable difference. The additional data per BSP node added over 900 bytes to the level data, too, so the attempted optimisation had to go.
The newly added rooms to the demo level
One tweak that did boost performance noticeably was to cache the projected X coordinate of each vertex. All vertices in the map have at least two walls connected to them and so are projected to the screen at least twice if within the view. I already had a table that was used to indicate whether a vertex had been transformed around the camera or not that frame so it was easy enough to add the X coordinate of the projected vertex to that table, adding around a 15% boost to the framerate.
Points are projected to the screen by dividing their X (left/right) or Z (up/down) component by their Y (depth) component. Division is slower than multiplication so I tried to calculate the reciprocal of the depth for the vertex then perform all subsequent projection operations by multiplying the X or Z component by this reciprocal. Unfortunately, this resulted in a lack of precision owing to my use of 16-bit fixed-point numbers (walls "wobbled" as you moved the camera) and performance was about the same as it had been before, so I rolled back the changes.
The block of screenshots in the above text shows a new region that has been added to the demo level, and the image below is a map of that level — fans of DOOM may notice that it's based on a small portion of E2M7 (The Spawning Vats).
Map of the level
This level now uses every one of the 256 walls that are available, so is probably a good indication of the maximum size of a single level (and at 6,626 bytes it's certainly rather taxing on the limited amount of memory in a TI-83+ calculator).
This is, however, the maximum size of a single level. It does not take long to load and unload levels, so it would be quite possible to construct a continuous level that appears larger by unloading the current one and loading a different one when the user moves to a particular region. This could be implemented in an obvious manner (such as the player stepping into a teleporter) or transparently (by moving the player into an identical copy of the room he left to hide the transition). The latter option also introduces the option of level geometry that would otherwise be impossible in a 2D-based engine, such as rooms above rooms. Special effects could also be tried, such as an infinite corridor that warps you back to the beginning when you reach its end.
However this feature is implemented, there would need to be some way to trigger the action. The above animated screenshot demonstrates the current trigger system which is used to set a sector in motion. A sector, in this instance, is a region with a particular floor height and ceiling height. Each wall indicates which sector is in front of it and which sector is behind it. Convex sub-sectors contain sets of walls and also indicate which sector they are part of, and are attached to the leaves of the BSP tree. Given a point, you can quickly find out which convex sub-sector it is in by walking the BSP tree. When you have found the convex sub-sector you can then look up its sector. This is currently used to set the player's height, as the sector tells you the floor height.
If you keep track of the player's sector each frame you can tell when they have moved from one sector to another. This then fires an event, reporting which sector the player used to be in and which they are in now. In the above screenshot, the platform is set to descend whenever the sector surrounding it is entered from any sector other than the platform itself (this is to stop it from automatically descending when the player walks off the top of the raised platform). It is also set to rise whenever the platform's own sector is entered. This produces a simple lift; doors are handled in a similar fashion elsewhere in the level.
If you'd like to try this demo on your calculator, you can download the binaries for the TI-83 and TI-83+ in Nostromo.zip. As ever, please back up any important files on your calculator before running the demo; it may well clear your RAM. For those without calculators, an animated screenshot is available.
Sunday, 21st November 2010
One of the larger problems with the 3D engine for the TI-83+ calculator series I have been working on is that it's possible to move the camera through walls. This doesn't make the world feel especially solid, so I've started working on some collision detection routines.
Work commitments have left me with little time to spend on this project over the last couple of weeks so progress has been very slow, but I've got a basic collision detection system mostly working.
I spend most of the above screenshot running into walls. The code seems to work relatively well and quite quickly, though it's far from perfect. The still image shows the new settings screen, which is hopefully a little easier to use than remembering which keys do what. It also has the advantage of displaying the state of the current settings.
The walls are stored as line segments between two 2D vertices, and the collision detection has to ensure that the player does not get too close to any of these walls. The technique I have used starts by calculating the closest point on the line to the player.
The above image shows a wall (the solid line segment) and three possible player positions (the heavy dots). The arrows point to the closest point on the wall's line. The closest point on the line to the top player position is past the end of the line segment, so it is ignored. The other two closest points lie on the line segment, so these are checked in more detail.
The distance between the closest point on the line and the player position is then calculated and compared to a threshold value (the radius of the player). The above image highlights the out-of-bounds region in tan. The lower player position is outside this region so is ignored, but the upper player position is inside it and needs to be corrected.
The correction is quite straightforward. We know the closest point on the wall to the player. The angle of the wall's normal is stored in the level file, so we can easily calculate a vector from that to push the player a fixed distance away from the wall.
In addition to the above 2D checks, a very simple height check is performed for "upper and lower"-type walls. These are walls with a central hole so you can pass over or under them, and are used to connect sectors with varying floor and ceiling heights. The top of the player's head is used to check the ceiling height. Rather than use the height of the player's feet to check the floor height their knee height is used. This is to allow the player to climb low walls (such as the edges of steps).
When I first implemented these collision detection techniques I checked every wall in the map. This halved the framerate in places, and as the framerate is not particularly high in the first place I needed to find a way to reduce the number of tests. Taking further inspiration from DOOM I implemented a "blockmap". This breaks the map down into square blocks and each block contains a list of which walls pass through it. To perform collision detection I look up which block the player is in and from that I can retrieve a reduced list of which walls they may end up walking into. The original implementation had to check well over a hundred walls for each movement; the blockmap reduces this to 26 in the worst case scenario for the current level design.
Sadly, this additional blockmap enlarged the size of the map quite a bit, so I've attempted to reduce it a little. For simplicity and performance most structures referred to other structures by pointer (for example a sub sector contained a list of pointers to walls and each wall contained pointers to a front and back sector). I've changed most of these to now refer to other structures by index, which shaved a few hundred bytes off the map at the cost of a few hundred clock cycles. Overall performance still isn't great, though I haven't found it noticeably slower than the previous demos.
I added very primitive physics for moving the player up and down relative to the floor to complement the collision detection. This retrieves the floor height from the sector directly under the centre of the player and compares it to the current player height. If the new floor height is higher than the old floor height then the player's foot height is set to a point half way between the two; this smoothes the animation slightly when climbing up stairs (rather than just snapping to the new floor height). When moving from a higher floor to a lower floor the player's downward speed is increased to roughly simulate gravity.
A demo for the TI-83+ series and TI-83 can be found in Nostromo.zip. As always, this is a piece of software in development and there may be calculator-crashing bugs, so please back up any important files before running it.