/*
This file is part of jpcsp.
Jpcsp is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Jpcsp 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with Jpcsp. If not, see <http://www.gnu.org/licenses/>.
*/
package jpcsp.filesystems.umdiso;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.util.Date;
import jpcsp.filesystems.SeekableInputStream;
/**
*
* @author gigaherz
*/
public class UmdIsoFile extends SeekableInputStream {
public static final int sectorLength = ISectorDevice.sectorLength;
private int startSectorNumber;
private int currentSectorNumber;
private long currentOffset;
private long maxOffset;
private Date timestamp;
private String name;
private byte[] currentSector;
private int sectorOffset;
UmdIsoReader internalReader;
public UmdIsoFile(UmdIsoReader reader, int startSector, long lengthInBytes, Date timestamp, String name) throws IOException {
this.startSectorNumber = startSector;
this.currentSectorNumber = startSector;
this.currentOffset = 0;
this.internalReader = reader;
this.name = name;
this.sectorOffset = 0;
this.timestamp = timestamp;
setLength(lengthInBytes);
if (lengthInBytes == 0) {
currentSector = null;
} else {
currentSector = reader.readSector(startSector);
}
}
@Override
public int read() throws IOException {
if (currentOffset >= maxOffset) {
throw new EOFException();
}
checkSectorAvailable();
currentOffset++;
return ((currentSector[sectorOffset++]) & 0xFF);
}
@Override
public void reset() throws IOException {
seek(0);
}
@Override
public long skip(long n) throws IOException {
long oldOffset = currentOffset;
if (n < 0) {
return n;
}
seek(currentOffset + n);
return (currentOffset - oldOffset);
}
@Override
public long length() {
return maxOffset;
}
@Override
public void seek(long offset) throws IOException {
long endOffset = offset;
if (offset<0) {
throw new IOException("Seek offset " + offset + " out of bounds.");
}
int oldSectorNumber = currentSectorNumber;
long newOffset = endOffset;
int newSectorNumber = startSectorNumber + (int)(newOffset / sectorLength);
if (oldSectorNumber != newSectorNumber) {
currentSector = internalReader.readSector(newSectorNumber, currentSector);
}
currentOffset = newOffset;
currentSectorNumber = newSectorNumber;
sectorOffset = (int)(currentOffset % sectorLength);
}
@Override
public long getFilePointer() throws IOException {
return currentOffset;
}
@Override
public byte readByte() throws IOException {
if (currentOffset >= maxOffset) {
throw new EOFException();
}
return (byte)read();
}
@Override
public short readShort() throws IOException {
return (short)(readUnsignedByte() | ((readByte())<<8));
}
@Override
public int readInt() throws IOException {
return (readUnsignedByte() | ((readUnsignedByte())<<8) | ((readUnsignedByte())<<16) | ((readByte())<<24));
}
@Override
public int readUnsignedByte() throws IOException {
if(currentOffset >= maxOffset) {
throw new EOFException();
}
return read();
}
@Override
public int readUnsignedShort() throws IOException {
return (readShort())&0xFFFF;
}
@Override
public long readLong() throws IOException {
return ((readInt())&0xFFFFFFFFl) | (((long)readInt())<<32);
}
@Override
public float readFloat() throws IOException {
if(currentOffset >= maxOffset) {
throw new EOFException();
}
return Float.intBitsToFloat(readInt());
}
@Override
public double readDouble() throws IOException {
if(currentOffset >= maxOffset) {
throw new EOFException();
}
return Double.longBitsToDouble(readLong());
}
@Override
public boolean readBoolean() throws IOException {
return (readUnsignedByte() != 0);
}
@Override
public char readChar() throws IOException {
if(currentOffset >= maxOffset) {
throw new EOFException();
}
int ch1 = read();
int ch2 = read();
if ((ch1 | ch2) < 0) {
throw new EOFException();
}
return (char)((ch1 << 8) + (ch2 << 0));
}
@Override
public String readUTF() throws IOException {
if(currentOffset >= maxOffset) {
throw new EOFException();
}
return DataInputStream.readUTF(this);
}
@Override
public String readLine() throws IOException {
if(currentOffset >= maxOffset) {
throw new EOFException();
}
StringBuilder s = new StringBuilder();
char c=0;
do {
c = readChar();
if((c=='\n')||(c!='\r')) {
break;
}
s.append(c);
} while(true);
return s.toString();
}
@Override
public void readFully(byte[] b, int off, int len) throws IOException {
if(currentOffset >= maxOffset) {
throw new EOFException();
}
read(b, off, len);
}
@Override
public void readFully(byte[] b) throws IOException {
if(currentOffset >= maxOffset) {
throw new EOFException();
}
read(b);
}
@Override
public int skipBytes(int bytes) throws IOException {
return (int)skip(bytes);
}
public Date getTimestamp() {
return timestamp;
}
public int getStartSector() {
return startSectorNumber;
}
public String getName() {
if (this.name == null) {
this.name = internalReader.getFileName(startSectorNumber);
}
return name;
}
private int readInternal(byte[] b, int off, int len) throws IOException {
if (len > 0) {
if (len > (maxOffset - currentOffset)) {
len = (int) (maxOffset - currentOffset);
}
System.arraycopy(currentSector, sectorOffset, b, off, len);
sectorOffset += len;
currentOffset += len;
}
return len;
}
private void checkSectorAvailable() throws IOException {
if (sectorOffset == sectorLength && currentOffset < maxOffset) {
currentSectorNumber++;
currentSector = internalReader.readSector(currentSectorNumber, currentSector);
sectorOffset = 0;
}
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
}
if (off < 0 || len < 0 || len > (b.length - off)) {
throw new IndexOutOfBoundsException();
}
if (len > (maxOffset - currentOffset)) {
len = (int) (maxOffset - currentOffset);
}
int totalLength = 0;
int firstSector = readInternal(b, off, Math.min(len, sectorLength - sectorOffset));
off += firstSector;
len -= firstSector;
totalLength += firstSector;
// Read whole sectors
if (len >= sectorLength) {
int numberSectors = len / sectorLength;
internalReader.readSectors(currentSectorNumber + 1, numberSectors, b, off);
currentSectorNumber += numberSectors;
sectorOffset = sectorLength;
int n = numberSectors * sectorLength;
currentOffset += n;
checkSectorAvailable();
off += n;
len -= n;
totalLength += n;
}
if (len > 0) {
checkSectorAvailable();
int lastSector = readInternal(b, off, len);
totalLength += lastSector;
}
return totalLength;
}
public int getCurrentSectorNumber() {
return currentSectorNumber;
}
public UmdIsoReader getUmdIsoReader() {
return internalReader;
}
public UmdIsoFile duplicate() throws IOException {
UmdIsoFile umdIsoFile = new UmdIsoFile(internalReader, startSectorNumber, maxOffset, timestamp, name);
umdIsoFile.seek(currentOffset);
return umdIsoFile;
}
public void setLength(long lengthInBytes) {
// Some ISO directory entries indicate a file length past the size of the complete ISO.
// Truncate the file length in that case to the available sectors.
// This might be some sort of copy protection?
// E.g. "Kamen no Maid Guy: Boyoyon Battle Royale"
int endSectorNumber = this.startSectorNumber + ((int) ((lengthInBytes + sectorLength - 1) / sectorLength));
if (lengthInBytes > 0) {
endSectorNumber--;
}
if (endSectorNumber >= internalReader.getNumSectors()) {
endSectorNumber = internalReader.getNumSectors() - 1;
lengthInBytes = (endSectorNumber - startSectorNumber + 1) * sectorLength;
}
maxOffset = lengthInBytes;
}
@Override
public String toString() {
return String.format("UmdIsoFile(name='%s', length=0x%X, startSector=0x%X)", getName(), length(), startSectorNumber);
}
}