/**
* Copyright 2014
* SMEdit https://github.com/StarMade/SMEdit
* SMTools https://github.com/StarMade/SMTools
*
* 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 jo.sm.ship.logic;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
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;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
import jo.sm.data.BlockTypes;
import jo.sm.data.CubeIterator;
import jo.sm.logic.IOLogic;
import jo.sm.logic.utils.DebugLogic;
import jo.sm.mods.IPluginCallback;
import jo.sm.ship.data.Block;
import jo.sm.ship.data.Chunk;
import jo.sm.ship.data.Data;
import jo.vecmath.Point3i;
import jo.vecmath.logic.Point3iLogic;
public class DataLogic {
private static final Logger log = Logger.getLogger(DataLogic.class.getName());
public static Map<Point3i, Data> readFiles(File dataDir, String prefix, IPluginCallback cb) throws IOException {
cb.setStatus("Reading " + prefix);
Map<Point3i, Data> data = new HashMap<>();
List<File> files = new ArrayList<>();
for (File dataFile : dataDir.listFiles()) {
if (dataFile.getName().endsWith(".smd2")
&& dataFile.getName().startsWith(prefix)) {
files.add(dataFile);
}
}
if (files.isEmpty()) {
for (File dataFile : dataDir.listFiles()) {
if (dataFile.getName().endsWith(".smd2")) {
if ((dataFile.toString().contains("server-database"))
&& !dataFile.getName().startsWith("ENTITY_SHIP_")) {
continue;
}
files.add(dataFile);
}
}
}
cb.startTask(files.size());
for (File dataFile : files) {
readDataFromEntityFile(dataFile, data);
cb.workTask(1);
}
cb.endTask();
return data;
}
private static void readDataFromEntityFile(File dataFile,
Map<Point3i, Data> data) throws IOException, FileNotFoundException {
String[] parts = dataFile.getName().split("\\.");
int l = parts.length;
Point3i position = new Point3i(Integer.parseInt(parts[l - 4]),
Integer.parseInt(parts[l - 3]),
Integer.parseInt(parts[l - 2]));
log.log(Level.INFO, "Reading from " + dataFile.getName() + " - " + position);
//System.out.println("Reading from " + dataFile.getName() + " - " + position);
Data datum = DataLogic.readFile(new FileInputStream(dataFile), true, position);
data.put(position, datum);
}
public static Data readFile(InputStream is, boolean close) throws IOException {
return readFile(is, close, null);
}
public static Data readFile(InputStream is, boolean close, Point3i superChunkIndex) throws IOException {
//System.out.println("Reading...");
Point3i superChunkOrigin = ShipLogic.getSuperChunkOriginFromIndex(superChunkIndex);
System.out.println("Super Chunk Index: " + superChunkIndex);
System.out.println("Super Chunk Origin: " + superChunkOrigin);
System.out.println("Super Chunk Lower: " + ShipLogic.getSuperChunkLowerFromOrigin(superChunkOrigin));
System.out.println("Super Chunk Upper: " + ShipLogic.getSuperChunkUpperFromOrigin(superChunkOrigin));
Point3i min = null;
Point3i max = null;
DataInputStream dis;
if (is instanceof DataInputStream) {
dis = (DataInputStream) is;
} else {
dis = new DataInputStream(is);
}
Data data = new Data();
data.setUnknown1(dis.readInt());
int[][][][] offsetSizeTable = new int[16][16][16][2];
IOLogic.readFully(dis, offsetSizeTable);
// index offsets
Map<Integer, Point3i> chunkOffsets = new HashMap<>();
int maxOffset = -1;
for (CubeIterator i = new CubeIterator(new Point3i(), new Point3i(15, 15, 15)); i.hasNext();) {
Point3i p = i.next();
int offset = offsetSizeTable[p.z][p.y][p.x][0];
if (offset >= 0) {
chunkOffsets.put(offset, p);
maxOffset = Math.max(maxOffset, offset);
}
}
long[][][] timestampTable = new long[16][16][16];
IOLogic.readFully(dis, timestampTable);
//data.setTimestampTable(unknown3);
List<Chunk> chunks = new ArrayList<>();
for (int offset = 0; offset <= maxOffset; offset++) {
byte[] chunkData = new byte[5120];
try {
dis.readFully(chunkData);
} catch (EOFException e) {
break;
}
//System.out.println(ByteUtils.toStringDump(chunkData));
DataInputStream dis2 = new DataInputStream(new ByteArrayInputStream(chunkData));
Chunk chunk = new Chunk();
chunk.setTimestamp(dis2.readLong());
chunk.setPosition(IOLogic.readPoint3i(dis2));
chunk.setType(dis2.readByte());
int compressedLen = dis2.readInt();
//System.out.println("Chunk "+chunk.getPosition());
//System.out.println("CompressedLen="+compressedLen);
byte[] compressedData = new byte[compressedLen];
dis2.readFully(compressedData);
DataInputStream dis3 = new DataInputStream(new InflaterInputStream(new ByteArrayInputStream(compressedData)));
Block[][][] blocks = new Block[16][16][16];
//int blockCount = 0;
byte[] inbuf = new byte[3];
for (int z = 0; z < 16; z++) {
for (int y = 0; y < 16; y++) {
for (int x = 0; x < 16; x++) {
blocks[x][y][z] = new Block();
dis3.readFully(inbuf);
int bitfield = toUnsignedInt(inbuf);
blocks[x][y][z].setBlockID((short) ((bitfield >> 0) & 0x7ff));
blocks[x][y][z].setHitPoints((short) ((bitfield >> 11) & 0x1ff));
blocks[x][y][z].setActive(((bitfield >> 20) & 0x1) == 1);
blocks[x][y][z].setOrientation((short) (((bitfield >> 21) & 0x7)
| ((bitfield >> (20 - 3)) & 0x8)));
//if (BlockTypes.isHull(blocks[x][y][z].getBlockID()))
// System.out.println(" Block "+Integer.toHexString(bitfield)
// +" (id="+ blocks[x][y][z].getBlockID()+", hp="+ blocks[x][y][z].getHitPoints()+")"
// +" "+ByteUtils.toString(inbuf));
//if (bitfield != 0)
// blockCount++;
if (blocks[x][y][z].getBlockID() <= 0) {
blocks[x][y][z] = null; // clear out unneeded blocks
} else if (DebugLogic.HULL_ONLY) {
if (!BlockTypes.isAnyHull(blocks[x][y][z].getBlockID())) {
blocks[x][y][z] = null; // clear out unneeded blocks
}
}
}
}
}
//System.out.println("Block count="+blockCount);
chunk.setBlocks(blocks);
min = Point3iLogic.min(min, chunk.getPosition());
max = Point3iLogic.max(max, chunk.getPosition());
if (chunkOffsets.containsKey(offset)) {
Point3i chunkIndex = chunkOffsets.get(offset);
Point3i expected = ShipLogic.getChunkPositionFromSuperchunkOriginAndChunkIndex(superChunkOrigin, chunkIndex);
if (!chunk.getPosition().equals(expected)) {
log.log(Level.INFO, "Chunk #" + offset + "/" + chunkOffsets.get(offset) + " expected in position " + expected + ", but actually in " + chunk.getPosition());
//System.out.println("Chunk #" + offset + "/" + chunkOffsets.get(offset) + " expected in position " + expected + ", but actually in " + chunk.getPosition());
chunk.setPosition(expected);
}
chunks.add(chunk);
} else {
log.log(Level.INFO, "Orphan chunk '" + chunk.getPosition() + "/" + offset);
//System.out.println("Orphan chunk '" + chunk.getPosition() + "/" + offset);
}
// backtrack offset table
/*
if ((superChunkIndex != null) && (superChunkIndex.z < 0) && (superChunkIndex.y == 0) && (superChunkIndex.x == 0))
{
if ((chunk.getPosition().x == 0) && (chunk.getPosition().y == 0))
{
System.out.println("Chunk "+chunk.getPosition());
for (int z = 0; z < 16; z++)
{
System.out.print("|");
for (int x = 15; x >= 0; x--)
{
boolean any = false;
for (int y = 0; y < 16; y++)
if (blocks[x][y][z] != null)
{
if (blocks[x][y][z].getBlockID() == BlockTypes.HULL_COLOR_BLACK_ID)
System.out.print("#");
else if (blocks[x][y][z].getBlockID() == BlockTypes.HULL_COLOR_WHITE_ID)
System.out.print(".");
else if (blocks[x][y][z].getBlockID() == BlockTypes.HULL_COLOR_YELLOW_ID)
System.out.print(",");
else
System.out.print("?");
any = true;
break;
}
if (!any)
System.out.print(" ");
}
System.out.println("|");
}
}
}
*/
}
data.setChunks(chunks.toArray(new Chunk[0]));
if (close) {
dis.close();
}
if (chunks.isEmpty()) {
log.log(Level.INFO, "NO CHUNKS!");
//System.out.println("NO CHUNKS!");
} else if (chunkOffsets.isEmpty()) {
log.log(Level.INFO, "NO OFFSET TABLE!");
//System.out.println("NO OFFSET TABLE!");
}
log.log(Level.INFO, "Range: " + min + " -> " + max);
//System.out.println("Range: " + min + " -> " + max);
return data;
}
public static void writeFile(Point3i superChunkIndex, Data data, OutputStream os, boolean close, IPluginCallback cb) throws IOException {
Point3i superChunkOrigin = ShipLogic.getSuperChunkOriginFromIndex(superChunkIndex);
if (cb != null) {
cb.setStatus("Writing " + superChunkIndex);
}
DataOutputStream dos;
if (os instanceof DataOutputStream) {
dos = (DataOutputStream) os;
} else {
dos = new DataOutputStream(os);
}
dos.writeInt(0); // non-compressed header
int[][][][] offsetSizeTable = new int[16][16][16][2];
for (int z = 0; z < 16; z++) {
for (int y = 0; y < 16; y++) {
for (int x = 0; x < 16; x++) {
offsetSizeTable[x][y][z][0] = -1;
}
}
}
long[][][] timestampTable = new long[16][16][16];
ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
DataOutputStream dos2 = new DataOutputStream(baos2);
for (int i = 0; i < data.getChunks().length; i++) {
Chunk chunk = data.getChunks()[i];
ByteArrayOutputStream baos3 = new ByteArrayOutputStream();
//int blockCount = 0;
try (DataOutputStream dos3 = new DataOutputStream(new DeflaterOutputStream(baos3))) {
//int blockCount = 0;
for (int z = 0; z < 16; z++) {
for (int y = 0; y < 16; y++) {
for (int x = 0; x < 16; x++) {
Block b = chunk.getBlocks()[x][y][z];
int bitfield = 0;
if (b != null) {
bitfield |= ((b.getBlockID() & 0x7ff) << 0);
bitfield |= ((b.getHitPoints() & 0x1ff) << 11);
bitfield |= ((b.getOrientation() & 0x8) << (20 - 3));
bitfield |= ((b.getOrientation() & 0x7) << 21);
// blockCount++;
// if (BlockTypes.isHull(b.getBlockID()))
// System.out.println(" Block "+Integer.toHexString(bitfield)
// +" (id="+b.getBlockID()+", hp="+b.getHitPoints()+")"
// +" "+ByteUtils.toString(fromUnsignedInt(bitfield)));
}
dos3.write(fromUnsignedInt(bitfield));
}
}
}
}
byte[] compressedData = baos3.toByteArray();
//System.out.println("Block count="+blockCount);
dos2.writeLong(chunk.getTimestamp());
IOLogic.write(dos2, chunk.getPosition());
dos2.writeByte(chunk.getType());
//System.out.println("CompressedLen="+compressedData.length);
dos2.writeInt(compressedData.length);
dos2.write(compressedData);
for (int j = 25 + compressedData.length; j < 5120; j++) {
dos2.writeByte(0);
}
Point3i chunkIndex = ShipLogic.getChunkIndexFromSuperchunkOriginAndChunkPosition(superChunkOrigin, chunk.getPosition());
try {
offsetSizeTable[chunkIndex.z][chunkIndex.y][chunkIndex.x][1] = 25 + compressedData.length;
offsetSizeTable[chunkIndex.z][chunkIndex.y][chunkIndex.x][0] = i;
timestampTable[chunkIndex.z][chunkIndex.y][chunkIndex.x] = chunk.getTimestamp();
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
log.log(Level.INFO, "ChunkPosition: " + chunk.getPosition() + "SuperChunkIndex: " + superChunkIndex + ", index=" + chunkIndex);
//System.out.println("ChunkPosition: " + chunk.getPosition() + "SuperChunkIndex: " + superChunkIndex + ", index=" + chunkIndex);
}
}
dos2.flush();
// chunk index
IOLogic.write(dos, offsetSizeTable);
IOLogic.write(dos, timestampTable);
dos.write(baos2.toByteArray());
if (close) {
dos.close();
}
}
public static Point3i getLocalIndex(Point3i superChunkIndex,
Point3i superChunkLower, Point3i universePoint) {
Point3i index = new Point3i(universePoint);
index.sub(superChunkLower);
index.scale(1, 16);
// weird reversal
if (superChunkIndex.x < 0) {
index.x = 15 - index.x;
}
if (superChunkIndex.y < 0) {
index.y = 15 - index.y;
}
if (superChunkIndex.z < 0) {
index.z = 15 - index.z;
}
return index;
}
private static byte[] fromUnsignedInt(int i) {
byte[] outbuf = new byte[3];
outbuf[2] = (byte) (i & 0xff);
i >>= 8;
outbuf[1] = (byte) (i & 0xff);
i >>= 8;
outbuf[0] = (byte) (i & 0xff);
return outbuf;
}
private static int toUnsignedInt(byte[] bytes) {
return toUnsignedInt(bytes, 0, bytes.length);
}
private static int toUnsignedInt(byte[] bytes, int o, int l) {
long v = 0;
for (int i = 0; i < l; i++) {
v = (v << 8) | (bytes[o + i] & 0xff);
}
return (int) v;
}
public static void writeFiles(Map<Point3i, Data> data, File baseDir,
String baseName, IPluginCallback cb) throws IOException {
// clean up first
File[] oldFiles = baseDir.listFiles();
if (oldFiles != null) {
for (File f : oldFiles) {
if (f.getName().startsWith(baseName)) {
f.delete();
}
}
}
if (cb != null) {
cb.setStatus("Writing " + baseName);
cb.startTask(data.size());
}
for (Point3i p : data.keySet()) {
File dataFile = new File(baseDir, baseName + "." + p.x + "." + p.y + "." + p.z + ".smd2");
// TEST
/*
byte[] original = FileUtils.readFile(dataFile.toString());
//String oTxt = ByteUtils.toStringDump(original);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
writeFile(data.get(p), baos, true);
byte[] save = baos.toByteArray();
String sTxt = ByteUtils.toStringDump(save);
if (oTxt.equals(sTxt))
System.out.println("Identical");
else
{
StringTokenizer oST = new StringTokenizer(oTxt, "\r\n");
StringTokenizer sST = new StringTokenizer(sTxt, "\r\n");
while (oST.hasMoreTokens())
{
String oLine = oST.nextToken();
String sLine = sST.nextToken();
if (!oLine.equals(sLine))
{
System.out.println("O: "+oLine);
System.out.println("S: "+sLine);
}
}
}
try
{
readFile(new ByteArrayInputStream(save), true);
}
catch (Exception e)
{
e.printStackTrace();
return;
}
*/
if (dataFile.exists()) {
File dest = new File(baseDir, baseName + "." + p.x + "." + p.y + "." + p.z + ".smd2.bak");
if (dest.exists()) {
dest.delete();
}
dataFile.renameTo(dest);
}
writeFile(p, data.get(p), new FileOutputStream(dataFile), true, cb);
if (cb != null) {
cb.workTask(1);
}
}
if (cb != null) {
cb.endTask();
}
}
}