mirror of
https://github.com/aaru-dps/Aaru.git
synced 2025-12-16 11:14:25 +00:00
393 lines
16 KiB
C#
393 lines
16 KiB
C#
// /***************************************************************************
|
|
// Aaru Data Preservation Suite
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// Filename : Symbian.cs
|
|
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
|
//
|
|
// Component : Symbian plugin.
|
|
//
|
|
// --[ Description ] ----------------------------------------------------------
|
|
//
|
|
// Identifies Symbian installer (.sis) packages and shows information.
|
|
//
|
|
// --[ License ] --------------------------------------------------------------
|
|
//
|
|
// This library is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as
|
|
// published by the Free Software Foundation; either version 2.1 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This library is distributed in the hope that it will be useful, but
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public
|
|
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
// Copyright © 2011-2025 Natalia Portillo
|
|
// ****************************************************************************/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using Aaru.CommonTypes.Interfaces;
|
|
using Aaru.Helpers;
|
|
using Aaru.Logging;
|
|
using Spectre.Console;
|
|
using Marshal = Aaru.Helpers.Marshal;
|
|
|
|
namespace Aaru.Archives;
|
|
|
|
public sealed partial class Symbian
|
|
{
|
|
#region IArchive Members
|
|
|
|
/// <inheritdoc />
|
|
public bool Identify(IFilter filter)
|
|
{
|
|
if(filter.DataForkLength < Marshal.SizeOf<SymbianHeader>()) return false;
|
|
|
|
Stream stream = filter.GetDataForkStream();
|
|
|
|
var hdr = new byte[Marshal.SizeOf<SymbianHeader>()];
|
|
|
|
stream.EnsureRead(hdr, 0, hdr.Length);
|
|
|
|
SymbianHeader header = Marshal.ByteArrayToStructureLittleEndian<SymbianHeader>(hdr);
|
|
|
|
if(header.uid1 == SYMBIAN9_MAGIC) return true;
|
|
|
|
if(header.uid3 != SYMBIAN_MAGIC) return false;
|
|
|
|
return header.uid2 is EPOC_MAGIC or EPOC6_MAGIC;
|
|
}
|
|
|
|
public void GetInformation(IFilter filter, Encoding encoding, out string information)
|
|
{
|
|
information = "";
|
|
var description = new StringBuilder();
|
|
var languages = new List<string>();
|
|
var capabilities = new Dictionary<uint, uint>();
|
|
Stream stream = filter.GetDataForkStream();
|
|
|
|
if(stream.Length < Marshal.SizeOf<SymbianHeader>()) return;
|
|
|
|
var buffer = new byte[Marshal.SizeOf<SymbianHeader>()];
|
|
|
|
stream.Seek(0, SeekOrigin.Begin);
|
|
stream.EnsureRead(buffer, 0, buffer.Length);
|
|
SymbianHeader sh = Marshal.ByteArrayToStructureLittleEndian<SymbianHeader>(buffer);
|
|
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.uid1[/] = [teal]{0}[/]", sh.uid1);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.uid2[/] = [teal]{0}[/]", sh.uid2);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.uid3[/] = [teal]{0}[/]", sh.uid3);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.uid4[/] = [teal]{0}[/]", sh.uid4);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.crc16[/] = [teal]{0}[/]", sh.crc16);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.languages[/] = [teal]{0}[/]", sh.languages);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.files[/] = [teal]{0}[/]", sh.files);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.requisites[/] = [teal]{0}[/]", sh.requisites);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.inst_lang[/] = [teal]{0}[/]", sh.inst_lang);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.inst_files[/] = [teal]{0}[/]", sh.inst_files);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.inst_drive[/] = [teal]{0}[/]", sh.inst_drive);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.capabilities[/] = [teal]{0}[/]", sh.capabilities);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.inst_version[/] = [teal]{0}[/]", sh.inst_version);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.options[/] = [teal]{0}[/]", sh.options);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.type[/] = [teal]{0}[/]", sh.type);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.major[/] = [teal]{0}[/]", sh.major);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.minor[/] = [teal]{0}[/]", sh.minor);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.variant[/] = [teal]{0}[/]", sh.variant);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.lang_ptr[/] = [teal]{0}[/]", sh.lang_ptr);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.files_ptr[/] = [teal]{0}[/]", sh.files_ptr);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.reqs_ptr[/] = [teal]{0}[/]", sh.reqs_ptr);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.certs_ptr[/] = [teal]{0}[/]", sh.certs_ptr);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.comp_ptr[/] = [teal]{0}[/]", sh.comp_ptr);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.sig_ptr[/] = [teal]{0}[/]", sh.sig_ptr);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.caps_ptr[/] = [teal]{0}[/]", sh.caps_ptr);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.instspace[/] = [teal]{0}[/]", sh.instspace);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.maxinsspc[/] = [teal]{0}[/]", sh.maxinsspc);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.reserved1[/] = [teal]{0}[/]", sh.reserved1);
|
|
AaruLogging.Debug(MODULE_NAME, "[navy]sh.reserved2[/] = [teal]{0}[/]", sh.reserved2);
|
|
|
|
_release6 = false;
|
|
|
|
if(sh.uid1 == SYMBIAN9_MAGIC)
|
|
{
|
|
description.AppendLine(Localization.Symbian_Installation_File);
|
|
description.AppendLine(Localization.Symbian_9_1_or_later);
|
|
description.AppendFormat(Localization.Application_ID_0, sh.uid3).AppendLine();
|
|
_release6 = true;
|
|
|
|
description.AppendLine();
|
|
description.AppendLine(Localization.This_file_format_is_not_yet_implemented);
|
|
|
|
information = description.ToString();
|
|
|
|
return;
|
|
}
|
|
|
|
if(sh.uid3 == SYMBIAN_MAGIC)
|
|
{
|
|
description.AppendLine(Localization.Symbian_Installation_File);
|
|
|
|
switch(sh.uid2)
|
|
{
|
|
case EPOC_MAGIC:
|
|
description.AppendLine(Localization.Symbian_3_or_later);
|
|
|
|
break;
|
|
case EPOC6_MAGIC:
|
|
description.AppendLine(Localization.Symbian_6_or_later);
|
|
_release6 = true;
|
|
|
|
break;
|
|
default:
|
|
description.AppendFormat(Localization.Unknown_EPOC_magic_0, sh.uid2).AppendLine();
|
|
|
|
break;
|
|
}
|
|
|
|
description.AppendFormat(Localization.Application_ID_0, sh.uid1).AppendLine();
|
|
}
|
|
|
|
if(sh.options.HasFlag(SymbianOptions.IsUnicode))
|
|
_encoding = Encoding.Unicode;
|
|
else
|
|
_encoding = encoding ?? Encoding.GetEncoding("windows-1252");
|
|
|
|
var br = new BinaryReader(stream);
|
|
|
|
// Go to enumerate languages
|
|
br.BaseStream.Seek(sh.lang_ptr, SeekOrigin.Begin);
|
|
for(var i = 0; i < sh.languages; i++) languages.Add(((LanguageCodes)br.ReadUInt16()).ToString("G"));
|
|
|
|
// Go to component record
|
|
br.BaseStream.Seek(sh.comp_ptr, SeekOrigin.Begin);
|
|
|
|
var componentRecord = new ComponentRecord
|
|
{
|
|
names = new string[languages.Count]
|
|
};
|
|
|
|
buffer = new byte[sizeof(uint) * languages.Count];
|
|
|
|
// Read the component string lenghts
|
|
stream.EnsureRead(buffer, 0, buffer.Length);
|
|
ReadOnlySpan<byte> span = buffer;
|
|
componentRecord.namesLengths = MemoryMarshal.Cast<byte, uint>(span)[..languages.Count].ToArray();
|
|
|
|
// Read the component string pointers
|
|
stream.EnsureRead(buffer, 0, buffer.Length);
|
|
span = buffer;
|
|
componentRecord.namesPointers = MemoryMarshal.Cast<byte, uint>(span)[..languages.Count].ToArray();
|
|
|
|
for(var i = 0; i < sh.languages; i++)
|
|
{
|
|
AaruLogging.Debug(MODULE_NAME,
|
|
Localization.Found_component_name_for_language_0_at_1_with_a_length_of_2_bytes,
|
|
languages[i],
|
|
componentRecord.namesPointers[i],
|
|
componentRecord.namesLengths[i]);
|
|
|
|
br.BaseStream.Seek(componentRecord.namesPointers[i], SeekOrigin.Begin);
|
|
buffer = br.ReadBytes((int)componentRecord.namesLengths[i]);
|
|
componentRecord.names[i] = _encoding.GetString(buffer);
|
|
}
|
|
|
|
// Go to capabilities (???)
|
|
br.BaseStream.Seek(sh.caps_ptr, SeekOrigin.Begin);
|
|
|
|
for(var i = 0; i < sh.capabilities; i++)
|
|
{
|
|
uint cap_Key = br.ReadUInt32();
|
|
uint cap_Value = br.ReadUInt32();
|
|
capabilities.Add(cap_Key, cap_Value);
|
|
}
|
|
|
|
description.AppendFormat(Localization.UIDs_checksum_0, sh.uid4).AppendLine();
|
|
description.AppendFormat(Localization.Archive_options_0, sh.options).AppendLine();
|
|
description.AppendFormat(Localization.CRC16_of_header_0, sh.crc16).AppendLine();
|
|
description.AppendLine();
|
|
|
|
switch(sh.type)
|
|
{
|
|
case SymbianType.Application:
|
|
description.AppendLine(Localization.SIS_contains_an_application);
|
|
|
|
break;
|
|
}
|
|
|
|
description.AppendFormat(Localization.Component_version_0_1, sh.major, sh.minor).AppendLine();
|
|
|
|
description.AppendLine();
|
|
|
|
description.AppendFormat(Localization.File_contains_0_languages, sh.languages).AppendLine();
|
|
|
|
for(var i = 0; i < languages.Count; i++)
|
|
{
|
|
if(i > 0) description.Append(", ");
|
|
description.Append($"[italic][rosybrown]{languages[i]}[/][/]");
|
|
}
|
|
|
|
description.AppendLine();
|
|
description.AppendLine();
|
|
|
|
for(var i = 0; i < languages.Count; i++)
|
|
{
|
|
description.AppendFormat(Localization.Component_name_for_language_with_code_0_1,
|
|
languages[i],
|
|
Markup.Escape(componentRecord.names[i]))
|
|
.AppendLine();
|
|
}
|
|
|
|
description.AppendLine();
|
|
|
|
description.AppendFormat(Localization.File_contains_0_files_pointer_1, sh.files, sh.files_ptr).AppendLine();
|
|
description.AppendFormat(Localization.File_contains_0_requisites, sh.requisites).AppendLine();
|
|
|
|
uint offset = sh.reqs_ptr;
|
|
|
|
if(sh.requisites > 0)
|
|
{
|
|
for(var r = 0; r < sh.requisites; r++)
|
|
{
|
|
br.BaseStream.Seek(offset, SeekOrigin.Begin);
|
|
|
|
var requisiteRecord = new RequisiteRecord
|
|
{
|
|
uid = br.ReadUInt32(),
|
|
majorVersion = br.ReadUInt16(),
|
|
minorVersion = br.ReadUInt16(),
|
|
variant = br.ReadUInt32()
|
|
};
|
|
|
|
buffer = br.ReadBytes(sizeof(uint) * languages.Count);
|
|
span = buffer;
|
|
requisiteRecord.namesLengths = MemoryMarshal.Cast<byte, uint>(span)[..languages.Count].ToArray();
|
|
|
|
buffer = br.ReadBytes(sizeof(uint) * languages.Count);
|
|
span = buffer;
|
|
requisiteRecord.namesPointers = MemoryMarshal.Cast<byte, uint>(span)[..languages.Count].ToArray();
|
|
|
|
description.AppendFormat(Localization.Requisite_0, r).AppendLine();
|
|
|
|
description.AppendFormat("\t" + Localization.Required_UID_0_version_1_2,
|
|
Markup.Escape(DecodePlatformUid(requisiteRecord.uid)),
|
|
requisiteRecord.majorVersion,
|
|
requisiteRecord.minorVersion)
|
|
.AppendLine();
|
|
|
|
description.AppendFormat("\t" + Localization.Required_variant_0, requisiteRecord.variant).AppendLine();
|
|
|
|
offset = (uint)br.BaseStream.Position;
|
|
|
|
for(var i = 0; i < languages.Count; i++)
|
|
{
|
|
br.BaseStream.Seek(requisiteRecord.namesPointers[i], SeekOrigin.Begin);
|
|
buffer = br.ReadBytes((int)requisiteRecord.namesLengths[i]);
|
|
|
|
description.AppendFormat("\t" + Localization.Requisite_for_language_0_1,
|
|
languages[i],
|
|
Markup.Escape(_encoding.GetString(buffer)))
|
|
.AppendLine();
|
|
}
|
|
|
|
description.AppendLine();
|
|
}
|
|
}
|
|
|
|
// description.AppendLine(Localization.Capabilities);
|
|
// foreach(KeyValuePair<uint, uint> kvp in capabilities)
|
|
// description.AppendFormat("{0} = {1}", kvp.Key, kvp.Value).AppendLine();
|
|
|
|
// Set instance values
|
|
_files = [];
|
|
_conditions = [];
|
|
|
|
uint currentFile = 0;
|
|
offset = sh.files_ptr;
|
|
var conditionLevel = 0;
|
|
_options = [];
|
|
|
|
// Get only the options records
|
|
do
|
|
{
|
|
Parse(br, ref offset, ref currentFile, sh.files, languages, ref conditionLevel, true);
|
|
} while(currentFile < sh.files);
|
|
|
|
// Get all other records
|
|
offset = sh.files_ptr;
|
|
currentFile = 0;
|
|
conditionLevel = 0;
|
|
|
|
do
|
|
{
|
|
Parse(br, ref offset, ref currentFile, sh.files, languages, ref conditionLevel, false);
|
|
} while(currentFile < sh.files);
|
|
|
|
description.AppendLine();
|
|
|
|
// Files appear on .sis in the reverse order they should be processed
|
|
_files.Reverse();
|
|
|
|
// Conditions do as well
|
|
_conditions.Reverse();
|
|
|
|
if(_files.Any(static t => t.language is null))
|
|
{
|
|
description.AppendLine(Localization.Files_for_all_languages);
|
|
|
|
foreach(DecodedFileRecord file in _files.Where(static t => t.language is null))
|
|
description.AppendLine($"[green]{Markup.Escape(file.destinationName)}[/]");
|
|
|
|
description.AppendLine();
|
|
}
|
|
|
|
foreach(string lang in languages)
|
|
{
|
|
if(_files.All(t => t.language != lang)) continue;
|
|
|
|
description.AppendFormat(Localization.Files_for_0_language, lang).AppendLine();
|
|
|
|
foreach(DecodedFileRecord file in _files.Where(t => t.language == lang))
|
|
description.AppendLine($"[green]{Markup.Escape(file.destinationName)}[/]");
|
|
|
|
description.AppendLine();
|
|
}
|
|
|
|
if(_options.Count > 0)
|
|
{
|
|
for(var i = 0; i < _options.Count; i++)
|
|
{
|
|
OptionRecord option = _options[i];
|
|
|
|
description.AppendFormat(Localization.Option_0, i + 1).AppendLine();
|
|
|
|
foreach(KeyValuePair<string, string> kvp in option.names)
|
|
{
|
|
description.AppendFormat("\t" + Localization.Name_for_language_0_1,
|
|
kvp.Key,
|
|
Markup.Escape(kvp.Value))
|
|
.AppendLine();
|
|
}
|
|
}
|
|
|
|
description.AppendLine();
|
|
}
|
|
|
|
if(_conditions.Count > 0)
|
|
{
|
|
description.AppendLine(Localization.Conditions);
|
|
foreach(string condition in _conditions) description.AppendLine(Markup.Escape($"[green]{condition}[/]"));
|
|
}
|
|
|
|
information = description.ToString();
|
|
}
|
|
|
|
#endregion
|
|
} |