Files
Aaru-aaru-dps/Aaru.Archives/Rar/Helpers.cs

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;
}
}
}