Remedy Archive System

From Just Solve the File Format Problem
(Difference between revisions)
Jump to: navigation, search
m (decrypt: i and key are cast to signed char, not unsigned char)
 
(6 intermediate revisions by one user not shown)
Line 13: Line 13:
  
 
Numbers are in little-endian byte order.
 
Numbers are in little-endian byte order.
 +
 +
=== Metadata ===
  
 
The file's header has the following structure:
 
The file's header has the following structure:
Line 18: Line 20:
 
struct RASHeader {
 
struct RASHeader {
 
   uint8_t magic[4]; // "RAS\0"
 
   uint8_t magic[4]; // "RAS\0"
   uint32_t encryptionKey;
+
   int32_t encryptionKey;
 
};
 
};
 
</pre>
 
</pre>
Line 47: Line 49:
 
|-
 
|-
 
|| 1.2 || 3 || Max Payne
 
|| 1.2 || 3 || Max Payne
 +
|-
 +
|| 1.2 || 4 || Max Payne 2
 
|}
 
|}
  
Line 55: Line 59:
 
struct RASFileListEntry {
 
struct RASFileListEntry {
 
   char name[]; // NUL-terminated
 
   char name[]; // NUL-terminated
   uint32_t unknown0;
+
   uint32_t uncompressedSize;
   uint32_t unknown1;
+
   uint32_t compressedSize;
 
   uint32_t unknown2;
 
   uint32_t unknown2;
   uint32_t unknown3;
+
   uint32_t dirIndex;
 
   uint32_t unknown4;
 
   uint32_t unknown4;
   uint32_t unknown5;
+
   uint32_t compressionMode; // 1 = some form of Lempel-Ziv, 3 = uncompressed
   uint32_t unknown6;
+
   RASTimestamp timestamp;
  uint32_t unknown7;
+
  uint32_t unknown8;
+
  uint32_t unknown9;
+
 
};
 
};
 
</pre>
 
</pre>
Line 74: Line 75:
 
struct RASDirListEntry {
 
struct RASDirListEntry {
 
   char name[]; // NUL-terminated
 
   char name[]; // NUL-terminated
   uint16_t unknown0;
+
   RASTimestamp timestamp;
  uint16_t unknown1;
+
  uint16_t unknown2;
+
  uint16_t unknown3;
+
  uint16_t unknown4;
+
  uint16_t unknown5;
+
  uint16_t unknown6;
+
  uint16_t unknown7;
+
 
};
 
};
 
</pre>
 
</pre>
  
Each directory is listed with its full name which starts and ends with a backslash (<code>\</code>). The top-most directory is named <code>\</code>.  
+
Each directory is listed with its full name which starts and ends with a backslash (<code>\</code>). The top-most directory is named <code>\</code>. A file's <code>dirIndex</code> is a 0-based index into the directory list.
 +
 
 +
Timestamps are encoded using the same structure as SYSTEMTIME on Windows:
 +
 
 +
<pre>
 +
struct RASTimestamp {
 +
  uint16_t year;
 +
  uint16_t month;
 +
  uint16_t dayOfWeek; // 0 = Sunday, 1 = Monday, ..., 6 = Saturday
 +
  uint16_t day;
 +
  uint16_t hour;
 +
  uint16_t minute;
 +
  uint16_t second;
 +
  uint16_t millisecond;
 +
};
 +
</pre>
 +
 
 +
=== Data ===
 +
 
 +
The file data follows directly after the file and directory lists. Each file is stored in turn with no padding; skipping to a specific file requires adding up the <code>compressedSize</code> members of all files preceding it.
  
 
== Encryption ==
 
== Encryption ==
Line 91: Line 104:
 
Depending on the generation of the RAS file format, different encryption schemes are used.
 
Depending on the generation of the RAS file format, different encryption schemes are used.
  
=== RAS1 (Max Payne) ===
+
=== RAS1+2 (Max Payne 1+2) ===
  
 
<pre>
 
<pre>
 
void decrypt(uint8_t *buf, size_t count, int32_t key) {
 
void decrypt(uint8_t *buf, size_t count, int32_t key) {
 
   size_t i;
 
   size_t i;
  uint8_t a;
 
  uint8_t b;
 
  
 
   if (key == 0) {
 
   if (key == 0) {
Line 116: Line 127:
 
<pre>
 
<pre>
 
uint8_t rotateLeftByte(uint8_t a, uint8_t b) {
 
uint8_t rotateLeftByte(uint8_t a, uint8_t b) {
   return (a &lt;&lt; b) | (a &gt;&gt; (8 - b));
+
   return (uint8_t)((a &lt;&lt; b) | (a &gt;&gt; (8 - b)));
 
}
 
}
 
</pre>
 
</pre>
 +
 +
Note that the algorithm relies heavily on signed-integer wraparound and C's integer promotion rules; an operation like <code>((int8_t)i) + 3</code> will sign-extend the 8-bit value of <code>i</code> to 32 bits before adding 3.
  
 
[[Category:Remedy Entertainment]]
 
[[Category:Remedy Entertainment]]

Latest revision as of 15:47, 22 November 2025

File Format
Name Remedy Archive System
Ontology
Extension(s) .ras
Released 2001

Remedy Archive System is used to store game data for Remedy Entertainment games such as Max Payne and Max Payne 2. The metadata (central directory) following the header is encrypted.

Contents

[edit] Identification

Files begin with signature bytes 52 41 53 00.

[edit] Format details

Numbers are in little-endian byte order.

[edit] Metadata

The file's header has the following structure:

struct RASHeader {
  uint8_t magic[4]; // "RAS\0"
  int32_t encryptionKey;
};

The next section of the header must be decrypted first:

// decrypted structure
struct RASMetadata {
  uint32_t fileCount;
  uint32_t dirCount;
  uint32_t fileListLength;
  uint32_t dirListLength;
  float32_t version; // binary32 according to IEEE 754
  uint32_t unknown5;
  uint32_t unknown6;
  uint32_t unknown7;
  uint32_t compatibility;
};

The following version and compatibility values are known:

Version Compatibility Game
1.2 3 Max Payne
1.2 4 Max Payne 2

What follows are fileListLength encrypted bytes of file metadata that can be decrypted using encryptionKey. (It is not necessary to remember the last value of key from decrypting RASMetadata.) Each entry has the following structure:

// decrypted structure
struct RASFileListEntry {
  char name[]; // NUL-terminated
  uint32_t uncompressedSize;
  uint32_t compressedSize;
  uint32_t unknown2;
  uint32_t dirIndex;
  uint32_t unknown4;
  uint32_t compressionMode; // 1 = some form of Lempel-Ziv, 3 = uncompressed
  RASTimestamp timestamp;
};

Next is a directory list of dirCount elements which must be decrypted similarly.

// decrypted structure
struct RASDirListEntry {
  char name[]; // NUL-terminated
  RASTimestamp timestamp;
};

Each directory is listed with its full name which starts and ends with a backslash (\). The top-most directory is named \. A file's dirIndex is a 0-based index into the directory list.

Timestamps are encoded using the same structure as SYSTEMTIME on Windows:

struct RASTimestamp {
  uint16_t year;
  uint16_t month;
  uint16_t dayOfWeek; // 0 = Sunday, 1 = Monday, ..., 6 = Saturday
  uint16_t day;
  uint16_t hour;
  uint16_t minute;
  uint16_t second;
  uint16_t millisecond;
};

[edit] Data

The file data follows directly after the file and directory lists. Each file is stored in turn with no padding; skipping to a specific file requires adding up the compressedSize members of all files preceding it.

[edit] Encryption

Depending on the generation of the RAS file format, different encryption schemes are used.

[edit] RAS1+2 (Max Payne 1+2)

void decrypt(uint8_t *buf, size_t count, int32_t key) {
  size_t i;

  if (key == 0) {
    key = 1;
  }
  for (i = 0; i < count; i++) {
    uint8_t a = buf[i];
    uint8_t b = ((uint8_t)(i % 5)) & 7;
    buf[i] = rotateLeftByte(a, b);
    key = key * 171 + (key / 177) * -30269;
    buf[i] = (uint8_t)((((((int8_t)i) + 3) * 6) ^ buf[i]) + ((int8_t)key));
  }
}

If your programming language doesn't support the rotateLeftByte operation, it can be emulated using:

uint8_t rotateLeftByte(uint8_t a, uint8_t b) {
  return (uint8_t)((a << b) | (a >> (8 - b)));
}

Note that the algorithm relies heavily on signed-integer wraparound and C's integer promotion rules; an operation like ((int8_t)i) + 3 will sign-extend the 8-bit value of i to 32 bits before adding 3.

Personal tools
Namespaces

Variants
Actions
Navigation
Toolbox