Checking in GameLoader.cpp, GameLoader.h, and ROMSet.h (not currently used in build) because I need to make a substantial modification in my local tree

This commit is contained in:
Bart Trzynadlowski 2017-03-26 16:38:55 +00:00
parent 9e19b46692
commit ba3a0b5e2a
4 changed files with 530 additions and 76 deletions

View file

@ -1,12 +1,9 @@
#ifndef INCLUDED_GAME_H
#define INCLUDED_GAME_H
#include "Util/NewConfig.h"
#include "Pkgs/unzip.h"
#include <map>
#include <set>
#include <string>
#include <memory>
//TODO: separate Game and ROM structs
struct Game
{
std::string name;
@ -45,77 +42,6 @@ struct Game
INPUT_ALL = 0x003FFFFF
};
uint32_t inputs = 0;
// Holds a ROM region
struct ROM
{
std::shared_ptr<uint8_t> data;
size_t size = 0;
void CopyTo(uint8_t *dest, size_t dest_size) const;
};
std::map<std::string, ROM> roms;
ROM get_rom(const std::string &region) const
{
auto it = roms.find(region);
return it == roms.end() ? ROM() : it->second;
}
};
//TODO: move to separate GameLoader.cpp
class GameLoader
{
private:
// Describes a file node in the game XML
struct File
{
typedef std::shared_ptr<File> ptr_t;
uint32_t offset;
std::string filename;
uint32_t crc32;
bool has_crc32;
static ptr_t Create(const GameLoader &loader, const Util::Config::Node &file_node);
bool Matches(const std::string &filename, uint32_t crc32) const;
};
// Describes a region node in the game XML
struct Region
{
typedef std::shared_ptr<Region> ptr_t;
std::string region_name;
size_t stride;
size_t chunk_size;
bool byte_swap;
typedef std::map<uint32_t, File::ptr_t> FilesByOffset_t;
FilesByOffset_t files_by_offset;
static ptr_t Create(const GameLoader &loader, const Util::Config::Node &region_node);
};
std::map<std::string, Game> m_game_info_by_game; // ROMs not loaded here
// Parsed XML
typedef std::map<std::string, Region::ptr_t> RegionsByName_t;
std::map<std::string, RegionsByName_t> m_regions_by_game;
std::string m_xml_filename;
// Zip file info
std::string m_zipfilename;
unzFile m_zf;
std::map<uint32_t, std::string> m_filename_by_crc32;
std::map<std::string, unz_file_info> m_zip_info_by_filename;
std::map<uint32_t, unz_file_info> m_zip_info_by_crc32;
static bool MissingAttrib(const GameLoader &loader, const Util::Config::Node &node, const std::string &attribute);
bool ParseXML(const Util::Config::Node &xml);
std::set<std::string> IdentifyGamesFileBelongsTo(const std::string &filename, uint32_t crc32) const;
const unz_file_info *LookupZippedFile(const File::ptr_t &file) const;
bool ComputeRegionSize(uint32_t *region_size, const Region::ptr_t &region) const;
bool LoadZippedFile(std::shared_ptr<uint8_t> *buffer, size_t *file_size, const GameLoader::File::ptr_t &file);
bool LoadRegion(Game::ROM *buffer, const GameLoader::Region::ptr_t &region);
bool LoadROMs(std::map<std::string, Game::ROM> *roms, const std::string &game_name);
bool LoadDefinitionXML(const std::string &filename);
public:
GameLoader(const Util::Config::Node &config);
bool Load(Game *game, const std::string &zipfilename);
};
#endif // INCLUDED_GAME_H

421
Src/GameLoader.cpp Normal file
View file

@ -0,0 +1,421 @@
#include "GameLoader.h"
#include "OSD/Logger.h"
#include "Util/NewConfig.h"
#include "Util/ConfigBuilders.h"
#include "Util/ByteSwap.h"
#include <algorithm>
#include <iostream>
bool GameLoader::MissingAttrib(const GameLoader &loader, const Util::Config::Node &node, const std::string &attribute)
{
if (node[attribute].Empty())
{
ErrorLog("%s: <%s> tag is missing required attribute '%s'.", loader.m_xml_filename.c_str(), node.Key().c_str(), attribute.c_str());
return true;
}
return false;
}
GameLoader::File::ptr_t GameLoader::File::Create(const GameLoader &loader, const Util::Config::Node &file_node)
{
if (GameLoader::MissingAttrib(loader, file_node, "name") | GameLoader::MissingAttrib(loader, file_node, "offset"))
return ptr_t();
ptr_t file = std::make_shared<File>();
file->offset = file_node["offset"].ValueAs<uint32_t>();
file->filename = Util::ToLower(file_node["name"].ValueAs<std::string>());
file->has_crc32 = file_node["crc32"].Exists();
file->crc32 = file->has_crc32 ? file_node["crc32"].ValueAs<uint32_t>() : 0;
return file;
}
bool GameLoader::File::Matches(const std::string &filename_to_match, uint32_t crc32_to_match) const
{
if (has_crc32)
return crc32_to_match == crc32;
return Util::ToLower(filename_to_match) == filename;
}
GameLoader::Region::ptr_t GameLoader::Region::Create(const GameLoader &loader, const Util::Config::Node &region_node)
{
if (GameLoader::MissingAttrib(loader, region_node, "name") | MissingAttrib(loader, region_node, "stride") | GameLoader::MissingAttrib(loader, region_node, "chunk_size"))
return ptr_t();
ptr_t region = std::make_shared<Region>();
region->region_name = region_node["name"].Value<std::string>();
region->stride = region_node["stride"].ValueAs<size_t>();
region->chunk_size = region_node["chunk_size"].ValueAs<size_t>();
region->byte_swap = region_node["byte_swap"].ValueAsDefault<bool>(false);
return region;
}
static void PopulateGameInfo(Game *game, const Util::Config::Node &game_node)
{
game->name = game_node["name"].ValueAs<std::string>();
game->title = game_node["identity/title"].ValueAsDefault<std::string>("Unknown");
game->version = game_node["identity/version"].ValueAsDefault<std::string>("");
game->manufacturer = game_node["identity/manufacturer"].ValueAsDefault<std::string>("Unknown");
game->year = game_node["identity/year"].ValueAsDefault<unsigned>(0);
game->stepping = game_node["hardware/stepping"].ValueAsDefault<std::string>("");
game->mpeg_board = game_node["hardware/mpeg_board"].ValueAsDefault<std::string>("");
game->encryption_key = game_node["hardware/encryption_key"].ValueAsDefault<uint32_t>(0);
std::map<std::string, uint32_t> input_flags
{
{ "common", Game::INPUT_COMMON },
{ "vehicle", Game::INPUT_VEHICLE },
{ "joystick1", Game::INPUT_JOYSTICK1 },
{ "joystick2", Game::INPUT_JOYSTICK2 },
{ "fighting", Game::INPUT_FIGHTING },
{ "vr4", Game::INPUT_VR4 },
{ "viewchange", Game::INPUT_VIEWCHANGE },
{ "shift4", Game::INPUT_SHIFT4 },
{ "shiftupdown", Game::INPUT_SHIFTUPDOWN },
{ "handbrake", Game::INPUT_HANDBRAKE },
{ "harley", Game::INPUT_HARLEY },
{ "gun1", Game::INPUT_GUN1 },
{ "gun2", Game::INPUT_GUN2 },
{ "analog_joystick", Game::INPUT_ANALOG_JOYSTICK },
{ "twin_joysticks", Game::INPUT_TWIN_JOYSTICKS },
{ "soccer", Game::INPUT_SOCCER },
{ "spikeout", Game::INPUT_SPIKEOUT },
{ "analog_gun1", Game::INPUT_ANALOG_GUN1 },
{ "analog_gun2", Game::INPUT_ANALOG_GUN2 },
{ "ski", Game::INPUT_SKI },
{ "magtruck", Game::INPUT_MAGTRUCK },
{ "fishing", Game::INPUT_FISHING }
};
for (auto &node: game_node["hardware/inputs"])
{
if (node.Key() == "input" && node["type"].Exists())
{
const std::string input_type = node["type"].ValueAs<std::string>();
game->inputs |= input_flags[input_type];
}
}
}
bool GameLoader::ParseXML(const Util::Config::Node &xml)
{
for (auto it = xml.begin(); it != xml.end(); ++it)
{
// Game node
auto &game_node = *it;
if (game_node.Key() != "game")
continue;
if (game_node["name"].Empty())
{
//TODO: associate line numbers in config
//ErrorLog("%s: Ignoring <game> tag with missing 'name' attribute.", m_xml_filename.c_str());
continue;
}
std::string game_name = game_node["name"].ValueAs<std::string>();
if (m_regions_by_game.find(game_name) != m_regions_by_game.end())
{
ErrorLog("%s: Ignoring redefinition of game '%s'.", m_xml_filename.c_str(), game_name.c_str());
continue;
}
RegionsByName_t &regions_by_name = m_regions_by_game[game_name];
PopulateGameInfo(&m_game_info_by_game[game_name], game_node);
for (auto it = game_node.begin(); it != game_node.end(); ++it)
{
auto &roms_node = *it;
if (roms_node.Key() != "roms")
continue;
// Regions defined uniquely per game
for (auto it = roms_node.begin(); it != roms_node.end(); ++it)
{
auto &region_node = *it;
if (region_node.Key() != "region")
continue;
Region::ptr_t region = Region::Create(*this, region_node);
if (!region)
continue;
if (regions_by_name.find(region->region_name) != regions_by_name.end())
{
ErrorLog("%s: Ignoring redefinition of region '%s' of '%s'.", m_xml_filename.c_str(), region->region_name.c_str(), game_name.c_str());
continue;
}
Region::FilesByOffset_t &files_by_offset = region->files_by_offset;
// Files defined uniquely per region
for (auto it = region_node.begin(); it != region_node.end(); ++it)
{
auto &file_node = *it;
if (file_node.Key() != "file")
continue;
File::ptr_t file = File::Create(*this, file_node);
if (!file)
continue;
// Ensure file offset not defined multiple times. We allow the same
// file to be reused, however (e.g., a blank file loaded at multiple
// offsets).
if (files_by_offset.find(file->offset) != files_by_offset.end())
{
ErrorLog("%s: Ignoring redefinition of offset 0x%x in region '%s' of '%s'.", m_xml_filename.c_str(), file->offset, region->region_name.c_str(), game_name.c_str());
continue;
}
files_by_offset[file->offset] = file;
}
// Check to ensure that some files were defined in the region
if (files_by_offset.empty())
ErrorLog("%s: No files defined in region '%s' of '%s'.", m_xml_filename.c_str(), region->region_name.c_str(), game_name.c_str());
else
regions_by_name[region->region_name] = region;
}
}
// Check to ensure that some ROM regions were defined for the game
if (regions_by_name.empty())
ErrorLog("%s: No ROM regions defined for '%s'.", m_xml_filename.c_str(), game_name.c_str());
}
// Check to ensure some games were defined
if (m_regions_by_game.empty())
{
ErrorLog("%s: No games defined.", m_xml_filename.c_str());
return true;
}
return false;
}
std::set<std::string> GameLoader::IdentifyGamesFileBelongsTo(const std::string &filename, uint32_t crc32) const
{
std::set<std::string> games;
for (auto &v_game: m_regions_by_game)
{
const std::string &game_name = v_game.first;
auto &regions_by_name = v_game.second;
for (auto &v_region: regions_by_name)
{
Region::ptr_t region = v_region.second;
for (auto &v_file: region->files_by_offset)
{
File::ptr_t file = v_file.second;
if (file->Matches(filename, crc32))
games.insert(game_name);
}
}
}
return games;
}
const unz_file_info *GameLoader::LookupZippedFile(const File::ptr_t &file) const
{
if (file->has_crc32)
{
auto it = m_zip_info_by_crc32.find(file->crc32);
if (it != m_zip_info_by_crc32.end())
return &it->second;
ErrorLog("'%s' with CRC32 0x%08x not found in '%s'.", file->filename.c_str(), file->crc32, m_zipfilename.c_str());
return 0;
}
auto it = m_zip_info_by_filename.find(file->filename);
if (it != m_zip_info_by_filename.end())
return &it->second;
ErrorLog("'%s' not found in '%s'.", file->filename.c_str(), m_zipfilename.c_str());
return 0;
}
bool GameLoader::ComputeRegionSize(uint32_t *region_size, const GameLoader::Region::ptr_t &region) const
{
// Files in region need not be loaded contiguously. To find region size,
// use maximum end_addr = offset + stride * (num_chunks - 1) + chunk_size.
std::vector<uint32_t> end_addr;
bool error = false;
for (auto &v_file: region->files_by_offset)
{
auto &file = v_file.second;
const unz_file_info *info = LookupZippedFile(file);
if (info)
{
if (info->uncompressed_size % region->chunk_size != 0)
{
std::string filename = file->filename;
auto it = m_filename_by_crc32.find(info->crc);
if (it != m_filename_by_crc32.end())
filename = it->second;
ErrorLog("File '%s' in '%s' is not sized in %d-byte chunks.", filename.c_str(), m_zipfilename.c_str(), region->chunk_size);
error = true;
}
uint32_t num_chunks = info->uncompressed_size / region->chunk_size;
end_addr.push_back(file->offset + region->stride * (num_chunks - 1) + region->chunk_size);
}
else
error = true;
}
if (!error)
*region_size = *std::max_element(end_addr.begin(), end_addr.end());
return error;
}
bool GameLoader::LoadZippedFile(std::shared_ptr<uint8_t> *buffer, size_t *file_size, const GameLoader::File::ptr_t &file)
{
unz_file_info info;
for (int err = unzGoToFirstFile(m_zf); err == UNZ_OK; err = unzGoToNextFile(m_zf))
{
char current_filename[256];
if (UNZ_OK != unzGetCurrentFileInfo(m_zf, &info, current_filename, sizeof(current_filename), NULL, 0, NULL, 0))
continue;
if (file->Matches(current_filename, info.crc))
{
// Found file, load it!
err = unzOpenCurrentFile(m_zf);
if (UNZ_OK != err)
{
ErrorLog("Unable to read '%s' from '%s'. Is zip file corrupt?", current_filename, m_zipfilename.c_str());
return true;
}
*file_size = info.uncompressed_size;
buffer->reset(new uint8_t[*file_size], std::default_delete<uint8_t[]>());
ZPOS64_T bytes_read = unzReadCurrentFile(m_zf, buffer->get(), *file_size);
if (bytes_read != *file_size)
{
ErrorLog("Unable to read '%s' from '%s'. Is zip file corrupt?", current_filename, m_zipfilename.c_str());
unzCloseCurrentFile(m_zf);
return true;
}
err = unzCloseCurrentFile(m_zf);
if (UNZ_CRCERROR == err)
ErrorLog("CRC error reading '%s' from '%s'. File may be corrupt.", current_filename, m_zipfilename.c_str());
return false;
}
}
if (file->has_crc32)
ErrorLog("'%s' with CRC32 0x%08x not found in '%s'.", file->filename.c_str(), file->crc32, m_zipfilename.c_str());
else
ErrorLog("'%s' not found in '%s'.", file->filename.c_str(), m_zipfilename.c_str());
return true;
}
bool GameLoader::LoadRegion(ROM *rom, const GameLoader::Region::ptr_t &region)
{
bool error = false;
for (auto &v_file: region->files_by_offset)
{
auto &file = v_file.second;
std::shared_ptr<uint8_t> tmp;
size_t file_size;
error |= LoadZippedFile(&tmp, &file_size, file);
if (!error)
{
size_t num_chunks = file_size / region->chunk_size;
for (size_t i = 0; i < num_chunks; i++)
{
uint8_t *dest = rom->data.get() + file->offset + i * region->stride;
uint8_t *src = tmp.get() + i * region->chunk_size;
memcpy(dest, src, region->chunk_size);
}
}
}
if (region->byte_swap)
Util::FlipEndian16(rom->data.get(), rom->size);
return error;
}
bool GameLoader::LoadROMs(ROMSet *rom_set, const std::string &game_name)
{
auto it = m_regions_by_game.find(game_name);
if (it == m_regions_by_game.end())
{
ErrorLog("Game '%s' not found in '%s'.", game_name.c_str(), m_zipfilename.c_str());
return true;
}
bool error = false;
auto &regions_by_name = it->second;
for (auto &v_region: regions_by_name)
{
auto &region = v_region.second;
uint32_t region_size = 0;
if (ComputeRegionSize(&region_size, region))
error |= true;
else
{
auto &rom = rom_set->rom_by_region[region->region_name];
rom.size = region_size;
rom.data.reset(new uint8_t[region_size], std::default_delete<uint8_t[]>());
error |= LoadRegion(&rom, region);
}
}
return error;
}
bool GameLoader::LoadDefinitionXML(const std::string &filename)
{
m_xml_filename = filename;
Util::Config::Node xml("xml");
if (Util::Config::FromXMLFile(&xml, filename))
return true;
return ParseXML(xml);
}
bool GameLoader::Load(Game *game, ROMSet *rom_set, const std::string &zipfilename)
{
*game = Game();
m_zf = NULL;
m_filename_by_crc32.clear();
m_zip_info_by_filename.clear();
m_zip_info_by_crc32.clear();
m_zipfilename = zipfilename;
m_zf = unzOpen(zipfilename.c_str());
if (NULL == m_zf)
{
ErrorLog("Could not open '%s'.", zipfilename.c_str());
return true;
}
// Identify all files in zip archive
int err = UNZ_OK;
for (err = unzGoToFirstFile(m_zf); err == UNZ_OK; err = unzGoToNextFile(m_zf))
{
unz_file_info file_info;
char filename_buffer[256];
if (UNZ_OK != unzGetCurrentFileInfo(m_zf, &file_info, filename_buffer, sizeof(filename_buffer), NULL, 0, NULL, 0))
continue;
std::string filename = Util::ToLower(filename_buffer);
m_zip_info_by_filename[filename] = file_info;
m_zip_info_by_crc32[file_info.crc] = file_info;
m_filename_by_crc32[file_info.crc] = filename;
}
if (err != UNZ_END_OF_LIST_OF_FILE)
{
ErrorLog("Unable to read the contents of '%s' (code 0x%x).", zipfilename.c_str(), err);
return true;
}
// Which game is this?
std::map<std::string, size_t> files_per_game;
std::set<std::string> all_games_found;
using value_type = std::pair<std::string, size_t>;
for (auto &v: m_filename_by_crc32)
{
std::set<std::string> games = IdentifyGamesFileBelongsTo(v.second, v.first);
all_games_found.insert(games.begin(), games.end());
for (auto &game: games)
files_per_game[game]++;
}
auto v = std::max_element(files_per_game.begin(), files_per_game.end(), [](const value_type &v1, const value_type &v2) { return v1.second < v2.second; });
if (v == files_per_game.end())
{
ErrorLog("No valid Model 3 ROMs found in '%s'.", zipfilename.c_str());
return true;
}
std::string game_name = v->first;
if (files_per_game.size() != 1)
ErrorLog("Multiple games found in '%s' (%s). Loading '%s'.", zipfilename.c_str(), std::string(Util::Format(", ").Join(all_games_found)).c_str(), game_name.c_str());
// Load it
*game = m_game_info_by_game[game_name];
bool error = LoadROMs(rom_set, game_name);
if (error)
*game = Game();
unzClose(m_zf);
return error;
}
GameLoader::GameLoader(const std::string &xml_file)
{
LoadDefinitionXML(xml_file);
}

72
Src/GameLoader.h Normal file
View file

@ -0,0 +1,72 @@
#ifndef INCLUDED_GAMELOADER_H
#define INCLUDED_GAMELOADER_H
#include "Util/NewConfig.h"
#include "Pkgs/unzip.h"
#include "Game.h"
#include "ROMSet.h"
#include <map>
#include <set>
class GameLoader
{
private:
// Describes a file node in the game XML
struct File
{
typedef std::shared_ptr<File> ptr_t;
uint32_t offset;
std::string filename;
uint32_t crc32;
bool has_crc32;
static ptr_t Create(const GameLoader &loader, const Util::Config::Node &file_node);
bool Matches(const std::string &filename, uint32_t crc32) const;
};
// Describes a region node in the game XML
struct Region
{
typedef std::shared_ptr<Region> ptr_t;
std::string region_name;
size_t stride;
size_t chunk_size;
bool byte_swap;
typedef std::map<uint32_t, File::ptr_t> FilesByOffset_t;
FilesByOffset_t files_by_offset;
static ptr_t Create(const GameLoader &loader, const Util::Config::Node &region_node);
};
std::map<std::string, Game> m_game_info_by_game; // ROMs not loaded here
// Parsed XML
typedef std::map<std::string, Region::ptr_t> RegionsByName_t;
std::map<std::string, RegionsByName_t> m_regions_by_game;
std::string m_xml_filename;
// Zip file info
std::string m_zipfilename;
unzFile m_zf;
std::map<uint32_t, std::string> m_filename_by_crc32;
std::map<std::string, unz_file_info> m_zip_info_by_filename;
std::map<uint32_t, unz_file_info> m_zip_info_by_crc32;
static bool MissingAttrib(const GameLoader &loader, const Util::Config::Node &node, const std::string &attribute);
bool ParseXML(const Util::Config::Node &xml);
std::set<std::string> IdentifyGamesFileBelongsTo(const std::string &filename, uint32_t crc32) const;
const unz_file_info *LookupZippedFile(const File::ptr_t &file) const;
bool ComputeRegionSize(uint32_t *region_size, const Region::ptr_t &region) const;
bool LoadZippedFile(std::shared_ptr<uint8_t> *buffer, size_t *file_size, const GameLoader::File::ptr_t &file);
bool LoadRegion(ROM *buffer, const GameLoader::Region::ptr_t &region);
bool LoadROMs(ROMSet *rom_set, const std::string &game_name);
bool LoadDefinitionXML(const std::string &filename);
public:
GameLoader(const std::string &xml_file);
bool Load(Game *game, ROMSet *rom_set, const std::string &zipfilename);
const std::map<std::string, Game> &GetGames() const
{
return m_game_info_by_game;
}
};
#endif // INCLUDED_GAMELOADER_H

35
Src/ROMSet.h Normal file
View file

@ -0,0 +1,35 @@
#ifndef INCLUDED_ROMSET_H
#define INCLUDED_ROMSET_H
#include <memory>
#include <string>
#include <map>
// Holds a single ROM region
struct ROM
{
std::shared_ptr<uint8_t> data;
size_t size = 0;
void CopyTo(uint8_t *dest, size_t dest_size) const
{
if (!data || !size || !dest || !dest_size)
return;
size_t bytes_to_copy = std::min(size, dest_size);
const uint8_t *src = data.get();
memcpy(dest, src, bytes_to_copy);
}
};
struct ROMSet
{
std::map<std::string, ROM> rom_by_region;
ROM get_rom(const std::string &region) const
{
auto it = rom_by_region.find(region);
return it == rom_by_region.end() ? ROM() : it->second;
}
};
#endif // INCLUDED_ROMSET_H