+ return data + 1 + ofsSize + lenSize;
+}
+
+tNTFS_Attrib *NTFS_GetAttrib(tNTFS_Disk *Disk, Uint32 MFTEntry, int Type, const char *Name, int DesIdx)
+{
+ ENTER("pDisk xMFTEntry xType sName iDesIdx",
+ Disk, MFTEntry, Type, Name, DesIdx);
+ int curIdx = 0;
+ // TODO: Scan cache of attributes
+
+ // Load MFT entry
+ tNTFS_FILE_Header *hdr = NTFS_GetMFT(Disk, MFTEntry);
+ LOG("hdr = %p", hdr);
+
+ tNTFS_FILE_Attrib *attr;
+ for( size_t ofs = hdr->FirstAttribOfs; ofs < hdr->RecordSize; ofs += attr->Size )
+ {
+ attr = (void*)( (tVAddr)hdr + ofs );
+ // Sanity #1: Type
+ if( ofs + 4 > hdr->RecordSize )
+ break ;
+ // End-of-list?
+ if( attr->Type == 0xFFFFFFFF )
+ break;
+ // Sanity #2: Type,Size
+ if( ofs + 8 > hdr->RecordSize )
+ break;
+ // Sanity #3: Reported size
+ if( attr->Size < sizeof(attr->Resident) )
+ break;
+ // Sanity #4: Reported size fits
+ if( ofs + attr->Size > hdr->RecordSize )
+ break;
+
+ // - Chceck if this attribute is the one requested
+ LOG("Type check %x == %x", attr->Type, Type);
+ if( attr->Type != Type )
+ continue;
+ if( Name ) {
+ LOG("Name check = '%s'", Name);
+ const void *name16 = (char*)attr + attr->NameOffset;
+ if( UTF16_CompareWithUTF8(attr->NameLength, name16, Name) != 0 )
+ continue ;
+ }
+ LOG("Idx check %i", curIdx);
+ if( curIdx++ != DesIdx )
+ continue ;
+
+ // - Construct (and cache) attribute description
+ ASSERT(attr->NameOffset % 1 == 0);
+ Uint16 *name16 = (Uint16*)attr + attr->NameOffset/2;
+ size_t namelen = UTF16_ConvertToUTF8(0, NULL, attr->NameLength, name16);
+ size_t edatalen = (attr->NonresidentFlag ? 0 : attr->Resident.AttribLen*4);
+ tNTFS_Attrib *ret = malloc( sizeof(tNTFS_Attrib) + namelen + 1 + edatalen );
+ if(!ret) {
+ LEAVE('n');
+ return NULL;
+ }
+ if( attr->NonresidentFlag )
+ ret->Name = (void*)(ret + 1);
+ else {
+ ret->ResidentData = ret + 1;
+ ret->Name = (char*)ret->ResidentData + edatalen;
+ }
+
+ ret->Disk = Disk;
+ ret->Type = attr->Type;
+ UTF16_ConvertToUTF8(namelen+1, ret->Name, attr->NameLength, name16);
+ ret->IsResident = !(attr->NonresidentFlag);
+
+ LOG("Creating with %x '%s'", ret->Type, ret->Name);
+
+ if( attr->NonresidentFlag )
+ {
+ ret->DataSize = attr->NonResident.RealSize;
+ ret->NonResident.CompressionUnitL2Size = attr->NonResident.CompressionUnitSize;
+ ret->NonResident.FirstPopulatedCluster = attr->NonResident.StartingVCN;
+ // Count data runs
+ const char *limit = (char*)attr + attr->Size;
+ int nruns = 0;
+ const char *datarun = (char*)attr + attr->NonResident.DataRunOfs;
+ while( (datarun = _GetDataRun(datarun, limit, 0, NULL, NULL)) )
+ nruns ++;
+ LOG("nruns = %i", nruns);
+ // Allocate data runs
+ ret->NonResident.nRuns = nruns;
+ ret->NonResident.Runs = malloc( sizeof(tNTFS_AttribDataRun) * nruns );
+ int i = 0;
+ datarun = (char*)attr + attr->NonResident.DataRunOfs;
+ Uint64 lastLCN = 0;
+ while( datarun && i < nruns )
+ {
+ tNTFS_AttribDataRun *run = &ret->NonResident.Runs[i];
+ datarun = _GetDataRun(datarun,limit, lastLCN, &run->Count, &run->LCN);
+ LOG("Run %i: %llx+%llx", i, run->LCN, run->Count);
+ lastLCN = run->LCN;
+ i ++;
+ }
+ }
+ else
+ {
+ ret->DataSize = edatalen;
+ memcpy(ret->ResidentData, (char*)attr + attr->Resident.AttribOfs, edatalen);
+ }
+
+ LEAVE('p', ret);
+ return ret;
+ }
+
+ NTFS_ReleaseMFT(Disk, MFTEntry, hdr);
+ LEAVE('n');
+ return NULL;
+}
+
+size_t NTFS_ReadAttribData(tNTFS_Attrib *Attrib, Uint64 Offset, size_t Length, void *Buffer)
+{
+ if( !Attrib )
+ return 0;
+ if( Offset >= Attrib->DataSize )
+ return 0;
+ if( Length > Attrib->DataSize )
+ Length = Attrib->DataSize;
+ if( Offset + Length > Attrib->DataSize )
+ Length = Attrib->DataSize - Offset;
+
+ if( Attrib->IsResident )
+ {
+ memcpy(Buffer, Attrib->ResidentData, Length);
+ return Length;
+ }
+ else
+ {
+ size_t ret = 0;
+ tNTFS_Disk *Disk = Attrib->Disk;
+ Uint64 first_cluster = Offset / Disk->ClusterSize;
+ size_t cluster_ofs = Offset % Disk->ClusterSize;
+ if( first_cluster < Attrib->NonResident.FirstPopulatedCluster ) {
+ Log_Warning("NTFS", "TODO: Ofs < FirstVCN");
+ }
+ first_cluster -= Attrib->NonResident.FirstPopulatedCluster;
+ if( Attrib->NonResident.CompressionUnitL2Size )
+ {
+ // TODO: Compression
+ Log_Warning("NTFS", "Compression unsupported");
+ // NOTE: Compressed blocks show up in pairs of runs
+ // - The first contains the compressed data
+ // - The second is a placeholder 'sparse' (LCN=0) to align to the compression unit
+ }
+ else
+ {
+ // Iterate through data runs until the desired run is located
+ for( int i = 0; i < Attrib->NonResident.nRuns && Length; i ++ )
+ {
+ tNTFS_AttribDataRun *run = &Attrib->NonResident.Runs[i];
+ if( first_cluster > run->Count ) {
+ first_cluster -= run->Count;
+ continue ;
+ }
+ size_t avail_bytes = (run->Count-first_cluster)*Disk->ClusterSize - cluster_ofs;
+ if( avail_bytes > Length )
+ avail_bytes = Length;
+ // Read from this extent
+ if( run->LCN == 0 ) {
+ memset(Buffer, 0, avail_bytes);
+ }
+ else {
+ VFS_ReadAt(Disk->FD,
+ (run->LCN + first_cluster)*Disk->ClusterSize + cluster_ofs,
+ avail_bytes,
+ Buffer
+ );
+ }
+ Length -= avail_bytes;
+ Buffer += avail_bytes;
+ ret += avail_bytes;
+ first_cluster = 0;
+ cluster_ofs = 0;
+ continue ;
+ }
+ }
+ return ret;
+ }