一、任务
制作一个 FAT12 镜像查看器。
支持命令:ls, ls -l, cat。
二、知识
学长的文章(写得好好): FAT12镜像查看工具_解析fat32镜像文件-CSDN博客
cat 需要访问数据区。
ls、ls -l 需要访问目录区。
1. MBR 区
从第 11
个字节开始的 25
个字节构成一个较为特殊的结构 BPB(Bios Parameter Block)
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| typedef struct BPB { u16 BPB_BytsPerSec; //每扇区字节数 u8 BPB_SecPerClus; //每簇扇区数 u16 BPB_RsvdSecCnt; //Boot记录占用的扇区数 u8 BPB_NumFATs; //FAT表个数 u16 BPB_RootEntCnt; //根目录最大文件数 u16 BPB_TotSec16; //扇区总数 u8 BPB_Media; //介质描述符 u16 BPB_FATSz16; //每个FAT表所占扇区数 u16 BPB_SecPerTrk; //每磁道扇区数(Sector/track) u16 BPB_NumHeads; //磁头数(面数) u32 BPB_HiddSec; //隐藏扇区数 u32 BPB_TotSec32; //如果BPB_ToSec16为0,该值为扇区数 } BPB; //25字节
|
2. FAT 表
FAT1
与 FAT2
互为备份,所以理论上两张表是一样的。
FAT 表项的值的含义:
- 通常情况下代表文件下一簇号。
- 值
>= 0xFF8
,该簇已经是文件最后一个簇。
- 值
= 0xFF7
,表示一个坏簇。
簇的编号与 FAT 表中的索引:每个簇都有一个唯一的编号,FAT 表中的项索引与这些簇的编号对应。例如,第一个簇的编号对应 FAT 表中的第一个项,第二个簇对应第二个项,依此类推。
3. 根目录区
一个目录项占据 32
字节。
1 2 3 4 5 6 7 8 9 10
| typedef struct RootEntry { char DIR_Name[11]; //长度名+扩展名 u8 DIR_Attr; //文件属性 char reserved[10]; //保留位 u16 DIR_WrtTime; //最后一次写入时间 u16 DIR_WrtDate; //最后一次写入日期 u16 DIR_FstClus; //开始簇号 u32 DIR_FileSize; //文件大小 } RootEntry; //32字节
|
由此,归结起来,FAT12访问文件的基本操作为:
- 首先通过根目录文件查找文件名,确定是哪一个条目,接着在条目中访问 DIR_FstClus 对应的开始簇号。
- 当一个簇号访问完后,通过FAT表项查询下一簇号,决定是结束还是继续访问下一簇号,重复第二条。
4. 数据区
1 2 3 4 5 6 7 8 9
| typedef struct Entry{ char FILE_NAME[11]; //长度名+扩展名 u8 FILE_Attr; //文件属性 char reserved[10]; //保留位 u16 DIR_WrtTime; //最后一次写入时间 u16 DIR_WrtDate; //最后一次写入日期 u16 DIR_FstClus; //开始簇号 u32 DIR_FileSize; //文件大小 }; //跟 RootEntry 一模一样
|
5. File Attribution
位 |
掩码 |
描述 |
0 |
0x01 |
只读 |
1 |
0x02 |
隐藏 |
2 |
0x04 |
系统 |
3 |
0x08 |
卷标 |
4 |
0x10 |
子目录 |
5 |
0x20 |
档案 |
6 |
0x40 |
设备(内部使用,磁盘上看不到) |
7 |
0x80 |
没有使用 |
0x0F 是 LFN。
三、注意事项
整体是做一个深度优先遍历。从本质上来说,本次实验难度仅存在于定位数据。这里列出一些值得注意的点。
1. 数据区起始
数据区起始于簇 2。事实上这句话我到现在还没读懂,我是通过插入空簇来实现下标与需求匹配的。
2. FAT 表项的计算
FAT 每个表项占 1.5 个字节,因此每次提取三个字节计算两个表项。
若这三个字节十六进制表示为 AB CD EF(按地址从小到大排列)
那么有
1 2
| 前一个FAT = DAB 后一个FAT = EFC
|
3. 关于无关的 Entry
由于 FAT12 文件名仅支持至多 8 位大写的文件名与至多 3 位大写的后缀,若文件名出现小写,在镜像中可能会出现一些 Entry 作为补丁。我们可以通过这些项获取原文件名,但在访问的适合需要将这些项剔除。
插件 Hex-editor。
5. 其它
1 2 3 4 5 6 7 8 9 10 11
| myString getSpecialName(Entry entry); struct LongFileNameEntry { uint8_t LFNOrd; uint16_t LFNPart1[5]; uint8_t LFNAttributes; uint8_t LFNReserved1; uint8_t LFNChecksum; uint16_t LFNPart2[6]; uint16_t LFNReserved2; uint16_t LFNPart3[2]; };
|
框架中出现的这些属于扩展功能,不需要实现。(某人没看题QwQ)
三、代码设计
暂时先不考虑 LFN 的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
| #include <cstring> #include <fstream> #include <iostream> #include <regex> #include <string> #include <vector> #include "myString.h" using namespace std; #define mp(x, y) make_pair((x), (y)) #define pii pair<int, int> #define ui unsigned int
const int N = 1e6 + 6; bool isLower(char ch) { return ch >= 'a' && ch <= 'z'; } bool isUpper(char ch) { return ch >= 'A' && ch <= 'Z'; } bool isDigit(char ch) { return ch >= '0' && ch <= '9'; } bool isDot(char ch) { return ch == '.'; }
int getChId(char ch) { if ('a' <= ch && ch <= 'z') return ch - 'a'; else if ('A' <= ch && ch <= 'Z') return ch - 'A' + 26; else return -1; }
myString imgData; struct BPB { uint16_t BPB_BytsPerSec; uint8_t BPB_SecPerClus; uint16_t BPB_RsvdSecCnt; uint8_t BPB_NumFATs; uint16_t BPB_RootEntCnt; uint16_t BPB_TotSec16; uint8_t BPB_Media; uint16_t BPB_FATSz16; uint16_t BPB_SecPerTrk; uint16_t BPB_NumHeads; uint32_t BPB_HiddSec; uint32_t BPB_TotSec32; };
struct Entry { char FILE_Name[11]; uint8_t FILE_Attr; char reserved[10]; uint16_t DIR_WrtTime; uint16_t DIR_WrtDate; uint16_t DIR_FstClus; uint32_t DIR_FileSize; myString fileName;
bool isDir() { return this->FILE_Attr & 0x10; } };
struct LongFileNameEntry { uint8_t LFNOrd; uint16_t LFNPart1[5]; uint8_t LFNAttributes; uint8_t LFNReserved1; uint8_t LFNChecksum; uint16_t LFNPart2[6]; uint16_t LFNReserved2; uint16_t LFNPart3[2]; }; BPB getBPB(myString str) { char* temp = str.toCharArray(); BPB bpb; bpb.BPB_BytsPerSec = *((uint16_t*)&temp[0]); bpb.BPB_SecPerClus = *((uint8_t*)&temp[2]); bpb.BPB_RsvdSecCnt = *((uint16_t*)&temp[3]); bpb.BPB_NumFATs = *((uint8_t*)&temp[5]); bpb.BPB_RootEntCnt = *((uint16_t*)&temp[6]); bpb.BPB_TotSec16 = *((uint16_t*)&temp[8]); bpb.BPB_Media = *((uint8_t*)&temp[10]); bpb.BPB_FATSz16 = *((uint16_t*)&temp[11]); bpb.BPB_SecPerTrk = *((uint16_t*)&temp[13]); bpb.BPB_NumHeads = *((uint16_t*)&temp[15]); bpb.BPB_HiddSec = *((uint32_t*)&temp[17]); bpb.BPB_TotSec32 = *((uint32_t*)&temp[21]); return bpb; } Entry getEntery(myString str) { char* temp = str.toCharArray(); Entry entry;
strncpy(entry.FILE_Name, temp, 11); entry.FILE_Name[11] = '\0'; entry.FILE_Attr = temp[11]; strncpy(entry.reserved, &temp[12], 10); entry.reserved[10] = '\0'; entry.DIR_WrtTime = *((uint16_t*)&temp[22]); entry.DIR_WrtDate = *((uint16_t*)&temp[24]); entry.DIR_FstClus = *((uint16_t*)&temp[26]); entry.DIR_FileSize = *((uint32_t*)&temp[28]); return entry; }
BPB bpb; vector<int> tabFAT; vector<Entry> rootEntry; vector<vector<Entry> > clusEntry; vector<int> beginRootFile; vector<myString> command_list = {"ls", "cat"}; vector<char> ls_options = {'l'}, cat_options = {};
int beginData; Entry rt, errorNode;
struct Command { Command() : opName(""), opParam(0), target(""), errorFlag(false), errorMessage("") {} myString opName; int opParam; myString target; bool errorFlag; myString errorMessage; void setError(myString str) { errorFlag = true; errorMessage = str; } };
void init(myString str); bool optionExist(char ch, myString opName); bool lExist(int opt); int getNextClus(int dataEntryId); ui getSize(Entry entry); vector<Entry> getChildrens(Entry, bool flag = false); pii countChildren(Entry entry); pii countChildrenRoot(); myString getEntryInfo(Entry entry, bool flag = false); myString getEntryName(Entry entry); myString lsRoot(int opt); myString ls(Entry entry, myString path, int opt); myString getClusData(int id); myString cat(Entry entry); Entry getEntry(myString path); myString getStartPath(myString path); Command get_command(myString input); myString getSpecialName(Entry entry); myString getNormalName(Entry entry); bool shouldLowerCase(myString str);
|