| /* |
| * 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; |
| } |
| } |