Decompiling Tenchu: Stealth Assassins part 7: the AFSMAKE.EXE side-quest
Previously in this series of articles, we’ve messed with the archive code of Tenchu and uncovered some juicy leftovers in the earliest versions of the game.
This time, we’ll lay off the dark magic for a bit and take a look at one of these artifacts: AFSMAKE.EXE
, presumably used to create the proprietary AFS archive files for this game.
Running AFSMAKE.EXE
AFSMAKE.EXE
is a 32-bit Windows PE program.
To run it on a x86_64 Debian system, Wine needs to be installed first:
$ sudo dpkg --add-architecture i386
$ sudo apt-get update
$ sudo apt-get install \
wine \
wine32
With that out of the way, it’s time to check out the loot:
$ wine AFSMAKE.EXE
option:
-p : pack
-x : extract
-l : list
$ wine AFSMAKE.EXE -l
Afs file list
option: <file>
$ wine AFSMAKE.EXE -p
AFS Volume Maker Version 2.00
optins:
<volume file name> <file>
$ wine AFSMAKE.EXE -x
optins:
<vol name> <filename> <destination>
Inconsistent formatting, capitalization and typos: telltale signs of a homemade tool. Is the lack of veneer superficial or is this program jank through and through? Only one way to find out.
Listings lost in translation
Let’s try a simple task, listing the files of the demo archive:
$ wine AFSMAKE.EXE -l TENCHU/DATA.VOL
âtâ@âCâïé¬éPé┬éαéáéΦé▄é╣é±
Curses! Obsolete 90’s character encoding strikes again and this time there aren’t any conveniently located English translations nearby. We’ll have to figure this one out.
Since we are dealing with a Windows program from Japan, it’s a fair bet that this is encoded in code page 932, also known as Shift-JIS. I’m too lazy to figure out how to tell Wine and my terminal emulator how to interpret this stuff, so we’ll use Python to do the conversion for us:
$ wine AFSMAKE.EXE -l TENCHU/DATA.VOL > mojibake.txt
$ file mojibake.txt
mojibake.txt: Non-ISO extended-ASCII text, with CRLF line terminators
$ python3
Python 3.9.2 (default, Feb 28 2021, 17:03:44)
[GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> fp=open("mojibake.txt", "rb")
>>> original_string = fp.read()
>>> original_string
b' \x83t\x83@\x83C\x83\x8b\x82\xaa\x82P\x82\xc2\x82\xe0\x82\xa0\x82\xe8\x82\xdc\x82\xb9\x82\xf1\r\n'
>>> original_string.decode("shift-jis")
' ファイルが1つもありません\r\n'
>>>
I still can’t understand Japanese, but Google Translate® tells me this means “There are no files”.
Drilling down the listing part of AFSMAKE.EXE
, it becomes apparent that this executable contains what is essentially a fully-featured version of afs.o
as covered before, but compiled for Windows and using the C standard library I/O functions instead of the bespoke disc file layer.
It’s close enough that the differences are probably due to macros in the original code used as a compatibility layer between the PlayStation and Windows environments.
Therefore, my first mistake was supplying the suffix .VOL
, as the AFS code concatenates it to the supplied path.
Let’s try again without the suffix:
$ wine AFSMAKE.EXE -l TENCHU/DATA
âtâ@âCâïé¬éPé┬éαéáéΦé▄é╣é±
No dice. What’s going on?
Further analysis reveals that the buffer used to hold said path is at most 16 bytes long and AFS rejects the user-supplied path if it’s more than 8 bytes long. That’s not for the filename of the archive, but for the whole path to the archive.
Even disregarding LFN (long filenames) which were introduced back in Windows 95, MS-DOS 2.0 supported directories back in 1983. Heck, the MIPS version of this code rejects paths longer than 75 bytes.
Fine, have it your way. Let’s try that again without that pesky directory in the way:
$ wine AFSMAKE.EXE -l DATA
K:\WORK\CDIMAGE\STAGE\YAMIJOU\TITLE_J.TIM
K:\WORK\CDIMAGE\STAGE\YAMIJOU\TIM.TPD
K:\WORK\CDIMAGE\STAGE\YAMIJOU\MAP.MAD
K:\WORK\CDIMAGE\STAGE\YAMIJOU\TITLE_I.TIM
K:\WORK\CDIMAGE\STAGE\YAMIJOU\STAGE.CON
...
K:\WORK\CDIMAGE\TRIAL\THEME\MATO.TMD
K:\WORK\CDIMAGE\TRIAL\THEME\THEME9.TPD
K:\WORK\CDIMAGE\TRIAL\THEME\UFO.TMD
K:\WORK\CDIMAGE\TRIAL\THEME\NOKI.TMD
K:\WORK\CDIMAGE\TRIAL\THEME\UFO2.TMD
1048 file(s)$
Finally, some conclusive results.
AFSMAKE.EXE
doesn’t output a newline character on the file count line, so the shell prompt isn’t on a new line.
Single-shot file extraction
Now we’ve managed to list the contents of an archive with AFSMAKE.EXE
, how about extracting some data?
$ wine AFSMAKE.EXE -x DATA 'K:\WORK\CDIMAGE\DEMO\START\CARD_E.TXT' card_e.txt
$ cat card_e.txt
Memory Card not found.
Memory Card damaged.
Memory Card not formatted.\Do you want to format it?
There is no saved data\for this game.
Memory Card full.
...
Couldn't read saved data.
Memory Card not found.\Game data can't be saved.\Okay?
Memory Card damaged.\Game data can't be saved.\Okay?
Memory Card doesn't have\enough space.\Game data can't be saved.\Okay?
Saving will overwrite\the previous save data.\Save anyway?
It works, but AFSMAKE.EXE
can only extract one file at a time, which means that my abomination from part 6 is sadly not obsolete yet for extracting all the files from AFS archives.
Archive creation
For the last feature, we actually have a relevant artifact found during the previous part from the original developers, VOLMAKE.BAT
.
It was most likely responsible for building the AFS archive as part of the original game’s development pipeline:
k:
cd \work\cdimage
del ..\data.vol
del data.vol
afsmake -p ..\data *
copy ..\data.vol .
… Oh, right, the game expects that everything inside the archive is under K:\WORK\CDIMAGE
.
On Linux, we can define drive letters for Wine by creating symbolic links inside ~/.wine/dosdevices
:
$ ln -s ~/Documents/K: ~/.wine/dosdevices/k:
$ cd ~/Documents/K:
$ wine cmd.exe
Microsoft Windows 6.1.7601
K:\>dir
Volume in drive K has no label.
Volume Serial Number is 0000-0000
Directory of K:\
3/10/2024 11:43 AM <DIR> WORK
0 files 0 bytes
1 directory 5,356,400,640 bytes free
K:\>
That exact trick won’t work on Windows since this relies on a feature specific to Wine.
It would probably require either having a K:
drive or using a mount point to work around this issue.
With the K:
drive available, we can try and create an AFS archive to run with the game:
$ WINEPATH=~/.local/bin wine cmd.exe
Microsoft Windows 6.1.7601
K:\>cd work\cdimage
K:\work\CDIMAGE>afsmake -p ..\DATA *
AFS Volume Maker Version 2.00
Create New volume... ..\DATA
add: K:\work\CDIMAGE\ANIM\ENGLISH\STAGE10A.CAD
add: K:\work\CDIMAGE\ANIM\ENGLISH\STAGE10R.CAD
add: K:\work\CDIMAGE\ANIM\ENGLISH\STAGE11A.CAD
add: K:\work\CDIMAGE\ANIM\ENGLISH\STAGE11R.CAD
add: K:\work\CDIMAGE\ANIM\ENGLISH\STAGE12A.CAD
...
add: K:\work\CDIMAGE\TRIAL\THEME\YATATE.TMD
add: K:\work\CDIMAGE\TRIAL\THEME\YKAGARI.TMD
add: K:\work\CDIMAGE\TRIAL\THEME\YOROI.TMD
add: K:\work\CDIMAGE\TRIAL\THEME\YUKIMORI.TMD
add: K:\work\CDIMAGE\TRIAL\THEME\ZIZOU.TMD
ok. update 1047 file(s).
K:\work\CDIMAGE>
That’s every feature of AFSMAKE.EXE
exercised.
However, it’s not very useful unless we can get the game to use newly created archives.
Modding Tenchu
While I could try and get the PCdrv
backend of the game’s virtual file system working once more, it doesn’t use AFS archives and therefore wouldn’t show off AFSMAKE.EXE
in action.
Therefore, let’s try repacking a modified version of the game.
Repacking a PlayStation ISO
We have the means to create new AFS archives, but we need to create a new ISO file for DuckStation.
PlayStation CD-ROM images are special enough that conventional tools such as xorriso
won’t cut it, so I’ll use MKPSXISO, a purpose-built tool.
Sadly, the Linux version is built for Ubuntu and won’t work on this Debian distribution, so I’ll use the Windows version instead.
First, we need to generate a XML specification for the CD image to that the tool knows what to do.
We can just use dumpsxiso
to rip the game and generate it for us:
$ WINEPATH=~/.local/bin wine cmd.exe
Microsoft Windows 6.1.7601
Z:\home\boricj\Documents\tenchu-hacked>dumpsxiso -s spec.xml "..\..\ISOs\Rittai Ninja Katsugeki - Tenchu - Shinobi Gaisen (Japan) (Track 1).bin"
DUMPSXISO 2.04 - PlayStation ISO dumping tool
2017 Meido-Tek Productions (John "Lameguy" Wilbert Villamor/Lameguy64)
2020 Phoenix (SadNES cITy)
2021-2022 Silent, Chromaryu, G4Vi, and spicyjpeg
ISO descriptor:
System ID : PLAYSTATION
Volume ID :
Volume Set ID :
Publisher ID :
Data Prep. ID :
Application ID : PLAYSTATION
Volume Create Date : 1999010214210800+36
Volume Modify Date : 0000000000000000+0
Volume Expire Date : 0000000000000000+0
ISO contents:
Extracting SYSTEM.CNF;1...
SYSTEM.CNF
Extracting SLPS_019.01;1...
SLPS_019.01
...
TENCHU\MOVIE\ENDAYA.STR
Extracting ENDING.STR;1...
TENCHU\MOVIE\ENDING.STR
Extracting CD.CA;1...
TENCHU\CD.CA
WARNING: The CDDA file TENCHU\CD.CA is out of the iso file bounds.
This usually means that the game has audio tracks, and they are on separate files.
As DUMPSXISO does not support dumping from a cue file, you should use an iso file containing all tracks.
DUMPSXISO will write the file as a dummy (silent) cdda file.
This is generally fine, when the real CDDA file is also a dummy file.
If it is not dummy, you WILL lose this audio data in the rebuilt iso.
Z:\home\boricj\Documents\tenchu-hacked>
It’s complaining about the second audio track easter egg that we found in part 2, but we don’t need it for our proof-of-concept so we’ll ignore that error message.
After replacing TENCHU\DATA.VOL
with our modified version, we can repack the game with mkpsxiso
:
Z:\home\boricj\Documents\tenchu-hacked>mkpsxiso -y -o "..\..\ISOs\Rittai Ninja Katsugeki - Tenchu - Shinobi Gaisen (Japan) (Hacked).iso" spec.xml
MKPSXISO 2.04 - PlayStation ISO Image Maker
2017-2022 Meido-Tek Productions (John "Lameguy" Wilbert Villamor/Lameguy64)
2021-2022 Silent, Chromaryu, G4Vi, and spicyjpeg
Building ISO Image: ..\..\ISOs\Rittai Ninja Katsugeki - Tenchu - Shinobi Gaisen (Japan) (Hacked).bin + mkpsxiso.cue
Scanning tracks...
Track #1 data:
Identifiers:
System : PLAYSTATION
Application : PLAYSTATION
Creation Date : 1999010214210800+36
License file: license_data.dat
Parsing directory tree...
Files Total: 24
Directories: 3
Total file system size: 739972128 bytes (314614 sectors)
Track #2 audio:
DA File CD.CA
Writing ISO
Writing files...
Packing SYSTEM.CNF... Done.
Packing SLPS_019.01... Done.
Packing TENCHU\RUN.EXE... Done.
Packing TENCHU\MENU.EXE... Done.
Packing TENCHU\MAIN.EXE... Done.
Packing TENCHU\TRIAL.EXE... Done.
Packing TENCHU\ENDING.EXE... Done.
Packing TENCHU\DATA.VOL... Done.
Packing XA TENCHU\MOVIE\SME.STR... Done.
Packing XA TENCHU\MOVIE\ACQUIRE.STR... Done.
Packing XA TENCHU\MOVIE\OPEN06.STR... Done.
Packing XA TENCHU\MOVIE\TRAINING.STR... Done.
Packing XA TENCHU\XA\TORA.XA... Done.
Packing XA TENCHU\XA\MUSIC.XA... Done.
Packing XA TENCHU\XA\STAGES.XA... Done.
Packing XA TENCHU\XA\EVENT_F.XA... Done.
Packing XA TENCHU\XA\EVENT_E.XA... Done.
Packing XA TENCHU\XA\EVENT_I.XA... Done.
Packing XA TENCHU\XA\EVENT_J.XA... Done.
Packing XA TENCHU\XA\INTRO.XA... Done.
Packing XA TENCHU\MOVIE\ENDRIKI.STR... Done.
Packing XA TENCHU\MOVIE\ENDAYA.STR... Done.
Packing XA TENCHU\MOVIE\ENDING.STR... Done.
Writing CDDA tracks...
Packing audio TENCHU/CD.WAV... Done.
Writing license data...Ok.
Writing directories... Ok.
ISO image generated successfully.
Total image size: 747785472 bytes (317936 sectors)
With this, we have an ISO file that’ll boot inside DuckStation.
Demonstration
We’ve repacked our modified version of Rittai Ninja Katsugeki Tenchu: Shinobi Gaisen, it’s time to try it out. While inside a level, by pressing the Select button the player can display a map:
Each level has a map, whose file is named CHIZU.TIM
.
For the first level Punish the Evil Merchant, this file is located inside the AFS archive at K:\WORK\CDIMAGE\STAGE\AKINDO\CHIZU.TIM
.
The original Japanese release of Tenchu has different, hand-drawn maps than the newer releases.
In this mod, I’ve replaced that file with the version from Rittai Ninja Katsugeki Tenchu and we can see the result in-game:
The newer releases display a small red cross that follows the player on the map. The original release didn’t and its maps weren’t accurate to the level’s geometry, which is why the location of the small red cross is wrong relative to the hand-drawn map.
Hardly the mod of the century, but the proof-of-concept is done.
Conclusion
We’ve tested out AFSMAKE.EXE
found inside the demo version of Rittai Ninja Katsugeki Tenchu, discovered it is quite janky, but nevertheless managed to use it to create a modified version of Rittai Ninja Katsugeki Tenchu: Shinobi Gaisen.