diff --git a/Src/GameLoader.cpp b/Src/GameLoader.cpp index e078203..a32a0fa 100644 --- a/Src/GameLoader.cpp +++ b/Src/GameLoader.cpp @@ -5,6 +5,7 @@ #include "Util/ByteSwap.h" #include "Util/Format.h" #include +#include #include #include @@ -161,18 +162,68 @@ GameLoader::Region::ptr_t GameLoader::Region::Create(const GameLoader &loader, c { if (GameLoader::MissingAttrib(loader, region_node, "name") | MissingAttrib(loader, region_node, "stride") | GameLoader::MissingAttrib(loader, region_node, "chunk_size")) // no || to easier detect errors return ptr_t(); + + if (region_node["byte_swap"].Exists() && region_node["byte_layout"].Exists()) + { + ErrorLog("%s: '%s' region has both 'byte_swap' and 'byte_layout' attributes. Use one or the other.", loader.m_xml_filename.c_str(), region_node["name"].Value().c_str()); + return ptr_t(); + } + ptr_t region = std::make_shared(); + region->region_name = region_node["name"].Value(); + region->stride = region_node["stride"].ValueAs(); + if (region->stride == 0) + { + ErrorLog("%s: '%s' stride length must be greater than 0.", loader.m_xml_filename.c_str(), region->region_name.c_str()); + return ptr_t(); + } + region->chunk_size = region_node["chunk_size"].ValueAs(); - region->byte_swap = region_node["byte_swap"].ValueAsDefault(false); + if (region->chunk_size == 0) + { + ErrorLog("%s: '%s' chunk size must be greater than 0.", loader.m_xml_filename.c_str(), region->region_name.c_str()); + return ptr_t(); + } + region->required = region_node["required"].ValueAsDefault(true); + + // Byte layout. If byte_swap was specified, construct the byte swapped layout string based on + // stride size. If byte_swap is set to false, empty layout string is fine. + if (region_node["byte_swap"].Exists()) + { + if (region_node["byte_swap"].ValueAs()) + { + // Special case: if chunk size and stride are both 1, change them both to 2 so we can allow byte + // swapping (these values are used for singular ROMs that don't need to be merged; technically, + // the stride and chunk size should be 2 since they are 16-bit ROMs). + if (region->stride == 1 && region->chunk_size == 1) + { + region->stride = 2; + region->chunk_size = 2; + } + + std::string byte_layout; + for (size_t i = 0; i < region->stride; i++) + { + byte_layout += '0' + (i ^ 1); + } + region->byte_layout = byte_layout; + } + } + else + { + region->byte_layout = region_node["byte_layout"].ValueAsDefault(std::string()); + + } + return region; } bool GameLoader::Region::AttribsMatch(const ptr_t &other) const { - return stride == other->stride && chunk_size == other->chunk_size && byte_swap == other->byte_swap; + return stride == other->stride && chunk_size == other->chunk_size && byte_layout == other->byte_layout; } bool GameLoader::Region::FindFileIndexByOffset(size_t *idx, uint32_t offset) const @@ -451,7 +502,7 @@ void GameLoader::LogROMDefinition(const std::string &game_name, const RegionsByN InfoLog("%s:", game_name.c_str()); for (auto &v2: regions_by_name) { - InfoLog(" %s: stride=%zu, chunk size=%zu, byte swap=%d", v2.first.c_str(), v2.second->stride, v2.second->chunk_size, v2.second->byte_swap ? 1 : 0); + InfoLog(" %s: stride=%zu, chunk size=%zu, byte layout=%s", v2.first.c_str(), v2.second->stride, v2.second->chunk_size, v2.second->byte_layout.c_str()); for (auto &file: v2.second->files) { InfoLog(" %s, crc32=0x%08x, offset=0x%08x", file->filename.c_str(), file->crc32, file->offset); @@ -727,19 +778,78 @@ bool GameLoader::ComputeRegionSize(uint32_t *region_size, const GameLoader::Regi return error; } -// We need to preserve the absolute offsets in order for byte swapping to work -// properly when chunk size is 1 -static inline void CopyBytes(uint8_t *dest_base, uint32_t dest_offset, const uint8_t *src_base, uint32_t src_offset, uint32_t size, uint32_t byte_swap) +static bool ApplyLayout(ROM *rom, const std::string &byte_layout, size_t stride, const std::string ®ion_name) { - for (uint32_t i = 0; i < size; i++) + // Empty layout means do nothing + if (byte_layout.size() == 0) + return false; + + // Validate that the layout string includes the same number of bytes as the region stride. The + // stride is block size that the ROM files all contribute to. We also verify that each byte is + // used once and only once. + if (byte_layout.size() != stride) { - dest_base[(dest_offset + i) ^ byte_swap] = src_base[src_offset + i]; + ErrorLog("Byte layout of '%s' region does not match the stride length (%d bytes but should be %d bytes).", region_name.c_str(), byte_layout.size(), stride); + return true; } + + if (stride > 8) + { + ErrorLog("Region '%s' has stride larger than 8 (%d), which is currently unsupported.", region_name.c_str(), stride); + return true; + } + + std::vector byte_offsets; + for (char c: byte_layout) + { + if (isdigit(c)) + { + byte_offsets.push_back(c - '0'); + } + else + { + ErrorLog("Byte layout of '%s' region contains non-numeric characters. Use single-digit byte indices only.", region_name.c_str()); + return true; + } + } + + // Check all byte indices 0..N-1 are present + std::vector sorted(byte_offsets); + std::sort(sorted.begin(), sorted.end()); // ascending order + size_t expected_offset = 0; + for (size_t offset: sorted) + { + if (offset != expected_offset) + { + ErrorLog("Byte layout of '%s' region must specify all byte offsets exactly once.", region_name.c_str()); + return true; + } + expected_offset += 1; + } + + // Okay, all good. Now we can reshuffle the region memory according to layout. + uint8_t *buffer = new uint8_t[stride]; + uint8_t *dest = rom->data.get(); + for (size_t dest_offset = 0; (dest_offset + stride) <= rom->size; dest_offset += stride) + { + // Copy current region bytes to temporary buffer. The layout offsets refer to this original layout. + memcpy(buffer, dest + dest_offset, stride); + + // Place the bytes back into the ROM region in the layout order specified. + for (size_t i = 0; i < stride; i++) + { + dest[dest_offset + i] = buffer[byte_offsets[i]]; + } + } + delete [] buffer; + + return false; // no error } bool GameLoader::LoadRegion(ROM *rom, const GameLoader::Region::ptr_t ®ion, const ZipArchive &zip) const { bool error = false; + for (auto &file: region->files) { std::shared_ptr tmp; @@ -752,8 +862,6 @@ bool GameLoader::LoadRegion(ROM *rom, const GameLoader::Region::ptr_t ®ion, c if (region->chunk_size == region->stride) { memcpy(dest + file->offset, src, file_size); - if (region->byte_swap) - Util::FlipEndian16(dest + file->offset, file_size); } else { @@ -762,16 +870,21 @@ bool GameLoader::LoadRegion(ROM *rom, const GameLoader::Region::ptr_t ®ion, c uint32_t src_offset = 0; uint32_t chunk_size = (uint32_t)region->chunk_size; // cache these as pointer dereferencing cripples performance in a tight loop uint32_t stride = (uint32_t)region->stride; - uint32_t byte_swap = region->byte_swap; for (uint32_t i = 0; i < num_chunks; i++) { - CopyBytes(dest, dest_offset, src, src_offset, chunk_size, byte_swap); + memcpy(dest + dest_offset, src + src_offset, chunk_size); dest_offset += stride; src_offset += chunk_size; } } } } + + if (!error) + { + error = ApplyLayout(rom, region->byte_layout, region->stride, region->region_name); + } + return error; } diff --git a/Src/GameLoader.h b/Src/GameLoader.h index d3d8785..70703bc 100644 --- a/Src/GameLoader.h +++ b/Src/GameLoader.h @@ -31,7 +31,7 @@ private: std::string region_name; size_t stride; size_t chunk_size; - bool byte_swap; + std::string byte_layout; bool required; std::vector files; static ptr_t Create(const GameLoader &loader, const Util::Config::Node ®ion_node); diff --git a/Src/Model3/DSB.h b/Src/Model3/DSB.h index 411ab08..cbe00fc 100644 --- a/Src/Model3/DSB.h +++ b/Src/Model3/DSB.h @@ -324,8 +324,8 @@ private: // M68K CPU M68KCtx M68K; - static const int k_framePeriod = 11000000/60; - static const int k_timerPeriod = 11000000/1000; // 1KHz timer + static constexpr int k_framePeriod = 11000000/60; + static constexpr int k_timerPeriod = 11000000/1000; // 1KHz timer int m_cyclesElapsedThisFrame; int m_nextTimerInterruptCycles; }; diff --git a/Src/Model3/Model3GraphicsState.h b/Src/Model3/Model3GraphicsState.h index 1675c1e..5096835 100644 --- a/Src/Model3/Model3GraphicsState.h +++ b/Src/Model3/Model3GraphicsState.h @@ -1,12 +1,12 @@ /** ** Supermodel ** A Sega Model 3 Arcade Emulator. - ** Copyright 2011-2016 Bart Trzynadlowski, Nik Henson + ** Copyright 2011-2016 Bart Trzynadlowski, Nik Henson ** ** This file is part of Supermodel. ** ** Supermodel 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 + ** 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. ** @@ -18,10 +18,10 @@ ** You should have received a copy of the GNU General Public License along ** with Supermodel. If not, see . **/ - + /* * Model3GraphicsState.h - * + * * Minimalistic implementation of IEmulator designed to load and view graphics * state. */ @@ -52,7 +52,7 @@ public: void SaveState(CBlockFile *SaveState) override { } - + void LoadState(CBlockFile *SaveState) override { m_real3D.LoadState(SaveState); @@ -116,7 +116,7 @@ public: else rom_set.get_rom("vrom").CopyTo(m_vrom.get(), 64*0x100000); int stepping = ((m_game.stepping[0] - '0') << 4) | (m_game.stepping[2] - '0'); - m_real3D.SetStepping(stepping, false); + m_real3D.SetStepping(stepping); return OKAY; } diff --git a/Src/OSD/SDL/Main.cpp b/Src/OSD/SDL/Main.cpp index d9b3c32..f8af4ee 100644 --- a/Src/OSD/SDL/Main.cpp +++ b/Src/OSD/SDL/Main.cpp @@ -24,6 +24,23 @@ * * Main program driver for the SDL port. * + * Bugs and Issues to Address + * -------------------------- + * - -gfx-state crashes when ENABLE_DEBUGGER is also enabled: + * + * #0 0x00007ff6586392a0 in CSoundBoard::GetDSB (this=0x1128) at Src/Model3/SoundBoard.cpp:552 + * #1 0x00007ff6587072cf in Debugger::CSupermodelDebugger::CreateDSBCPUDebug (model3=0x0) at Src/Debugger/SupermodelDebugger.cpp:269 + * #2 0x00007ff6587076e9 in Debugger::CSupermodelDebugger::AddCPUs (this=0x185da75df40) at Src/Debugger/SupermodelDebugger.cpp:361 + * #3 0x00007ff6586f91a6 in Debugger::CDebugger::Attach (this=0x185da75df40) at Src/Debugger/Debugger.cpp:263 + * #4 0x00007ff658705312 in Debugger::CConsoleDebugger::Attach (this=0x185da75df40) at Src/Debugger/ConsoleDebugger.cpp:2707 + * #5 0x00007ff65862c6fc in Supermodel (game=..., rom_set=0x6cc8dff0e0, Model3=0x185da7598d0, Inputs=0x185da752590, Outputs=0x0, Debugger=std::shared_ptr (use count 3, weak count 0) = {...}) + * at Src/OSD/SDL/Main.cpp:978 + * #6 0x00007ff658634ba3 in SDL_main (argc=3, argv=0x185da4e68a0) at Src/OSD/SDL/Main.cpp:2056 + * #7 0x00007ff658720141 in main_getcmdline () + * #8 0x00007ff6585b13b1 in __tmainCRTStartup () at C:/M/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/crtexe.c:321 + * #9 0x00007ff6585b14e6 in mainCRTStartup () at C:/M/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/crtexe.c:202 + * - Need to address all the stale TO-DOs below and clear them out :) + * * To Do Before Next Release * ------------------------- * - Thoroughly test config system (do overrides work as expected? XInput @@ -547,6 +564,7 @@ void Screenshot() #include static std::string s_gfxStatePath; +static const std::string k_gfxAnalysisPath = "GraphicsAnalysis/"; static std::string GetFileBaseName(const std::string &file) { @@ -603,7 +621,7 @@ static void TestPolygonHeaderBits(IEmulator *Emu) if ((unknownPolyBits[idx] & mask)) { Emu->RenderFrame(); - std::string file = Util::Format() << s_analysisPath << GetFileBaseName(s_gfxStatePath) << "." << "poly" << "." << idx << "_" << Util::Hex(mask) << ".bmp"; + std::string file = Util::Format() << k_gfxAnalysisPath << GetFileBaseName(s_gfxStatePath) << "." << "poly" << "." << idx << "_" << Util::Hex(mask) << ".bmp"; SaveFrameBuffer(file); } } @@ -619,7 +637,7 @@ static void TestPolygonHeaderBits(IEmulator *Emu) if ((unknownCullingNodeBits[idx] & mask)) { Emu->RenderFrame(); - std::string file = Util::Format() << s_analysisPath << GetFileBaseName(s_gfxStatePath) << "." << "culling" << "." << idx << "_" << Util::Hex(mask) << ".bmp"; + std::string file = Util::Format() << k_gfxAnalysisPath << GetFileBaseName(s_gfxStatePath) << "." << "culling" << "." << idx << "_" << Util::Hex(mask) << ".bmp"; SaveFrameBuffer(file); } } @@ -628,7 +646,7 @@ static void TestPolygonHeaderBits(IEmulator *Emu) glReadBuffer(readBuffer); // Generate the HTML GUI - std::string file = Util::Format() << s_analysisPath << "_" << GetFileBaseName(s_gfxStatePath) << ".html"; + std::string file = Util::Format() << k_gfxAnalysisPath << "_" << GetFileBaseName(s_gfxStatePath) << ".html"; std::ofstream fs(file); if (!fs.good()) ErrorLog("Unable to open '%s' for writing.", file.c_str()); @@ -1603,8 +1621,13 @@ static void Help(void) #ifdef SUPERMODEL_DEBUGGER puts(" -disable-debugger Completely disable debugger functionality"); puts(" -enter-debugger Enter debugger at start of emulation"); - puts(""); #endif // SUPERMODEL_DEBUGGER +#ifdef DEBUG + puts(" -gfx-state= Produce graphics analysis for save state (works only"); + puts(" with the legacy 3D engine and requires a"); + puts(" GraphicsAnalysis directory to exist)"); +#endif + puts(""); } struct ParsedCommandLine