Skip to content
View in the app

A better way to browse. Learn more.

ResHax

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.
Help us keep the site running.

Pizza Tycoon 2 / Fast Food Tycoon 2

Featured Replies

  • Author
  • Localization

m00k00, posted Sat Sep 09, 2017 7:46 pm (26394)


Hello everybody.

I figured that this is most likely the best place on the web to ask for help in this regard. The Game is loading all it's data from .PAK files. It seems like there is no encryption or packing involved. I could really need a helping Hand in understanding how those files are build and to possibly be able to extract their contents and maybe even pack together new .PAK files. So if anyone if you have a little time to share and wants to help me out I would really appreciate that.

The smallest sample I can provide is actually an Update File (230kb in size), but i suspect it to be structured just like all the other .PAK files.

http://www46.zippyshare.com/v/4R2YDuzq/file.html

Thanks a lot in advance and happy hacking everyone :)
  • Author
  • Localization

aluigi, posted Sun Sep 10, 2017 12:40 pm (26409)


The table containing the information about the archived files is obfuscated with some sort of sequential sequence of bytes but with something missing.

I leave here a debugging script for who is interested in checking this stuff, no it's NOT an extraction script so do NOT use it:
Code:
idstring "ResourceFile-ID-"
goto 0x60
get SIZE long
get DUMMY long
get DUMMY long  # obfuscated flag?
savepos OFFSET
encryption xor "\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9"
log MEMORY_FILE OFFSET SIZE
encryption "" ""
get FILES long MEMORY_FILE
for i = 0 < FILES
    get DUMMY1 long MEMORY_FILE
    get DUMMY2 long MEMORY_FILE
    get DUMMY3 long MEMORY_FILE
    get DUMMY4 long MEMORY_FILE
    get DUMMY5 long MEMORY_FILE
next i
  • Author
  • Localization

m00k00, posted Sun Sep 10, 2017 6:38 pm (26418)


Thanks for investing your time, aluigi, it's really appreciated!
Is there something I could do to assist further with this thingy? For example providing more files?

Just let me know, please. Thanks again for your efforts! :)
  • Author
  • Localization

aluigi, posted Sun Sep 10, 2017 8:08 pm (26420)


Without the table containing the information of the archived files you can only "rip" the known file formats using a file ripper:
viewtopic.php?f=17&t=712
  • Author
  • Localization

picusiaubas, posted Wed Oct 06, 2021 9:35 pm (66884)


Hello! Nice to see some people are still interested in this old classic. IMO it's so much better than the new Pizza Connection 3. I have spent many hours playing it and quite some time now digging into the game files as well. I want to share some of my findings (and also ask some questions!):

PAK files are quite easy to extract when you take a look at what is happening with them in the exe file. And all of them are packed and obfuscated the same way as that update file m00k00 shared. Basically a header with filenames and lengths starts at byte 108 and has a length defined at byte 96. It can be deobfuscated with a simple XOR operation (I attached a CPP script with the algorithm). When deobfuscated, it contains this object:
Code:
  size_t numberOfFiles;
  file files[];

Where file object looks like this:
Code:
  FILETIME filetime;
  DWORD fileLength;
  DWORD offsetFromBeginningOfHeader;
  CHAR *filename;

First 8 bytes is still a bit of a mystery to me, but it definitely looks like a FILETIME object containing the date that file was created. They all point to some date in the year 2000, which makes a lot of sense. Filenames are stored after the files array with filename field initially containing offset to the corresponding string from the end of files array. Anyway, it should be clear how this works from the CPP file.

Enjoy!

Unpack.cpp

  • Author
  • Localization

picusiaubas, posted Wed Oct 06, 2021 10:01 pm (66885)


Alright, so extracting resources from PAK was the easy part (in reality not easy at all, I spent a lot of time trying to understand ASM and generated C pseudo-code from HexRays to get it to work). But after extracting all the game files I was both excited (because they contained some cool stuff in simple TGA file format) and prepared for more suffering when I realized most of the game files are in (probably) custom SPR format. I continued investigating disassembled code for loading these sprites into memory, but this time it looks a bit more complicated.
I started with a very simple sprite - head_m_00.dat (normally it would have spr extension, but I changed that to be able to upload it here). It's one of the first files the game is loading, so it was convenient for me to debug it.
From the disassembled code I realized it contains 3 parts:

* Header consisting of the first 33 bytes. First 4 bytes and bytes 25-28 contain (for some reason) the same number (in this case it's 3000), which is length of the next part. Byte 5 is some kind of sprite type indicator (probably there are more different formats than this one). Why it has a value of 8 for this texture? I don't know, but perhaps that's an indicator it's using 8 bit RGB colours?
* First part which looks like a table with references to colour palette. Its length in this case is defined in header (3000), but what are the dimensions of the actual texture? Could it be bytes 19-20 and 21-22? I think it's very likely. In this file they are equal to 0x3C and 0x32, which is 60x50. Exactly 3000 pixels! Also I noticed there are a lot of "07" values in this block. Could they correspond to empty pixels?
* Second part Distinctively in HEX editor looking part of 768 bytes, starting at byte 3033 (header lenght first part length) and ending at the end of the file. Its length (768 bytes) is hard-coded in the exe file. Looks like a 8 bit RGB colour palette to me? In the disassembled code I can see it's processing 3 consecutive elements from this part in each iteration, which makes sense if it is a colour palette.

Maybe anyone has some ideas about this file? I would be really grateful!
  • Author
  • Localization

picusiaubas, posted Thu Oct 07, 2021 9:17 pm (66906)


Yes! I think I was right with my assumptions in the previous post. At least some of them. I tried visualizing head_m_XX.spr files using dimensions of 60x50 and that color palette I found in the last 768 bytes of the file. Here is a result and corresponding image in the game:

Image

The one that I marked definitely matches that head shape from the game. Others from my form probably also do match other options I can pick, but they are not as distinctive. This one is obvious. Lots of 0x07 values in the texture probably just correspond to the background color (which takes a significant part of the image). However, the colors are not right at all. But I am not really concerned with that for now. Probably it can be fixed by doing some manipulations on the palette (or I just messed something up reading the palette from the file).

What bothers me is that these head_X_XX.spr are pretty much the only ones that can be constructed using this simple algorithm. I tried other SPR files and they seem to use some kind of compression for the block containing palette references. Take for example the one that I attached, called litfass01_0.spr:
The first four bytes indicate that block with palette references is 821 bytes in length. Cool. I jump to that position calculating from the end of the header (which is always 33 bytes in size). I can see that the color palette really starts at this position. This texture is probably not using all the 256 colors, so its palette has several zero values.
But I have a problem trying to construct an actual image from these references... If my assumption is correct and bytes 19-20 and 21-22 from the header indicate image dimensions (which I think indeed is correct), it means this texture is 256x256 pixels in size. If it was uncompressed, like that head_m_XX.spr images, it would take whopping 65536 bytes! Yet it only takes 821 byte. So there must be some smart compression used in this block:

Code:
FF 05 5E 47 47 47 47 B1 47 D1 47 47 FF 05 FF 03
5B D1 D1 47 B1 47 D1 47 B1 47 5B 47 5B 47 FF 03
FF 02 5B 53 59 53 B1 9C 9C 9C 47 47 B1 B1 5B 5B
5B D1 FF 02 FF 01 5B 5B 59 53 DF CF 53 B3 47 47
5B 47 5B D1 5B D1 5B B3 FF 01 FF 01 5B 53 53 CF
53 CF 53 53 EE 5B 5B 5B 5B 5B B1 5B B1 D1 FF 01
48 DE 5B 53 53 59 53 59 53 53 5B 53 5B EE 47 5B
D1 5B 42 45 B6 5B 53 53 53 CF CF 53 53 53 5C 5B
EE 5B 5B 5B 5B B1 8F 3A FF 01 4D EE 5A 53 59 53
53 5B 53 5B 53 5B 53 B1 5B 47 D2 35 FF 01 FF 01
4F D2 D1 58 F3 58 59 AF 53 53 53 CF 53 DE 47 8F
4A 34 FF 01 FF 02 4E 75 B3 EE 59 58 58 FD 58 CD
53 9C 4D 4E 49 33 FF 02 FF 02 BB 50 4D 42 47 D1
B1 47 47 42 75 4D 4E 49 35 2F FF 02 FF 02 BC 4E
49 90 50 8C 8C D2 48 48 4F 49 33 4B 29 23 FF 02
FF 02 79 9E 37 37 4D 61 79 7B 62 4E 43 4F 2C 50
28 2F FF 02 FF 02 7D 61 48 41 4D 92 64 7C 78 68
74 30 40 2E 44 34 FF 02 FF 02 6B 7D 61 B5 F0 F7
F7 D8 91 D0 57 52 3E 2A 2C 35 FF 02 FF 02 AC 80
63 BD A6 E5 F8 F5 E2 E0 57 57 58 47 2B 35 FF 02
FF 02 83 82 AB C4 A7 C3 F1 C0 B8 76 3F 52 3E 40
38 34 FF 02 FF 02 83 6D 6C DB ED E9 EA A4 E6 EF
89 57 3E 40 38 4C FF 02 FF 02 82 6C C7 C9 EB DB
DA DC E6 8D CE 57 1C 2A 38 29 FF 02 FF 02 82 7F
CA CB EB EA A6 A5 E6 8B 3F 3B 3E 40 38 35 FF 02
FF 02 65 7F C9 ED FE C2 C1 C3 D7 E0 DD 57 3D 2A
38 2F FF 02 FF 02 6F DC 98 FA FA FE E9 F2 D7 F4
DD 3B 3E 31 32 3A FF 02 FF 02 6D A9 96 EB FE F2
E8 E8 D5 E0 DD 3C 27 4F 2F 22 FF 02 FF 02 71 AA
C6 FA FA FE F2 F9 F6 E1 53 2C 29 1F 13 22 FF 02
FF 02 71 AD C6 ED FA FA FE FE D7 D4 4F 51 1B 14
15 15 FF 02 FF 02 9B 97 A7 EB FA FE FA FE D7 D4
39 20 16 14 13 1A FF 02 FF 02 6F CA C6 EB FA FE
FE FE E4 B7 39 19 16 1A 1A 25 FF 02 FF 02 66 CA
81 FA FA FE FA FE E7 D4 4F 20 1F 1F 21 1F FF 02
FF 02 9A 99 98 ED FA FA FA FA E4 B7 4F 18 1F 1A
25 1A FF 02 FF 02 73 71 CC FB FB FA FA FE E7 60
4F 19 14 14 1A 22 FF 02 FF 02 72 67 87 CB ED FA
FA FA A2 69 39 23 14 1A 1A 7E FF 02 FF 02 55 70
72 AE 85 C8 EC EB A0 B7 54 29 16 16 15 46 FF 02
FF 01 47 A1 55 56 73 86 84 6E CA BE 69 4F 1E 17
16 14 3A 32 FF 01 FF 01 47 5D 94 6A C5 AB A8 86
CA 7A B7 4F 26 1D 17 24 32 4E FF 01 FF 01 47 47
8E 93 95 BF D9 A5 C4 9F D6 4F 29 2D 36 2C 49 4D
B6 38 53 47 47 58 5A 77 E3 B9 BA D3 9D 5F 5E 5E
B3 B6 4E 75 A3 FF 01 75 DF B1 42 EE 74 FC B2 B2
8A B0 58 5B 4D B6 75 47 49 51 FF 01 4E 42 DF 59
DF 59 5B 47 5B 47 75 8C 5B DE 53 B3 4F 34 FF 01
FF 02 4D 42 5B DF 88 FD F3 58 DF 59 DF 53 9C 9E
B6 45 2F FF 01 FF 03 4D D2 47 D1 53 5B 53 D1 B1
75 B4 48 4F 45 FF 03 FF 05 4E D2 5E B4 5E 5E 4D
48 4F 44 FF 05

To me, it would make sense to just store it as [X, Y, PaletteElement] pairs for sparse textures containing lots of empty space. But is it the case with this one? No, I don't think so. At least I cannot see any pattern like that. Disassembled code also doesn't give any clues about this block. Looks like it just gets loaded into the memory like this and when I put a breakpoint on the memory block in Ida, it only gets triggered when the object is destroyed. Maybe it looks familiar to someone? I noticed that some values appear more often than others, for example 0xFF or 0x02. But does it mean anything? Any help is much appreciated. I attached the sprite I am talking about to this post.
  • Author
  • Localization

picusiaubas, posted Fri Nov 05, 2021 10:07 pm (67384)


I have spent a big part of my free time on this and there are some updates (mostly bad ones):

I decompiled parts of the code that deal with animation files (*.ani). Looks like there is just a variable number of animations packed into that *.ani file. For example, for people moving on the streets (say kids_m_sma.ani) there would be 8 animations that correspond to movement in each direction. For people's movement inside the restaurant (fe. kids_m_big.ani), there would be 8 animations for movement, 4 animations for sitting at the table, and 4 animations for eating. "Animation" itself is just a sequence of 24 sprites. Each of them of course in that stupid compressed SPR format seen in the previous post. So it's impossible to do anything with these animations without understanding the compression algorithm apart from changing movement speed. Which I did, but there is not much sense in that anyway).

And regarding the SPR files: I actually found some pieces of code that process the compressed part I showed in the previous post. But I have no clue what is going on there.
I found out it generates a new array of 512 bytes in size from the last 768 bytes in the SPR file (which I assumed to be a color palette) and uses it somewhere.
Then it iterates through the compressed part. "FF" values from the previous post are some kind of "separators". It would read that array from one "FF" byte to another, doing something with the byte which comes just after "FF" - either of 05/03/02/01 in this case. And then processing the rest of the values till the next "FF" value.

Here is another good example of 3 sprites with small size, same shape but different colors: m_kids.spr, m_teens.spr, m_studies.spr.
Corresponding in-game images for kids, teens and studies (sprites are probably without background):

Image

Both parts of SPR files are different when I compare them. But the first part is somewhat similar. It's only that "separator" that is different.
The game is using ddraw.dll and DirectX 7. Maybe it's some kind of format that this DDRAW thing renders automatically and I'm digging too deep missing something obvious?

To finish the post on a positive note, I managed to relatively easily decompile the code that sets and displays building attendance numbers. They are all hard-coded into an exe file, so binary patch is required. I did that and managed to make a train station "alive". This empty train station has always been super irritating and illogical to me. Now it's full of people!

Image

I am not sure how to share the details on what bytes need to be patched as the offsets probably depend on the game version. But if anyone is interested, just drop me your exe file and I can do it.

I also accidentally found the code which calculates income when pizza is served to the customer (from some hard-coded strings the developers have left for debugging purposes :lol: ). It revealed each served customer is counted as 100 pizzas sold. But what is more interesting, there are hidden tips percentage coefficients for each customer group. Which are added to each of these 100 sold pizzas. I find this both fascinating and illogical. Fascinating because it's not mentioned anywhere I had no clue something like this was happening while I was playing the game for the last 20 years. And illogical because tips is something that waiters get - and not the restaurant owner.

Oh, and I found a way to enable fps indication which was used by developers/testers to check performance back in a day. Cool, but kind of useless thing :P
  • Author
  • Localization

picusiaubas, posted Sun Nov 07, 2021 9:52 pm (67424)


A little bit more effort during the weekend and I think I approximately know how this compression works.
To begin with I decided to ignore colours completely and just try to understand how pixel positions are calculated. This turned out to be a brilliant idea.
For that 3 images from the previous post I found this simple algorithm works for image contours:

Code:
* Read 2 bytes from the first block (which starts at byte 33)

* If first of these bytes is not zero - move X offset by amount defined in the second of the read bytes.
* If first of these bytes is zero - pixel has a colour at current X offset. Mark it and move X offset by 1.

* If current X offset is equal to image width, it means that row is filled. Reset X offset to 0 and increase Y offset by 1.

* Repeat same steps till all rows are filled (Y offset is equal to image height).

Real image dimensions are defined in bytes 8-9 (width which is equal to 12) and in bytes 10-11 (height which is equal to 16).
In Hex Rays this algorithm was barely understandable with 3 while loops and tons of strange bit shift operations. While in reality, it's pretty easy. But I doubt it would work for any sprite in the game as there are 5 or 6 more similar pieces of code in the same method with minor differences.

Nonetheless, running this algorithm on the images from previous post gave me this result:
Image

Which is indeed correct. But there is so much more to find out.. First of all how the colours are constructed. That part looks really complicated in Hex Rays.
  • Author
  • Localization

picusiaubas, posted Mon Nov 08, 2021 9:50 pm (67436)


OMG! Sometimes you just have to drop decompiled code and trust your intuition..
Today I was trying to understand something from the way Hex-Rays handles colors on this little sprite. With not much of luck. And then decided to try something very obvious.
I played around with the algorithm I posted in the previous post. An obvious thing to do was to replace the second step, which was
Code:
* If first of these bytes is zero - pixel has a color at current X offset. Mark it and move X offset by 1.

with this:
Code:
* If first of these bytes is zero - pixel has a color. Get second of the read bytes and multiply it by 3. That's the offset in the color palette (which is the last 768 bytes of the SPR file).
RGB value of the color is defined in the next 3 color palette bytes. Mark it at current X offset and move X offset by 1.

And it worked like a magic! Here is the result:
Image
Sure, the colors are slightly off. But they do make sense. And that gives me lots of optimism and motivation to keep digging into this.

picusiaubas wrote:
I found out it generates a new array of 512 bytes in size from the last 768 bytes in the SPR file (which I assumed to be a color palette) and uses it somewhere.

BTW, I'm absolutely sure this part is what is happening in the game code. It is generating a 512 bytes color palette from the 768 bytes palette. Yet I was almost able to get it to work with 768 bytes color palette. My only explanation for this is that it is reducing bit depth on purpose to increase performance. Well, maybe it made sense back in a day..
Guest
This topic is now closed to further replies.

Account

Navigation

Search

Search

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.