/*
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.HLE.VFS.xmb;
import static jpcsp.util.Utilities.readUnaligned16;
import static jpcsp.util.Utilities.readUnaligned32;
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.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Map;
import jpcsp.HLE.TPointer;
import jpcsp.HLE.VFS.AbstractVirtualFile;
import jpcsp.HLE.VFS.IVirtualFile;
import jpcsp.HLE.VFS.IVirtualFileSystem;
import jpcsp.HLE.VFS.iso.UmdIsoVirtualFileSystem;
import jpcsp.HLE.kernel.types.SceIoStat;
import jpcsp.HLE.modules.IoFileMgrForUser;
import jpcsp.HLE.modules.IoFileMgrForUser.IoOperation;
import jpcsp.HLE.modules.IoFileMgrForUser.IoOperationTiming;
import jpcsp.filesystems.umdiso.UmdIsoReader;
import jpcsp.format.PBP;
import jpcsp.settings.Settings;
import jpcsp.util.Utilities;
public class XmbIsoVirtualFile extends AbstractVirtualFile {
protected static class PbpSection {
int index;
int size;
int offset;
boolean availableInContents;
String umdFilename;
File cacheFile;
}
private String umdFilename;
private String umdName;
protected long filePointer;
protected long totalLength;
protected static final String[] umdFilenames = new String [] {
"PSP_GAME/PARAM.SFO",
"PSP_GAME/ICON0.PNG",
"PSP_GAME/ICON1.PMF",
"PSP_GAME/PIC0.PNG",
"PSP_GAME/PIC1.PNG",
"PSP_GAME/SND0.AT3"
};
protected byte[] contents;
protected PbpSection[] sections;
public XmbIsoVirtualFile(String umdFilename) {
super(null);
this.umdFilename = umdFilename;
umdName = new File(umdFilename).getName();
File cacheDirectory = new File(getCacheDirectory());
boolean createCacheFiles = !cacheDirectory.isDirectory();
if (createCacheFiles) {
cacheDirectory.mkdirs();
}
try {
UmdIsoReader iso = new UmdIsoReader(umdFilename);
IVirtualFileSystem vfs = new UmdIsoVirtualFileSystem(iso);
sections = new PbpSection[umdFilenames.length + 1];
sections[0] = new PbpSection();
sections[0].index = 0;
sections[0].offset = 0;
sections[0].size = 0x28;
sections[0].availableInContents = true;
int offset = 0x28;
SceIoStat stat = new SceIoStat();
for (int i = 0; i < umdFilenames.length; i++) {
PbpSection section = new PbpSection();
section.index = i + 1;
section.offset = offset;
section.umdFilename = umdFilenames[i];
if (vfs.ioGetstat(section.umdFilename, stat) >= 0) {
section.size = (int) stat.size;
}
String cacheFileName = getCacheFileName(section);
File cacheFile = new File(cacheFileName);
// Create only cache files for PARAM.SFO and ICON0.PNG
if (createCacheFiles && i < 2) {
IVirtualFile vFile = vfs.ioOpen(section.umdFilename, IoFileMgrForUser.PSP_O_RDONLY, 0);
if (vFile != null) {
section.size = (int) vFile.length();
byte[] buffer = new byte[section.size];
int length = vFile.ioRead(buffer, 0, buffer.length);
vFile.ioClose();
OutputStream os = new FileOutputStream(cacheFile);
os.write(buffer, 0, length);
os.close();
}
}
if (cacheFile.canRead()) {
section.cacheFile = cacheFile;
}
sections[section.index] = section;
offset += section.size;
}
totalLength = offset;
contents = new byte[offset];
ByteBuffer buffer = ByteBuffer.wrap(contents).order(ByteOrder.LITTLE_ENDIAN);
buffer.putInt(PBP.PBP_MAGIC);
buffer.putInt(0x10000); // version
for (int i = 1; i < sections.length; i++) {
buffer.putInt(sections[i].offset);
}
vfs.ioExit();
} catch (FileNotFoundException e) {
log.debug("XmbIsoVirtualFile", e);
} catch (IOException e) {
log.debug("XmbIsoVirtualFile", e);
}
}
protected String getCacheDirectory() {
return String.format("%1$s%2$cUmdBrowserCache%2$c%3$s", Settings.getInstance().readString("emu.tmppath"), File.separatorChar, umdName);
}
protected String getCacheFileName(PbpSection section) {
return String.format("%s%c%s", getCacheDirectory(), File.separatorChar, section.umdFilename.substring(9));
}
protected void readSection(PbpSection section) {
if (section.size > 0) {
try {
if (section.cacheFile != null) {
if (log.isDebugEnabled()) {
log.debug(String.format("XmbIsoVirtualFile.readSection from Cache %s", section.cacheFile));
}
InputStream is = new FileInputStream(section.cacheFile);
is.read(contents, section.offset, section.size);
is.close();
} else {
if (log.isDebugEnabled()) {
log.debug(String.format("XmbIsoVirtualFile.readSection from UMD %s", section.umdFilename));
}
UmdIsoReader iso = new UmdIsoReader(umdFilename);
IVirtualFileSystem vfs = new UmdIsoVirtualFileSystem(iso);
IVirtualFile vFile = vfs.ioOpen(section.umdFilename, IoFileMgrForUser.PSP_O_RDONLY, 0);
if (vFile != null) {
vFile.ioRead(contents, section.offset, section.size);
vFile.ioClose();
}
vfs.ioExit();
}
} catch (IOException e) {
log.debug("readSection", e);
}
// PARAM.SFO?
if (section.index == 1) {
// Patch the CATEGORY in the PARAM.SFO:
// the VSH is checking that the CATEGORY value is starting
// with 'M' (meaning MemoryStick) and not 'U' (UMD).
// Change the first letter 'U' into 'M'.
int offset = section.offset;
int keyTableOffset = readUnaligned32(contents, offset + 8) + offset;
int valueTableOffset = readUnaligned32(contents, offset + 12) + offset;
int numberKeys = readUnaligned32(contents, offset + 16);
for (int i = 0; i < numberKeys; i++) {
int keyOffset = readUnaligned16(contents, offset + 20 + i * 16);
String key = Utilities.readStringZ(contents, keyTableOffset + keyOffset);
if ("CATEGORY".equals(key)) {
int valueOffset = readUnaligned32(contents, offset + 20 + i * 16 + 12);
char valueFirstChar = (char) contents[valueTableOffset + valueOffset];
// Change the first letter 'U' into 'M'.
if (valueFirstChar == 'U') {
contents[valueTableOffset + valueOffset] = 'M';
}
break;
}
}
}
}
section.availableInContents = true;
}
protected int ioRead(PbpSection section, TPointer outputPointer, int offset, int length) {
if (filePointer < section.offset || filePointer >= section.offset + section.size) {
return 0;
}
length = Math.min(length, section.size - (int) (filePointer - section.offset));
if (length > 0) {
if (!section.availableInContents) {
readSection(section);
}
outputPointer.setArray(offset, contents, (int) filePointer, length);
}
return length;
}
@Override
public int ioRead(TPointer outputPointer, int outputLength) {
int remaining = (int) Math.min(outputLength, contents.length - filePointer);
int offset = 0;
for (int i = 0; remaining > 0 && i < sections.length; i++) {
int length = ioRead(sections[i], outputPointer, offset, remaining);
filePointer += length;
offset += length;
remaining -= length;
}
return offset;
}
@Override
public int ioRead(byte[] outputBuffer, int outputOffset, int outputLength) {
return super.ioRead(outputBuffer, outputOffset, outputLength);
}
@Override
public long length() {
return totalLength;
}
@Override
public long ioLseek(long offset) {
filePointer = offset;
return filePointer;
}
@Override
public int ioClose() {
filePointer = 0L;
return 0;
}
@Override
public Map<IoOperation, IoOperationTiming> getTimings() {
// Do not delay IO operations on faked EBOOT.PBP files
return IoFileMgrForUser.noDelayTimings;
}
public IVirtualFile ioReadForLoadExec() throws FileNotFoundException, IOException {
UmdIsoReader iso = getIsoReader();
IVirtualFileSystem vfs = new UmdIsoVirtualFileSystem(iso);
return vfs.ioOpen("PSP_GAME/SYSDIR/EBOOT.BIN", IoFileMgrForUser.PSP_O_RDONLY, 0);
}
public UmdIsoReader getIsoReader() throws FileNotFoundException, IOException {
return new UmdIsoReader(umdFilename);
}
}