BizHawk/mednafen/src/PSFLoader.cpp

337 lines
7.3 KiB
C++

/* Mednafen - Multi-system Emulator
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
TODO:
Time string parsing convenience functions.
Character set autodetect heuristics and conversion for when the "utf8" tag is missing.
*/
#include "mednafen.h"
#include "PSFLoader.h"
#include "mednafen-endian.h"
#include "general.h"
#include <limits.h>
#include <trio/trio.h>
#include <ctype.h>
//#include <iconv.h>
#include <zlib.h>
PSFTags::PSFTags()
{
}
PSFTags::~PSFTags()
{
}
void PSFTags::AddTag(char *tag_line)
{
char *eq;
// Transform 0x01-0x1F -> 0x20
for(unsigned int i = 0; i < strlen(tag_line); i++)
if((unsigned char)tag_line[i] < 0x20)
tag_line[i] = 0x20;
eq = strchr(tag_line, '=');
if(eq)
{
*eq = 0;
MDFN_trim(tag_line);
MDFN_trim(eq + 1);
for(unsigned int i = 0; i < strlen(tag_line); i++)
tag_line[i] = tolower(tag_line[i]);
if(TagExists(tag_line))
tags[tag_line] = tags[std::string(tag_line)] + std::string(1, '\n') + std::string(eq + 1);
else
tags[tag_line] = std::string(eq + 1);
}
}
#if 0
static const char *DetectCharset(const uint8 *data, const uint32 data_size)
{
static const char *TestCharsets[] = { "UTF-8", /*"SJIS",*/ "WINDOWS-1252" };
for(unsigned int i = 0; i < sizeof(TestCharsets) / sizeof(TestCharsets[0]); i++)
{
iconv_t cd;
cd = iconv_open("UTF-32", TestCharsets[i]);
if(cd != (iconv_t)-1)
{
size_t in_len = data_size;
size_t out_len = data_size * 4 + 4;
char *in_ptr = (char *)data;
char *const out_ptr_mem = new char[out_len];
char *out_ptr = out_ptr_mem;
if(iconv(cd, (ICONV_CONST char **)&in_ptr, &in_len, &out_ptr, &out_len) != (size_t)-1)
{
delete[] out_ptr_mem;
return(TestCharsets[i]);
}
delete[] out_ptr_mem;
}
}
return(NULL);
}
#endif
void PSFTags::LoadTags(const uint8 *data_in, uint32 size)
{
std::vector<char> tags_heap;
char *data;
char *spos;
//const char *detected_charset = DetectCharset(data_in, size);
tags_heap.resize(size + 1);
tags_heap[size] = 0;
memcpy(&tags_heap[0], data_in, size);
data = &tags_heap[0];
spos = data;
while(size)
{
if(*data == 0x0A || *data == 0x00)
{
*data = 0;
if(data - spos)
{
if(*(data - 1) == 0xD) // handle \r
*(data - 1) = 0;
AddTag(spos);
}
spos = data + 1; // Skip \n for next tag
}
size--;
data++;
}
}
int64 PSFTags::GetTagI(const char *name)
{
std::map<std::string, std::string>::iterator it;
it = tags.find(name);
if(it != tags.end())
{
long long ret = 0;
std::string &tmp = tags[name];
trio_sscanf(tmp.c_str(), "%lld", &ret);
return(ret);
}
return(0); // INT64_MIN
}
bool PSFTags::TagExists(const char *name)
{
if(tags.find(name) != tags.end())
return(true);
return(false);
}
std::string PSFTags::GetTag(const char *name)
{
std::map<std::string, std::string>::iterator it;
it = tags.find(name);
if(it != tags.end())
return(it->second);
return("");
}
void PSFTags::EraseTag(const char *name)
{
std::map<std::string, std::string>::iterator it;
it = tags.find(name);
if(it != tags.end())
tags.erase(it);
}
PSFLoader::PSFLoader()
{
}
PSFLoader::~PSFLoader()
{
}
bool PSFLoader::TestMagic(uint8 version, MDFNFILE *fp)
{
if(fp->size < (3 + 1 + 4 + 4 + 4))
return(false);
if(memcmp(fp->data, "PSF", 3))
return(false);
if(fp->data[3] != version)
return(false);
return(true);
}
PSFTags PSFLoader::LoadInternal(uint8 version, uint32 max_exe_size, MDFNFILE *fp, uint32 level, bool force_ignore_pcsp)
{
uint32 reserved_size, compressed_size, compressed_crc32;
bool _lib_present = false;
PSFTags tags;
std::vector<uint8> decompress_buffer;
uLongf decompress_len;
if(!TestMagic(version, fp))
throw(MDFN_Error(0, _("Not a PSF(version=0x%02x) file!"), version));
reserved_size = MDFN_de32lsb(fp->data + 4);
compressed_size = MDFN_de32lsb(fp->data + 8);
compressed_crc32 = MDFN_de32lsb(fp->data + 12);
if(fp->size < (16 + reserved_size + compressed_size))
throw(MDFN_Error(0, _("PSF is missing at least %u bytes of data!"), 16 + reserved_size + compressed_size - fp->size));
if(crc32(0, fp->data + 16 + reserved_size, compressed_size) != compressed_crc32)
throw(MDFN_Error(0, _("PSF compressed CRC32 mismatch(data is corrupt)!")));
{
const uint8 *tag_section = fp->data + 16 + reserved_size + compressed_size;
uint32 tag_section_size = fp->size - 16 - reserved_size - compressed_size;
if(tag_section_size > 5 && !memcmp(tag_section, "[TAG]", 5))
tags.LoadTags(tag_section + 5, tag_section_size - 5);
}
//
// Handle minipsf simple _lib
//
if(level < 15)
{
if(tags.TagExists("_lib"))
{
std::string tp = tags.GetTag("_lib");
if(!MDFN_IsFIROPSafe(tp))
{
throw(MDFN_Error(0, _("Referenced path \"%s\" is potentially unsafe. See \"filesys.untrusted_fip_check\" setting."), tp.c_str()));
}
MDFNFILE subfile(MDFN_MakeFName(MDFNMKF_AUX, 0, tp.c_str()).c_str(), NULL, NULL);
LoadInternal(version, max_exe_size, &subfile, level + 1);
_lib_present = true;
}
}
//
//
//
decompress_buffer.resize(max_exe_size);
decompress_len = max_exe_size;
switch( uncompress((Bytef *)&decompress_buffer[0], &decompress_len, (const Bytef *)(fp->data + 16 + reserved_size), compressed_size) )
{
default:
throw(MDFN_Error(0, "zlib unknown error"));
case Z_OK: break;
case Z_MEM_ERROR:
throw(MDFN_Error(0, "zlib Z_MEM_ERROR"));
case Z_BUF_ERROR:
throw(MDFN_Error(0, _("PSF decompressed size exceeds maximum allowed!")));
case Z_DATA_ERROR:
throw(MDFN_Error(0, _("PSF compressed data is bad.")));
}
HandleReserved(fp->data + 16, reserved_size);
HandleEXE(&decompress_buffer[0], decompress_len, force_ignore_pcsp | _lib_present);
decompress_buffer.resize(0);
//
// handle libN
//
if(level < 15)
{
for(unsigned int n = 2; n <= INT_MAX; n++)
{
char tmpbuf[32];
trio_snprintf(tmpbuf, 32, "_lib%d", (int)n);
if(tags.TagExists(tmpbuf))
{
MDFNFILE subfile(MDFN_MakeFName(MDFNMKF_AUX, 0, tags.GetTag(tmpbuf).c_str()).c_str(), NULL, NULL);
LoadInternal(version, max_exe_size, &subfile, level + 1, true);
}
else
break;
}
}
return(tags);
}
PSFTags PSFLoader::Load(uint8 version, uint32 max_exe_size, MDFNFILE *fp)
{
return(LoadInternal(version, max_exe_size, fp, 0, false));
}
void PSFLoader::HandleReserved(const uint8 *data, uint32 len)
{
}
void PSFLoader::HandleEXE(const uint8 *data, uint32 len, bool ignore_pcsp)
{
}