package hextostring.convert;
import java.nio.charset.Charset;
import java.util.LinkedList;
import java.util.List;
import hextostring.ConvertOptions;
import hextostring.debug.DebuggableLine;
import hextostring.debug.DebuggableLineList;
import hextostring.evaluate.EvaluatorFactory;
import hextostring.evaluate.string.StringEvaluator;
import hextostring.replacement.HexToStrStrategy;
import hextostring.replacement.ReplacementType;
import hextostring.replacement.Replacements;
/**
* Abstract converter using a definite encoding.
*
* @author Maxime PIA
*/
public abstract class AbstractConverter implements Converter {
private Charset charset;
private Replacements replacements;
private StringEvaluator japaneseStringEvaluator;
public AbstractConverter(Charset charset) {
setCharset(charset);
}
/**
* Getter on the charset used by the converter.
*
* @param charset
* The charset used by the converter.
*/
protected Charset getCharset() {
return charset;
}
/**
* Sets the charset for the converter and adapts its evaluators accordingly.
*
* @param charset
* The new charset for this converter.
*/
protected void setCharset(Charset charset) {
this.charset = charset;
this.japaneseStringEvaluator =
EvaluatorFactory.getStringEvaluatorInstance();
}
@Override
public void setReplacements(Replacements r) {
replacements = r;
if (replacements == null) {
replacements = ConvertOptions.DEFAULT_REPLACEMENTS;
}
}
/**
* Verifies if the input is valid and sets it to lowercase without spaces.
*
* @param hex
* The input string.
* @return
* The lowercase, sans spaces version of the input string.
*/
protected static String preProcessHex(String hex) {
String lowercaseHex =
hex.toLowerCase().replace(" ", "").replace("\n", "");
if (!lowercaseHex.matches("[a-f0-9]+")) {
throw new IllegalArgumentException("Invalid hex string.");
}
return lowercaseHex;
}
/**
* Inputs strings may contains areas of zeros. This method removes them.
*
* @param hex
* The lowercase version of the input string.
* @return A list a strings found between areas of zeros.
*/
protected abstract List<String> extractConvertibleChunks(String hex);
/**
* Calls extractConvertibleChunks of hex parts of a transitory string and
* re-glue mixed parts together whenever possible.
*
* @param mixed
* The transitory string
* @return A list of convertible transitory strings.
*/
private List<String> extractConvertibleChunksFromMixedString(String mixed) {
List<String> result = new LinkedList<>();
List<String> parts = HexToStrStrategy.splitParts(mixed);
List<String> previousHexChunks = null;
String previousHexPart = null, previousReadablePart = null;
boolean hexPart = true, stringPreceded = false;
for (String part : parts) {
int lastIndex = result.size() - 1;
if (hexPart) {
List<String> chunks = extractConvertibleChunks(part);
if (chunks.size() > 0 && previousReadablePart != null) {
// the first chunk corresponds to the start of the hex part
boolean stringPrecedes = part.indexOf(chunks.get(0)) == 0;
if (stringPrecedes) {
result.set(
lastIndex,
result.get(lastIndex) + chunks.get(0)
);
chunks = chunks.subList(1, chunks.size());
stringPreceded = true;
} else {
stringPreceded = false;
}
}
result.addAll(chunks);
previousHexPart = part;
previousHexChunks = chunks;
} else {
part = "-" + part + "-"; // restore "-" removed by splitParts
boolean stringFollows = false;
if (previousHexChunks.size() + (stringPreceded ? 1 : 0) > 0) {
String lastChunk = result.get(lastIndex);
// the last chunk corresponds to the end of the hex part
stringFollows = previousHexPart.lastIndexOf(lastChunk)
+ lastChunk.length() == previousHexPart.length();
if (stringFollows) {
result.set(lastIndex, lastChunk + part);
}
}
if (!stringFollows) {
result.add(part);
}
previousReadablePart = part;
}
hexPart = !hexPart;
}
return result;
}
/**
* Converts a hex string into several Japanese lines
*
* @param hex
* A hex string copied from Cheat Engine's memory viewer.
* @return The result of the conversion wrapped into a debuggable object.
*/
@Override
public DebuggableLineList convert(String hex) {
DebuggableLineList lines = new DebuggableLineList(preProcessHex(hex));
lines.setHexInputAfterHexReplacements(
replacements.apply(lines.getHexInput(), ReplacementType.HEX2HEX)
);
lines.setHexInputAfterStrReplacements(
replacements.apply(
lines.getHexInputAfterHexReplacements(),
ReplacementType.HEX2STR
)
);
List<String> hexCollection = extractConvertibleChunksFromMixedString(
lines.getHexInputAfterStrReplacements()
);
for (String hexChunk : hexCollection) {
DebuggableLine line = new DebuggableLine(hexChunk);
line.setReadableString(HexToStrStrategy.toReadableString(
line.getHex(),
charset
));
line.setReadableStringAfterReplacements(replacements.apply(
line.getReadableString(),
ReplacementType.STR2STR
));
line.setEvaluationResult(
japaneseStringEvaluator.evaluate(
line.getReadableStringAfterReplacements()
)
);
lines.addLine(line);
}
return lines;
}
}