Added: #2360 SOL file (Flash Local Shared Object - flash cookie) editor

This commit is contained in:
Jindra Petřík
2024-11-10 19:50:55 +01:00
parent 0b776dd06a
commit 3712c3be45
59 changed files with 3842 additions and 150 deletions
@@ -0,0 +1,54 @@
/*
* Copyright (C) 2010-2024 JPEXS, All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
*/
package com.jpexs.decompiler.flash.sol;
import com.jpexs.decompiler.flash.amf.amf0.Amf0InputStream;
import com.jpexs.decompiler.flash.amf.amf0.Amf0OutputStream;
import com.jpexs.helpers.MemoryInputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
*
* @author JPEXS
*/
public class FilePathTag extends Tag {
public static final int ID = 3;
public String filePath;
public FilePathTag(byte[] data, boolean forceWriteAsLong) {
super(ID, "DefineFilePath", data, forceWriteAsLong);
}
@Override
public void readData() throws IOException {
Amf0InputStream is = new Amf0InputStream(new MemoryInputStream(data));
int filePathLen = is.readU16("filePath");
filePath = new String(is.readBytes(filePathLen), "UTF-8");
}
@Override
public void writeData(OutputStream os) throws IOException {
Amf0OutputStream aos = new Amf0OutputStream(os);
byte[] filePathData = filePath.getBytes("UTF-8");
aos.writeU16(filePathData.length);
aos.writeBytes(filePathData);
}
}
@@ -0,0 +1,149 @@
/*
* Copyright (C) 2010-2024 JPEXS, All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
*/
package com.jpexs.decompiler.flash.sol;
import com.jpexs.decompiler.flash.amf.amf0.Amf0InputStream;
import com.jpexs.decompiler.flash.amf.amf0.Amf0OutputStream;
import com.jpexs.decompiler.flash.amf.amf3.Amf3InputStream;
import com.jpexs.decompiler.flash.amf.amf3.Amf3OutputStream;
import com.jpexs.decompiler.flash.amf.amf3.NoSerializerExistsException;
import com.jpexs.decompiler.flash.amf.amf3.Traits;
import com.jpexs.helpers.MemoryInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author JPEXS
*/
public class LsoTag extends Tag {
public static final int ID = 2;
public String fileName;
public int amfVersion;
public Map<String, Object> amfValues = new LinkedHashMap<>();
public LsoTag(byte[] data, boolean forceWriteAsLong) {
super(ID, "DefineLso", data, forceWriteAsLong);
}
public LsoTag(String fileName, int amfVersion, Map<String, Object> amfValues) {
super(ID, "DefineLso", new byte[0], true);
this.fileName = fileName;
this.amfVersion = amfVersion;
this.amfValues = amfValues;
}
@Override
public void readData() throws IOException {
Amf0InputStream is = new Amf0InputStream(new MemoryInputStream(data));
byte[] expectedSig = new byte[]{'T', 'C', 'S', 'O'};
byte[] actualSig = is.readBytes(4);
if (!Arrays.equals(actualSig, expectedSig)) {
throw new IllegalArgumentException("Not a SOL file - invalid signature");
}
is.skip(6); //00 04 00 00 00 00
int filenameLen = is.readU16("filenameLen");
fileName = new String(is.readBytes(filenameLen), "UTF-8");
amfVersion = (int) is.readU32("amfVersion");
if (amfVersion != 0 && amfVersion != 3) {
throw new IllegalArgumentException("Unsupported AMF version");
}
byte[] amfData = is.readBytes(is.available());
if (amfVersion == 0) {
Amf0InputStream ais = new Amf0InputStream(new MemoryInputStream(amfData));
List<Object> complexObjects = new ArrayList<>();
while (ais.available() > 0) {
String varName = ais.readUtf8("varName");
try {
Object varValue = ais.readValue("varValue");
amfValues.put(varName, varValue);
} catch (NoSerializerExistsException ex) {
throw new IllegalArgumentException("Serializer for class " + ex.getClassName() + " not found");
}
ais.read(); //ending byte
}
ais.resolveMapReferences(amfValues);
}
if (amfVersion == 3) {
Amf3InputStream ais = new Amf3InputStream(new MemoryInputStream(amfData));
List<Object> objectsTable = new ArrayList<>();
List<Traits> traitsTable = new ArrayList<>();
List<String> stringTable = new ArrayList<>();
while (ais.available() > 0) {
String varName = ais.readUtf8Vr("varName", new ArrayList<>());
try {
Object varValue = ais.readValue("varValue", new HashMap<>(), objectsTable, traitsTable, stringTable);
amfValues.put(varName, varValue);
} catch (NoSerializerExistsException ex) {
throw new IllegalArgumentException("Serializer for class " + ex.getClassName() + " not found");
}
ais.read(); //ending byte
}
}
}
@Override
public void writeData(OutputStream os) throws IOException {
Amf0OutputStream aos = new Amf0OutputStream(os);
aos.write(new byte[] {'T', 'C', 'S', 'O'});
aos.write(new byte[] {0x00,0x04,0x00,0x00,0x00,0x00});
byte[] fileNameData = fileName.getBytes("UTF-8");
aos.writeU16(fileNameData.length);
aos.write(fileNameData);
aos.writeU32(amfVersion);
if (amfVersion == 0) {
List<Object> complexObjects = new ArrayList<>();
for(String key: amfValues.keySet()) {
aos.writeUtf8(key);
aos.writeValue(amfValues.get(key), complexObjects);
aos.write(0);
}
}
if (amfVersion == 3) {
Amf3OutputStream a3os = new Amf3OutputStream(os);
List<String> stringTable = new ArrayList<>();
List<Traits> traitTable = new ArrayList<>();
List<Object> objectTable = new ArrayList<>();
for(String key: amfValues.keySet()) {
try {
a3os.writeUtf8Vr(key, new ArrayList<>()); //Intentionally not using string table
a3os.writeValue(amfValues.get(key), new HashMap<>(), stringTable, traitTable, objectTable);
} catch (NoSerializerExistsException ex) {
throw new IllegalArgumentException("Serializer not found for class " + ex.getClassName());
}
aos.write(0);
}
}
}
}
@@ -0,0 +1,225 @@
/*
* Copyright (C) 2010-2024 JPEXS, All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
*/
package com.jpexs.decompiler.flash.sol;
import com.jpexs.decompiler.flash.EndOfStreamException;
import com.jpexs.decompiler.flash.amf.amf0.Amf0OutputStream;
import com.jpexs.decompiler.flash.exporters.amf.amf0.Amf0Exporter;
import com.jpexs.decompiler.flash.exporters.amf.amf3.Amf3Exporter;
import com.jpexs.decompiler.flash.importers.amf.amf0.Amf0Importer;
import com.jpexs.decompiler.flash.importers.amf.amf3.Amf3Importer;
import com.jpexs.decompiler.flash.importers.amf.amf3.Amf3ParseException;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
*
* @author JPEXS
*/
public class SolFile {
private DataInputStream is;
private long pos = 0;
private List<Tag> tags = new ArrayList<>();
public SolFile(InputStream is) throws IOException {
this.is = new DataInputStream(is);
readTags();
}
public SolFile(String fileName, int amfVersion, Map<String, Object> amfValues) {
tags.add(new LsoTag(fileName, amfVersion, amfValues));
}
public void writeTo(OutputStream os) throws IOException {
for (Tag t: tags) {
writeTag(os, t);
}
}
private void readTags() throws IOException {
while (is.available() > 0) {
Tag t = readTag();
t.readData();
tags.add(t);
}
}
private int readInternal() throws IOException {
int ret = is.read();
if (ret == -1) {
throw new EndOfStreamException();
}
pos++;
return ret;
}
private int readUI16() throws IOException {
int b1 = readInternal();
int b2 = readInternal();
int ret = (b1 << 8) + b2;
return ret;
}
private long readUI32() throws IOException {
int b1 = readInternal();
int b2 = readInternal();
int b3 = readInternal();
int b4 = readInternal();
return ((b1 << 24) + (b2 << 16) + (b3 << 8) + b4) & 0xffffffff;
}
private byte[] readBytes(int count) throws IOException {
byte[] ret = new byte[count];
try {
is.readFully(ret);
} catch (EOFException eof) {
throw new EndOfStreamException();
}
pos += count;
return ret;
}
private void skipBytes(int count) throws IOException {
is.skip(count);
pos += count;
}
private void writeTag(OutputStream os, Tag t) throws IOException{
Amf0OutputStream aos = new Amf0OutputStream(os);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
t.writeData(baos);
int contentLength = baos.size();
if (contentLength > 0x3F || t.forceWriteAsLong) {
aos.writeU16((t.getTagType() << 6) | 0x3F);
aos.writeU32(baos.size());
} else {
aos.writeU16((t.getTagType() << 6) | contentLength);
}
aos.writeBytes(baos.toByteArray());
}
private Tag readTag() throws IOException {
long headerPos = pos;
int tagTypeAndLength = readUI16();
int contentLength = tagTypeAndLength & 0x3F;
boolean writeAsLong = false;
if (contentLength == 0x3F) {
contentLength = (int) readUI32();
writeAsLong = true;
}
int tagType = tagTypeAndLength >> 6;
byte[] data = readBytes(contentLength);
switch (tagType) {
case LsoTag.ID:
return new LsoTag(data, writeAsLong);
case FilePathTag.ID:
return new FilePathTag(data, writeAsLong);
default:
return new UnknownTag(tagType, data, writeAsLong);
}
}
public List<Tag> getTags() {
return tags;
}
private LsoTag getLsoTag() throws IOException {
for (Tag t : getTags()) {
if (t instanceof LsoTag) {
return (LsoTag) t;
}
}
return null;
}
public int getAmfVersion() throws IOException {
LsoTag lsoTag = getLsoTag();
if (lsoTag == null) {
return -1;
}
return lsoTag.amfVersion;
}
public String getFileName() throws IOException {
LsoTag lsoTag = getLsoTag();
if (lsoTag == null) {
return null;
}
return lsoTag.fileName;
}
public Map<String, Object> getAmfValues() throws IOException {
LsoTag lsoTag = getLsoTag();
if (lsoTag == null) {
return new HashMap<>();
}
return lsoTag.amfValues;
}
public static void main(String[] args) throws FileNotFoundException, IOException {
SolFile sol0 = new SolFile(new FileInputStream("testdata/sharedobjects/data/amf0test.sol"));
sol0.writeTo(new FileOutputStream("testdata/sharedobjects/data/out/amf0test.sol"));
for (Tag t : sol0.getTags()) {
if (t instanceof LsoTag) {
LsoTag lt = (LsoTag) t;
String amf0string = Amf0Exporter.amfMapToString(lt.amfValues, 0, "\r\n");
Amf0Importer importer = new Amf0Importer();
try {
Map<String, Object> imported = importer.stringToAmfMap(amf0string);
String amf0stringNew = Amf0Exporter.amfMapToString(imported, 0, "\r\n");
System.err.println("same0 = " + amf0stringNew.equals(amf0string));
} catch (Amf3ParseException ex) {
ex.printStackTrace();
}
}
}
SolFile sol3 = new SolFile(new FileInputStream("testdata/sharedobjects/data/amf3test.sol"));
sol3.writeTo(new FileOutputStream("testdata/sharedobjects/data/out/amf3test.sol"));
for (Tag t : sol3.getTags()) {
if (t instanceof LsoTag) {
LsoTag lt = (LsoTag) t;
String amf3string = Amf3Exporter.amfMapToString(lt.amfValues, " ", "\r\n", 0);
Amf3Importer importer = new Amf3Importer();
try {
Map<String, Object> imported = importer.stringToAmfMap(amf3string);
String amf3stringNew = Amf3Exporter.amfMapToString(imported, " ", "\r\n", 0);;
System.err.println("same3 = " + amf3stringNew.equals(amf3string));
} catch (Amf3ParseException ex) {
ex.printStackTrace();
}
}
}
}
}
@@ -0,0 +1,50 @@
/*
* Copyright (C) 2010-2024 JPEXS, All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
*/
package com.jpexs.decompiler.flash.sol;
import java.io.IOException;
import java.io.OutputStream;
/**
* SOL file tag.
* @author JPEXS
*/
public abstract class Tag {
protected boolean forceWriteAsLong;
private final int tagType;
private final String tagName;
protected byte[] data;
public Tag(int tagType, String tagName, byte[] data, boolean forceWriteAsLong) {
this.tagType = tagType;
this.tagName = tagName;
this.data = data;
this.forceWriteAsLong = forceWriteAsLong;
}
public abstract void readData() throws IOException;
public abstract void writeData(OutputStream os) throws IOException;
public int getTagType() {
return tagType;
}
public boolean isForceWriteAsLong() {
return forceWriteAsLong;
}
}
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2010-2024 JPEXS, All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
*/
package com.jpexs.decompiler.flash.sol;
import java.io.IOException;
import java.io.OutputStream;
/**
*
* @author JPEXS
*/
public class UnknownTag extends Tag {
public UnknownTag(int tagType, byte[] data, boolean forceWriteAsLong) {
super(tagType, "Unknown(" + tagType + ")", data, forceWriteAsLong);
}
@Override
public void readData() {
}
@Override
public void writeData(OutputStream os) throws IOException {
os.write(data);
}
}
@@ -0,0 +1,4 @@
/**
* Reading Local Shared Objects.
*/
package com.jpexs.decompiler.flash.sol;