blob: e23785d224021af85d6525b5d467833f1cadb9ef [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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.android.jobb;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
public class ObbFile {
public static final int OBB_OVERLAY = (1 << 0);
public static final int OBB_SALTED = (1 << 1);
static final int kFooterTagSize = 8; /* last two 32-bit integers */
static final int kFooterMinSize = 33; /* 32-bit signature version (4 bytes)
* 32-bit package version (4 bytes)
* 32-bit flags (4 bytes)
* 64-bit salt (8 bytes)
* 32-bit package name size (4 bytes)
* >=1-character package name (1 byte)
* 32-bit footer size (4 bytes)
* 32-bit footer marker (4 bytes)
*/
static final int kMaxBufSize = 32768; /* Maximum file read buffer */
static final long kSignature = 0x01059983; /* ObbFile signature */
static final int kSigVersion = 1; /* We only know about signature version 1 */
/* offsets in version 1 of the header */
static final int kPackageVersionOffset = 4;
static final int kFlagsOffset = 8;
static final int kSaltOffset = 12;
static final int kPackageNameLenOffset = 20;
static final int kPackageNameOffset = 24;
long mPackageVersion = -1, mFlags;
String mPackageName;
byte[] mSalt = new byte[8];
public ObbFile() {}
public boolean readFrom(String filename)
{
File obbFile = new File(filename);
return readFrom(obbFile);
}
public boolean readFrom(File obbFile)
{
return parseObbFile(obbFile);
}
static public long get4LE(ByteBuffer buf) {
buf.order(ByteOrder.LITTLE_ENDIAN);
return (buf.getInt() & 0xFFFFFFFFL);
}
public void setPackageName(String packageName) {
mPackageName = packageName;
}
public void setSalt(byte[] salt) {
if ( salt.length != mSalt.length ) {
throw new RuntimeException("salt must be " + mSalt.length + " characters in length");
}
System.arraycopy(salt, 0, mSalt, 0, mSalt.length);
}
public void setPackageVersion(long packageVersion) {
mPackageVersion = packageVersion;
}
public void setFlags(long flags) {
mFlags = flags;
}
public boolean parseObbFile(File obbFile)
{
try {
long fileLength = obbFile.length();
if (fileLength < kFooterMinSize) {
throw new RuntimeException("file is only " + fileLength + " (less than " + kFooterMinSize + " minimum)");
}
RandomAccessFile raf = new RandomAccessFile(obbFile, "r");
raf.seek(fileLength - kFooterTagSize);
byte[] footer = new byte[kFooterTagSize];
raf.readFully(footer);
ByteBuffer footBuf = ByteBuffer.wrap(footer);
footBuf.position(4);
long fileSig = get4LE(footBuf);
if (fileSig != kSignature) {
throw new RuntimeException("footer didn't match magic string (expected 0x" + Long.toHexString(kSignature) + ";got 0x" +
Long.toHexString(fileSig)+ ")");
}
footBuf.rewind();
long footerSize = get4LE(footBuf);
if (footerSize > fileLength - kFooterTagSize
|| footerSize > kMaxBufSize) {
throw new RuntimeException("claimed footer size is too large (0x" + Long.toHexString(footerSize) + "; file size is 0x" +
Long.toHexString(fileLength)+ ")");
}
if (footerSize < (kFooterMinSize - kFooterTagSize)) {
throw new RuntimeException("claimed footer size is too small (0x" + Long.toHexString(footerSize) + "; minimum size is 0x" +
Long.toHexString(kFooterMinSize - kFooterTagSize));
}
long fileOffset = fileLength - footerSize - kFooterTagSize;
raf.seek(fileOffset);
footer = new byte[(int)footerSize];
raf.readFully(footer);
footBuf = ByteBuffer.wrap(footer);
long sigVersion = get4LE(footBuf);
if (sigVersion != kSigVersion) {
throw new RuntimeException("Unsupported ObbFile version " + sigVersion );
}
footBuf.position(kPackageVersionOffset);
mPackageVersion = get4LE(footBuf);
footBuf.position(kFlagsOffset);
mFlags = get4LE(footBuf);
footBuf.position(kSaltOffset);
footBuf.get(mSalt);
footBuf.position(kPackageNameLenOffset);
long packageNameLen = get4LE(footBuf);
if (packageNameLen == 0
|| packageNameLen > (footerSize - kPackageNameOffset)) {
throw new RuntimeException("bad ObbFile package name length (0x" + Long.toHexString(packageNameLen) +
"; 0x" + Long.toHexString(footerSize - kPackageNameOffset) + "possible)");
}
byte[] packageNameBuf = new byte[(int)packageNameLen];
footBuf.position(kPackageNameOffset);
footBuf.get(packageNameBuf);
mPackageName = new String(packageNameBuf);
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
public boolean writeTo(String fileName)
{
File obbFile = new File(fileName);
return writeTo(obbFile);
}
public boolean writeTo(File obbFile) {
if ( !obbFile.exists() )
return false;
try {
long fileLength = obbFile.length();
RandomAccessFile raf = new RandomAccessFile(obbFile, "rw");
raf.seek(fileLength);
if (null == mPackageName || mPackageVersion == -1) {
throw new RuntimeException("tried to write uninitialized ObbFile data");
}
FileChannel fc = raf.getChannel();
ByteBuffer bbInt = ByteBuffer.allocate(4);
bbInt.order(ByteOrder.LITTLE_ENDIAN);
bbInt.putInt(kSigVersion);
bbInt.rewind();
fc.write(bbInt);
bbInt.rewind();
bbInt.putInt((int)mPackageVersion);
bbInt.rewind();
fc.write(bbInt);
bbInt.rewind();
bbInt.putInt((int)mFlags);
bbInt.rewind();
fc.write(bbInt);
raf.write(mSalt);
bbInt.rewind();
bbInt.putInt(mPackageName.length());
bbInt.rewind();
fc.write(bbInt);
raf.write(mPackageName.getBytes());
bbInt.rewind();
bbInt.putInt(mPackageName.length()+kPackageNameOffset);
bbInt.rewind();
fc.write(bbInt);
bbInt.rewind();
bbInt.putInt((int)kSignature);
bbInt.rewind();
fc.write(bbInt);
raf.close();
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
}