Why my file operations fail on the Mac

My first thought for why things fails is the the Environment.SpecialFolder don’t map, and thus I found this page talking about it

So I ran the code on my Windows XP box and got:

Desktop                 C:\Documents and Settings\spilgrim\Desktop
Programs                C:\Documents and Settings\spilgrim\Start Menu\Programs
Personal                C:\Documents and Settings\spilgrim\My Documents
Personal                C:\Documents and Settings\spilgrim\My Documents
Favorites               C:\Documents and Settings\spilgrim\Favorites
Startup                 C:\Documents and Settings\spilgrim\Start Menu\Programs\Startup
Recent                  C:\Documents and Settings\spilgrim\Recent
SendTo                  C:\Documents and Settings\spilgrim\SendTo
StartMenu               C:\Documents and Settings\spilgrim\Start Menu
MyMusic                 C:\Documents and Settings\spilgrim\My Documents\My Music
DesktopDirectory        C:\Documents and Settings\spilgrim\Desktop
MyComputer
Templates               C:\Documents and Settings\spilgrim\Templates
ApplicationData         C:\Documents and Settings\spilgrim\Application Data
LocalApplicationData    C:\Documents and Settings\spilgrim\Local Settings\Application Data
InternetCache           C:\Documents and Settings\spilgrim\Local Settings\Temporary Internet Files
Cookies                 C:\Documents and Settings\spilgrim\Cookies
History                 C:\Documents and Settings\spilgrim\Local Settings\History
CommonApplicationData   C:\Documents and Settings\All Users\Application Data
System                  C:\WINDOWS\system32
ProgramFiles            C:\Program Files
MyPictures              C:\Documents and Settings\spilgrim\My Documents\My Pictures
CommonProgramFiles      C:\Program Files\Common Files

and on my MacBook I get

Desktop                 /Users/parents/Desktop
Programs
Personal                /Users/parents
Favorites
Startup
Recent
SendTo
StartMenu
MyMusic                 /Users/parents/Music
DesktopDirectory        /Users/parents/Desktop
MyComputer
Templates
ApplicationData         /Users/parents/.config
LocalApplicationData    /Users/parents/.local/share
InternetCache
Cookies
History
CommonApplicationData   /usr/share
System
ProgramFiles
MyPictures              /Users/parents/Pictures
CommonProgramFiles

One question raised on that page is why are there two Personal values, and it’s because MyDocuments is the second Enumeration, but .ToString() only names the first in order of an enum set.

So the mystery deepens as I’m using Environment.SpecialFolder.Personal, and therefore it should work, but it seems to be failing to create the data directory, time to explore more…

After some a bit more playing around I found the file “SAVE/player.cha” in my Mac folder structure, and realised I was doing string concatenation with DOS slashes, etc. hard coded all over the place, so I refactored the code, and now it saves/loads files fine.

I then created a Mac .app bundle, so you can now just download form Google Code the Mac OS X version of the game, un-tar/gzip it, and as long as you have Mono installed, tada, instant game action. Albeit slow…

Only problem to solve is that the save files are case sensitive, in how they are searched and managed, and I’ve not got a standard schema in place. I may have to build some wrapper code to manage this, so odd DOS/Windows casing works fine on Mac/Linux systems.

Also I’ll have to make a Linux tar ball for those Linux/Mono people to test play with.

Curse of the Azure Bonds port working on Mac OS X

Just tried running the game under Mono on my MacBook, and it like works!

Except for

  • Saved games/player file IO does not work. I think this is due to the new Vista locations, thus I’ll need a new pick a location code, in which case I could leave Windows XP working in the old location…
  • It runs really slow in combat, ie you can see the icon redraws
  • There is no sound
  • The right click context menu do not seem to work
  • You have to lunch it from the terminal prompt

Curse of the Azure Bonds – build 1.1.0 released

I push version 1.1.0 of project to the Google Code project today.

Changes in this version:

  • Game now installs and plays on Windows Vista (thus minor version update)
  • Saved games and other generated data is now located under “My Documents\Curse of the Azure Bonds”
  • Installer now has custom graphics, and cleans up older installers correctly
  • Added basic crash logging

The first is the most important, as previously I had just assumed it worked, and not thought much more of it. But Ken posted a comment saying it didn’t work, and well that made me feel bad. So much Virtual PC 2007 time later, it now works.

As always any issues post here in the comments, email me directly, or post on the issue list.

Curse of the Azure Bonds – build 1.0.21

Build 1.0.21 has been released. Fixed in this version:

  • Issue 36, Fixed staff-sling weapon targeting
  • Issue 44, Fixed the modify player screen so edits to exceptional strength stay
  • Issue 45, Fixed the Order menu (found off Encamp, Alter), so you can select and place the party members
  • Issue 46, Fixed horizontal menu scroll via comma and dot keys

I also reworked how the combat engine builds target lists, and intend to change it to an A* system, but at this point it is a direct path cost based system, so I added a caching layer, and when all graphic’s and sounds turned off (non commented change) the combat is over in sub-second.

To do this  I hacked the game apart to dynamically turn graphics/sound/delays off, and then used the Red Gate Ant Performance Profiler to find the overworked code and it worked really well. I cheated and just used their 14-day trail version. But I did provide some feedback, and it has some really nice features like the ability to zoom to parts of the run history, and give guidance on hotspots, and you can drill down and see the code related to the CPU time. Check it out if your application is running a little slow.

As always, give the newer version a try, if you find any issues, even “old known” issues, let me know by: commenting here, email me directly, or add them to the issue list

Reverse Engineering ‘Pools of Darkness’: Part 1

I thought I would reverse engineer Pools of Darkness to capture the process in a serries of blog posts.

This is the first, and I’ll add links to the later posts as we go. Part 2

Prerequisite:

Open game.exe in IDA Pro, jump to the start function (Ctrl+E). Here it is:

01-darkness-in-ida

In summary: it moves the executable to a new location in memory, then “jumps to the next function” via pushing the new memory address (seg001:0038) onto that stack and the returning to it (which is just a pop and jump).

The next blob of code seg001:0038 – seg001:00FE is a fancy pants scrambler, which by the way is exactly the same as Curse of the Azure Bonds had.

Line seg001:00FE is the interesting line:

seg001:00FE     jmp     dword ptr cs:[bx]

This is where the descrambler jumps to the actual game code.  The code in IDA is the scrambled version, so we want to get access to the descrambled memory layout.  So quit IDA Pro, and don’t keep this database.

For Curse I used the DOS debugger to get the descrambled memory, so lets do it again.

Here is the generic script to dump the memory:

g AAAA:37
p
g BBBB:fe
d 0 ffff
a BBBB:fe
mov dx,CCCC
mov ds,dx

p
p
d 0 ffff
a 1ab1:103
mov dx,DDDD
mov ds,dx

p
p
d 0 ffff
q

To get the values of AAAA, BBBB, CCCC and DDDD we start from a command prompt:

enter R to get the register dump

C:\games\DARKNESS>debug game.exe
-r
AX=0000  BX=0000  CX=F8CB  DX=0000  SP=0080  BP=0000  SI=0000  DI=0000
DS=0BA1  ES=0BA1  SS=1DC7  CS=1AED  IP=0012   NV UP EI PL NZ NA PO NC
1AED:0012 8CC0          MOV     AX,ES
-

DS is the beginning of the memory, and CCCC is DS + 0x1000 and DDDD is DS + 0x2000, AAAA is CS, and BBBB is DS + 10 + word_1F3CC, which happens to be 0x11C5, but I just step (p) six times, and read the value from AX

now we can run the debugger a couple more time to double check the base address are the same each run…. which they are.

now we can run the script as input to debug.exe to dump the 192Kb of game ram, via this command

debug game.exe < run.txt > out.txt

I found a few times I had to kill the process, as the quit (q) command at the end was not working…

But you now have a file looking like this

-g 1aed:0037
AX=0038  BX=0000  CX=0000  DX=0001  SP=007C  BP=0000  SI=FFFF  DI=FFFF
DS=1AED  ES=1D76  SS=1DC7  CS=1AED  IP=0037   NV DN EI PL NZ NA PE NC
1AED:0037 CB            RETF
-p
AX=0038  BX=0000  CX=0000  DX=0001  SP=0080  BP=0000  SI=FFFF  DI=FFFF
DS=1AED  ES=1D76  SS=1DC7  CS=1D76  IP=0038   NV DN EI PL NZ NA PE NC
1D76:0038 06            PUSH	ES
-g 1d76:fe
AX=0BA1  BX=0000  CX=0000  DX=F000  SP=4000  BP=0000  SI=2576  DI=4000
DS=0BA1  ES=0BA1  SS=2576  CS=1D76  IP=00FE   NV UP EI PL NZ NA PO NC
1D76:00FE 2E            CS:
1D76:00FF FF2F          JMP	FAR [BX]                           CS:0000=0002
-d 0 ffff
0BA1:0000  CD 20 FF 9F 00 9A F0 FE-1D F0 4F 03 95 05 8A 03   . ........O.....
0BA1:0010  95 05 17 03 95 05 84 05-03 04 01 00 02 FF FF FF   ................
0BA1:0020  FF FF FF FF FF FF FF FF-FF FF FF FF 42 0B F1 49   ............B..I
0BA1:0030  95 05 14 00 18 00 A1 0B-FF FF FF FF 00 00 00 00   ................
0BA1:0040  05 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
0BA1:0050  CD 21 CB 00 00 00 00 00-00 00 00 00 00 20 20 20   .!...........

Next we need to turn that into a binary file, the code I wrote for Curse required only the hex dump lines so we trim the extra lines from out.txt file first.

#include <iostream>
#include <stdio.h>

using namespace std;

unsigned char decode(char h, char l)
{
    unsigned char hv, lv;
    if ( h >= 'A' && h <= 'F' )
        hv = h - 'A' +10;
    else
        hv = h - '0';

    if ( l >= 'A' && l <= 'F' )
        lv = l - 'A' +10;
    else
        lv = l - '0';

    return (lv+(hv*16));
}

void main()
{
    const int lineSize = 1024;
    char line[lineSize];

    FILE *out = fopen( "dump.bin", "wb" );

    unsigned char byte;

    while(cin.good())
    {
        cin.getline( line, lineSize );

        int offset=11;
        for(int i=0; i<8; i++)
        {
            char h,l;
            h = line[offset+(i*3)];
            l = line[offset+(i*3)+1];

            byte = decode(h, l);

            fwrite(&byte,1,1,out);
        }

        offset=35;
        for(int i=0; i<8; i++)
        {
            char h,l;
            h = line[offset+(i*3)];
            l = line[offset+(i*3)+1];

            byte = decode(h, l);

            fwrite(&byte,1,1,out);
        }
    }
    fclose(out);
}

Now using this like so:

dumpparse.exe < out2.txt

We get file dump.bin

Which we will load into IDA Pro in Part 2