[build-scripts] launcher-generator: great refactoring

* add checks for produced output
* proper padding for resource section
* support adding more icons that were in template binary (fixed `updateGroupIcon`)
* modernize code: use fields and getters instead of accessing struct parts by name

GitOrigin-RevId: 9263568e575e2b8490c9af420836c6ac82056f71
This commit is contained in:
Vladislav Rassokhin
2022-09-26 20:17:25 +02:00
committed by intellij-monorepo-bot
parent 50995f90ec
commit 37f1d76f9a
38 changed files with 1312 additions and 880 deletions

View File

@@ -18,13 +18,17 @@
package com.pme.exe;
import com.pme.util.BitsUtil;
import com.pme.util.OffsetTrackingInputStream;
import com.pme.util.StreamUtil;
import java.io.*;
import java.lang.reflect.Array;
import java.nio.charset.StandardCharsets;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
/**
* @author Sergey Zhulin
@@ -99,8 +103,8 @@ public abstract class Bin {
public abstract void report(OutputStreamWriter writer) throws IOException;
public static class Structure extends Bin {
private ArrayList<Bin> myMembers = new ArrayList<>(1);
private final HashMap<String, Bin> myMembersMap = new HashMap<>(1);
private final ArrayList<Bin> myMembers = new ArrayList<>(1);
private final HashMap<String, Bin> myMembersMap = new LinkedHashMap<>(1);
public Structure(String name) {
super(name);
@@ -122,7 +126,7 @@ public abstract class Bin {
Bin.Structure structure = (Bin.Structure)binStructure;
ArrayList<Bin> members = structure.getMembers();
for (Bin bin : members) {
Bin valueMember = getMember(bin.getName());
Bin valueMember = myMembersMap.get(bin.getName());
if (valueMember != null) {
valueMember.copyFrom(bin);
}
@@ -141,25 +145,13 @@ public abstract class Bin {
public ArrayList<Bin> getMembers() {
return myMembers;
}
public void addMember(Bin bin, String description) {
public <T extends Bin> T addMember(T bin, String description) {
bin.setDescription(description);
addMember(bin);
return addMember(bin);
}
public void insertMember(int index, Bin bin) {
ArrayList<Bin> list = new ArrayList<>(myMembers.size() + 1);
for ( int i = 0; i < index; ++i ){
list.add( myMembers.get( i ) );
}
list.add( bin );
for ( int i = index; i < myMembers.size(); ++i ){
list.add( myMembers.get( i ) );
}
myMembers = list;
addMemberToMapOnly(bin);
}
public Bin addMember(Bin bin) {
public <T extends Bin> T addMember(T bin) {
myMembers.add(bin);
addMemberToMapOnly(bin);
return bin;
@@ -171,24 +163,6 @@ public abstract class Bin {
myMembersMap.put(bin.getName(), bin);
}
public Bin getMember(String name) {
return myMembersMap.get(name);
}
public Bin.Value getValueMember(String name) {
return (Bin.Value) myMembersMap.get(name);
}
public Bin.Structure getStructureMember(String name) {
return (Bin.Structure) myMembersMap.get(name);
}
public Bin.Txt getTxtMember(String name) { return (Bin.Txt)myMembersMap.get(name); }
public long getValue(String name) {
return ((Bin.Value) myMembersMap.get(name)).getValue();
}
@Override
public void read(DataInput stream) throws IOException {
for (Bin bin : myMembers) {
@@ -238,6 +212,37 @@ public abstract class Bin {
}
}
public static abstract class ReadOnlyValue extends Value {
public ReadOnlyValue(String name) {
super(name);
}
@Override
public long sizeInBytes() {
throw new IllegalStateException();
}
@Override
public void read(DataInput stream) {
throw new IllegalStateException();
}
@Override
public void write(DataOutput stream) {
throw new IllegalStateException();
}
@Override
public void report(OutputStreamWriter writer) {
throw new IllegalStateException();
}
@Override
public Value setValue(long value) {
throw new IllegalStateException();
}
}
public static class Byte extends Value {
public Byte(String name) {
super(name);
@@ -252,8 +257,9 @@ public abstract class Bin {
public long getValue() {
return getRawValue();
}
@Override
public Value setValue(long value) {
public Byte setValue(long value) {
setRawValue(value);
return this;
}
@@ -265,14 +271,17 @@ public abstract class Bin {
@Override
public void write(DataOutput stream) throws IOException {
stream.writeByte((byte) getRawValue());
stream.writeByte((byte)getRawValue());
}
@Override
public void report(OutputStreamWriter writer) throws IOException {
_report(writer, getDescription(), (byte) getValue());
_report(writer, getDescription(), (byte)getValue());
}
public String toString() {
return BitsUtil.byteToHexString((int)getValue());
}
}
public static class Word extends Value {
@@ -295,7 +304,7 @@ public abstract class Bin {
}
@Override
public Value setValue(long value) {
public Word setValue(long value) {
setRawValue(Short.toUnsignedLong(Short.reverseBytes((short)value)));
return this;
}
@@ -321,6 +330,10 @@ public abstract class Bin {
}
public static class DWord extends Value {
public DWord() {
super("");
}
public DWord(String name) {
super(name);
}
@@ -331,7 +344,7 @@ public abstract class Bin {
}
@Override
public Value setValue(long value) {
public DWord setValue(long value) {
setRawValue(Integer.toUnsignedLong(Integer.reverseBytes((int)value)));
return this;
}
@@ -371,7 +384,7 @@ public abstract class Bin {
}
@Override
public Value setValue(long value) {
public LongLong setValue(long value) {
setRawValue(Long.reverseBytes(value));
return this;
}
@@ -400,8 +413,8 @@ public abstract class Bin {
public static class Padding extends Bin {
private final int myBytes;
public Padding(int bytes) {
super("Padding");
public Padding(String name, int bytes) {
super(name);
myBytes = bytes;
}
@@ -412,13 +425,12 @@ public abstract class Bin {
@Override
public void read(DataInput stream) throws IOException {
if (stream instanceof OffsetTrackingInputStream) {
long offset = ((OffsetTrackingInputStream) stream).getOffset();
int skip = bytesToSkip(offset);
if (skip > 0) {
stream.skipBytes(skip);
}
long offset = StreamUtil.getOffset(stream);
int skip = bytesToSkip(offset);
if (skip > 0) {
stream.skipBytes(skip);
}
//resetOffsets(offset);
}
private int bytesToSkip(long offset) {
@@ -438,67 +450,49 @@ public abstract class Bin {
@Override
public void report(OutputStreamWriter writer) {
}
@Override
public String toString() {
return "Padding{" + getName() + "," + myBytes + '}';
}
}
public static class Txt extends Bin {
/**
* Fixed size character (1 byte) string, UTF-8
*/
public static class CharStringFS extends Bin {
private final StringBuffer myBuffer = new StringBuffer();
private final Bin.Value mySize;
private byte[] myBytes;
public Txt(String name, byte[] bytes) {
super(name);
myBytes = bytes;
mySize = new DWord("").setValue(bytes.length);
setValue();
}
public Txt(String name, String string) {
super(name);
myBytes = new byte[string.length() * 2];
byte[] bytes = string.getBytes(StandardCharsets.US_ASCII);
for (int i = 0; i < bytes.length; ++i) {
myBytes[i * 2] = bytes[i];
myBytes[i * 2 + 1] = 0;
}
mySize = new DWord("").setValue(myBytes.length);
setValue();
}
public Txt(String name, Bin.Value size) {
public CharStringFS(String name, Bin.Value size) {
super(name);
mySize = size;
}
public Txt(String name, int size) {
public CharStringFS(String name, int size) {
this(name, new DWord("size").setValue(size));
}
public String getText() {
return myBuffer.toString();
}
@Override
public long sizeInBytes() {
return mySize.getValue();
}
private void setValue(){
for (int i = 0; i < mySize.getValue(); ++i) {
int b = java.lang.Byte.toUnsignedInt(myBytes[i]);
if (b != 0) {
myBuffer.append((char) b);
}
}
}
@Override
public void read(DataInput stream) throws IOException {
long size = mySize.getValue();
myBuffer.setLength(0);
myBytes = new byte[(int) mySize.getValue()];
for (int i = 0; i < mySize.getValue(); ++i) {
myBytes = new byte[(int)size];
for (int i = 0; i < size; ++i) {
myBytes[i] = stream.readByte();
}
setValue();
for (int i = 0; i < size; ++i) {
int b = java.lang.Byte.toUnsignedInt(myBytes[i]);
if (b != 0) {
myBuffer.append((char)b);
}
}
}
@Override
@@ -510,12 +504,96 @@ public abstract class Bin {
public void report(OutputStreamWriter writer) throws IOException {
_report(writer, myBuffer.toString());
}
public String getValue() {
return myBuffer.toString();
}
@Override
public String toString() {
return "CharStringFS{size=" + mySize.getValue() + ", value=" + getValue() + "}";
}
}
public static class WChar extends Bin {
/**
* Size-prefixed wide character (2 bytes) string, UTF-16.
* <p>
* Size is {@linkplain Word} by default.
*/
public static class WCharStringSP extends Bin {
private static final String EMPTY_STRING = "";
private final Value mySize;
private String myValue;
public WChar(String name) {
public WCharStringSP() {
this(new Word());
}
public WCharStringSP(Value size) {
super("");
mySize = size;
}
public String getValue() {
return myValue;
}
public void setValue(String value) {
myValue = value;
mySize.setValue(value.length());
}
@Override
public long sizeInBytes() {
return mySize.sizeInBytes() + mySize.getValue() * 2;
}
@Override
public void read(DataInput stream) throws IOException {
mySize.read(stream);
long size = mySize.getValue();
if (size == 0) {
myValue = EMPTY_STRING;
return;
}
StringBuilder builder = new StringBuilder();
for (int i = 0; i < size; ++i) {
char c = BitsUtil.readChar(stream);
builder.append(c);
}
myValue = builder.toString();
}
@Override
public void write(DataOutput stream) throws IOException {
assert mySize.getValue() == myValue.length();
mySize.write(stream);
for (int i = 0; i < myValue.length(); i++) {
stream.writeShort(Short.toUnsignedInt(Short.reverseBytes((short)myValue.charAt(i))));
}
}
@Override
public void report(OutputStreamWriter writer) throws IOException {
_report(writer, myValue);
}
@Override
public String toString() {
return "WCharStringSP{" +
"size=" + mySize +
", value=" + myValue +
'}';
}
}
/**
* Null-terminated wide character (2 bytes) string, UTF-16
*/
public static class WCharStringNT extends Bin {
private String myValue;
public WCharStringNT(String name) {
super(name);
}
@@ -555,34 +633,33 @@ public abstract class Bin {
public void setValue(String value) {
myValue = value;
}
@Override
public String toString() {
return "WCharStringNT{value=" + myValue + '}';
}
}
public static class Bytes extends Bin {
private byte[] myBytes;
private Value myStartOffset;
private Value mySize;
private final Value myStartOffset;
private final Value mySize;
private static final int ourBytesInRow = 16;
public Bytes(String name, Bin.Value size) {
super(name);
myStartOffset = null;
mySize = size;
}
public Bytes(String name, long size) {
super(name);
myStartOffset = null;
mySize = new DWord("size").setValue(size);
}
public Bytes(String name, Bin.Value startOffset, Bin.Value size) {
super(name);
reset(startOffset, size);
}
public void reset(int startOffset, int size) {
reset(new DWord("startOffset").setValue(startOffset), new DWord("size").setValue(size));
}
public void reset(Bin.Value startOffset, Bin.Value size) {
myStartOffset = startOffset;
mySize = size;
}
@@ -611,8 +688,18 @@ public abstract class Bin {
@Override
public void read(DataInput stream) throws IOException {
if (myStartOffset != null) {
RandomAccessFile file = (RandomAccessFile) stream;
file.seek(myStartOffset.getValue());
long offset = myStartOffset.getValue();
long streamOffset = StreamUtil.getOffset(stream);
if (streamOffset != offset) {
if (offset > streamOffset) {
//noinspection UseOfSystemOutOrSystemErr
System.err.printf("WARN: non-continuous read: reading offset %#x, current stream offset %#x %n", offset, streamOffset);
} else {
//noinspection UseOfSystemOutOrSystemErr
System.err.printf("WARN: out of order read: reading offset %#x, current stream offset %#x %n", offset, streamOffset);
}
StreamUtil.seek(stream, offset);
}
}
myBytes = new byte[(int) mySize.getValue()];
stream.readFully(myBytes);
@@ -623,8 +710,6 @@ public abstract class Bin {
stream.write(myBytes, 0, (int) sizeInBytes());
}
private final StringBuffer myBuffer = new StringBuffer();
@Override
public void report(OutputStreamWriter writer) throws IOException {
_report(writer, getName());
@@ -634,6 +719,7 @@ public abstract class Bin {
rowCount++;
}
int byteCount = 0;
StringBuilder myBuffer = new StringBuilder();
for (int i = 0; i < rowCount; i++) {
myBuffer.setLength(0);
myBuffer.append("\n");
@@ -644,10 +730,18 @@ public abstract class Bin {
writer.write(myBuffer.toString());
}
}
@Override
public String toString() {
return "Bytes{" +
"StartOffset=" + myStartOffset +
", Size=" + mySize +
'}';
}
}
public static class ArrayOfBins<T extends Bin> extends Bin {
private Bin[] myValues;
public static class ArrayOfBins<T extends Bin> extends Bin implements Iterable<T> {
private ArrayList<T> myValues;
private final Bin.Value mySize;
private final Class<T> myClass;
private Bin.Value myCountHolder = null;
@@ -663,20 +757,19 @@ public abstract class Bin {
this(name, cl, new DWord("size").setValue(size));
}
public void addBin( Bin bin ){
Bin[] newArray = new Bin[myValues.length+1];
System.arraycopy( myValues, 0, newArray, 0, myValues.length );
newArray[myValues.length] = bin;
myValues = newArray;
mySize.setValue( mySize.getValue() + 1 );
public void addBin(T bin) {
myValues.add(bin);
if (myCountHolder != null) {
myCountHolder.setValue(myValues.size());
}
}
@Override
public void copyFrom(Bin bin) {
//noinspection unchecked
ArrayOfBins<T> value = (ArrayOfBins<T>)bin;
for (int i = 0; i < myValues.length; i++) {
myValues[i].copyFrom( value.get(i) );
for (int i = 0; i < myValues.size(); i++) {
myValues.get(i).copyFrom(value.myValues.get(i));
}
}
@@ -685,14 +778,16 @@ public abstract class Bin {
}
private void init() {
myValues = (Bin[]) Array.newInstance(myClass, (int) mySize.getValue());
int size = (int)mySize.getValue();
myValues = new ArrayList<>(size);
for (int i = 0; i < myValues.length; i++) {
for (int i = 0; i < size; i++) {
try {
Bin bin = myClass.newInstance();
T bin = myClass.getDeclaredConstructor().newInstance();
bin.setName("[" + i + "]");
myValues[i] = bin;
} catch (InstantiationException | IllegalAccessException e) {
myValues.add(bin);
}
catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException(e.getMessage());
}
}
@@ -703,7 +798,7 @@ public abstract class Bin {
super.resetOffsets(newOffset);
long offset = getOffset();
if (myCountHolder != null) {
myCountHolder.setValue(myValues.length);
myCountHolder.setValue(myValues.size());
}
for (Bin bin : myValues) {
bin.resetOffsets(offset);
@@ -712,16 +807,11 @@ public abstract class Bin {
}
public int size() {
return myValues.length;
}
public Bin[] getArray() {
return myValues;
return myValues.size();
}
public T get(int index) {
//noinspection unchecked
return (T) myValues[index];
return myValues.get(index);
}
@Override
@@ -750,11 +840,16 @@ public abstract class Bin {
@Override
public void report(OutputStreamWriter writer) throws IOException {
writer.write("\n" + "Array size: " + myValues.length);
writer.write("\n" + getName() + " array size: " + myValues.size());
for (Bin value : myValues) {
value.report(writer);
}
}
@Override
public Iterator<T> iterator() {
return myValues.iterator();
}
}
protected void _report(OutputStreamWriter buffer, String name, int value) throws IOException {

View File

@@ -1,8 +0,0 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.pme.exe;
public enum ExeFormat {
UNKNOWN, X86, X64, ARM64
}

View File

@@ -23,76 +23,77 @@ import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Arrays;
import java.util.Optional;
import java.util.ArrayList;
/**
* @author Sergey Zhulin
* Date: Mar 30, 2006
* Time: 4:14:38 PM
*/
public class ExeReader extends Bin.Structure{
private ArrayOfBins<ImageSectionHeader> mySectionHeaders;
private SectionReader[] mySections;
public class ExeReader extends Bin.Structure {
private final ArrayOfBins<ImageSectionHeader> mySectionHeaders;
private final ArrayList<Section> mySections = new ArrayList<>();
private final PeHeaderReader myPeHeader;
private ImageOptionalHeader myImageOptionalHeader;
private Bin.Bytes myBytes;
private final ImageOptionalHeader myImageOptionalHeader;
private final Bin.Bytes myPadding;
private final Bin.Bytes myMsDosStub;
private final MsDosHeader myMsDosHeader;
public ExeReader(String name, ExeFormat exeFormat) {
public ExeReader(String name) {
super(name);
myMsDosHeader = new MsDosHeader();
addMember( myMsDosHeader );
//noinspection SpellCheckingInspection
Bin.Value member = myMsDosHeader.getValueMember("lfanew");
ValuesAdd size = new ValuesAdd( member, new DWord("").setValue( myMsDosHeader.sizeInBytes() ) );
myMsDosStub = new Bytes( "MsDos stub program", size );
addMember( myMsDosStub );
myPeHeader = new PeHeaderReader(member, exeFormat);
addMember( myPeHeader );
if (exeFormat == ExeFormat.UNKNOWN) {
return;
}
myImageOptionalHeader = (ImageOptionalHeader) myPeHeader.getMember("Image Optional Header");
//noinspection unchecked
mySectionHeaders = (ArrayOfBins<ImageSectionHeader>)myPeHeader.getMember( "ImageSectionHeaders" );
addSizeHolder( myImageOptionalHeader.getValueMember( "SizeOfImage" ) ); //b164
}
public long sizeOfHeaders(){
return myPeHeader.sizeInBytes() + myMsDosStub.sizeInBytes() + myMsDosHeader.sizeInBytes();
myMsDosHeader = addMember(new MsDosHeader());
ValuesAdd msDosStubSize = new ValuesAdd(myMsDosHeader.getPEHeaderOffset(), new DWord("").setValue(myMsDosHeader.sizeInBytes()));
myMsDosStub = addMember(new Bytes("MsDos stub program", msDosStubSize));
myPeHeader = addMember(new PeHeaderReader(myMsDosHeader.getPEHeaderOffset()));
myPadding = new Bytes("Padding between headers and first segment",
new ValuesAdd(myPeHeader.getImageOptionalHeader().getSizeOfHeaders(), new ReadOnlyValue("") {
@Override
public long getValue() {
return myPeHeader.sizeInBytes() + myMsDosStub.sizeInBytes() + myMsDosHeader.sizeInBytes();
}
}));
addMember(myPadding);
myImageOptionalHeader = myPeHeader.getImageOptionalHeader();
mySectionHeaders = myPeHeader.getImageSectionHeaders();
}
@Override
public long sizeInBytes() {
long result = 0;
long va = 0;
for ( int i = 0; i < mySectionHeaders.size(); ++i){
ImageSectionHeader header = mySectionHeaders.get(i);
Value virtualAddress = header.getValueMember("VirtualAddress");
if ( va < virtualAddress.getValue() ){
result = mySections[i].sizeInBytes() + virtualAddress.getValue();
}
long result = myPeHeader.sizeInBytes() + myMsDosStub.sizeInBytes() + myMsDosHeader.sizeInBytes() + myPadding.sizeInBytes();
for (Section section : mySections) {
result += section.sizeInBytes();
}
long div = result / 0x1000;
long r = result % 0x1000;
if ( r != 0 ){
div++;
}
result = div * 0x1000;
return result;
}
public SectionReader[] getSections(){
return mySections;
public long virtualSizeInBytes() {
// AKA VirtualAddress + VirtualSize of the latest section, must be dividable by SectionAlignment
long sectionAlignment = myImageOptionalHeader.getSectionAlignment().getValue();
long result = 0;
long lastVA = 0;
for (ImageSectionHeader header : mySectionHeaders) {
long va = ((Value)header.getVirtualAddress()).getValue();
if (lastVA < va) {
result = header.getVirtualSize().getValue() + va;
}
}
if (result % sectionAlignment != 0) {
result += sectionAlignment - result % sectionAlignment;
}
return result;
}
public ArrayOfBins<ImageSectionHeader> getSectionHeaders(){
public ArrayOfBins<ImageSectionHeader> getSectionHeaders() {
return mySectionHeaders;
}
public SectionReader getSectionReader( String sectionName ){
for (SectionReader section : mySections) {
public PeHeaderReader getPeHeader() {
return myPeHeader;
}
public Section getSectionReader(String sectionName) {
for (Section section : mySections) {
if (sectionName.equals(section.getSectionName())) {
return section;
}
@@ -107,43 +108,33 @@ public class ExeReader extends Bin.Structure{
return;
}
long filePointer = getOffset() + sizeOfHeaders();
DWord sizeOfHeaders = myPeHeader.getImageOptionalHeader().getSizeOfHeaders();
assert sizeOfHeaders.getValue() == mySectionHeaders.get(0).getPointerToRawData().getValue();
Bin.Value mainSectionsOffset;
mySections = new SectionReader[mySectionHeaders.size()];
for ( int i = 0; i < mySectionHeaders.size(); ++i ){
ImageSectionHeader sectionHeader = mySectionHeaders.get(i);
Bin.Value startOffset = sectionHeader.getValueMember( "PointerToRawData" );
Bin.Value rva = sectionHeader.getValueMember( "VirtualAddress" );
if ( i == 0 ){
long size = startOffset.getValue() - filePointer;
if ( myBytes == null ){
myBytes = new Bytes( "Alignment", size );
addMemberToMapOnly( myBytes );
} else {
myBytes = (Bytes)getMember( "Alignment" );
myBytes.reset( (int)filePointer, (int)size );
}
myBytes.read( stream );
}
mainSectionsOffset = new ValuesAdd( rva, startOffset );
mySections[i] = new SectionReader( sectionHeader, startOffset, mainSectionsOffset, myImageOptionalHeader );
mySections[i].read( stream );
mySections.clear();
for (ImageSectionHeader sectionHeader : mySectionHeaders) {
Section section = Section.newSection(sectionHeader, myImageOptionalHeader);
mySections.add(section);
section.read(stream);
}
resetOffsets( 0 );
resetOffsets(0);
}
@Override
public void resetOffsets(long newOffset) {
super.resetOffsets(newOffset);
long mainOffset = myPeHeader.getOffset() + myPeHeader.sizeInBytes() + myBytes.sizeInBytes();
long mainOffset = myPeHeader.getOffset() + myPeHeader.sizeInBytes() + myPadding.sizeInBytes();
long mainOffset2 = myPeHeader.sizeInBytes() + myMsDosStub.sizeInBytes() + myMsDosHeader.sizeInBytes() + myPadding.sizeInBytes();
assert mainOffset == myPeHeader.getImageOptionalHeader().getSizeOfHeaders().getValue();
assert mainOffset == mainOffset2;
long offset = 0;
for (SectionReader section : mySections) {
for (Section section : mySections) {
section.resetOffsets(mainOffset + offset);
offset += section.sizeInBytes();
}
// Update SizeOfImage with not real bytes size, but with virtual size
myImageOptionalHeader.getSizeOfImage().setValue(virtualSizeInBytes());
}
/**
@@ -151,39 +142,40 @@ public class ExeReader extends Bin.Structure{
* afterwards.
*/
public void sectionVirtualAddressFixup() {
long virtualAddress = mySectionHeaders.get(0).getValueMember("VirtualAddress").getValue();
long virtualAddress = mySectionHeaders.get(0).getVirtualAddress().getValue();
long sectionAlignment = myImageOptionalHeader.getValue("SectionAlignment");
for (Bin sectionHeader : mySectionHeaders.getArray()) {
Value virtualAddressMember = ((ImageSectionHeader)sectionHeader).getValueMember("VirtualAddress");
long sectionAlignment = myImageOptionalHeader.getSectionAlignment().getValue();
for (ImageSectionHeader header : mySectionHeaders) {
// Section always starts from an address aligned to IMAGE_OPTIONAL_HEADER::SectionAlignment, which is 0x1000 by default
if (virtualAddress % sectionAlignment != 0)
if (virtualAddress % sectionAlignment != 0) {
virtualAddress += sectionAlignment - virtualAddress % sectionAlignment;
}
virtualAddressMember.setValue(virtualAddress);
virtualAddress += ((ImageSectionHeader)sectionHeader).getValueMember("VirtualSize").getValue();
header.getVirtualAddress().setValue(virtualAddress);
virtualAddress += header.getVirtualSize().getValue();
}
// Update the relative virtual address of the Base Relocation Table, if any.
Optional<ImageSectionHeader> relocationSectionHeader = Arrays.stream((ImageSectionHeader[])mySectionHeaders.getArray())
.filter(sectionHeader -> ".reloc".equals(sectionHeader.getTxtMember("Name").getText())).findFirst();
if (relocationSectionHeader.isPresent()) {
Bin.ArrayOfBins imageDataDirectories = (Bin.ArrayOfBins)myImageOptionalHeader.getMember("ImageDataDirectories");
ImageDataDirectory relocationDataDirectory = (ImageDataDirectory)imageDataDirectories.get(5);
Value virtualAddressMember = relocationDataDirectory.getValueMember("VirtualAddress");
virtualAddressMember.setValue(relocationSectionHeader.get().getValue("VirtualAddress"));
}
updateDataDirectoryFromSectionHeader(Section.RESOURCES_SECTION_NAME, ImageDataDirectory.IMAGE_DIRECTORY_ENTRY_RESOURCE);
updateDataDirectoryFromSectionHeader(Section.RELOCATIONS_SECTION_NAME, ImageDataDirectory.IMAGE_DIRECTORY_ENTRY_BASERELOC);
// The binary size has been changed as the result, update it in the size holders:
updateSizeOffsetHolders();
// Resources section may have changed virtual offset, re-shake of all nested structures required to have correct addresses,
// also the binary size may have been changed as the result
resetOffsets(0);
}
private void updateDataDirectoryFromSectionHeader(String name, int index) {
ImageSectionHeader header = getSection(name);
ImageDataDirectory directory = myImageOptionalHeader.getImageDataDirectories().get(index);
directory.getVirtualAddress().setValue(header.getVirtualAddress().getValue());
directory.getSize().setValue(header.getVirtualSize().getValue());
}
@Override
public void write(DataOutput stream) throws IOException {
super.write(stream);
myBytes.write(stream);
for (SectionReader section : mySections) {
for (Section section : mySections) {
section.write(stream);
}
}
@@ -191,24 +183,19 @@ public class ExeReader extends Bin.Structure{
@Override
public void report(OutputStreamWriter writer) throws IOException {
super.report(writer);
myBytes.report( writer );
myPadding.report(writer);
mySectionHeaders.report(writer);
for (SectionReader section : mySections) {
for (Section section : mySections) {
section.report(writer);
}
}
public ExeFormat getExeFormat() {
long machine = myPeHeader.getImageFileHeader().getMachine();
if (machine == 0x14c) {
return ExeFormat.X86;
private ImageSectionHeader getSection(String name) {
for (ImageSectionHeader header : mySectionHeaders) {
if (name.equals(header.getSectionName())) {
return header;
}
}
if (machine == 0x8664) {
return ExeFormat.X64;
}
if (machine == 0xAA64) {
return ExeFormat.ARM64;
}
throw new UnsupportedOperationException("Unsupported machine code " + machine);
throw new IllegalStateException("Cannot find section with name " + name);
}
}

View File

@@ -1,6 +1,6 @@
/*
* Copyright 2006 ProductiveMe Inc.
* Copyright 2013-2018 JetBrains s.r.o.
* Copyright 2013-2022 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,12 +23,30 @@ package com.pme.exe;
* Time: 10:40:56 PM
*/
public class ImageDataDirectory extends Bin.Structure {
public static final int IMAGE_DIRECTORY_ENTRY_RESOURCE = 2;
@SuppressWarnings("SpellCheckingInspection")
public static final int IMAGE_DIRECTORY_ENTRY_BASERELOC = 5;
private final DWord myVirtualAddress;
private final DWord mySize;
public ImageDataDirectory(String name) {
super(name);
addMember( new DWord( "VirtualAddress" ) );
addMember( new DWord( "Size" ) );
myVirtualAddress = addMember(new DWord("VirtualAddress"));
mySize = addMember(new DWord("Size"));
}
public ImageDataDirectory( ){
this("");
// Used by Bin.ArrayOfBins
@SuppressWarnings("unused")
public ImageDataDirectory() {
this("");
}
public DWord getVirtualAddress() {
return myVirtualAddress;
}
public DWord getSize() {
return mySize;
}
}

View File

@@ -27,12 +27,12 @@ import java.io.OutputStreamWriter;
*/
public class ImageFileHeader extends Bin.Structure {
private final ImageFileHeader.Machine myMachine;
private final Word myNumberOfSections;
public ImageFileHeader() {
super("Image File Header");
myMachine = new Machine();
addMember(myMachine);
addMember( new Word( "NumberOfSections" ) );
myMachine = addMember(new Machine());
myNumberOfSections = addMember(new Word("NumberOfSections"));
addMember( new DWord( "TimeDateStamp" ) );
addMember( new DWord( "PointerToSymbolTable" ) );
addMember( new DWord( "NumberOfSymbols" ) );
@@ -44,6 +44,10 @@ public class ImageFileHeader extends Bin.Structure {
return myMachine.getValue();
}
public Word getNumberOfSections() {
return myNumberOfSections;
}
static class Machine extends Bin.Word{
Machine() {
@@ -58,8 +62,12 @@ public class ImageFileHeader extends Bin.Structure {
_report(writer ,"Machine: Intel 386" );
} else if ( machine == 0x0002 ) {
_report( writer, "Machine: Intel 64" );
} else if ( machine == 0x8664 ) {
_report( writer, "Machine: AMD64" );
} else if ( machine == 0xAA64 ) {
_report( writer, "Machine: ARM64" );
} else {
_report( writer, "Machine: Unknown" );
_report( writer, String.format("Machine: Unknown (%#06x)", machine));
}
}
}

View File

@@ -17,61 +17,159 @@
package com.pme.exe;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.reflect.InvocationTargetException;
/**
* @author Sergey Zhulin
* Date: Mar 31, 2006
* Time: 6:01:16 PM
*/
public class ImageOptionalHeader extends Bin.Structure {
public ImageOptionalHeader(ExeFormat format) {
super("Image Optional Header");
addMember( new Word( "Magic" ) );
addMember( new Byte( "MajorLinkerVersion"));
addMember( new Byte( "MinorLinkerVersion"));
addMember( new DWord( "SizeOfCode" ) );
addMember( new DWord( "SizeOfInitializedData" ) );
addMember( new DWord( "SizeOfUninitializedData" ) );
addMember( new DWord( "AddressOfEntryPoint" ) );
addMember( new DWord( "BaseOfCode" ) );
if (format == ExeFormat.X86) {
addMember( new DWord( "BaseOfData" ) );
addMember( new DWord( "ImageBase" ) );
}
else {
addMember(new LongLong("ImageBase"));
}
addMember( new DWord( "SectionAlignment" ) );
addMember( new DWord( "FileAlignment" ) );
addMember( new Word( "MajorOperatingSystemVersion" ) );
addMember( new Word( "MinorOperatingSystemVersion" ) );
addMember( new Word( "MajorImageVersion" ) );
addMember( new Word( "MinorImageVersion" ) );
addMember( new Word( "MajorSubsystemVersion" ) );
addMember( new Word( "MinorSubsystemVersion" ) );
addMember( new DWord( "Win32VersionValue" ) );
addMember( new DWord( "SizeOfImage" ) );
addMember( new DWord( "SizeOfHeaders" ) );
addMember( new DWord( "CheckSum" ) );
addMember( new Word( "Subsystem" ) );
addMember( new Word( "DllCharacteristics" ) );
if (format == ExeFormat.X86) {
addMember( new DWord( "SizeOfStackReserve" ) );
addMember( new DWord( "SizeOfStackCommit" ) );
addMember( new DWord( "SizeOfHeapReserve" ) );
addMember( new DWord( "SizeOfHeapCommit" ) );
}
else {
addMember( new LongLong( "SizeOfStackReserve" ) );
addMember( new LongLong( "SizeOfStackCommit" ) );
addMember( new LongLong( "SizeOfHeapReserve" ) );
addMember( new LongLong( "SizeOfHeapCommit" ) );
}
addMember( new DWord( "LoaderFlags" ) );
DWord numberOfRvaAndSizes = (DWord)addMember( new DWord( "NumberOfRvaAndSizes") );
ArrayOfBins<ImageDataDirectory> imageDataDirectories =
new ArrayOfBins<>("ImageDataDirectories", ImageDataDirectory.class, numberOfRvaAndSizes);
imageDataDirectories.setCountHolder( numberOfRvaAndSizes );
addMember( imageDataDirectories );
private final ArrayOfBins<ImageDataDirectory> myImageDataDirectories;
private final DWord mySectionAlignment;
private final DWord myFileAlignment;
private final DWord mySizeOfImage;
private final DWord mySizeOfHeaders;
public ImageOptionalHeader() {
super("Image Optional Header");
Magic magic = addMember(new Magic());
addMember(new Byte("MajorLinkerVersion"));
addMember(new Byte("MinorLinkerVersion"));
addMember(new DWord("SizeOfCode"));
addMember(new DWord("SizeOfInitializedData"));
addMember(new DWord("SizeOfUninitializedData"));
addMember(new DWord("AddressOfEntryPoint"));
addMember(new DWord("BaseOfCode"));
addMember(new Bytes("BaseOfData,ImageBase", 8));
mySectionAlignment = addMember(new DWord("SectionAlignment"));
myFileAlignment = addMember(new DWord("FileAlignment"));
addMember(new Word("MajorOperatingSystemVersion"));
addMember(new Word("MinorOperatingSystemVersion"));
addMember(new Word("MajorImageVersion"));
addMember(new Word("MinorImageVersion"));
addMember(new Word("MajorSubsystemVersion"));
addMember(new Word("MinorSubsystemVersion"));
addMember(new DWord("Win32VersionValue"));
mySizeOfImage = addMember(new DWord("SizeOfImage"));
mySizeOfHeaders = addMember(new DWord("SizeOfHeaders"));
addMember(new DWord("CheckSum"));
addMember(new Word("Subsystem"));
addMember(new Word("DllCharacteristics"));
addMember(new Dependent(magic, DWord.class, LongLong.class, "SizeOfStackReserve"));
addMember(new Dependent(magic, DWord.class, LongLong.class, "SizeOfStackCommit"));
addMember(new Dependent(magic, DWord.class, LongLong.class, "SizeOfHeapReserve"));
addMember(new Dependent(magic, DWord.class, LongLong.class, "SizeOfHeapCommit"));
addMember(new DWord("LoaderFlags"));
DWord numberOfImageDataDirectories = addMember(new DWord("NumberOfRvaAndSizes"));
myImageDataDirectories = new ArrayOfBins<>("ImageDataDirectories", ImageDataDirectory.class, numberOfImageDataDirectories);
myImageDataDirectories.setCountHolder(numberOfImageDataDirectories);
addMember(myImageDataDirectories);
}
public ArrayOfBins<ImageDataDirectory> getImageDataDirectories() {
return myImageDataDirectories;
}
public DWord getSectionAlignment() {
return mySectionAlignment;
}
public DWord getFileAlignment() {
return myFileAlignment;
}
public DWord getSizeOfImage() {
return mySizeOfImage;
}
public DWord getSizeOfHeaders() {
return mySizeOfHeaders;
}
enum Type {
PE32,
PE32Plus,
}
private static class Magic extends Word {
private Magic() {
super("Magic");
}
public Type getType() {
short value = (short)getValue();
switch (value) {
case 0x10B -> {
return Type.PE32;
}
case 0x20B -> {
return Type.PE32Plus;
}
default -> throw new IllegalStateException("Unsupported magic: " + Integer.toHexString(value));
}
}
}
private static class Dependent extends Value {
private final Magic myMagic;
private final DWord myPe32;
private final LongLong myPe32Plus;
private Dependent(Magic magic, Class<DWord> pe32, Class<LongLong> pe32plus, String name) {
super(name);
myMagic = magic;
try {
myPe32 = pe32.getDeclaredConstructor(String.class).newInstance(name);
myPe32Plus = pe32plus.getDeclaredConstructor(String.class).newInstance(name);
}
catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
private Value getCurrent() {
return switch (myMagic.getType()) {
case PE32 -> myPe32;
case PE32Plus -> myPe32Plus;
};
}
@Override
public long sizeInBytes() {
return getCurrent().sizeInBytes();
}
@Override
public void read(DataInput stream) throws IOException {
getCurrent().read(stream);
}
@Override
public void write(DataOutput stream) throws IOException {
getCurrent().write(stream);
}
@Override
public void report(OutputStreamWriter writer) throws IOException {
getCurrent().report(writer);
}
@Override
public long getValue() {
return getCurrent().getValue();
}
@Override
public Value setValue(long value) {
getCurrent().setValue(value);
return this;
}
}
}

View File

@@ -22,18 +22,44 @@ package com.pme.exe;
* Date: Apr 1, 2006
* Time: 1:54:57 PM
*/
public class ImageSectionHeader extends Bin.Structure{
public class ImageSectionHeader extends Bin.Structure {
private final CharStringFS myNameTxt;
private final DWord myVirtualSize;
private final DWord myVirtualAddress;
private final DWord mySizeOfRawData;
private final DWord myPointerToRawData;
public ImageSectionHeader() {
super("Image section header");
addMember( new Txt( "Name", 8 ));
addMember( new DWord( "VirtualSize"));
addMember( new DWord( "VirtualAddress"));
addMember( new DWord( "SizeOfRawData"));
addMember( new DWord( "PointerToRawData"));
addMember( new DWord( "PointerToRelocations"));
addMember( new DWord( "PointerToLineNumbers"));
addMember( new Word( "NumberOfRelocations"));
addMember( new Word( "NumberOfLineNumbers"));
addMember( new DWord( "Characteristics"));
addMember(myNameTxt = new CharStringFS("Name", 8));
addMember(myVirtualSize = new DWord("VirtualSize"));
addMember(myVirtualAddress = new DWord("VirtualAddress"));
addMember(mySizeOfRawData = new DWord("SizeOfRawData"));
addMember(myPointerToRawData = new DWord("PointerToRawData"));
addMember(new DWord("PointerToRelocations"));
addMember(new DWord("PointerToLineNumbers"));
addMember(new Word("NumberOfRelocations"));
addMember(new Word("NumberOfLineNumbers"));
addMember(new DWord("Characteristics"));
}
public String getSectionName() {
return myNameTxt.getValue();
}
public DWord getVirtualSize() {
return myVirtualSize;
}
public DWord getVirtualAddress() {
return myVirtualAddress;
}
public DWord getSizeOfRawData() {
return mySizeOfRawData;
}
public DWord getPointerToRawData() {
return myPointerToRawData;
}
}

View File

@@ -17,8 +17,8 @@
package com.pme.exe;
import java.io.IOException;
import java.io.DataInput;
import java.io.IOException;
/**
* @author Sergey Zhulin
@@ -26,10 +26,13 @@ import java.io.DataInput;
* Time: 2:10:01 PM
*/
public class MsDosHeader extends Bin.Structure {
private final Word myMagic;
private final DWord myPEHeaderOffset;
@SuppressWarnings("SpellCheckingInspection")
public MsDosHeader() {
super("MSDOS Header");
addMember( new Word( "magic" ), "Magic number" );
myMagic = addMember( new Word( "magic" ), "Magic number" );
addMember( new Word( "cblp" ), "Bytes on last page of file" );
addMember( new Word( "cp" ), "Pages in file" );
addMember( new Word( "crlc" ), "Relocations" );
@@ -47,15 +50,19 @@ public class MsDosHeader extends Bin.Structure {
addMember( new Word( "oemid" ), "OEM identifier (for e_oeminfo)" );
addMember( new Word( "oeminfo" ), "OEM information; e_oemid specific" );
addMember( new ArrayOfBins<>( "res2", Word.class, 10 ), "Reserved words" );
addMember( new DWord( "lfanew" ), "File address of new exe header" );
myPEHeaderOffset = addMember( new DWord( "lfanew" ), "File address of new exe header" );
}
@Override
public void read(DataInput stream) throws IOException {
super.read( stream );
long magic = getValue( "magic" );
long magic = myMagic.getValue();
if (magic != 0x5a4d) {
throw new InvalidMsDosHeaderException("First two chars in exe file must be 'MZ'");
}
}
public DWord getPEHeaderOffset() {
return myPEHeaderOffset;
}
}

View File

@@ -22,26 +22,31 @@ package com.pme.exe;
* Date: Mar 31, 2006
* Time: 5:00:49 PM
*/
public class PeHeaderReader extends Bin.Structure{
public class PeHeaderReader extends Bin.Structure {
private final ImageFileHeader myImageFileHeader;
private final ImageOptionalHeader myImageOptionalHeader;
private final ArrayOfBins<ImageSectionHeader> myImageSectionHeaders;
public PeHeaderReader(Bin.Value startOffset, ExeFormat exeFormat) {
super( "PE Header" );
public PeHeaderReader(Bin.Value startOffset) {
super("PE Header");
addOffsetHolder(startOffset);
addMember( new DWord( "Signature" ) );
myImageFileHeader = new ImageFileHeader();
addMember(myImageFileHeader);
if (exeFormat == ExeFormat.UNKNOWN) {
return;
}
addMember(new ImageOptionalHeader(exeFormat));
Bin.Value numberOfSections = myImageFileHeader.getValueMember("NumberOfSections");
ArrayOfBins<ImageSectionHeader> imageSectionHeaders = new ArrayOfBins<>("ImageSectionHeaders", ImageSectionHeader.class, numberOfSections);
imageSectionHeaders.setCountHolder( numberOfSections );
addMember( imageSectionHeaders );
addMember(new DWord("Signature"));
myImageFileHeader = addMember(new ImageFileHeader());
myImageOptionalHeader = addMember(new ImageOptionalHeader());
Bin.Word numberOfSections = myImageFileHeader.getNumberOfSections();
myImageSectionHeaders = addMember(new ArrayOfBins<>("ImageSectionHeaders", ImageSectionHeader.class, numberOfSections));
myImageSectionHeaders.setCountHolder(numberOfSections);
}
public ImageFileHeader getImageFileHeader() {
return myImageFileHeader;
}
public ImageOptionalHeader getImageOptionalHeader() {
return myImageOptionalHeader;
}
public ArrayOfBins<ImageSectionHeader> getImageSectionHeaders() {
return myImageSectionHeaders;
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2006 ProductiveMe Inc.
* Copyright 2013-2022 JetBrains s.r.o.
*
* 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 com.pme.exe;
import com.pme.exe.res.ResourceSectionReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
public abstract class Section extends Bin.Structure {
@SuppressWarnings("SpellCheckingInspection") public static final String RESOURCES_SECTION_NAME = ".rsrc";
@SuppressWarnings("SpellCheckingInspection") public static final String RELOCATIONS_SECTION_NAME = ".reloc";
private final String myName;
protected Section(ImageSectionHeader header, String name) {
super(name);
myName = name;
addOffsetHolder(header.getPointerToRawData());
addSizeHolder(header.getSizeOfRawData());
}
public static Section newSection(ImageSectionHeader header, ImageOptionalHeader imageOptionalHeader) {
if (RESOURCES_SECTION_NAME.equals(header.getSectionName())) {
return new ResourceSectionReader(header, imageOptionalHeader);
}
else {
return new Simple(header);
}
}
public String getSectionName() {
return myName;
}
@Override
public void report(OutputStreamWriter writer) throws IOException {
_report(writer, "Section name: " + myName);
super.report(writer);
}
public static class Simple extends Section {
public Simple(ImageSectionHeader header) {
super(header, header.getSectionName());
addMember(new Bin.Bytes("Section Raw Data", header.getSizeOfRawData()));
}
}
}

View File

@@ -1,65 +0,0 @@
/*
* Copyright 2006 ProductiveMe Inc.
* Copyright 2013-2022 JetBrains s.r.o.
*
* 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 com.pme.exe;
import com.pme.exe.res.ResourceSectionReader;
import java.io.OutputStreamWriter;
import java.io.IOException;
/**
* @author Sergey Zhulin
* Date: Apr 5, 2006
* Time: 9:48:35 AM
*/
public class SectionReader extends Bin.Structure{
private final Bin.Txt myName;
public SectionReader( ImageSectionHeader sectionHeader, Bin.Value startOffset, Bin.Value mainSectionsOffset, ImageOptionalHeader imageOptionalHeader ) {
super("Section");
myName = (Bin.Txt)sectionHeader.getMember("Name");
String sectionName = myName.getText();
if ( ".rsrc".equals(sectionName) ){
addMember( new ResourceSectionReader( sectionHeader, startOffset, mainSectionsOffset, imageOptionalHeader ) );
} else {
Bin.Value size = sectionHeader.getValueMember( "SizeOfRawData" );
addMember( new Simple( sectionName, startOffset, size ) );
}
}
public String getSectionName(){
return myName.getText();
}
@Override
public void report(OutputStreamWriter writer) throws IOException {
_report( writer, "Section name: " + myName.getText() );
super.report(writer);
}
public static class Simple extends Bin.Structure{
public Simple(String name, Bin.Value startOffset, Bin.Value size) {
super(name);
addOffsetHolder( startOffset );
addSizeHolder( size );
addMember( new Bin.Bytes( "Raw data", size ) );
}
}
}

View File

@@ -17,11 +17,9 @@
package com.pme.exe.res;
import com.pme.exe.Bin;
import java.io.IOException;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/**
* @author Sergey Zhulin
@@ -29,29 +27,32 @@ import java.io.DataOutput;
* Time: 3:58:05 PM
*/
public class DataEntry extends LevelEntry {
private final ResourceSectionReader mySection;
private RawResource myRawResource = null;
public DataEntry( ResourceSectionReader section, Bin.Value offsetHolder ) {
private final DWord myRVA;
private final DWord mySize;
private final RawResource myRawResource;
private final DWord myLanguage;
public DataEntry(ResourceSectionReader section, DWord offset, DWord language) {
super("DataEntry");
addMember(new DWord("RVA"));
addMember(new DWord("Size"));
myLanguage = language;
addMember(myRVA = new DWord("RVA"));
addMember(mySize = new DWord("Size"));
addMember(new DWord("Code Page"));
addMember(new DWord("Reserved"));
mySection = section;
addOffsetHolder( new ValuesSub( offsetHolder, mySection.getStartOffset() ) );
addOffsetHolder(new ValuesSub(offset, section.getStartOffset()));
myRawResource = new RawResource(section, myRVA, mySize);
}
public RawResource getRawResource() {
return myRawResource;
}
public void initRawData(){
myRawResource = new RawResource( mySection, (DWord) getValueMember("RVA"), (DWord) getValueMember("Size"));
getLevel().addLevelEntry( myRawResource );
public DWord getLanguage() {
return myLanguage;
}
public void insertRawData( int index ){
myRawResource = new RawResource( mySection, (DWord) getValueMember("RVA"), (DWord) getValueMember("Size"));
getLevel().insertLevelEntry( index, myRawResource );
public void initRawData(){
getLevel().addLevelEntry(myRawResource);
}
@Override
@@ -64,4 +65,12 @@ public class DataEntry extends LevelEntry {
public void write(DataOutput stream) throws IOException {
super.write(stream);
}
@Override
public String toString() {
return "DataEntry{" +
"RVA=" + myRVA +
", Size=" + mySize +
'}';
}
}

View File

@@ -20,10 +20,9 @@ package com.pme.exe.res;
import com.pme.exe.Bin;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Iterator;
/**
* @author Sergey Zhulin
@@ -31,27 +30,40 @@ import java.util.ArrayList;
* Time: 11:22:12 PM
*/
public class DirectoryEntry extends LevelEntry {
private ArrayOfBins<EntryDescription> myIdEntries;
private static final long DIRECTORY_ENTRY_FLAG = 0xffff_ffff_8000_0000L;
public static final int RT_BITMAP = 2;
public static final int RT_ICON = 3;
public static final int RT_STRING = 6;
public static final int RT_GROUP_ICON = RT_ICON + 11;
public static final int RT_VERSION = 16;
private final ArrayOfBins<EntryDescription> myNamedEntries;
private final ArrayOfBins<EntryDescription> myIdEntries;
private final ResourceSectionReader mySection;
private final ArrayList<DirectoryEntry> mySubDirs = new ArrayList<>();
private final ArrayList<DataEntry> myData = new ArrayList<>();
private final long myIdOrName;
public DirectoryEntry(ResourceSectionReader section, EntryDescription entry, long idOrName) {
super( createName( entry ) );
super(String.valueOf(idOrName));
myIdOrName = idOrName;
addMember(new DWord("Characteristics"));
addMember(new DWord("TimeDateStamp"));
addMember(new Word("MajorVersion"));
addMember(new Word("MinorVersion"));
addMember(new Word("NumberOfNamedEntries"));
addMember(new Word("NumberOfIdEntries"));
Word numberOfNamedEntries = addMember(new Word("NumberOfNamedEntries"));
Word numberOfIdEntries = addMember(new Word("NumberOfIdEntries"));
mySection = section;
if ( entry != null ){
Value flagValue = new DWord( "flag" ).setValue(0xffff_ffff_8000_0000L);
Bin.Value offset = entry.getValueMember("OffsetToData");
addOffsetHolder( new ValuesSub( offset, new ValuesAdd( mySection.getStartOffset(), flagValue ) ));
if (entry != null) {
Value flagValue = new DWord("flag").setValue(DIRECTORY_ENTRY_FLAG);
Bin.Value offset = entry.getOffsetToData();
addOffsetHolder(new ValuesSub(offset, new ValuesAdd(mySection.getStartOffset(), flagValue)));
}
myNamedEntries = addMember(new ArrayOfBins<>("Named entries", EntryDescription.class, numberOfNamedEntries));
myNamedEntries.setCountHolder(numberOfNamedEntries);
myIdEntries = addMember(new ArrayOfBins<>("Id entries", EntryDescription.class, numberOfIdEntries));
myIdEntries.setCountHolder(numberOfIdEntries);
}
public long getIdOrName() {
@@ -62,103 +74,72 @@ public class DirectoryEntry extends LevelEntry {
return mySection;
}
public static String createName( EntryDescription entry ){
String name = "IRD";
if ( entry != null ){
name += entry.getValueMember( "Name" ).getValue();
}
return name;
}
public ArrayList<DirectoryEntry> getSubDirs() {
return mySubDirs;
}
public DirectoryEntry findSubDir( String name ){
public ArrayList<DataEntry> getData() {
return myData;
}
public DirectoryEntry findSubDir(long id) {
for (DirectoryEntry directoryEntry : mySubDirs) {
if (directoryEntry.getName().equals(name)) {
if (directoryEntry.getIdOrName() == id) {
return directoryEntry;
}
}
return null;
}
public RawResource getRawResource(int index) {
DataEntry dataEntry = myData.get(index);
public RawResource getRawResource() {
Iterator<DataEntry> iterator = myData.iterator();
assert iterator.hasNext();
DataEntry dataEntry = iterator.next();
assert !iterator.hasNext();
return dataEntry.getRawResource();
}
public void insertDataEntry( int index, DataEntry dataEntry ){
myData.add(dataEntry );
getLevel().insertLevelEntry( index, dataEntry );
public void addDataEntry(DataEntry dataEntry) {
getLevel().addLevelEntry(dataEntry);
myData.add(dataEntry);
}
public void addDataEntry( DataEntry dataEntry ){
myData.add(dataEntry );
getLevel().addLevelEntry( dataEntry );
public void addDirectoryEntry(DirectoryEntry dir) {
getLevel().addLevelEntry(dir);
mySubDirs.add(dir);
}
public void insertDirectoryEntry( int index, DirectoryEntry dir ){
getLevel().insertLevelEntry( index, dir );
mySubDirs.add( dir );
}
public void addDirectoryEntry( DirectoryEntry dir ){
getLevel().addLevelEntry( dir );
mySubDirs.add( dir );
}
public void addIdEntry( EntryDescription entry ){
if ( myIdEntries == null ){
Word numberOfNamedEntries = (Word) getMember("NumberOfIdEntries");
myIdEntries = new ArrayOfBins<>("Id entries", EntryDescription.class, 0);
addMember(myIdEntries);
myIdEntries.setCountHolder(numberOfNamedEntries);
}
myIdEntries.addBin( entry );
public void addIdEntry(EntryDescription entry) {
myIdEntries.addBin(entry);
}
@Override
public void read(DataInput stream) throws IOException {
super.read(stream);
Word numberOfNamedEntries = (Word) getMember("NumberOfNamedEntries");
ArrayOfBins<EntryDescription> namedEntries = new ArrayOfBins<>("Named entries", EntryDescription.class, numberOfNamedEntries);
addMember(namedEntries);
namedEntries.setCountHolder(numberOfNamedEntries);
namedEntries.read(stream);
Word numberOfIdEntries = (Word) getMember("NumberOfIdEntries");
myIdEntries = new ArrayOfBins<>("Id entries", EntryDescription.class, numberOfIdEntries);
addMember(myIdEntries);
myIdEntries.setCountHolder(numberOfIdEntries);
myIdEntries.read(stream);
processEntries(namedEntries);
processEntries(myNamedEntries);
processEntries(myIdEntries);
}
private void processEntries(ArrayOfBins<EntryDescription> entries) {
for (int i = 0; i < entries.size(); ++i) {
EntryDescription entry = entries.get(i);
Bin.Value offset = entry.getValueMember("OffsetToData");
Bin.Value name = entry.getValueMember("Name");
if ((offset.getValue() & 0xffff_ffff_8000_0000L) != 0) {
addDirectoryEntry( new DirectoryEntry( mySection, entry, name.getValue()) );
} else {
addDataEntry( new DataEntry( mySection, offset) );
for (EntryDescription entry : entries) {
DWord offset = entry.getOffsetToData();
DWord name = entry.getNameW();
if ((offset.getValue() & DIRECTORY_ENTRY_FLAG) != 0) {
addDirectoryEntry(new DirectoryEntry(mySection, entry, name.getValue()));
}
else {
addDataEntry(new DataEntry(mySection, offset, name));
}
}
}
@Override
public void report(OutputStreamWriter writer) throws IOException {
super.report(writer);
public String toString() {
return "DirectoryEntry{" +
", idOrName=" + myIdOrName +
", subs=" + mySubDirs +
", data=" + myData +
'}';
}
@Override
public void write(DataOutput stream) throws IOException {
super.write(stream);
}
}

View File

@@ -1,6 +1,6 @@
/*
* Copyright 2006 ProductiveMe Inc.
* Copyright 2013-2018 JetBrains s.r.o.
* Copyright 2013-2022 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,9 +25,20 @@ import com.pme.exe.Bin;
* Time: 3:59:15 PM
*/
public class EntryDescription extends Bin.Structure {
private final DWord myNameW;
private final DWord myOffsetToData;
public EntryDescription() {
super("Entry");
addMember(new DWord("Name"));
addMember(new DWord("OffsetToData"));
addMember(myNameW = new DWord("Name"));
addMember(myOffsetToData = new DWord("OffsetToData"));
}
public DWord getNameW() {
return myNameW;
}
public DWord getOffsetToData() {
return myOffsetToData;
}
}

View File

@@ -19,7 +19,11 @@ package com.pme.exe.res;
import com.pme.exe.Bin;
import java.io.*;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
/**
* @author Sergey Zhulin
@@ -28,33 +32,28 @@ import java.io.*;
*/
public class Level extends Bin.Structure {
private Level mySubLevel = null;
private final int myDepth;
public Level() {
super("Level");
public Level(int depth) {
super("Level" + depth);
myDepth = depth;
}
public void addLevelEntry(LevelEntry dir) {
if (mySubLevel == null) {
mySubLevel = new Level();
mySubLevel = new Level(myDepth + 1);
}
dir.setLevel(mySubLevel);
addMember(dir);
}
public void insertLevelEntry(int index, LevelEntry dir) {
if (mySubLevel == null) {
mySubLevel = new Level();
}
dir.setLevel(mySubLevel);
insertMember(index,dir);
}
@Override
public long sizeInBytes() {
long size = super.sizeInBytes();
if (mySubLevel != null) {
return super.sizeInBytes() + mySubLevel.sizeInBytes();
size += mySubLevel.sizeInBytes();
}
return super.sizeInBytes();
return size;
}
@Override
@@ -67,6 +66,7 @@ public class Level extends Bin.Structure {
@Override
public void read(DataInput stream) throws IOException {
sortMembersIfNeeded();
super.read(stream);
if (mySubLevel != null) {
mySubLevel.read(stream);
@@ -81,6 +81,19 @@ public class Level extends Bin.Structure {
}
}
@SuppressWarnings("SSBasedInspection")
private void sortMembersIfNeeded() {
ArrayList<Bin> members = getMembers();
if (members.stream().allMatch(bin -> bin instanceof RawResource)) {
// Somehow RawResources could go out of order on disk, let's sort them, so we won't need to seek them in input stream
members.sort((o1, o2) -> {
assert o1 instanceof RawResource;
assert o2 instanceof RawResource;
return Long.compare(((RawResource)o1).getRawOffset().getValue(), ((RawResource)o2).getRawOffset().getValue());
});
}
}
@Override
public void report(OutputStreamWriter writer) throws IOException {
super.report(writer);
@@ -88,4 +101,13 @@ public class Level extends Bin.Structure {
mySubLevel.report(writer);
}
}
@Override
public String toString() {
return "Level{" +
"depth=" + myDepth +
", members_num=" + getMembers().size() +
", sub=" + mySubLevel +
'}';
}
}

View File

@@ -1,6 +1,6 @@
/*
* Copyright 2006 ProductiveMe Inc.
* Copyright 2013-2018 JetBrains s.r.o.
* Copyright 2013-2022 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,20 +23,39 @@ package com.pme.exe.res;
* Time: 3:12:58 PM
*/
public class RawResource extends LevelEntry {
private final Bytes myRawResource;
private final DWord myVirtualAddress;
private final DWord mySize;
private final Value myRawOffset;
public RawResource(ResourceSectionReader section, DWord rva, DWord size) {
super("Raw Resource");
Value offsetHolder = new ValuesAdd(rva, section.getMainSectionsOffset());
Bytes bytes = new Bytes("Raw Resource", offsetHolder, size);
bytes.addOffsetHolder(offsetHolder);
bytes.addSizeHolder(size);
addMember(bytes);
myVirtualAddress = rva;
mySize = size;
myRawOffset = new ValuesAdd(myVirtualAddress, section.getSectionVAtoRawOffset());
myRawResource = new Bytes("Raw Resource Data", myRawOffset, size);
myRawResource.addOffsetHolder(myRawOffset);
myRawResource.addSizeHolder(size);
addMember(myRawResource);
addMember(new Padding("Resource Padding", 8));
}
public Bytes getBytes(){
return (Bytes)getMember( "Raw Resource" );
public byte[] getBytes(){
return myRawResource.getBytes();
}
public void setBytes( byte[] bytes ){
Bytes mem = (Bytes) getMember("Raw Resource");
mem.setBytes( bytes );
myRawResource.setBytes(bytes);
}
public Value getRawOffset() {
return myRawOffset;
}
@Override
public String toString() {
return "RawResource{" +
"VirtualAddress=" + myVirtualAddress +
", RawOffset=" + myRawOffset +
", Size=" + mySize +
'}';
}
}

View File

@@ -18,94 +18,45 @@
package com.pme.exe.res;
import com.pme.exe.Bin;
import com.pme.exe.ImageDataDirectory;
import com.pme.exe.ImageOptionalHeader;
import com.pme.exe.ImageSectionHeader;
import com.pme.exe.Section;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Iterator;
/**
* @author Sergey Zhulin
* Date: Apr 6, 2006
* Time: 7:10:09 PM
*/
public class ResourceSectionReader extends Bin.Structure {
private final Level myRoot = new Level();
private final Bin.Value myStartOffset;
private final Bin.Value myMainSectionsOffset;
private final Bin.Value mySize;
private Bin.Bytes myBytes;
private long myFileAlignment;
public class ResourceSectionReader extends Section {
private final Level myRoot = new Level(0);
private final Bin.DWord myStartOffset;
private final Bin.DWord mySectionVAtoRawOffset;
public ResourceSectionReader( ImageSectionHeader sectionHeader, Bin.Value startOffset, Bin.Value mainSectionsOffset, ImageOptionalHeader imageOptionalHeader) {
super(((Bin.Txt)sectionHeader.getMember("Name")).getText());
myStartOffset = startOffset;
mySize = sectionHeader.getValueMember( "SizeOfRawData" );
Bin.Value virtualSize = sectionHeader.getValueMember( "VirtualSize" );
myMainSectionsOffset = mainSectionsOffset;
addOffsetHolder(startOffset);
myFileAlignment = imageOptionalHeader.getValue("FileAlignment");
public ResourceSectionReader(ImageSectionHeader header, ImageOptionalHeader imageOptionalHeader) {
super(header, header.getSectionName());
myStartOffset = header.getPointerToRawData();
mySectionVAtoRawOffset = new ValuesAdd(header.getVirtualAddress(), myStartOffset);
//noinspection unchecked
ArrayOfBins<ImageDataDirectory> imageDataDirs = (ArrayOfBins<ImageDataDirectory>)imageOptionalHeader.getMember( "ImageDataDirectories" );
Bin[] bins = imageDataDirs.getArray();
ImageDataDirectory imageDataDirectory = (ImageDataDirectory)bins[2];
Value size = imageDataDirectory.getValueMember("Size");
addSizeHolder(mySize);
addSizeHolder(virtualSize);
addSizeHolder(size);
myRoot.addLevelEntry(new DirectoryEntry( this, null, 0));
myRoot.addLevelEntry(new DirectoryEntry(this, null, 0));
addMember(myRoot);
addMember(new Padding("FileAlignment in ResourceSection", (int)imageOptionalHeader.getFileAlignment().getValue()));
addSizeHolder(header.getVirtualSize());
}
public DirectoryEntry getRoot(){
return (DirectoryEntry)myRoot.getMember( "IRD" );
public DirectoryEntry getRoot() {
Iterator<Bin> iterator = myRoot.getMembers().iterator();
DirectoryEntry entry = (DirectoryEntry)iterator.next();
assert !iterator.hasNext();
return entry;
}
public Value getStartOffset() {
public DWord getStartOffset() {
return myStartOffset;
}
public Value getMainSectionsOffset() {
return myMainSectionsOffset;
}
@Override
public long sizeInBytes() {
long size = super.sizeInBytes() + myBytes.sizeInBytes();
if (size % myFileAlignment != 0)
size = (size / myFileAlignment + 1) * myFileAlignment;
return size;
}
@Override
public void read(DataInput stream) throws IOException {
super.read(stream);
DWord size = new DWord("size");
size.setValue((mySize.getValue() - myRoot.sizeInBytes()));
DWord startOffset = new DWord("startOffset");
startOffset.setValue((myStartOffset.getValue() + myRoot.sizeInBytes()));
myBytes = new Bin.Bytes("Raw data", startOffset, size);
myBytes.read(stream);
}
@Override
public void write(DataOutput stream) throws IOException {
super.write(stream);
myBytes.write(stream);
long paddingSize = sizeInBytes() - super.sizeInBytes() - myBytes.sizeInBytes();
if (paddingSize != 0)
stream.write(new byte[(int)paddingSize]);
}
@Override
public void report(OutputStreamWriter writer) throws IOException {
super.report(writer);
myBytes.report(writer);
public DWord getSectionVAtoRawOffset() {
return mySectionVAtoRawOffset;
}
}

View File

@@ -17,7 +17,7 @@
package com.pme.exe.res;
import com.pme.exe.Bin;
import com.pme.exe.Bin.WCharStringSP;
import java.io.*;
@@ -27,41 +27,30 @@ import java.io.*;
* Time: 12:43:28 PM
*/
public class StringTable {
final String[] strings = new String[16];
final WCharStringSP[] strings = new WCharStringSP[16];
public StringTable(byte[] bytes) throws IOException {
ByteArrayInputStream bytesStream = new ByteArrayInputStream(bytes);
DataInputStream stream = new DataInputStream(bytesStream);
Bin.Word count = new Bin.Word();
for (int i = 0; i < 16; ++i) {
count.read(stream);
if ( count.getValue() != 0 ){
Bin.Txt txt = new Bin.Txt("", (int)(count.getValue() * 2));
txt.read( stream );
strings[i] = txt.getText();
} else {
strings[i] = "";
}
strings[i] = new WCharStringSP();
strings[i].read(stream);
}
}
public void setString( int index, String string ){
strings[index] = string;
strings[index].setValue(string);
}
public byte[] getBytes() throws IOException {
int size = 0;
for (String string : strings) {
size += 2 + string.length() * 2;
for (WCharStringSP string : strings) {
size += string.sizeInBytes();
}
ByteArrayOutputStream bytesStream = new ByteArrayOutputStream(size);
DataOutputStream stream = new DataOutputStream(bytesStream);
for (String string : strings) {
int count = string.length();
new Bin.Word().setValue(count).write(stream);
if (count != 0) {
new Bin.Txt("", string).write(stream);
}
for (WCharStringSP string : strings) {
string.write(stream);
}
return bytesStream.toByteArray();
}

View File

@@ -20,24 +20,20 @@ public class StringTableDirectory {
for (DirectoryEntry subDir : directoryEntry.getSubDirs()) {
Entry e = new Entry();
e.startID = (int) subDir.getIdOrName();
e.resource = subDir.getRawResource(0);
e.table = new StringTable(e.resource.getBytes().getBytes());
e.resource = subDir.getRawResource();
e.table = new StringTable(e.resource.getBytes());
myEntries.add(e);
}
}
public void setString(int id, String value) {
boolean found = false;
for (Entry entry : myEntries) {
if (entry.startID == (id / 16)+1) {
entry.table.setString(id % 16, value);
found = true;
break;
return;
}
}
if (!found) {
throw new IllegalArgumentException("Cannot find string entry with ID " + id);
}
throw new IllegalArgumentException("Cannot find string entry with ID " + id);
}
public void save() throws IOException {

View File

@@ -35,8 +35,9 @@ public class ValuesAdd extends Bin.DWord {
}
@Override
public Value setValue(long value) {
return myActual.setValue(value + myMinus.getValue());
public DWord setValue(long value) {
myActual.setValue(value + myMinus.getValue());
return this;
}
@Override

View File

@@ -35,8 +35,9 @@ public class ValuesSub extends Bin.DWord {
}
@Override
public Value setValue(long value) {
return myActual.setValue(value - myMinus.getValue());
public DWord setValue(long value) {
myActual.setValue(value - myMinus.getValue());
return this;
}
@Override

View File

@@ -18,8 +18,6 @@
package com.pme.exe.res.icon;
import com.pme.exe.Bin;
import com.pme.exe.res.Level;
import com.pme.exe.res.LevelEntry;
/**
* @author Sergey Zhulin
@@ -27,17 +25,23 @@ import com.pme.exe.res.LevelEntry;
* Time: 11:35:48 AM
*/
public class GroupIconResource extends Bin.Structure {
public GroupIconResource( Bin.Value idCount ) {
private final ArrayOfBins<GroupIconResourceDirectory> myIcons;
private final IconHeader myHeader;
public GroupIconResource() {
super("GroupIcon");
addMember(new IconHeader());
Level level = new Level();
ArrayOfBins<GroupIconResourceDirectory> arrayOfBins = new ArrayOfBins<>("Icon directories", GroupIconResourceDirectory.class, idCount);
myHeader = addMember(new IconHeader());
Word count = myHeader.getCount();
myIcons = addMember(new ArrayOfBins<>("Icon directories", GroupIconResourceDirectory.class, count));
myIcons.setCountHolder(count);
}
addMember(level);
Bin[] array = arrayOfBins.getArray();
for (Bin bin : array) {
level.addLevelEntry((LevelEntry) bin);
}
public IconHeader getHeader() {
return myHeader;
}
public ArrayOfBins<GroupIconResourceDirectory> getIcons() {
return myIcons;
}
}

View File

@@ -1,6 +1,6 @@
/*
* Copyright 2006 ProductiveMe Inc.
* Copyright 2013-2018 JetBrains s.r.o.
* Copyright 2013-2022 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,23 +17,20 @@
package com.pme.exe.res.icon;
import com.pme.exe.res.LevelEntry;
/**
* @author Sergey Zhulin
* Date: Apr 28, 2006
* Time: 11:38:20 AM
*/
public class GroupIconResourceDirectory extends LevelEntry {
public class GroupIconResourceDirectory extends IconBase {
private final Word myDwId;
public GroupIconResourceDirectory() {
super("Icon Directory");
addMember( new Byte( "bWidth" ) );
addMember( new Byte( "bHeight" ) );
addMember( new Byte( "bColorCount" ) );
addMember( new Byte( "bReserved" ) );
addMember( new Word( "wPlanes" ) );
addMember( new Word( "wBitCount" ) );
addMember( new DWord( "dwBytesInRes" ) );
addMember( new Word( "dwId" ) );
myDwId = addMember(new Word("dwId" ) );
}
public Word getDwId() {
return myDwId;
}
}

View File

@@ -1,6 +1,6 @@
/*
* Copyright 2006 ProductiveMe Inc.
* Copyright 2013-2018 JetBrains s.r.o.
* Copyright 2013-2022 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,19 +18,19 @@
package com.pme.exe.res.icon;
import com.pme.exe.res.LevelEntry;
import com.pme.exe.Bin;
/**
* @author Sergey Zhulin
* Date: Apr 27, 2006
* Time: 1:44:11 PM
*/
public class RawBytes extends LevelEntry {
public RawBytes( Bin.Value offsetHolder, Bin.Value size ) {
super("Raw Bytes");
Bytes bytes = new Bytes("Raw Bytes", offsetHolder, size);
bytes.addOffsetHolder( offsetHolder );
bytes.addSizeHolder( size );
addMember( bytes );
public abstract class IconBase extends LevelEntry {
protected final DWord myDwBytesInRes;
public IconBase(String name) {
super(name);
addMember(new Byte("bWidth"));
addMember(new Byte("bHeight"));
addMember(new Byte("bColorCount"));
addMember(new Byte("bReserved"));
addMember(new Word("wPlanes"));
addMember(new Word("wBitCount"));
myDwBytesInRes = addMember(new DWord("dwBytesInRes"));
}
}

View File

@@ -17,41 +17,23 @@
package com.pme.exe.res.icon;
import com.pme.exe.res.LevelEntry;
import java.io.DataInput;
import java.io.IOException;
/**
* @author Sergey Zhulin
* Date: Apr 27, 2006
* Time: 1:13:15 PM
*/
public class IconDirectory extends LevelEntry {
private RawBytes myRawBytes;
public class IconDirectory extends IconBase {
private final Bytes myBytes;
public IconDirectory() {
super("Icon Directory");
addMember( new Byte( "bWidth" ) );
addMember( new Byte( "bHeight" ) );
addMember( new Byte( "bColorCount" ) );
addMember( new Byte( "bReserved" ) );
addMember( new Word( "wPlanes" ) );
addMember( new Word( "wBitCount" ) );
addMember( new DWord( "dwBytesInRes" ) );
addMember( new DWord( "dwImageOffset" ) );
DWord dwImageOffset = addMember(new DWord("dwImageOffset"));
myBytes = new Bytes("Raw Bytes internal", dwImageOffset, myDwBytesInRes);
myBytes.addOffsetHolder(dwImageOffset);
myBytes.addSizeHolder(myDwBytesInRes);
}
public byte[] getRawBytes(){
Bytes bytes = (Bytes)myRawBytes.getMember( "Raw Bytes" );
return bytes.getBytes();
}
@Override
public void read(DataInput stream) throws IOException {
super.read(stream);
RawBytes bytes = new RawBytes( getValueMember( "dwImageOffset" ),
getValueMember( "dwBytesInRes" ));
myRawBytes = bytes;
getLevel().addLevelEntry( bytes );
public Bytes getBytes() {
return myBytes;
}
}

View File

@@ -18,8 +18,6 @@
package com.pme.exe.res.icon;
import com.pme.exe.Bin;
import com.pme.exe.res.Level;
import com.pme.exe.res.LevelEntry;
import java.io.*;
@@ -29,43 +27,54 @@ import java.io.*;
* Time: 12:52:09 PM
*/
public class IconFile extends Bin.Structure {
private final Level myImages = new Level();
private final File myFile;
private final IconHeader myHeader;
private final ArrayOfBins<IconDirectory> myIcons;
public static class IconWrongFormat extends IOException {
public IconWrongFormat( File file ) {
public IconWrongFormat(File file) {
super("Icon file has wrong format:" + file.getPath());
}
}
public IconFile(File file) {
super(file.getName());
myFile = file;
addMember(new IconHeader());
public IconFile() {
super("IconFile");
myHeader = addMember(new IconHeader());
Word idCount = myHeader.getCount();
myIcons = addMember(new ArrayOfBins<>("Icon directories", IconDirectory.class, idCount));
myIcons.setCountHolder(idCount);
}
public void read() throws IOException {
try (RandomAccessFile stream = new RandomAccessFile(myFile, "r")) {
public IconHeader getHeader() {
return myHeader;
}
public ArrayOfBins<IconDirectory> getIcons() {
return myIcons;
}
public void read(File file) throws IOException {
setName(file.getName());
try (RandomAccessFile stream = new RandomAccessFile(file, "r")) {
read(stream);
}
catch (IOException exception) {
throw new IconWrongFormat(file);
}
}
@Override
public void read(DataInput stream) throws IOException {
try {
super.read(stream);
Word idCount = (Word) ((Bin.Structure) getMember("Header")).getMember("idCount");
ArrayOfBins<IconDirectory> iconDirs = new ArrayOfBins<>("Icon directories", IconDirectory.class, idCount);
iconDirs.setCountHolder(idCount);
addMember(myImages);
Bin[] array = iconDirs.getArray();
for (Bin bin : array) {
myImages.addLevelEntry((LevelEntry) bin);
}
myImages.read(stream);
super.read(stream);
for (IconDirectory icon : myIcons) {
icon.getBytes().read(stream);
}
catch (IOException exception) {
throw new IconWrongFormat(myFile);
}
@Override
public void write(DataOutput stream) throws IOException {
super.write(stream);
for (IconDirectory icon : myIcons) {
icon.getBytes().write(stream);
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* Copyright 2006 ProductiveMe Inc.
* Copyright 2013-2018 JetBrains s.r.o.
* Copyright 2013-2022 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,10 +25,16 @@ import com.pme.exe.Bin;
* Time: 11:30:10 AM
*/
public class IconHeader extends Bin.Structure{
private final Word myCount;
public IconHeader() {
super("Header");
super("Icon Header");
addMember(new Word("idReserved"));
addMember(new Word("idType"));
addMember(new Word("idCount"));
myCount = addMember(new Word("idCount"));
}
public Word getCount() {
return myCount;
}
}

View File

@@ -19,12 +19,11 @@ package com.pme.exe.res.icon;
import com.pme.exe.Bin;
import com.pme.exe.res.*;
import com.pme.util.OffsetTrackingInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.io.*;
import java.util.HashSet;
import java.util.Set;
/**
* @author Sergey Zhulin
@@ -33,70 +32,109 @@ import java.util.ArrayList;
*/
public class IconResourceInjector {
public void injectIcon(File file, DirectoryEntry root, String iconId) throws IOException {
IconFile iconFile = new IconFile(file);
iconFile.read();
public static void injectIcon(InputStream stream, DirectoryEntry root, int iconId) throws IOException {
IconFile iconFile = new IconFile();
iconFile.read(new OffsetTrackingInputStream(new DataInputStream(stream)));
//updateGroupIcon(root, iconId, iconFile);
long languageId = getLanguage(root);
DirectoryEntry iconsDir = root.findSubDir("IRD3");
Level iconFileLevel = (Level) iconFile.getMember("Level");
ArrayList<Bin> icons = iconFileLevel.getMembers();
if (icons.size() == iconsDir.getSubDirs().size()) {
for (int i = 0; i < icons.size(); i++) {
DirectoryEntry subDirIcon = iconsDir.findSubDir("IRD" + (i+1));
IconDirectory iconDirectory = (IconDirectory) iconFileLevel.getMembers().get(i);
RawResource rawResource = subDirIcon.getRawResource(0);
rawResource.setBytes(iconDirectory.getRawBytes());
DirectoryEntry iconsDir = root.findSubDir(DirectoryEntry.RT_ICON);
Bin.ArrayOfBins<IconDirectory> sourceIcons = iconFile.getIcons();
if (sourceIcons.size() < iconsDir.getSubDirs().size()) {
throw new IOException(
String.format("Number of icons in template file '%d' is larger than in provided icon file '%d'",
iconsDir.getSubDirs().size(), sourceIcons.size()));
}
updateGroupIcon(root, iconId, iconFile);
for (int i = 0; i < sourceIcons.size(); i++) {
IconDirectory iconDirectory = sourceIcons.get(i);
DirectoryEntry subDirIcon = iconsDir.findSubDir(i + 1);
if (subDirIcon == null) {
subDirIcon = insertIcon(iconsDir, i+1, languageId);
}
subDirIcon.getRawResource().setBytes(iconDirectory.getBytes().getBytes());
}
}
private static long getLanguage(DirectoryEntry root) {
// Language ID is Name of third level entries
Set<Long> languages = new HashSet<>();
// First search across icons only
{
DirectoryEntry first = root.findSubDir(DirectoryEntry.RT_ICON);
for (DirectoryEntry second : first.getSubDirs()) {
for (DataEntry third : second.getData()) {
languages.add(third.getLanguage().getValue());
}
}
}
else {
throw new IOException("Count of icons in template file doesn't match the count of icons in provided icon file");
if (languages.size() == 1) {
return languages.iterator().next();
}
// Then across whole file
for (DirectoryEntry first : root.getSubDirs()) {
for (DirectoryEntry second : first.getSubDirs()) {
for (DataEntry third : second.getData()) {
languages.add(third.getLanguage().getValue());
}
}
}
if (languages.size() != 1) {
throw new IllegalStateException("There should be only one language in resources, but found: " + languages);
}
return languages.iterator().next();
}
private static void updateGroupIcon(DirectoryEntry root, String iconId, IconFile iconFile) throws IOException {
DirectoryEntry subDirGroupIcon = root.findSubDir("IRD14").findSubDir(iconId);
RawResource groupIcon = subDirGroupIcon.getRawResource(0);
private static void updateGroupIcon(DirectoryEntry root, int iconId, IconFile iconFile) throws IOException {
DirectoryEntry subDirGroupIcon = root.findSubDir(DirectoryEntry.RT_GROUP_ICON).findSubDir(iconId);
RawResource groupIcon = subDirGroupIcon.getRawResource();
Bin.Value idCount = iconFile.getStructureMember("Header").getValueMember("idCount");
GroupIconResource groupIconResource = new GroupIconResource(idCount);
groupIconResource.copyFrom(iconFile);
Level level = (Level) groupIconResource.getMember("Level");
ArrayList<Bin> directories = level.getMembers();
for (int i = 0; i < directories.size(); ++i) {
GroupIconResourceDirectory grpDir = (GroupIconResourceDirectory) directories.get(i);
grpDir.getValueMember("dwId").setValue(i + 1);
GroupIconResource gir = new GroupIconResource();
gir.getHeader().copyFrom(iconFile.getHeader());
Bin.ArrayOfBins<IconDirectory> sourceIcons = iconFile.getIcons();
for (int i = 0; i < sourceIcons.size(); i++) {
IconDirectory icon = sourceIcons.get(i);
GroupIconResourceDirectory bin = new GroupIconResourceDirectory();
bin.copyFrom(icon);
bin.getDwId().setValue(i + 1);
gir.getIcons().addBin(bin);
}
long size = groupIconResource.sizeInBytes();
ByteArrayOutputStream bytesStream = new ByteArrayOutputStream((int) size);
long size = gir.sizeInBytes();
ByteArrayOutputStream bytesStream = new ByteArrayOutputStream((int)size);
DataOutputStream stream = new DataOutputStream(bytesStream);
groupIconResource.write(stream);
groupIcon.getBytes().setBytes(bytesStream.toByteArray());
gir.write(stream);
groupIcon.setBytes(bytesStream.toByteArray());
}
private static void insertIcon(DirectoryEntry iconsDir, IconDirectory iconDirectory, int index) {
EntryDescription entryDescription = new EntryDescription();
Bin.Value name = entryDescription.getValueMember("Name");
name.setValue(index + 1);
private static DirectoryEntry insertIcon(DirectoryEntry level1, int index, long languageId) {
// We should construct and correctly add second and third level entries to a resource
// Second level contains ID of icon
// Third level contains languageId and raw data
DirectoryEntry entryDirIcon2 = new DirectoryEntry(iconsDir.getSection(), entryDescription, name.getValue());
iconsDir.insertDirectoryEntry(index, entryDirIcon2);
iconsDir.addIdEntry(entryDescription);
ResourceSectionReader section = level1.getSection();
EntryDescription entry409 = new EntryDescription();
Bin.Value name409 = entry409.getValueMember("Name");
name409.setValue(0x409);
EntryDescription description2 = new EntryDescription();
description2.getNameW().setValue(index);
DirectoryEntry level2 = new DirectoryEntry(section, description2, index);
entryDirIcon2.addIdEntry(entry409);
level1.addDirectoryEntry(level2);
level1.addIdEntry(description2);
Bin.Value offset = entry409.getValueMember("OffsetToData");
DataEntry dataEntry = new DataEntry(entryDirIcon2.getSection(), offset);
EntryDescription description3 = new EntryDescription();
description3.getNameW().setValue(languageId);
Bin.DWord offset = description3.getOffsetToData();
DataEntry level3 = new DataEntry(section, offset, description3.getNameW());
entryDirIcon2.insertDataEntry(index, dataEntry);
dataEntry.insertRawData(index);
RawResource rawRes = dataEntry.getRawResource();
rawRes.setBytes(iconDirectory.getRawBytes());
level2.addIdEntry(description3);
level2.addDataEntry(level3);
level3.initRawData();
return level2;
}
}

View File

@@ -1,6 +1,6 @@
/*
* Copyright 2006 ProductiveMe Inc.
* Copyright 2013-2018 JetBrains s.r.o.
* Copyright 2013-2022 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,19 +28,21 @@ import java.io.IOException;
* Time: 1:35:49 PM
*/
public class FixedFileInfo extends Bin.Structure {
public static final String FILE_VERSION_MS = "dwFileVersionMS";
public static final String FILE_VERSION_LS = "dwFileVersionLS";
public static final String PRODUCT_VERSION_MS = "dwProductVersionMS";
public static final String PRODUCT_VERSION_LS = "dwProductVersionLS";
public static final long MAGIC = 0xFEEF04BDL;
private final DWord mySignature;
private final DWord myFileVersionMS;
private final DWord myFileVersionLS;
private final DWord myProductVersionMS;
private final DWord myProductVersionLS;
public FixedFileInfo() {
super("FixedFileInfo");
addMember( new DWord( "dwSignature" ) );
mySignature = addMember(new DWord("dwSignature"));
addMember( new DWord( "dwStrucVersion" ) );
addMember( new DWord(FILE_VERSION_MS) );
addMember( new DWord(FILE_VERSION_LS) );
addMember( new DWord(PRODUCT_VERSION_MS) );
addMember( new DWord(PRODUCT_VERSION_LS) );
myFileVersionMS = addMember(new DWord("dwFileVersionMS"));
myFileVersionLS = addMember(new DWord("dwFileVersionLS"));
myProductVersionMS = addMember(new DWord("dwProductVersionMS"));
myProductVersionLS = addMember(new DWord("dwProductVersionLS"));
addMember( new DWord( "dwFileFlagsMask" ) );
addMember( new DWord( "dwFileFlags" ) );
addMember( new DWord( "dwFileOS" ) );
@@ -53,17 +55,19 @@ public class FixedFileInfo extends Bin.Structure {
@Override
public void read(DataInput stream) throws IOException {
super.read(stream);
long signature = getValue("dwSignature");
assert signature == 0xFEEF04BDL : "Incorrect signature; expected " + 0xFEEF04BDL + ", found " + signature;
long signature = mySignature.getValue();
if (signature != MAGIC) {
throw new IllegalStateException(String.format("Incorrect signature; expected %#010x, found %#010x", MAGIC, signature));
}
}
public void setFileVersion(int mostSignificantVersion, int leastSignificantVersion) {
((DWord) getMember(FILE_VERSION_MS)).setValue(mostSignificantVersion);
((DWord) getMember(FILE_VERSION_LS)).setValue(leastSignificantVersion);
myFileVersionMS.setValue(mostSignificantVersion);
myFileVersionLS.setValue(leastSignificantVersion);
}
public void setProductVersion(int mostSignificantVersion, int leastSignificantVersion) {
((DWord) getMember(PRODUCT_VERSION_MS)).setValue(mostSignificantVersion);
((DWord) getMember(PRODUCT_VERSION_LS)).setValue(leastSignificantVersion);
myProductVersionMS.setValue(mostSignificantVersion);
myProductVersionLS.setValue(leastSignificantVersion);
}
}

View File

@@ -1,6 +1,6 @@
/*
* Copyright 2006 ProductiveMe Inc.
* Copyright 2013-2018 JetBrains s.r.o.
* Copyright 2013-2022 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,10 @@
package com.pme.exe.res.vi;
import com.pme.exe.Bin;
import java.util.Optional;
/**
* @author Sergey Zhulin
* Date: May 10, 2006
@@ -32,7 +36,15 @@ public class StringFileInfo extends VersionInfoBin {
});
}
public StringTable getFirstStringTable() {
return (StringTable) getMember("StringTable0");
public StringTable getSoleStringTable() {
long count = getMembers().stream().filter(bin -> bin instanceof StringTable).count();
if (count > 1) {
throw new IllegalStateException("More than one StringTable found, indicates that there's more than one lanugage in executable");
}
Optional<Bin> optional = getMembers().stream().filter(bin -> bin instanceof StringTable).findFirst();
if (optional.isEmpty()) {
throw new IllegalStateException("No StringTable's found");
}
return (StringTable)optional.get();
}
}

View File

@@ -1,6 +1,6 @@
/*
* Copyright 2006 ProductiveMe Inc.
* Copyright 2013 JetBrains s.r.o.
* Copyright 2013-2022 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,11 @@ package com.pme.exe.res.vi;
import com.pme.exe.Bin;
/**
* <a href="https://learn.microsoft.com/en-us/windows/win32/menurc/stringtable">StringTable structure</a>
* <p>
* Each StringTable structure's szKey member indicates the appropriate language and code page for displaying the text in that StringTable structure.
*/
public class StringTable extends VersionInfoBin {
public StringTable(String name) {
@@ -30,15 +35,21 @@ public class StringTable extends VersionInfoBin {
});
}
public String getStringValue(String key) {
for (Bin bin : getMembers()) {
if (bin instanceof StringTableEntry entry && bin.getName().equals(key)) {
return entry.getValue();
}
}
throw new IllegalStateException("Could not find string with key: " + key);
}
public void setStringValue(String key, String value) {
for (Bin bin : getMembers()) {
if (bin.getName().equals(key)) {
StringTableEntry entry = (StringTableEntry) bin;
((WChar) entry.getMember("Value")).setValue(value);
((Word) entry.getMember("wValueLength")).setValue(value.length() + 1);
if (bin instanceof StringTableEntry entry && bin.getName().equals(key)) {
entry.setValue(value);
return;
}
}
assert false: "Could not find string with key " + key;
throw new IllegalStateException("Could not find string with key: " + key);
}
}

View File

@@ -1,6 +1,6 @@
/*
* Copyright 2006 ProductiveMe Inc.
* Copyright 2013-2018 JetBrains s.r.o.
* Copyright 2013-2022 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,28 +21,40 @@ import java.io.DataInput;
import java.io.IOException;
/**
* <a href="https://learn.microsoft.com/en-us/windows/win32/menurc/string-str">String stucture</a>
*
* @author Sergey Zhulin
* Date: May 10, 2006
* Time: 8:26:03 PM
*/
public class StringTableEntry extends VersionInfoBin {
private final WCharStringNT myValue;
public StringTableEntry() {
super("<unnamed>");
addMember(new WChar("Value"));
addMember(new Padding(4));
myValue = addMember(new WCharStringNT("Value"));
addMember(new Padding("Padding2", 4));
}
public String getValue() {
return myValue.getValue();
}
public void setValue(String value) {
myValue.setValue(value);
// myValueLength has size in words, not bytes
myValueLength.setValue(value.length() + 1);
}
@Override
public void read(DataInput stream) throws IOException {
super.read(stream);
WChar key = (WChar) getMember("szKey");
setName(key.getValue());
setName(myKey.getValue());
assert myValue.sizeInBytes() == myValueLength.getValue() * 2;
}
@Override
public String toString() {
WChar key = (WChar) getMember("szKey");
WChar value = (WChar) getMember("Value");
return key.getValue() + " = " + value.getValue();
return myKey.getValue() + " = " + myValue.getValue();
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.pme.exe.res.vi;
@@ -6,6 +6,13 @@ package com.pme.exe.res.vi;
public class Var extends VersionInfoBin {
public Var(String name) {
super(name, "Translation");
addMember(new DWord("Translation"));
addMember(new ArrayOfBins<>("Translation", DWord.class, new ReadOnlyValue("ValueLength/4") {
@Override
public long getValue() {
long size = myValueLength.getValue();
assert size % 4 == 0;
return size / 4; // DWord size in bytes
}
}));
}
}

View File

@@ -1,6 +1,6 @@
/*
* Copyright 2006 ProductiveMe Inc.
* Copyright 2013-2018 JetBrains s.r.o.
* Copyright 2013-2022 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,7 @@
package com.pme.exe.res.vi;
import com.pme.util.OffsetTrackingInputStream;
import com.pme.util.StreamUtil;
import java.io.DataInput;
import java.io.IOException;
@@ -28,26 +28,20 @@ public class VersionInfo extends VersionInfoBin {
public VersionInfo() {
super("VersionInfo", "VS_VERSION_INFO");
myFixedFileInfo = new FixedFileInfo();
addMember(myFixedFileInfo);
addMember(new Padding(4));
myStringFileInfo = new StringFileInfo();
addMember(myStringFileInfo);
myFixedFileInfo = addMember(new FixedFileInfo());
addMember(new Padding("Padding2", 4));
myStringFileInfo = addMember(new StringFileInfo());
addMember(new VarFileInfo());
}
@Override
public void read(DataInput stream) throws IOException {
long startOffset = -1;
if (stream instanceof OffsetTrackingInputStream) {
startOffset = ((OffsetTrackingInputStream) stream).getOffset();
}
long startOffset = StreamUtil.getOffset(stream);
super.read(stream);
if (stream instanceof OffsetTrackingInputStream) {
long offset = ((OffsetTrackingInputStream) stream).getOffset();
long length = getValue("wLength");
assert startOffset + length == offset: "Length specified in version info header (" + length +
") does not match actual version info length (" + (offset - startOffset) + ")";
long offset = StreamUtil.getOffset(stream);
long length = myLength.getValue();
if (startOffset + length != offset) {
throw new IOException(String.format("Length specified in version info header %#4x does not match actual version info length %#4x", length, (offset - startOffset)));
}
}

View File

@@ -1,29 +1,30 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.pme.exe.res.vi;
import com.pme.exe.Bin;
import com.pme.util.OffsetTrackingInputStream;
import com.pme.util.StreamUtil;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.RandomAccessFile;
public class VersionInfoBin extends Bin.Structure {
public abstract class VersionInfoBin extends Bin.Structure {
private String myExpectedName;
private VersionInfoFactory myChildFactory;
protected final Word myLength;
protected final WCharStringNT myKey;
protected final Word myValueLength;
public VersionInfoBin(String name) {
super(name);
Word length = new Word("wLength");
addMember(length);
addSizeHolder(length);
addMember(new Word("wValueLength"));
myLength = addMember(new Word("wLength"));
addSizeHolder(myLength);
myValueLength = addMember(new Word("wValueLength"));
addMember(new Word("wType"));
addMember(new WChar("szKey"));
addMember(new Padding(4));
myKey = addMember(new WCharStringNT("szKey"));
addMember(new Padding("Padding", 4));
}
public VersionInfoBin(String versionInfo, String expectedName) {
@@ -38,20 +39,21 @@ public class VersionInfoBin extends Bin.Structure {
@Override
public void read(DataInput stream) throws IOException {
OffsetTrackingInputStream inputStream = (OffsetTrackingInputStream) stream;
long startOffset = inputStream.getOffset();
long startOffset = StreamUtil.getOffset(stream);
assert startOffset % 4 == 0;
super.read(stream);
if (myExpectedName != null) {
String signature = ((WChar) getMember("szKey")).getValue();
assert signature.equals(myExpectedName): "Expected signature " + myExpectedName + ", found '" + signature + "'";
String signature = myKey.getValue();
if (!signature.equals(myExpectedName)) {
throw new IllegalStateException("Expected signature '" + myExpectedName + "', found '" + signature + "'");
}
}
if (myChildFactory != null) {
long length = getValue("wLength");
long length = myLength.getValue();
int i = 0;
while(inputStream.getOffset() < startOffset + length) {
while (StreamUtil.getOffset(stream) < startOffset + length) {
VersionInfoBin child = myChildFactory.createChild(i++);
child.read(inputStream);
child.read(stream);
addMember(child);
}
}
@@ -59,18 +61,12 @@ public class VersionInfoBin extends Bin.Structure {
@Override
public void write(DataOutput stream) throws IOException {
long startOffset = -1;
if (stream instanceof RandomAccessFile) {
startOffset = ((RandomAccessFile) stream).getFilePointer();
assert startOffset % 4 == 0;
}
long startOffset = StreamUtil.getOffset(stream);
super.write(stream);
if (stream instanceof RandomAccessFile) {
long offset = ((RandomAccessFile) stream).getFilePointer();
long realLength = offset - startOffset;
long expectedLength = getValue("wLength");
assert realLength == expectedLength: "Actual length does not match calculated length for " + getName() +
": expected " + expectedLength + ", actual " + realLength + ", sizeInBytes() " + sizeInBytes();
}
long offset = StreamUtil.getOffset(stream);
long realLength = offset - startOffset;
long expectedLength = myLength.getValue();
assert realLength == expectedLength : "Actual length does not match calculated length for " + getName() +
": expected " + expectedLength + ", actual " + realLength + ", sizeInBytes() " + sizeInBytes();
}
}

View File

@@ -17,9 +17,7 @@
package com.pme.launcher;
import com.pme.exe.ExeFormat;
import com.pme.exe.ExeReader;
import com.pme.exe.SectionReader;
import com.pme.exe.*;
import com.pme.exe.res.DirectoryEntry;
import com.pme.exe.res.RawResource;
import com.pme.exe.res.ResourceSectionReader;
@@ -31,8 +29,11 @@ import com.pme.util.OffsetTrackingInputStream;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static java.lang.String.format;
/**
* @author Sergey Zhulin
@@ -54,28 +55,22 @@ public class LauncherGenerator {
public void load() throws IOException {
RandomAccessFile stream = new RandomAccessFile(myTemplate, "r");
ExeReader formatReader = new ExeReader(myTemplate.getName(), ExeFormat.UNKNOWN);
formatReader.read(stream);
stream.seek(0L);
myReader = new ExeReader(myTemplate.getName(), formatReader.getExeFormat());
myReader = new ExeReader(myTemplate.getName());
myReader.read(stream);
stream.close();
SectionReader sectionReader = myReader.getSectionReader(".rsrc");
ResourceSectionReader resourceReader = (ResourceSectionReader) sectionReader.getMember(".rsrc");
myRoot = resourceReader.getRoot();
DirectoryEntry subDir = myRoot.findSubDir("IRD6");
ResourceSectionReader resourceSection = (ResourceSectionReader)myReader.getSectionReader(Section.RESOURCES_SECTION_NAME);
myRoot = resourceSection.getRoot();
DirectoryEntry subDir = myRoot.findSubDir(DirectoryEntry.RT_STRING);
myStringTableDirectory = new StringTableDirectory(subDir);
RawResource versionInfoResource = getVersionInfoResource();
ByteArrayInputStream bytesStream = new ByteArrayInputStream(versionInfoResource.getBytes().getBytes());
myVersionInfo = new VersionInfo();
myVersionInfo.read(new OffsetTrackingInputStream(new DataInputStream(bytesStream)));
myVersionInfo.read(new OffsetTrackingInputStream(new DataInputStream(new ByteArrayInputStream(versionInfoResource.getBytes()))));
}
private RawResource getVersionInfoResource() {
DirectoryEntry viDir = myRoot.findSubDir("IRD16").findSubDir( "IRD1" );
return viDir.getRawResource(0);
DirectoryEntry viDir = myRoot.findSubDir(DirectoryEntry.RT_VERSION).findSubDir(1);
return viDir.getRawResource();
}
private void saveVersionInfo() throws IOException {
@@ -92,24 +87,137 @@ public class LauncherGenerator {
myReader.resetOffsets(0);
myReader.sectionVirtualAddressFixup();
if (myExePath.exists()) {
//noinspection ResultOfMethodCallIgnored
myExePath.delete();
}
//noinspection ResultOfMethodCallIgnored
myExePath.getParentFile().mkdirs();
//noinspection ResultOfMethodCallIgnored
myExePath.createNewFile();
RandomAccessFile exeStream = new RandomAccessFile(myExePath, "rw");
myReader.write(exeStream);
exeStream.close();
// verifyVersionInfo();
verifySize();
verifySections();
verifyVersionInfo();
}
private void verifySize() throws IOException {
long fileSize = Files.size(myExePath.toPath());
long exeSize = myReader.sizeInBytes();
if (fileSize != exeSize) {
throw new RuntimeException(format("Produced file size mismatch, on disk: %d, in memory %d", fileSize, exeSize));
}
}
private void verifySections() {
ImageOptionalHeader imageOptionalHeader = myReader.getPeHeader().getImageOptionalHeader();
final int FileAlignment = (int)imageOptionalHeader.getFileAlignment().getValue();
final int SectionAlignment = (int)imageOptionalHeader.getSectionAlignment().getValue();
List<String> errors = new ArrayList<>();
Bin.ArrayOfBins<ImageSectionHeader> sections = myReader.getSectionHeaders();
for (ImageSectionHeader header : sections) {
String name = header.getSectionName();
long sizeOfRawData = header.getSizeOfRawData().getValue();
long pointerToRawData = header.getPointerToRawData().getValue();
long virtualAddress = header.getVirtualAddress().getValue();
long virtualSize = header.getVirtualSize().getValue();
if (pointerToRawData == 0) {
errors.add(format("Section '%s' may not have zero PointerToRawData", name));
}
if (virtualAddress == 0) {
errors.add(format("Section '%s' may not have zero VirtualAddress", name));
}
if (sizeOfRawData % FileAlignment != 0) {
errors.add(
format("SizeOfRawData of section '%s' isn't dividable by 'FileAlignment' (%#x): %#x", name, FileAlignment, sizeOfRawData));
}
if (pointerToRawData % FileAlignment != 0) {
errors.add(
format("PointerToRawData of section '%s' isn't dividable by 'FileAlignment' (%#x): %#x", name, FileAlignment, pointerToRawData));
}
if (virtualAddress % SectionAlignment != 0) {
errors.add(
format("VirtualAddress of section '%s' isn't dividable by 'SectionAlignment' (%#x): %#x", name, SectionAlignment,
virtualAddress));
}
if (name.equals(Section.RESOURCES_SECTION_NAME) && virtualSize < sizeOfRawData) {
errors.add(
format("VirtualSize of section '%s' is smaller than SizeOfRawData (%#x): %#x", name, sizeOfRawData, virtualSize));
}
}
Bin.ArrayOfBins<ImageDataDirectory> imageDataDirs = imageOptionalHeader.getImageDataDirectories();
check(errors, "resources",
imageDataDirs.get(ImageDataDirectory.IMAGE_DIRECTORY_ENTRY_RESOURCE),
getSection(sections, Section.RESOURCES_SECTION_NAME));
check(errors, "relocations",
imageDataDirs.get(ImageDataDirectory.IMAGE_DIRECTORY_ENTRY_BASERELOC),
getSection(sections, Section.RELOCATIONS_SECTION_NAME));
//SizeOfImage
//The size of the image, in bytes, including all headers. Must be a multiple of SectionAlignment.
long sizeOfImage = imageOptionalHeader.getSizeOfImage().getValue();
if (sizeOfImage % SectionAlignment != 0) {
errors.add(format("SizeOfImage isn't dividable by 'SectionAlignment' (%#x): %#x", SectionAlignment, sizeOfImage));
}
if (!errors.isEmpty()) {
StringBuilder msg = new StringBuilder();
msg.append("Output verification failed with ").append(errors.size()).append(" error");
if (errors.size() > 1) msg.append("s");
msg.append(":\n");
for (String error : errors) {
msg.append('\t').append(error).append('\n');
}
throw new RuntimeException(msg.toString());
}
}
private static void check(List<String> errors, String name, ImageDataDirectory idd, ImageSectionHeader ish) {
long iddSize = idd.getSize().getValue();
long ishVirtualSize = ish.getVirtualSize().getValue();
if (iddSize != ishVirtualSize) {
errors.add(format("Incorrect '%s' sizes: %#x, %#x", name, iddSize, ishVirtualSize));
}
long iddAddress = idd.getVirtualAddress().getValue();
long ishAddress = ish.getVirtualAddress().getValue();
if (iddAddress != ishAddress) {
errors.add(format("Incorrect '%s' virtual address: %#x, %#x", name, iddAddress, ishAddress));
}
}
private static ImageSectionHeader getSection(Bin.ArrayOfBins<ImageSectionHeader> sections, String name) {
for (ImageSectionHeader header : sections) {
if (name.equals(header.getSectionName())) {
return header;
}
}
throw new IllegalStateException("Cannot find section with name " + name);
}
private void verifyVersionInfo() throws IOException {
String versionInfoPath = myExePath + ".version";
try (RandomAccessFile versionInfoStream = new RandomAccessFile(versionInfoPath, "rw")) {
myVersionInfo.resetOffsets(0);
myVersionInfo.write(versionInfoStream);
}
ByteArrayOutputStream data1 = new ByteArrayOutputStream((int)myVersionInfo.sizeInBytes());
myVersionInfo.resetOffsets(0);
myVersionInfo.write(new DataOutputStream(data1));
VersionInfo copy = new VersionInfo();
copy.read(new OffsetTrackingInputStream(new DataInputStream(new FileInputStream(versionInfoPath))));
copy.read(new OffsetTrackingInputStream(new DataInputStream(new ByteArrayInputStream(data1.toByteArray()))));
ByteArrayOutputStream data2 = new ByteArrayOutputStream((int)copy.sizeInBytes());
copy.resetOffsets(0);
copy.write(new DataOutputStream(data2));
if (!Arrays.equals(data1.toByteArray(), data2.toByteArray())) {
throw new IllegalStateException("Load and save of VersionInfo produced different binary results");
}
}
public void setResourceString(int id, String value) {
@@ -117,27 +225,21 @@ public class LauncherGenerator {
}
public void setVersionInfoString(String key, String value) {
StringTable stringTable = myVersionInfo.getStringFileInfo().getFirstStringTable();
if (stringTable != null) {
stringTable.setStringValue(key, value);
}
StringTable stringTable = myVersionInfo.getStringFileInfo().getSoleStringTable();
stringTable.setStringValue(key, value);
}
public void injectBitmap(int id, byte[] bitmapFileData) {
DirectoryEntry subDirBmp = myRoot.findSubDir("IRD2").findSubDir("IRD" + id);
RawResource bmpRes = subDirBmp.getRawResource(0);
DirectoryEntry subDirBmp = myRoot.findSubDir(DirectoryEntry.RT_BITMAP).findSubDir(id);
RawResource bmpRes = subDirBmp.getRawResource();
// strip off BITMAPFILEHEADER
byte[] bitmapResourceData = new byte[bitmapFileData.length-14];
byte[] bitmapResourceData = new byte[bitmapFileData.length - 14];
System.arraycopy(bitmapFileData, 14, bitmapResourceData, 0, bitmapResourceData.length);
bmpRes.setBytes(bitmapResourceData);
}
public void injectIcon(int id, final InputStream iconStream) throws IOException {
Path f = Files.createTempFile("launcher", "ico");
try (iconStream) {
Files.copy(iconStream, f, StandardCopyOption.REPLACE_EXISTING);
}
new IconResourceInjector().injectIcon(f.toFile(), myRoot, "IRD" + id);
IconResourceInjector.injectIcon(iconStream, myRoot, id);
}
public void setVersionNumber(int majorVersion, int minorVersion, int bugfixVersion) {

View File

@@ -39,6 +39,6 @@ public class BitsUtil {
public static char readChar(DataInput stream) throws IOException {
int b1 = stream.readByte();
int b2 = stream.readByte();
return (char) (b1 + (b2 << 8));
return (char) (b1 & 0xFF | ((b2 & 0xFF) << 8));
}
}

View File

@@ -0,0 +1,42 @@
package com.pme.util;
import java.io.*;
public class StreamUtil {
public static long getOffset(DataInput stream) throws IOException {
if (stream instanceof OffsetTrackingInputStream) {
return ((OffsetTrackingInputStream)stream).getOffset();
}
if (stream instanceof RandomAccessFile) {
return ((RandomAccessFile)stream).getFilePointer();
}
throw new IOException("OffsetTrackingInputStream or RandomAccessFile expected, got " + stream.getClass().getName());
}
public static long getOffset(DataOutput stream) throws IOException {
if (stream instanceof RandomAccessFile) {
return ((RandomAccessFile)stream).getFilePointer();
}
if (stream instanceof DataOutputStream) {
return ((DataOutputStream)stream).size();
}
throw new IOException("RandomAccessFile or DataOutputStream expected, got " + stream.getClass().getName());
}
public static void seek(DataInput stream, long pos) throws IOException {
if (stream instanceof OffsetTrackingInputStream inputStream) {
long current = inputStream.getOffset();
if (current < pos) {
inputStream.skipBytes((int)(pos - current));
} else {
throw new IOException(String.format("Cannot go backwards in OffsetTrackingInputStream: current offset %#010x, seek %#010x", current, pos));
}
return;
}
if (stream instanceof RandomAccessFile) {
((RandomAccessFile)stream).seek(pos);
return;
}
throw new IOException("OffsetTrackingInputStream or RandomAccessFile expected, got " + stream.getClass().getName());
}
}