mirror of
https://github.com/aaru-dps/Aaru.git
synced 2026-07-08 17:56:18 +00:00
217 lines
7.0 KiB
C#
217 lines
7.0 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Text;
|
|
|
|
namespace Aaru.Archives;
|
|
|
|
public sealed partial class Rar
|
|
{
|
|
/// <summary>Read a RAR 5.0 variable-length integer from the stream.</summary>
|
|
/// <remarks>
|
|
/// Each byte contributes 7 payload bits (bits 0-6).
|
|
/// Bit 7 is the continuation flag: 1 means more bytes follow, 0 means last byte.
|
|
/// Payload bits are accumulated LSB-first.
|
|
/// </remarks>
|
|
static ulong ReadVint(Stream stream)
|
|
{
|
|
ulong result = 0;
|
|
var shift = 0;
|
|
|
|
for(;;)
|
|
{
|
|
int b = stream.ReadByte();
|
|
|
|
if(b < 0) break;
|
|
|
|
result |= (ulong)(b & 0x7F) << shift;
|
|
|
|
if((b & 0x80) == 0) break;
|
|
|
|
shift += 7;
|
|
|
|
// Prevent infinite loop on malformed data (max 10 bytes = 70 bits)
|
|
if(shift >= 70) break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>Convert an MS-DOS packed date/time value to a <see cref="DateTime" />.</summary>
|
|
static DateTime DosToDateTime(uint dosDateTime)
|
|
{
|
|
int second = (int)(dosDateTime & 0x1F) * 2;
|
|
var minute = (int)(dosDateTime >> 5 & 0x3F);
|
|
var hour = (int)(dosDateTime >> 11 & 0x1F);
|
|
var day = (int)(dosDateTime >> 16 & 0x1F);
|
|
var month = (int)(dosDateTime >> 21 & 0x0F);
|
|
int year = (int)(dosDateTime >> 25 & 0x7F) + 1980;
|
|
|
|
if(day < 1) day = 1;
|
|
if(month < 1) month = 1;
|
|
if(month > 12) month = 12;
|
|
|
|
int maxDay = DateTime.DaysInMonth(year, month);
|
|
|
|
if(day > maxDay) day = maxDay;
|
|
if(hour > 23) hour = 23;
|
|
if(minute > 59) minute = 59;
|
|
if(second > 59) second = 59;
|
|
|
|
return new DateTime(year, month, day, hour, minute, second);
|
|
}
|
|
|
|
/// <summary>Decode a RAR 1.x-4.x Unicode filename from the raw header data.</summary>
|
|
/// <remarks>
|
|
/// The raw data contains a null-terminated ASCII name followed by a compact
|
|
/// encoding of Unicode high bytes using 2-bit mode flags per character.
|
|
/// </remarks>
|
|
static string DecodeUnicodeFilename(byte[] nameData, int nameLength, Encoding encoding)
|
|
{
|
|
// Find the null terminator separating ASCII part from Unicode encoding
|
|
var asciiEnd = 0;
|
|
|
|
while(asciiEnd < nameLength && nameData[asciiEnd] != 0) asciiEnd++;
|
|
|
|
if(asciiEnd >= nameLength) return encoding.GetString(nameData, 0, nameLength);
|
|
|
|
// Part 2 starts after the null terminator
|
|
int uniPos = asciiEnd + 1;
|
|
var flagBits = 0;
|
|
var flagByte = 0;
|
|
var highByte = 0;
|
|
var asciiPos = 0;
|
|
var result = new StringBuilder(asciiEnd + 16);
|
|
|
|
if(uniPos < nameLength) highByte = nameData[uniPos++] << 8;
|
|
|
|
while(asciiPos < asciiEnd)
|
|
{
|
|
if(flagBits == 0)
|
|
{
|
|
if(uniPos >= nameLength) break;
|
|
|
|
flagByte = nameData[uniPos++];
|
|
flagBits = 8;
|
|
}
|
|
|
|
flagBits -= 2;
|
|
int mode = flagByte >> flagBits & 3;
|
|
|
|
switch(mode)
|
|
{
|
|
case 0:
|
|
// Low byte from ASCII part, use stored high byte
|
|
result.Append((char)(highByte | nameData[asciiPos++]));
|
|
|
|
break;
|
|
|
|
case 1:
|
|
// Read one byte, combine with high byte
|
|
if(uniPos >= nameLength) goto done;
|
|
|
|
result.Append((char)(highByte | nameData[uniPos++]));
|
|
asciiPos++;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
// Read two bytes as full LE character
|
|
if(uniPos + 1 >= nameLength) goto done;
|
|
|
|
result.Append((char)(nameData[uniPos] | nameData[uniPos + 1] << 8));
|
|
uniPos += 2;
|
|
asciiPos++;
|
|
|
|
break;
|
|
|
|
case 3:
|
|
// Repetition from ASCII string
|
|
if(uniPos >= nameLength) goto done;
|
|
|
|
int len = nameData[uniPos++];
|
|
|
|
if((len & 0x80) != 0)
|
|
{
|
|
// Read correction byte and repeat with high byte adjustment
|
|
if(uniPos >= nameLength) goto done;
|
|
|
|
byte correction = nameData[uniPos++];
|
|
int count = (len & 0x7F) + 2;
|
|
|
|
for(var i = 0; i < count && asciiPos < asciiEnd; i++, asciiPos++)
|
|
result.Append((char)(highByte | nameData[asciiPos] + correction & 0xFF));
|
|
}
|
|
else
|
|
{
|
|
// Repeat len+2 bytes as-is from ASCII string
|
|
int count = (len & 0x7F) + 2;
|
|
|
|
for(var i = 0; i < count && asciiPos < asciiEnd; i++, asciiPos++)
|
|
result.Append((char)nameData[asciiPos]);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
done:
|
|
|
|
return result.ToString();
|
|
}
|
|
|
|
/// <summary>Parse extended time fields from a RAR 1.x-4.x file header.</summary>
|
|
/// <remarks>
|
|
/// The extended time block encodes optional modification, creation, and access timestamps
|
|
/// with sub-second precision. Each timestamp has a 4-bit flags field:
|
|
/// bits 0-1 = number of extra bytes (0-3), bit 2 = has seconds count,
|
|
/// bit 3 = replaces DOS timestamp entirely (only for mtime).
|
|
/// </remarks>
|
|
static void ParseExtendedTime(byte[] headerData, int offset, int length, ref Entry entry)
|
|
{
|
|
if(offset + 2 > length) return;
|
|
|
|
var flags = BitConverter.ToUInt16(headerData, offset);
|
|
int pos = offset + 2;
|
|
|
|
// Modification time flags (bits 12-15)
|
|
int mtimeFlags = flags >> 12 & 0x0F;
|
|
|
|
if((mtimeFlags & 0x08) != 0 && pos + 4 <= length)
|
|
{
|
|
// Full timestamp replacement
|
|
var dosTime = BitConverter.ToUInt32(headerData, pos);
|
|
entry.LastWriteTime = DosToDateTime(dosTime);
|
|
pos += 4;
|
|
}
|
|
|
|
int mtimeExtra = mtimeFlags & 0x03;
|
|
|
|
for(var i = 0; i < mtimeExtra && pos < length; i++) pos++;
|
|
|
|
// Creation time flags (bits 8-11)
|
|
int ctimeFlags = flags >> 8 & 0x0F;
|
|
|
|
if((ctimeFlags & 0x08) != 0 && pos + 4 <= length)
|
|
{
|
|
var dosTime = BitConverter.ToUInt32(headerData, pos);
|
|
entry.CreationTime = DosToDateTime(dosTime);
|
|
entry.HasCreationTime = true;
|
|
pos += 4;
|
|
}
|
|
|
|
int ctimeExtra = ctimeFlags & 0x03;
|
|
|
|
for(var i = 0; i < ctimeExtra && pos < length; i++) pos++;
|
|
|
|
// Last access time flags (bits 4-7)
|
|
int atimeFlags = flags >> 4 & 0x0F;
|
|
|
|
if((atimeFlags & 0x08) != 0 && pos + 4 <= length)
|
|
{
|
|
var dosTime = BitConverter.ToUInt32(headerData, pos);
|
|
entry.LastAccessTime = DosToDateTime(dosTime);
|
|
entry.HasLastAccessTime = true;
|
|
pos += 4;
|
|
}
|
|
}
|
|
} |