mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-10 09:07:46 +08:00
GP-6503 Improve YAFFS2 impl so it doesn't trigger on windows sys32 files
YAFFS2 did not have very robust probe /matching logic, and was matching a random windows system32 file.
This commit is contained in:
+16
-8
@@ -168,17 +168,25 @@ public class GetInfoFSBFileHandler implements FSBFileHandler {
|
||||
//---------------------------------------------------------------------------------------------
|
||||
private static final Function<Object, String> PLAIN_TOSTRING = o -> o.toString();
|
||||
private static final Function<Object, String> SIZE_TOSTRING =
|
||||
o -> (o instanceof Long) ? FSUtilities.formatSize((Long) o) : o.toString();
|
||||
o -> (o instanceof Long l) ? FSUtilities.formatSize(l) : o.toString();
|
||||
private static final Function<Object, String> UNIX_ACL_TOSTRING =
|
||||
o -> (o instanceof Number) ? String.format("%05o", (Number) o) : o.toString();
|
||||
o -> (o instanceof Number num) ? String.format("%05o", num) : o.toString();
|
||||
private static final Function<Object, String> DATE_TOSTRING =
|
||||
o -> (o instanceof Date) ? FSUtilities.formatFSTimestamp((Date) o) : o.toString();
|
||||
o -> (o instanceof Date date) ? FSUtilities.formatFSTimestamp(date) : o.toString();
|
||||
private static final Function<Object, String> FSRL_TOSTRING =
|
||||
o -> (o instanceof FSRL) ? ((FSRL) o).toPrettyString().replace("|", "|\n\t") : o.toString();
|
||||
o -> (o instanceof FSRL fsrl) ? fsrl.toPrettyString().replace("|", "|\n\t") : o.toString();
|
||||
|
||||
//@formatter:off
|
||||
// predefined type-to-string mappings
|
||||
private static final Map<FileAttributeType, Function<Object, String>> FAT_TOSTRING_FUNCS =
|
||||
Map.ofEntries(entry(FSRL_ATTR, FSRL_TOSTRING), entry(SIZE_ATTR, SIZE_TOSTRING),
|
||||
entry(COMPRESSED_SIZE_ATTR, SIZE_TOSTRING), entry(CREATE_DATE_ATTR, DATE_TOSTRING),
|
||||
entry(MODIFIED_DATE_ATTR, DATE_TOSTRING), entry(ACCESSED_DATE_ATTR, DATE_TOSTRING),
|
||||
entry(UNIX_ACL_ATTR, UNIX_ACL_TOSTRING));
|
||||
Map.ofEntries(
|
||||
entry(FSRL_ATTR, FSRL_TOSTRING),
|
||||
entry(SIZE_ATTR, SIZE_TOSTRING),
|
||||
entry(COMPRESSED_SIZE_ATTR, SIZE_TOSTRING),
|
||||
entry(CREATE_DATE_ATTR, DATE_TOSTRING),
|
||||
entry(MODIFIED_DATE_ATTR, DATE_TOSTRING),
|
||||
entry(ACCESSED_DATE_ATTR, DATE_TOSTRING),
|
||||
entry(UNIX_ACL_ATTR, UNIX_ACL_TOSTRING)
|
||||
);
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
-155
@@ -1,155 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.file.formats.yaffs2;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import ghidra.app.plugin.core.analysis.AnalysisWorker;
|
||||
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
|
||||
import ghidra.app.util.bin.*;
|
||||
import ghidra.app.util.importer.MessageLog;
|
||||
import ghidra.file.analyzers.FileFormatAnalyzer;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.listing.Data;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
// analyzer for YAFFS2 image files
|
||||
// places header and extended tags (footer) structures, and annotates data locations
|
||||
public class YAFFS2Analyzer extends FileFormatAnalyzer implements AnalysisWorker {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "YAFFS2 Image Annotation (used in Android System and Userdata image files)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getDefaultEnablement(Program program) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Annotates YAFFS2 Image files (used in Android System and UserData Images.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAnalyze(Program program) {
|
||||
try {
|
||||
return YAFFS2Utils.isYAFFS2Image(program);
|
||||
}
|
||||
catch (Exception e) {
|
||||
// not a yaffs2 image
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPrototype() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private MessageLog log;
|
||||
|
||||
@Override
|
||||
public boolean analyze(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
|
||||
throws Exception {
|
||||
this.log = log;
|
||||
AutoAnalysisManager manager = AutoAnalysisManager.getAnalysisManager(program);
|
||||
return manager.scheduleWorker(this, null, false, monitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean analysisWorkerCallback(Program program, Object workerContext, TaskMonitor monitor)
|
||||
throws Exception, CancelledException {
|
||||
|
||||
Address address = program.getMinAddress();
|
||||
|
||||
ByteProvider provider = MemoryByteProvider.createProgramHeaderByteProvider(program, false);
|
||||
BinaryReader reader = new BinaryReader(provider, true);
|
||||
|
||||
int index = 0;
|
||||
byte[] block;
|
||||
long lastObjectType = 3; // initialized to object type 3 (a directory)
|
||||
YAFFS2Header header;
|
||||
YAFFS2ExtendedTags tags;
|
||||
YAFFS2Data dataBlock;
|
||||
|
||||
// loop over reader by record length (2112 bytes) and lay down the appropriate structures
|
||||
while (index < reader.length()) {
|
||||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// grab next block of data
|
||||
block = reader.readByteArray(index, YAFFS2Constants.RECORD_SIZE);
|
||||
|
||||
// header structure
|
||||
if (lastObjectType != 1) {
|
||||
header =
|
||||
new YAFFS2Header(Arrays.copyOfRange(block, 0, YAFFS2Constants.DATA_BUFFER_SIZE));
|
||||
DataType headerDataType = header.toDataType();
|
||||
Data headerData = createData(program, address.add(index), headerDataType);
|
||||
if (headerData == null) {
|
||||
log.appendMsg("Unable to create header.");
|
||||
}
|
||||
lastObjectType = header.getObjectType();
|
||||
}
|
||||
// data block structure
|
||||
else {
|
||||
dataBlock =
|
||||
new YAFFS2Data(Arrays.copyOfRange(block, 0, YAFFS2Constants.DATA_BUFFER_SIZE));
|
||||
DataType dataBlockDataType = dataBlock.toDataType();
|
||||
Data dataBlockData = createData(program, address.add(index), dataBlockDataType);
|
||||
if (dataBlockData == null) {
|
||||
log.appendMsg("Unable to create data block.");
|
||||
}
|
||||
}
|
||||
|
||||
// tags structure
|
||||
tags =
|
||||
new YAFFS2ExtendedTags(Arrays.copyOfRange(block, YAFFS2Constants.DATA_BUFFER_SIZE,
|
||||
YAFFS2Constants.RECORD_SIZE - 1));
|
||||
DataType tagsDataType = tags.toDataType();
|
||||
|
||||
// create data for this block
|
||||
Data tagsData =
|
||||
createData(program, address.add(index + YAFFS2Constants.DATA_BUFFER_SIZE),
|
||||
tagsDataType);
|
||||
if (tagsData == null) {
|
||||
log.appendMsg("Unable to create tags (footer).");
|
||||
}
|
||||
|
||||
//increment to next record
|
||||
index += YAFFS2Constants.RECORD_SIZE;
|
||||
}
|
||||
|
||||
changeDataSettings(program, monitor);
|
||||
removeEmptyFragments(program);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWorkerName() {
|
||||
return "YAFFS2Analyzer";
|
||||
}
|
||||
|
||||
}
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.file.formats.yaffs2;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
// a method to return a single YAFFS2 record
|
||||
public class YAFFS2Buffer {
|
||||
|
||||
private static int recordSize = YAFFS2Constants.RECORD_SIZE;
|
||||
private InputStream inStream;
|
||||
private OutputStream outStream;
|
||||
private byte[] recordBuffer;
|
||||
|
||||
public YAFFS2Buffer(InputStream inStream) {
|
||||
this(inStream, recordSize);
|
||||
}
|
||||
|
||||
public YAFFS2Buffer(InputStream inStream, int recordSize) {
|
||||
this.inStream = inStream;
|
||||
this.outStream = null;
|
||||
this.initialize(recordSize);
|
||||
}
|
||||
|
||||
private void initialize(int recordSize) {
|
||||
this.recordBuffer = new byte[recordSize];
|
||||
}
|
||||
|
||||
public byte[] readRecord() throws IOException {
|
||||
if (inStream == null) {
|
||||
if (outStream == null) {
|
||||
throw new IOException("input buffer is closed");
|
||||
}
|
||||
throw new IOException("reading from an output buffer");
|
||||
}
|
||||
long numBytes = inStream.read(recordBuffer, 0, recordSize);
|
||||
if (numBytes == -1) {
|
||||
return null;
|
||||
}
|
||||
return recordBuffer;
|
||||
}
|
||||
|
||||
public long skip(long numToSkip) throws IOException {
|
||||
return inStream.skip(numToSkip);
|
||||
}
|
||||
|
||||
public boolean isEOFRecord(byte[] record) {
|
||||
for (int i = 0, sz = getRecordSize(); i < sz; ++i) {
|
||||
if (record[i] != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getRecordSize() {
|
||||
return recordSize;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
if (inStream != System.in) {
|
||||
inStream.close();
|
||||
}
|
||||
inStream = null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.file.formats.yaffs2;
|
||||
|
||||
import ghidra.app.util.bin.StructConverter;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class YAFFS2Data implements StructConverter {
|
||||
|
||||
public YAFFS2Data(byte[] buffer) {
|
||||
}
|
||||
|
||||
// an array to hold data bytes for one record
|
||||
@Override
|
||||
public DataType toDataType() throws DuplicateNameException, IOException {
|
||||
Structure structure = new StructureDataType("yaffs2Data", 0);
|
||||
structure.add(new ArrayDataType(BYTE, YAFFS2Constants.DATA_BUFFER_SIZE, BYTE.getLength()),
|
||||
"data", null);
|
||||
return structure;
|
||||
}
|
||||
|
||||
}
|
||||
-171
@@ -1,171 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.file.formats.yaffs2;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class YAFFS2Entry {
|
||||
|
||||
private long fileOffset;
|
||||
private YAFFS2Header header;
|
||||
private YAFFS2ExtendedTags extendedTags;
|
||||
|
||||
/**
|
||||
* in YAFFS2 speak, a record is called a "chunk"
|
||||
* this class parses out a header chunk, reading the header and the footer in a record
|
||||
*/
|
||||
public YAFFS2Entry(byte[] buffer) {
|
||||
// header
|
||||
header = new YAFFS2Header(Arrays.copyOfRange(buffer, 0, YAFFS2Constants.HEADER_SIZE));
|
||||
|
||||
// extended tags
|
||||
extendedTags =
|
||||
new YAFFS2ExtendedTags(Arrays.copyOfRange(buffer, YAFFS2Constants.DATA_BUFFER_SIZE,
|
||||
YAFFS2Constants.RECORD_SIZE));
|
||||
}
|
||||
|
||||
public YAFFS2Entry() {
|
||||
}
|
||||
|
||||
public long getObjectId() {
|
||||
return extendedTags.getObjectId();
|
||||
}
|
||||
|
||||
public boolean isDirectory() {
|
||||
return header.isDirectory();
|
||||
}
|
||||
|
||||
public short getChecksum() {
|
||||
return header.getChecksum();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return header.getName();
|
||||
}
|
||||
|
||||
public long getYstMode() {
|
||||
return header.getYstMode();
|
||||
}
|
||||
|
||||
public long getYstUId() {
|
||||
return header.getYstUId();
|
||||
}
|
||||
|
||||
public long getYstGId() {
|
||||
return header.getYstGId();
|
||||
}
|
||||
|
||||
public String getYstATime() {
|
||||
return header.getYstATime();
|
||||
}
|
||||
|
||||
public String getYstMTime() {
|
||||
return header.getYstMTime();
|
||||
}
|
||||
|
||||
public String getYstCTime() {
|
||||
return header.getYstCTime();
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return header.getSize();
|
||||
}
|
||||
|
||||
public long getEquivId() {
|
||||
return header.getEquivId();
|
||||
}
|
||||
|
||||
public String getAliasFileName() {
|
||||
return header.getAliasFileName();
|
||||
}
|
||||
|
||||
public long getYstRDev() {
|
||||
return header.getYstRDev();
|
||||
}
|
||||
|
||||
public long getWinCTime() {
|
||||
return header.getWinCTime();
|
||||
}
|
||||
|
||||
public long getWinATime() {
|
||||
return header.getWinATime();
|
||||
}
|
||||
|
||||
public long getWinMTime() {
|
||||
return header.getWinMTime();
|
||||
}
|
||||
|
||||
public long getInbandObjId() {
|
||||
return header.getInbandObjId();
|
||||
}
|
||||
|
||||
public long getInbandIsShrink() {
|
||||
return header.getInbandIsShrink();
|
||||
}
|
||||
|
||||
public long getFileSizeHigh() {
|
||||
return header.getFileSizeHigh();
|
||||
}
|
||||
|
||||
public long getShadowsObject() {
|
||||
return header.getShadowsObject();
|
||||
}
|
||||
|
||||
public long getIsShrink() {
|
||||
return header.getIsShrink();
|
||||
}
|
||||
|
||||
public long getSequenceNumber() {
|
||||
return extendedTags.getSequenceNumber();
|
||||
}
|
||||
|
||||
public long getChunkId() {
|
||||
return extendedTags.getChunkId();
|
||||
}
|
||||
|
||||
public long getNumberBytes() {
|
||||
return extendedTags.getNumberBytes();
|
||||
}
|
||||
|
||||
public long getEccColParity() {
|
||||
return extendedTags.getEccColParity();
|
||||
}
|
||||
|
||||
public long getEccLineParity() {
|
||||
return extendedTags.getEccLineParity();
|
||||
}
|
||||
|
||||
public long getEccLineParityPrime() {
|
||||
return extendedTags.getEccLineParityPrime();
|
||||
}
|
||||
|
||||
public long getParentObjectId() {
|
||||
return header.getParentObjectId();
|
||||
}
|
||||
|
||||
public boolean isFile() {
|
||||
return header.isFile();
|
||||
}
|
||||
|
||||
public void setFileOffset(Long foffset) {
|
||||
fileOffset = foffset;
|
||||
}
|
||||
|
||||
public long getFileOffset() {
|
||||
return fileOffset;
|
||||
}
|
||||
|
||||
}
|
||||
-96
@@ -1,96 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.file.formats.yaffs2;
|
||||
|
||||
import ghidra.app.util.bin.StructConverter;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class YAFFS2ExtendedTags implements StructConverter {
|
||||
|
||||
// extended tags (a footer really)
|
||||
private long sequenceNumber;
|
||||
private long objectId;
|
||||
private long chunkId;
|
||||
private long numberBytes;
|
||||
private long eccColParity;
|
||||
private long eccLineParity;
|
||||
private long eccLineParityPrime;
|
||||
|
||||
public YAFFS2ExtendedTags(byte[] buffer) {
|
||||
|
||||
// parse extended tags structure
|
||||
sequenceNumber = YAFFS2Utils.parseInteger(buffer, 0, 4);
|
||||
objectId = YAFFS2Utils.parseInteger(buffer, 4, 4);
|
||||
chunkId = YAFFS2Utils.parseInteger(buffer, 8, 4);
|
||||
numberBytes = YAFFS2Utils.parseInteger(buffer, 12, 4);
|
||||
eccColParity = YAFFS2Utils.parseInteger(buffer, 16, 4);
|
||||
eccLineParity = YAFFS2Utils.parseInteger(buffer, 20, 4);
|
||||
eccLineParityPrime = YAFFS2Utils.parseInteger(buffer, 24, 4);
|
||||
|
||||
}
|
||||
|
||||
public YAFFS2ExtendedTags() {
|
||||
}
|
||||
|
||||
public long getObjectId() {
|
||||
return objectId;
|
||||
}
|
||||
|
||||
public long getSequenceNumber() {
|
||||
return sequenceNumber;
|
||||
}
|
||||
|
||||
public long getChunkId() {
|
||||
return chunkId;
|
||||
}
|
||||
|
||||
public long getNumberBytes() {
|
||||
return numberBytes;
|
||||
}
|
||||
|
||||
public long getEccColParity() {
|
||||
return eccColParity;
|
||||
}
|
||||
|
||||
public long getEccLineParity() {
|
||||
return eccLineParity;
|
||||
}
|
||||
|
||||
public long getEccLineParityPrime() {
|
||||
return eccLineParityPrime;
|
||||
}
|
||||
|
||||
// extended tags structure for analyzer
|
||||
@Override
|
||||
public DataType toDataType() throws DuplicateNameException, IOException {
|
||||
|
||||
Structure structure = new StructureDataType("yaffs2Tags", 0);
|
||||
structure.add(DWORD, "sequenceNumber", null);
|
||||
structure.add(DWORD, "objectId", null);
|
||||
structure.add(DWORD, "chunkId", null);
|
||||
structure.add(DWORD, "numberBytes", null);
|
||||
structure.add(DWORD, "eccColParity", null);
|
||||
structure.add(DWORD, "eccLineParity", null);
|
||||
structure.add(DWORD, "eccLineParityPrime", null);
|
||||
structure.add(new ArrayDataType(BYTE, 36, BYTE.getLength()), "unused", null);
|
||||
return structure;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+272
-93
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -15,129 +15,308 @@
|
||||
*/
|
||||
package ghidra.file.formats.yaffs2;
|
||||
|
||||
import static ghidra.formats.gfilesystem.fileinfo.FileAttributeType.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.app.util.bin.ByteArrayProvider;
|
||||
import ghidra.app.util.bin.ByteProvider;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import ghidra.app.util.bin.*;
|
||||
import ghidra.formats.gfilesystem.*;
|
||||
import ghidra.formats.gfilesystem.annotations.FileSystemInfo;
|
||||
import ghidra.formats.gfilesystem.factory.GFileSystemBaseFactory;
|
||||
import ghidra.formats.gfilesystem.fileinfo.*;
|
||||
import ghidra.program.model.lang.Endian;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
@FileSystemInfo(type = "yaffs2", description = "YAFFS2", factory = GFileSystemBaseFactory.class)
|
||||
public class YAFFS2FileSystem extends GFileSystemBase {
|
||||
/**
|
||||
* File system implementation for YAFFS2 images with 2048 byte pages and 64 bytes of OOB data
|
||||
* after each page.
|
||||
* <p>
|
||||
* The image file is made up of concatenated 2112 byte pages (2048 bytes data and 64 bytes OOB)
|
||||
* formatted such:
|
||||
* <p>
|
||||
* <pre>
|
||||
* page {
|
||||
* struct obj_hdr { [512 bytes] ... parentObjId, name, size, datetime, etc ... }
|
||||
* ...1536 bytes...
|
||||
* struct oob { [64 bytes] objId, misc etc }
|
||||
* } (repeated)
|
||||
* </pre>
|
||||
* <p>
|
||||
* If page was a File obj_hdr, the pages following will be the data of that file:
|
||||
* <p>
|
||||
* <pre>
|
||||
* page {
|
||||
* 2048 bytes filedata
|
||||
* struct oob { [64 bytes] .... }
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* NOTES:
|
||||
* <ul>
|
||||
* <li>There is no header / superblock at the beginning of the filesystem data, so parameters
|
||||
* (like the page size or OOB data size, endianness, etc) are not discoverable without some
|
||||
* guessing / try-and-see-if-it-produces-valid-looking-data.
|
||||
* <li>Changing the size of the page changes the default size of the OOB data, which can change the
|
||||
* layout of the OOB data.
|
||||
* <li>The OOB data might be written in YAFFS-original format, or might be written in more recent
|
||||
* Linux MTD format. (see mkyaff's --yaffs-ecclayout startup option). This impl only handles
|
||||
* YAFFS-original format.
|
||||
* <li>This impl has only been tested with images that are freshly created with no
|
||||
* usage / modifications.
|
||||
* </ul>
|
||||
*/
|
||||
@FileSystemInfo(type = "yaffs2", description = "YAFFS2", factory = YAFFS2FileSystemFactory.class)
|
||||
public class YAFFS2FileSystem extends AbstractFileSystem<YAFFS2FileSystem.Metadata> {
|
||||
static class Metadata {
|
||||
final long objId;
|
||||
int pageNum;
|
||||
int equivPageNum; // if hardlink
|
||||
|
||||
private Map<Long, GFileImpl> map = new HashMap<>();
|
||||
private Map<GFile, YAFFS2Entry> map2 = new HashMap<>();
|
||||
|
||||
public YAFFS2FileSystem(String fileSystemName, ByteProvider provider) {
|
||||
super(fileSystemName, provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(TaskMonitor monitor) throws IOException {
|
||||
byte[] bytes = provider.readBytes(0, YAFFS2Constants.MAGIC_SIZE);
|
||||
// check for initial byte equal to 0x03, 'directory'
|
||||
// and check that the first byte of the file name is null
|
||||
// ... this is the yaffs2 root level dir header
|
||||
return ((bytes[0] == 0x03) && (bytes[10] == 0x00));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open(TaskMonitor monitor) throws IOException, CancelledException {
|
||||
// TODO: should yaffsInput be closed?
|
||||
YAFFS2InputStream yaffs2Input = new YAFFS2InputStream(provider.getInputStream(0));
|
||||
|
||||
// go through the image file, looking at each header entry, ignoring the data, storing the dir tree
|
||||
while (!monitor.isCancelled()) {
|
||||
YAFFS2Entry headerEntry = yaffs2Input.getNextHeaderEntry();
|
||||
if (headerEntry == null) {
|
||||
break;
|
||||
}
|
||||
storeEntry(headerEntry, monitor);
|
||||
Metadata(long objId) {
|
||||
this.objId = objId;
|
||||
}
|
||||
}
|
||||
|
||||
private ByteProvider provider;
|
||||
private int oobSize;
|
||||
private int pageSize;
|
||||
private int stride;
|
||||
private Endian endian;
|
||||
|
||||
public YAFFS2FileSystem(ByteProvider provider, int pageSize, int oobSize, Endian endian,
|
||||
FSRLRoot fsFSRL, FileSystemService fsService) {
|
||||
super(fsFSRL, fsService);
|
||||
|
||||
this.pageSize = pageSize;
|
||||
this.oobSize = oobSize;
|
||||
this.stride = pageSize + oobSize;
|
||||
this.endian = endian;
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
map.clear();
|
||||
map2.clear();
|
||||
super.close();
|
||||
refManager.onClose();
|
||||
if (provider != null) {
|
||||
FSUtilities.uncheckedClose(provider, null);
|
||||
provider = null;
|
||||
}
|
||||
fsIndex.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GFile> getListing(GFile directory) throws IOException {
|
||||
if (directory == null || directory.equals(root)) {
|
||||
List<GFile> roots = new ArrayList<>();
|
||||
for (Long objId : map.keySet()) {
|
||||
GFile parentFile = map.get(objId).getParentFile();
|
||||
if (parentFile != null) {
|
||||
if (parentFile == root || parentFile.equals(root)) {
|
||||
GFile file = map.get(objId);
|
||||
roots.add(file);
|
||||
public void mount(TaskMonitor monitor) throws IOException, CancelledException {
|
||||
int pageCount = (int) (provider.length() / stride);
|
||||
if (provider.length() % stride != 0) {
|
||||
Msg.warn(this, "Non-integral yaffs2 file system image file length: %d, %d, %d"
|
||||
.formatted(provider.length(), pageSize, oobSize));
|
||||
}
|
||||
|
||||
// accumulate objId -> metadata(pagenum) mappings, allowing an earlier version of an object
|
||||
// to be overwritten by a 'newer' definition in a later page.
|
||||
Map<Long, Metadata> objIdToMetadata = new HashMap<>();
|
||||
for (int pageNum = 0; pageNum < pageCount; pageNum++) {
|
||||
monitor.checkCancelled();
|
||||
HeaderWithOOB page = readPage(pageNum);
|
||||
if (page == null) {
|
||||
break;
|
||||
}
|
||||
if (!page.hdr.isValid(provider)) {
|
||||
break;
|
||||
}
|
||||
Metadata metadata =
|
||||
objIdToMetadata.computeIfAbsent(page.oob.getObjectId(), Metadata::new);
|
||||
metadata.pageNum = pageNum;
|
||||
pageNum += page.hdr.getDataPageCount(pageSize);
|
||||
}
|
||||
|
||||
objIdToMetadata.entrySet()
|
||||
.stream()
|
||||
.sorted((o1, o2) -> Long.compare(o1.getKey(), o2.getKey()))
|
||||
.forEach(entry -> {
|
||||
try {
|
||||
long objId = entry.getKey();
|
||||
Metadata metadata = entry.getValue();
|
||||
HeaderWithOOB page = readPage(metadata.pageNum);
|
||||
if (metadata.pageNum == 0 && objId == 1 &&
|
||||
page.hdr.getParentObjectId() == 1) {
|
||||
// skip entry that is just the 'root' directory
|
||||
return;
|
||||
}
|
||||
|
||||
long parentObjId = page.hdr.getParentObjectId();
|
||||
GFile parent = parentObjId == 1
|
||||
? fsIndex.getRootDir()
|
||||
: fsIndex.getFileByIndex(parentObjId);
|
||||
if (parent == null) {
|
||||
parent = fsIndex.getRootDir();
|
||||
Msg.warn(this,
|
||||
"Unable to find parent %x of %x".formatted(parentObjId, objId));
|
||||
}
|
||||
String name = page.hdr.getName();
|
||||
switch (page.hdr.getObjectTypeEnum()) {
|
||||
case File:
|
||||
fsIndex.storeFileWithParent(name, parent, objId, false,
|
||||
page.hdr.calcFileSize(), metadata);
|
||||
break;
|
||||
case Directory:
|
||||
fsIndex.storeFileWithParent(name, parent, objId, true, -1,
|
||||
metadata);
|
||||
break;
|
||||
case Symlink:
|
||||
fsIndex.storeSymlinkWithParent(name, parent, objId,
|
||||
page.hdr.getAliasFileName(), 0, metadata);
|
||||
break;
|
||||
case Hardlink:
|
||||
Metadata equivTarget = objIdToMetadata.get(page.hdr.getEquivId());
|
||||
if (equivTarget == null) {
|
||||
Msg.warn(this,
|
||||
"Unable to find hardlink equiv: %x, %x, skipping: %s"
|
||||
.formatted(metadata.pageNum, page.hdr.getEquivId(),
|
||||
name));
|
||||
break;
|
||||
}
|
||||
|
||||
metadata.equivPageNum = equivTarget.pageNum;
|
||||
HeaderWithOOB equivTargetObj = readPage(metadata.equivPageNum);
|
||||
fsIndex.storeFileWithParent(name, parent, objId, false,
|
||||
equivTargetObj.hdr.calcFileSize(), metadata);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return roots;
|
||||
catch (IOException e) {
|
||||
// shouldn't happen
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private record HeaderWithOOB(YAFFS2Header hdr, YAFFS2OOBStruct oob) {}
|
||||
|
||||
private HeaderWithOOB readPage(int pageNum) throws IOException {
|
||||
byte[] pageBytes = provider.readBytes(pageToOffset(pageNum), stride);
|
||||
if (isDefaultPage(pageBytes)) {
|
||||
return null;
|
||||
}
|
||||
List<GFile> fileList = new ArrayList<>();
|
||||
for (Long objId : map.keySet()) {
|
||||
GFile parentFile = map.get(objId).getParentFile();
|
||||
if (parentFile == null) {
|
||||
continue;
|
||||
}
|
||||
if (parentFile.equals(directory)) {
|
||||
GFile file = map.get(objId);
|
||||
fileList.add(file);
|
||||
BinaryReader br =
|
||||
new BinaryReader(new ByteArrayProvider(pageBytes), endian == Endian.LITTLE);
|
||||
YAFFS2Header hdr = YAFFS2Header.read(br);
|
||||
br.setPointerIndex(pageSize);
|
||||
YAFFS2OOBStruct tag = YAFFS2OOBStruct.read(br, oobSize);
|
||||
return new HeaderWithOOB(hdr, tag);
|
||||
}
|
||||
|
||||
private boolean isDefaultPage(byte[] pageBytes) {
|
||||
// return true if page is entirely 00's or FF's
|
||||
byte b = pageBytes[0];
|
||||
if (b != 0 && b != -1) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < pageBytes.length; i++) {
|
||||
if (pageBytes[i] != b) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return fileList;
|
||||
return true;
|
||||
}
|
||||
|
||||
private long pageToOffset(int pageNum) {
|
||||
return pageNum * stride;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteProvider getByteProvider(GFile file, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
// get saved entry from selected file
|
||||
YAFFS2Entry entry = map2.get(file);
|
||||
|
||||
// exit if selection is a directory
|
||||
if (entry.isDirectory()) {
|
||||
throw new IOException(file.getName() + " is a directory");
|
||||
GFile resolvedFile = fsIndex.resolveSymlinks(file);
|
||||
Metadata metadata = fsIndex.getMetadata(resolvedFile);
|
||||
if (metadata == null) {
|
||||
return null;
|
||||
}
|
||||
int pageNum = metadata.pageNum;
|
||||
HeaderWithOOB page = readPage(pageNum);
|
||||
if (page.hdr.getObjectTypeEnum() == YAFFS2ObjectType.Hardlink) {
|
||||
pageNum = metadata.equivPageNum;
|
||||
page = readPage(pageNum);
|
||||
}
|
||||
|
||||
// recall size of file and offset into the file system image
|
||||
long fileOffset = entry.getFileOffset();
|
||||
long size = entry.getSize();
|
||||
|
||||
// return bytes for the selected file
|
||||
try (YAFFS2InputStream YAFFS2Input = new YAFFS2InputStream(provider.getInputStream(0))) {
|
||||
byte[] entryData = YAFFS2Input.getEntryData(fileOffset, size);
|
||||
return new ByteArrayProvider(entryData, file.getFSRL());
|
||||
RangeMappedByteProvider result =
|
||||
new RangeMappedByteProvider(provider, resolvedFile.getFSRL());
|
||||
long byteCount = page.hdr.calcFileSize();
|
||||
pageNum++;
|
||||
for (long offset = pageToOffset(pageNum); byteCount > 0; pageNum++) {
|
||||
int bytesInPage = (int) Math.min(byteCount, pageSize);
|
||||
result.addRange(offset, bytesInPage);
|
||||
byteCount -= bytesInPage;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void storeEntry(YAFFS2Entry entry, TaskMonitor monitor) {
|
||||
if (entry == null) {
|
||||
return;
|
||||
@Override
|
||||
public FileAttributes getFileAttributes(GFile file, TaskMonitor monitor) {
|
||||
if (fsIndex.getRootDir().equals(file)) {
|
||||
return FileAttributes.of( // file, attrs
|
||||
FileAttribute.create("Page Size", pageSize),
|
||||
FileAttribute.create("OOB Size", oobSize));
|
||||
}
|
||||
monitor.setMessage(entry.getName());
|
||||
Metadata metadata = fsIndex.getMetadata(file);
|
||||
if (metadata != null) {
|
||||
try {
|
||||
HeaderWithOOB origpage = readPage(metadata.pageNum);
|
||||
HeaderWithOOB page = origpage.hdr.getObjectTypeEnum() == YAFFS2ObjectType.Hardlink
|
||||
? readPage(metadata.equivPageNum)
|
||||
: origpage;
|
||||
YAFFS2ObjectType objType = page.hdr.getObjectTypeEnum();
|
||||
|
||||
// search the file listing for the parent object ID (need this since yaffs2 file names are not full paths)
|
||||
long parentObjectId = entry.getParentObjectId();
|
||||
long objectId = entry.getObjectId();
|
||||
GFile parentFile = (parentObjectId == 1) ? root : map.get(parentObjectId);
|
||||
|
||||
// skip the first header (always a meaningless, "root" header)
|
||||
if ((objectId == 1) & (parentObjectId == 1)) {
|
||||
return;
|
||||
return FileAttributes.of( // file attrs
|
||||
FileAttribute.create(NAME_ATTR, origpage.hdr.getName()),
|
||||
FileAttribute.create(PATH_ATTR,
|
||||
FilenameUtils.getFullPathNoEndSeparator(file.getPath())),
|
||||
objType == YAFFS2ObjectType.File
|
||||
? FileAttribute.create(SIZE_ATTR, page.hdr.calcFileSize())
|
||||
: null,
|
||||
FileAttribute.create(MODIFIED_DATE_ATTR,
|
||||
new Date(page.hdr.getYstMTime() * 1000)),
|
||||
FileAttribute.create(CREATE_DATE_ATTR, new Date(page.hdr.getYstCTime() * 1000)),
|
||||
FileAttribute.create(ACCESSED_DATE_ATTR,
|
||||
new Date(page.hdr.getYstATime() * 1000)),
|
||||
FileAttribute.create(FILE_TYPE_ATTR, convertFileType(objType)),
|
||||
FileAttribute.create(USER_ID_ATTR, page.hdr.getYstUId()),
|
||||
FileAttribute.create(GROUP_ID_ATTR, page.hdr.getYstGId()),
|
||||
FileAttribute.create(UNIX_ACL_ATTR, page.hdr.getYstMode()),
|
||||
origpage.hdr.getObjectTypeEnum() == YAFFS2ObjectType.Symlink
|
||||
? FileAttribute.create(SYMLINK_DEST_ATTR,
|
||||
origpage.hdr.getAliasFileName())
|
||||
: null,
|
||||
FileAttribute.create("Object Id", "%08x".formatted(origpage.oob.getObjectId())),
|
||||
FileAttribute.create("Parent Id",
|
||||
"%08x".formatted(origpage.hdr.getParentObjectId())),
|
||||
FileAttribute.create("Page Number", "%08x".formatted(metadata.pageNum)));
|
||||
}
|
||||
catch (IOException e) {
|
||||
// fall thru
|
||||
}
|
||||
}
|
||||
|
||||
// process the other headers
|
||||
GFileImpl file = GFileImpl.fromFilename(this, parentFile, entry.getName(),
|
||||
entry.isDirectory(), entry.getSize(), null);
|
||||
map.put(entry.getObjectId(), file);
|
||||
map2.put(file, entry);
|
||||
return null;
|
||||
}
|
||||
|
||||
private FileType convertFileType(YAFFS2ObjectType objType) {
|
||||
return switch (objType) {
|
||||
case File -> FileType.FILE;
|
||||
case Directory -> FileType.DIRECTORY;
|
||||
case Hardlink -> FileType.FILE;
|
||||
case Symlink -> FileType.SYMBOLIC_LINK;
|
||||
default -> FileType.OTHER;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return provider == null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.file.formats.yaffs2;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import ghidra.app.util.bin.BinaryReader;
|
||||
import ghidra.app.util.bin.ByteProvider;
|
||||
import ghidra.formats.gfilesystem.*;
|
||||
import ghidra.formats.gfilesystem.factory.GFileSystemFactoryByteProvider;
|
||||
import ghidra.formats.gfilesystem.factory.GFileSystemProbeByteProvider;
|
||||
import ghidra.program.model.lang.Endian;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class YAFFS2FileSystemFactory
|
||||
implements GFileSystemFactoryByteProvider<YAFFS2FileSystem>, GFileSystemProbeByteProvider {
|
||||
|
||||
private static final int MIN_REQUIRED_OBJHDRS = 2;
|
||||
private static final int MAX_OBJHDRS_TO_CHECK = 5;
|
||||
|
||||
@Override
|
||||
public boolean probe(ByteProvider byteProvider, FileSystemService fsService,
|
||||
TaskMonitor monitor) throws IOException, CancelledException {
|
||||
return hasObjHeaders(byteProvider, 2048, 64, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the stream appears to have a few valid yaffs2 obj_hdr structs at the start.
|
||||
*
|
||||
* @param bp {@link ByteProvider} stream
|
||||
* @param pageSize typically 2048
|
||||
* @param oobSize only tested with 64 byte
|
||||
* @param isLE only tested with LE
|
||||
* @return boolean true if it appears to be a valid YAFFS2 image
|
||||
* @throws IOException if error reading
|
||||
*/
|
||||
boolean hasObjHeaders(ByteProvider bp, int pageSize, int oobSize, boolean isLE)
|
||||
throws IOException {
|
||||
BinaryReader br = new BinaryReader(bp, isLE);
|
||||
long stride = pageSize + oobSize;
|
||||
int pageCount = (int) (bp.length() / stride);
|
||||
int foundCount = 0;
|
||||
for (int pageNum = 0; pageNum < pageCount && foundCount < MAX_OBJHDRS_TO_CHECK; pageNum++) {
|
||||
br.setPointerIndex(pageNum * stride);
|
||||
YAFFS2Header hdr = YAFFS2Header.read(br);
|
||||
if (!hdr.isValid(bp)) {
|
||||
return false;
|
||||
}
|
||||
pageNum += hdr.getDataPageCount(pageSize);
|
||||
foundCount++;
|
||||
}
|
||||
return foundCount > MIN_REQUIRED_OBJHDRS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GFileSystem create(FSRLRoot targetFSRL, ByteProvider byteProvider,
|
||||
FileSystemService fsService, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
try {
|
||||
YAFFS2FileSystem fs =
|
||||
new YAFFS2FileSystem(byteProvider, 2048, 64, Endian.LITTLE, targetFSRL, fsService);
|
||||
fs.mount(monitor);
|
||||
|
||||
return fs;
|
||||
}
|
||||
catch (IOException | CancelledException e) {
|
||||
FSUtilities.uncheckedClose(byteProvider, null);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+173
-134
@@ -16,82 +16,143 @@
|
||||
package ghidra.file.formats.yaffs2;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import ghidra.app.util.bin.StructConverter;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
import ghidra.app.util.bin.BinaryReader;
|
||||
import ghidra.app.util.bin.ByteProvider;
|
||||
import ghidra.util.NumericUtilities;
|
||||
|
||||
public class YAFFS2Header implements StructConverter {
|
||||
/**
|
||||
* <pre>
|
||||
* struct yaffs_obj_hdr // Length: 512 (0x200)
|
||||
* {
|
||||
* u32 type
|
||||
* u32 parent_obj_id
|
||||
* u16 sum_no_longer_used
|
||||
* YCHAR[256] name
|
||||
* u32 yst_mode
|
||||
* u32 yst_uid
|
||||
* u32 yst_gid
|
||||
* u32 yst_atime
|
||||
* u32 yst_mtime
|
||||
* u32 yst_ctime
|
||||
* u32 file_size_low
|
||||
* int equiv_id
|
||||
* YCHAR[160] alias
|
||||
* u32 yst_rdev
|
||||
* u32[2] win_ctime
|
||||
* u32[2] win_atime
|
||||
* u32[2] win_mtime
|
||||
* u32 inband_shadowed_obj_id
|
||||
* u32 inband_is_shrink
|
||||
* u32 file_size_high
|
||||
* u32[1] reserved
|
||||
* int shadows_obj
|
||||
* u32 is_shrink
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public class YAFFS2Header {
|
||||
|
||||
/**
|
||||
* Reads a YAFFS2 objhdr struct.
|
||||
*
|
||||
* @param br stream to read from
|
||||
* @return new YAFFS2Header, never null
|
||||
* @throws IOException if error reading
|
||||
*/
|
||||
public static YAFFS2Header read(BinaryReader br) throws IOException {
|
||||
YAFFS2Header result = new YAFFS2Header();
|
||||
result.objectType = br.readNextInt();
|
||||
result.parentObjectId = br.readNextUnsignedInt();
|
||||
result.checksum = br.readNextShort();
|
||||
result.fileName = readNextYaffs2String(br, 256);
|
||||
br.align(4 /*sizeof(int) */);
|
||||
result.ystMode = br.readNextUnsignedInt();
|
||||
result.ystUId = br.readNextUnsignedInt();
|
||||
result.ystGId = br.readNextUnsignedInt();
|
||||
result.ystATime = br.readNextUnsignedInt();
|
||||
result.ystMTime = br.readNextUnsignedInt();
|
||||
result.ystCTime = br.readNextUnsignedInt();
|
||||
result.fileSizeLow = br.readNextUnsignedInt();
|
||||
result.equivId = br.readNextUnsignedInt();
|
||||
result.aliasFileName = readNextYaffs2String(br, 160);
|
||||
result.ystRDev = br.readNextUnsignedInt();
|
||||
result.winCTime = br.readNextIntArray(2);
|
||||
result.winATime = br.readNextIntArray(2);
|
||||
result.winMTime = br.readNextIntArray(2);
|
||||
result.inbandObjId = br.readNextUnsignedInt();
|
||||
result.inbandIsShrink = br.readNextUnsignedInt();
|
||||
result.fileSizeHigh = br.readNextUnsignedInt();
|
||||
br.readNextInt(); // reserved
|
||||
result.shadowsObject = br.readNextUnsignedInt();
|
||||
result.isShrink = br.readNextUnsignedInt();
|
||||
// assert(br.index == +512)
|
||||
return result;
|
||||
}
|
||||
|
||||
// header objects
|
||||
private long objectType;
|
||||
private long parentObjectId;
|
||||
private short checksum;
|
||||
private String fileName;
|
||||
private long ystMode;
|
||||
private long ystUId;
|
||||
private long ystGId;
|
||||
private String ystATime;
|
||||
private String ystMTime;
|
||||
private String ystCTime;
|
||||
private long fileSizeLow;
|
||||
private long equivId;
|
||||
private String aliasFileName;
|
||||
private long ystRDev;
|
||||
private long winCTime;
|
||||
private long winATime;
|
||||
private long winMTime;
|
||||
private long inbandObjId;
|
||||
private long inbandIsShrink;
|
||||
private long fileSizeHigh;
|
||||
private long shadowsObject;
|
||||
private long isShrink;
|
||||
|
||||
/**
|
||||
* Construct an entry from an archive's header bytes.
|
||||
*/
|
||||
public YAFFS2Header(byte[] buffer) {
|
||||
|
||||
// parse header structure
|
||||
objectType = YAFFS2Utils.parseInteger(buffer, 0, 4);
|
||||
parentObjectId = YAFFS2Utils.parseInteger(buffer, 4, 4);
|
||||
checksum = (short) YAFFS2Utils.parseInteger(buffer, 8, 2);
|
||||
fileName = YAFFS2Utils.parseName(buffer, 10, 256);
|
||||
// skip 2 bytes for the "unknown1" short field
|
||||
ystMode = YAFFS2Utils.parseInteger(buffer, 268, 4);
|
||||
ystUId = YAFFS2Utils.parseInteger(buffer, 272, 4);
|
||||
ystGId = YAFFS2Utils.parseInteger(buffer, 276, 4);
|
||||
ystATime = YAFFS2Utils.parseDateTime(buffer, 280, 4);
|
||||
ystMTime = YAFFS2Utils.parseDateTime(buffer, 284, 4);
|
||||
ystCTime = YAFFS2Utils.parseDateTime(buffer, 288, 4);
|
||||
fileSizeLow = YAFFS2Utils.parseFileSize(buffer, 292, 4);
|
||||
equivId = YAFFS2Utils.parseInteger(buffer, 296, 4);
|
||||
aliasFileName = YAFFS2Utils.parseName(buffer, 300, 160);
|
||||
ystRDev = YAFFS2Utils.parseInteger(buffer, 460, 4);
|
||||
winCTime = buffer[464];
|
||||
winATime = buffer[472];
|
||||
winMTime = buffer[480];
|
||||
inbandObjId = YAFFS2Utils.parseInteger(buffer, 488, 4);
|
||||
inbandIsShrink = YAFFS2Utils.parseInteger(buffer, 492, 4);
|
||||
fileSizeHigh = YAFFS2Utils.parseInteger(buffer, 496, 4);
|
||||
// skip 4 bytes for the "reserved" int field
|
||||
shadowsObject = YAFFS2Utils.parseInteger(buffer, 504, 4);
|
||||
isShrink = YAFFS2Utils.parseInteger(buffer, 508, 4);
|
||||
|
||||
}
|
||||
private String fileName;
|
||||
private long ystMode;
|
||||
private long ystUId;
|
||||
private long ystGId;
|
||||
private long ystATime;
|
||||
private long ystMTime;
|
||||
private long ystCTime;
|
||||
private long fileSizeLow;
|
||||
private long equivId;
|
||||
private String aliasFileName;
|
||||
private long ystRDev;
|
||||
private int[] winCTime;
|
||||
private int[] winATime;
|
||||
private int[] winMTime;
|
||||
private long inbandObjId;
|
||||
private long inbandIsShrink;
|
||||
private long fileSizeHigh;
|
||||
private long shadowsObject;
|
||||
private long isShrink;
|
||||
|
||||
public YAFFS2Header() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of data pages that will follow this page.
|
||||
*
|
||||
* @param pageSize size of pages in this fs
|
||||
* @return the number of data pages that will follow this page
|
||||
*/
|
||||
public long getDataPageCount(int pageSize) {
|
||||
return getObjectTypeEnum() == YAFFS2ObjectType.File
|
||||
? NumericUtilities.getUnsignedAlignedValue(calcFileSize(), pageSize) / pageSize
|
||||
: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the data in this object appears valid
|
||||
*
|
||||
* @param bp stream that contains the entire yaffs2 image
|
||||
* @return boolean true if the data in this object appears valid
|
||||
*/
|
||||
public boolean isValid(ByteProvider bp) {
|
||||
YAFFS2ObjectType ote = getObjectTypeEnum();
|
||||
if (ote == YAFFS2ObjectType.File) {
|
||||
long filesize = calcFileSize();
|
||||
if (filesize < 0 || filesize > bp.length()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return ote != YAFFS2ObjectType.INVALID;
|
||||
}
|
||||
|
||||
public long getObjectType() {
|
||||
return objectType;
|
||||
}
|
||||
|
||||
public boolean isDirectory() {
|
||||
if (objectType == 3) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
public YAFFS2ObjectType getObjectTypeEnum() {
|
||||
return YAFFS2ObjectType.parse(objectType);
|
||||
}
|
||||
|
||||
public short getChecksum() {
|
||||
@@ -114,15 +175,15 @@ public class YAFFS2Header implements StructConverter {
|
||||
return ystGId;
|
||||
}
|
||||
|
||||
public String getYstATime() {
|
||||
public long getYstATime() {
|
||||
return ystATime;
|
||||
}
|
||||
|
||||
public String getYstMTime() {
|
||||
public long getYstMTime() {
|
||||
return ystMTime;
|
||||
}
|
||||
|
||||
public String getYstCTime() {
|
||||
public long getYstCTime() {
|
||||
return ystCTime;
|
||||
}
|
||||
|
||||
@@ -130,93 +191,71 @@ public class YAFFS2Header implements StructConverter {
|
||||
return fileSizeLow;
|
||||
}
|
||||
|
||||
public long getTotalSize() {
|
||||
return fileSizeLow | (fileSizeHigh << 32L);
|
||||
}
|
||||
|
||||
public long getEquivId() {
|
||||
return equivId;
|
||||
}
|
||||
|
||||
public String getAliasFileName() {
|
||||
return aliasFileName;
|
||||
}
|
||||
public String getAliasFileName() {
|
||||
return aliasFileName;
|
||||
}
|
||||
|
||||
public long getYstRDev() {
|
||||
return ystRDev;
|
||||
}
|
||||
public long getYstRDev() {
|
||||
return ystRDev;
|
||||
}
|
||||
|
||||
public long getWinCTime() {
|
||||
return winCTime;
|
||||
}
|
||||
public int[] getWinCTime() {
|
||||
return winCTime;
|
||||
}
|
||||
|
||||
public long getWinATime() {
|
||||
return winATime;
|
||||
}
|
||||
public int[] getWinATime() {
|
||||
return winATime;
|
||||
}
|
||||
|
||||
public long getWinMTime() {
|
||||
return winMTime;
|
||||
}
|
||||
public int[] getWinMTime() {
|
||||
return winMTime;
|
||||
}
|
||||
|
||||
public long getInbandObjId() {
|
||||
return inbandObjId;
|
||||
}
|
||||
public long getInbandObjId() {
|
||||
return inbandObjId;
|
||||
}
|
||||
|
||||
public long getInbandIsShrink() {
|
||||
return inbandIsShrink;
|
||||
}
|
||||
public long getInbandIsShrink() {
|
||||
return inbandIsShrink;
|
||||
}
|
||||
|
||||
public long getFileSizeHigh() {
|
||||
return fileSizeHigh;
|
||||
}
|
||||
public long getFileSizeHigh() {
|
||||
return fileSizeHigh;
|
||||
}
|
||||
|
||||
public long getShadowsObject() {
|
||||
return shadowsObject;
|
||||
}
|
||||
public long calcFileSize() {
|
||||
return fileSizeLow |
|
||||
(fileSizeHigh != NumericUtilities.MAX_UNSIGNED_INT32_AS_LONG ? fileSizeHigh : 0);
|
||||
}
|
||||
|
||||
public long getIsShrink() {
|
||||
return isShrink;
|
||||
}
|
||||
public long getShadowsObject() {
|
||||
return shadowsObject;
|
||||
}
|
||||
|
||||
public long getIsShrink() {
|
||||
return isShrink;
|
||||
}
|
||||
|
||||
public long getParentObjectId() {
|
||||
return parentObjectId;
|
||||
}
|
||||
|
||||
public boolean isFile() {
|
||||
if (objectType == 1) {
|
||||
return true;
|
||||
static String readNextYaffs2String(BinaryReader br, int len) throws IOException {
|
||||
// truncate both trailing 0's and FF's.
|
||||
byte[] bytes = br.readNextByteArray(len);
|
||||
int i = bytes.length - 1;
|
||||
while (i >= 0 && (bytes[i] == 0 || bytes[i] == -1)) {
|
||||
i--;
|
||||
}
|
||||
return false;
|
||||
return new String(bytes, 0, i + 1, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
// header structure for analyzer
|
||||
@Override
|
||||
public DataType toDataType() throws DuplicateNameException, IOException {
|
||||
|
||||
Structure structure = new StructureDataType( "yaffs2Hdr", 0 );
|
||||
structure.add( DWORD, "objectType", null );
|
||||
structure.add( DWORD, "parentObjectId", null );
|
||||
structure.add( WORD, "checksum", null );
|
||||
structure.add( STRING, YAFFS2Constants.FILE_NAME_SIZE, "fileName", null );
|
||||
structure.add( WORD, "unknown1", null );
|
||||
structure.add( DWORD, "ystMode", null );
|
||||
structure.add( DWORD, "ystUId", null );
|
||||
structure.add( DWORD, "ystGId", null );
|
||||
structure.add( DWORD, "ystATime", null );
|
||||
structure.add( DWORD, "ystMTime", null );
|
||||
structure.add( DWORD, "ystCTime", null );
|
||||
structure.add( DWORD, "fileSizeLow", null );
|
||||
structure.add( DWORD, "equivId", null );
|
||||
structure.add( STRING, YAFFS2Constants.ALIAS_FILE_NAME_SIZE, "aliasFileName", null );
|
||||
structure.add( DWORD, "ystRDev", null );
|
||||
structure.add( QWORD, "winCTime", null );
|
||||
structure.add( QWORD, "winATime", null );
|
||||
structure.add( QWORD, "winMTime", null );
|
||||
structure.add( DWORD, "inbandObjId", null );
|
||||
structure.add( DWORD, "inbandIsShrink", null );
|
||||
structure.add( DWORD, "fileSizeHigh", null );
|
||||
structure.add( DWORD, "reserved", null );
|
||||
structure.add( DWORD, "shadowsObject", null );
|
||||
structure.add( DWORD, "isShrink", null );
|
||||
structure.add(new ArrayDataType(BYTE, YAFFS2Constants.EMPTY_DATA_SIZE, BYTE.getLength()), "emptyData", null);
|
||||
return structure;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
-143
@@ -1,143 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.file.formats.yaffs2;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class YAFFS2InputStream implements Closeable {
|
||||
|
||||
private int dataBufferSize = YAFFS2Constants.DATA_BUFFER_SIZE;
|
||||
private static int recordSize = YAFFS2Constants.RECORD_SIZE;
|
||||
private boolean hasHitEOF;
|
||||
private long entrySize; // TODO: why is this a member var instead of local var?
|
||||
long fileEntryOffset;
|
||||
protected final YAFFS2Buffer buffer;
|
||||
private YAFFS2Entry currEntry;
|
||||
|
||||
public YAFFS2InputStream(InputStream is) {
|
||||
this(is, recordSize);
|
||||
}
|
||||
|
||||
public YAFFS2InputStream(InputStream is, int recordSize) {
|
||||
this.buffer = new YAFFS2Buffer(is, recordSize);
|
||||
this.hasHitEOF = false;
|
||||
this.fileEntryOffset = 0;
|
||||
}
|
||||
|
||||
// get the next header, skipping the data block records
|
||||
public YAFFS2Entry getNextHeaderEntry() throws IOException {
|
||||
|
||||
if (hasHitEOF) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// entry will be null on first try
|
||||
if (currEntry != null) {
|
||||
// if header is of type file, skip the data section to get to the next header object
|
||||
if (currEntry.isFile()) {
|
||||
long numToSkip = (recordSize) * (currEntry.getSize() / dataBufferSize) + recordSize;
|
||||
long skipped = buffer.skip(numToSkip);
|
||||
if (skipped < 0) {
|
||||
throw new RuntimeException("failed to skip current entry's data section");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get a new YAFFS2 record
|
||||
byte[] headerBuf = getRecord();
|
||||
|
||||
// check if we hit the EOF
|
||||
if (hasHitEOF) {
|
||||
currEntry = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
// parse the new buffer into a YAFFS2 entry
|
||||
currEntry = new YAFFS2Entry(headerBuf);
|
||||
|
||||
// save where this entry is located in the image file
|
||||
currEntry.setFileOffset(fileEntryOffset);
|
||||
|
||||
// compute the size of this entry
|
||||
entrySize = recordSize * (currEntry.getSize() / dataBufferSize) + recordSize;
|
||||
if (currEntry.isFile()) {
|
||||
entrySize = entrySize + recordSize;
|
||||
}
|
||||
|
||||
// compute where the next entry starts in the image file
|
||||
fileEntryOffset = fileEntryOffset + entrySize;
|
||||
|
||||
return currEntry;
|
||||
}
|
||||
|
||||
// get data for the selected file - skip to offset and return length bytes
|
||||
public byte[] getEntryData(long offset, long length) throws IOException {
|
||||
|
||||
long numberOfBuffers = length / dataBufferSize;
|
||||
long remainder = length % dataBufferSize;
|
||||
long numberToRead = recordSize * numberOfBuffers + remainder;
|
||||
int indx = 0;
|
||||
byte[] contents = new byte[(int) numberToRead];
|
||||
entrySize = offset + recordSize;
|
||||
|
||||
// skip to correct file location (offset gets us to the header, recordSize gets past that header to the start of data)
|
||||
long skipped = buffer.skip(offset + recordSize);
|
||||
if (skipped < 0) {
|
||||
throw new RuntimeException("failed to skip to the data section");
|
||||
}
|
||||
|
||||
// read fully populated buffers of data
|
||||
while (numberOfBuffers > 0) {
|
||||
byte[] dataBuf = getRecord();
|
||||
System.arraycopy(dataBuf, 0, contents, indx, dataBufferSize);
|
||||
numberOfBuffers -= 1;
|
||||
indx += dataBufferSize;
|
||||
}
|
||||
|
||||
// read another partial record?
|
||||
if (remainder > 0) {
|
||||
byte[] dataBuf = getRecord();
|
||||
System.arraycopy(dataBuf, 0, contents, indx, (int) remainder);
|
||||
}
|
||||
|
||||
return contents;
|
||||
}
|
||||
|
||||
private byte[] getRecord() throws IOException {
|
||||
if (hasHitEOF) {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] headerBuf = buffer.readRecord();
|
||||
|
||||
// check if we hit the end of file
|
||||
if (headerBuf == null) {
|
||||
hasHitEOF = true;
|
||||
}
|
||||
else if (buffer.isEOFRecord(headerBuf)) {
|
||||
hasHitEOF = true;
|
||||
}
|
||||
|
||||
// return null for EOF, the record if not
|
||||
return hasHitEOF ? null : headerBuf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
buffer.close();
|
||||
}
|
||||
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.file.formats.yaffs2;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import ghidra.app.util.bin.BinaryReader;
|
||||
|
||||
/**
|
||||
* Represents the data YAFFS2 puts in the OOB area of a page. See the yaffs_guts.h for
|
||||
* yaffs_tags or yaffs_ext_tags, etc.
|
||||
* <p>
|
||||
* The layout of data in the OOB data can vary depending the version, size, and MTD-vs-yaffs
|
||||
* option used in mkyaffs.
|
||||
* <p>
|
||||
* We currently only care about the objectId, but a more fully-featured implementation might need
|
||||
* more information from this area.
|
||||
*/
|
||||
public class YAFFS2OOBStruct {
|
||||
|
||||
/**
|
||||
* Reads a yaffs2_ext_tag-formatted OOB data area.
|
||||
*
|
||||
* @param br stream to read from
|
||||
* @param oobSize size of the OOB data
|
||||
* @return new {@link YAFFS2OOBStruct}, never null
|
||||
* @throws IOException if error reading
|
||||
*/
|
||||
public static YAFFS2OOBStruct read(BinaryReader br, int oobSize) throws IOException {
|
||||
long start = br.getPointerIndex();
|
||||
YAFFS2OOBStruct result = new YAFFS2OOBStruct();
|
||||
result.sequenceNumber = br.readNextUnsignedInt();
|
||||
result.objectId = br.readNextUnsignedInt();
|
||||
br.setPointerIndex(start + oobSize);
|
||||
return result;
|
||||
}
|
||||
|
||||
private long sequenceNumber;
|
||||
private long objectId;
|
||||
|
||||
public YAFFS2OOBStruct() {
|
||||
// empty
|
||||
}
|
||||
|
||||
public long getObjectId() {
|
||||
return objectId;
|
||||
}
|
||||
|
||||
public long getSequenceNumber() {
|
||||
return sequenceNumber;
|
||||
}
|
||||
}
|
||||
+17
-19
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -15,22 +15,20 @@
|
||||
*/
|
||||
package ghidra.file.formats.yaffs2;
|
||||
|
||||
public class YAFFS2Constants {
|
||||
|
||||
public final static int MAGIC_SIZE = 11;
|
||||
|
||||
public final static int FILE_NAME_SIZE = 256;
|
||||
|
||||
public final static int ALIAS_FILE_NAME_SIZE = 160;
|
||||
|
||||
public final static int RECORD_SIZE = 2112;
|
||||
|
||||
public final static int HEADER_SIZE = 512;
|
||||
|
||||
public final static int EXTENDED_TAGS_SIZE = 64;
|
||||
|
||||
public final static int DATA_BUFFER_SIZE = 2048;
|
||||
|
||||
public final static int EMPTY_DATA_SIZE = 1536;
|
||||
/**
|
||||
* Yaffs2 object type enum. The java enum ordinal must match the yaffs enum values.
|
||||
*/
|
||||
public enum YAFFS2ObjectType {
|
||||
Unknown, // 0
|
||||
File, // 1
|
||||
Symlink, // 2
|
||||
Directory, // 3
|
||||
Hardlink, // 4
|
||||
Special, // 5
|
||||
INVALID; // represents value that doesn't match, including Unknown
|
||||
|
||||
public static YAFFS2ObjectType parse(long i) {
|
||||
YAFFS2ObjectType[] values = values();
|
||||
return i > Unknown.ordinal() && i < INVALID.ordinal() ? values[(int) i] : INVALID;
|
||||
}
|
||||
}
|
||||
-122
@@ -1,122 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.file.formats.yaffs2;
|
||||
|
||||
import java.text.DateFormat;
|
||||
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
||||
public class YAFFS2Utils {
|
||||
|
||||
/**
|
||||
parse file names
|
||||
*/
|
||||
public static String parseName(byte[] buffer, final int offset, final int length) {
|
||||
StringBuffer result = new StringBuffer(length);
|
||||
int end = offset + length;
|
||||
|
||||
for (int i = offset; i < end; ++i) {
|
||||
byte b = buffer[i];
|
||||
if (b == 0) { // Trailing null
|
||||
break;
|
||||
}
|
||||
result.append((char) (b & 0xFF)); // Allow for sign-extension
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
read values are unsigned int, return as a long (because of Java)
|
||||
*/
|
||||
public static long parseInteger(final byte[] buffer, final int offset, final int length) {
|
||||
long result = 0;
|
||||
int end = offset + length;
|
||||
int start = offset;
|
||||
int j = 0;
|
||||
|
||||
for (int i = start; i < end; i++) {
|
||||
result += ((long) buffer[i] & 0xFF) << (8 * j);
|
||||
j++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
compute the file size, set file size to 0 if object type is dir
|
||||
*/
|
||||
public static long parseFileSize(final byte[] buffer, final int offset, final int length) {
|
||||
long result = 0;
|
||||
int end = offset + length;
|
||||
int start = offset;
|
||||
int j = 0;
|
||||
int k = 0;
|
||||
|
||||
for (int i = start; i < end; i++) {
|
||||
// check for 0xffffffff buffer value, a special case
|
||||
if (buffer[i] == -1)
|
||||
k++;
|
||||
|
||||
// compute integer result
|
||||
result += ((long) buffer[i] & 0xFF) << (8 * j);
|
||||
j++;
|
||||
}
|
||||
|
||||
// if special case was found (ex, for a dir header), return 0 as size
|
||||
if (k < 4) {
|
||||
return result;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
return the date/time string for the parsed file
|
||||
*/
|
||||
public static String parseDateTime(final byte[] buffer, final int offset, final int length) {
|
||||
long result = 0;
|
||||
int end = offset + length;
|
||||
int start = offset;
|
||||
int j = 0;
|
||||
|
||||
for (int i = start; i < end; i++) {
|
||||
result += ((long) buffer[i] & 0xFF) << (8 * j);
|
||||
j++;
|
||||
}
|
||||
|
||||
// note that input here needs to be ms, result above is in sec
|
||||
return DateFormat.getDateTimeInstance().format(result * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
no Magic Bytes, so check for an empty directory in the first header
|
||||
*/
|
||||
public final static boolean isYAFFS2Image(Program program) {
|
||||
byte[] bytes = new byte[YAFFS2Constants.MAGIC_SIZE];
|
||||
try {
|
||||
Address address = program.getMinAddress();
|
||||
program.getMemory().getBytes(address, bytes);
|
||||
}
|
||||
catch (Exception e) {
|
||||
}
|
||||
// check for initial byte equal to 0x03, 'directory'
|
||||
// and check that the first byte of the file name is null
|
||||
// ... this is the yaffs2 root level dir header
|
||||
return ((bytes[0] == 0x03) && (bytes[10] == 0x00));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user