Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
TheXTech/src/script/luna/mememu.cpp
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
2116 lines (1901 sloc)
61.3 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * TheXTech - A platform game engine ported from old source code for VB6 | |
| * | |
| * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code | |
| * Copyright (c) 2020-2022 Vitaly Novichkov <admin@wohlnet.ru> | |
| * | |
| * 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 3 of the License, or | |
| * 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, see <http://www.gnu.org/licenses/>. | |
| */ | |
| #include <Logger/logger.h> | |
| #include "mememu.h" | |
| #include "globals.h" | |
| #include "global_constants.h" | |
| #include "layers.h" | |
| #include "game_main.h" // GamePaused | |
| #include <unordered_map> | |
| #include <functional> | |
| #if defined(arm) && !defined(__SOFTFP__) && !defined(__VFP_FP__) && !defined(__MAVERICK__) | |
| # define ARM_BIDI_ENDIAN | |
| //inline void swap_halfes(uint64_t &x) | |
| //{ | |
| // uint64_t y = ((x & 0xFFFFFFFF00000000) >> 32) & 0xFFFFFFFF; | |
| // x = ((x << 32) & 0xFFFFFFFF00000000) & y; | |
| //} | |
| //#else // Do nothing | |
| //# define swap_halfes(x) | |
| #endif | |
| #if 0 // Unused yet | |
| SDL_FORCE_INLINE void toX86Endian(double in_d, uint8_t out[8]) | |
| { | |
| auto *in = reinterpret_cast<uint8_t*>(&in_d); | |
| #if defined(THEXTECH_BIG_ENDIAN) | |
| out[0] = in[7]; | |
| out[1] = in[6]; | |
| out[2] = in[5]; | |
| out[3] = in[4]; | |
| out[4] = in[3]; | |
| out[5] = in[2]; | |
| out[6] = in[1]; | |
| out[7] = in[0]; | |
| #elif defined(ARM_BIDI_ENDIAN) // some old devices | |
| out[4] = in[0]; | |
| out[5] = in[1]; | |
| out[6] = in[2]; | |
| out[7] = in[3]; | |
| out[0] = in[4]; | |
| out[1] = in[5]; | |
| out[2] = in[6]; | |
| out[3] = in[7]; | |
| #else // normal little endian | |
| out[0] = in[0]; | |
| out[1] = in[1]; | |
| out[2] = in[2]; | |
| out[3] = in[3]; | |
| out[4] = in[4]; | |
| out[5] = in[5]; | |
| out[6] = in[6]; | |
| out[7] = in[7]; | |
| #endif | |
| } | |
| SDL_FORCE_INLINE void fromX86Endian(const uint8_t in[8], double &out_d) | |
| { | |
| auto *out = reinterpret_cast<uint8_t*>(&out_d); | |
| #if defined(THEXTECH_BIG_ENDIAN) | |
| out[0] = in[7]; | |
| out[1] = in[6]; | |
| out[2] = in[5]; | |
| out[3] = in[4]; | |
| out[4] = in[3]; | |
| out[5] = in[2]; | |
| out[6] = in[1]; | |
| out[7] = in[0]; | |
| #elif defined(ARM_BIDI_ENDIAN) // some old devices | |
| out[4] = in[0]; | |
| out[5] = in[1]; | |
| out[6] = in[2]; | |
| out[7] = in[3]; | |
| out[0] = in[4]; | |
| out[1] = in[5]; | |
| out[2] = in[6]; | |
| out[3] = in[7]; | |
| #else // normal little endian | |
| out[0] = in[0]; | |
| out[1] = in[1]; | |
| out[2] = in[2]; | |
| out[3] = in[3]; | |
| out[4] = in[4]; | |
| out[5] = in[5]; | |
| out[6] = in[6]; | |
| out[7] = in[7]; | |
| #endif | |
| } | |
| #endif | |
| SDL_FORCE_INLINE void modifyByteX86(double &dst, size_t byte, uint8_t data) | |
| { | |
| auto *in = reinterpret_cast<uint8_t*>(&dst); | |
| SDL_assert(byte < 8); | |
| #if defined(THEXTECH_BIG_ENDIAN) | |
| in[7 - byte] = data; | |
| #elif defined(ARM_BIDI_ENDIAN) // some old devices | |
| byte += (byte < 4) ? +4 : -4; | |
| in[byte] = data; | |
| #else // normal little endian | |
| in[byte] = data; | |
| #endif | |
| } | |
| SDL_FORCE_INLINE void modifyByteX86(float &dst, size_t byte, uint8_t data) | |
| { | |
| auto *in = reinterpret_cast<uint8_t*>(&dst); | |
| SDL_assert(byte < 4); | |
| #if defined(THEXTECH_BIG_ENDIAN) | |
| in[3 - byte] = data; | |
| #else // normal little endian | |
| in[byte] = data; | |
| #endif | |
| } | |
| SDL_FORCE_INLINE void modifyByteX86(int16_t &dst, size_t byte, uint8_t data) | |
| { | |
| auto *in = reinterpret_cast<uint8_t*>(&dst); | |
| SDL_assert(byte < 2); | |
| #if defined(THEXTECH_BIG_ENDIAN) | |
| in[1 - byte] = data; | |
| #else // normal little endian | |
| in[byte] = data; | |
| #endif | |
| } | |
| SDL_FORCE_INLINE uint8_t getByteX86(const double &src, size_t byte) | |
| { | |
| const auto *in = reinterpret_cast<const uint8_t*>(&src); | |
| SDL_assert(byte < 8); | |
| #if defined(THEXTECH_BIG_ENDIAN) | |
| return in[7 - byte]; | |
| #elif defined(ARM_BIDI_ENDIAN) // some old devices | |
| byte += (byte < 4) ? +4 : -4; | |
| return in[byte]; | |
| #else // normal little endian | |
| return in[byte]; | |
| #endif | |
| } | |
| SDL_FORCE_INLINE uint8_t getByteX86(const float &src, size_t byte) | |
| { | |
| const auto *in = reinterpret_cast<const uint8_t*>(&src); | |
| SDL_assert(byte < 4); | |
| #if defined(THEXTECH_BIG_ENDIAN) | |
| return in[3 - byte]; | |
| #else // normal little endian | |
| return in[byte]; | |
| #endif | |
| } | |
| SDL_FORCE_INLINE uint8_t getByteX86(const int16_t &src, size_t byte) | |
| { | |
| const auto *in = reinterpret_cast<const uint8_t*>(&src); | |
| SDL_assert(byte < 2); | |
| #if defined(THEXTECH_BIG_ENDIAN) | |
| return in[1 - byte]; | |
| #else // normal little endian | |
| return in[byte]; | |
| #endif | |
| } | |
| /*----------------------------------------------* | |
| * Write memory value * | |
| *----------------------------------------------*/ | |
| SDL_FORCE_INLINE void memToValue(double &target, double value, FIELDTYPE ftype) | |
| { | |
| switch(ftype) | |
| { | |
| case FT_BYTE: | |
| target =static_cast<double>(static_cast<uint8_t>(value)); | |
| break; | |
| case FT_WORD: | |
| target =static_cast<double>(static_cast<int16_t>(static_cast<uint16_t>(value))); | |
| break; | |
| case FT_DWORD: | |
| target =static_cast<double>(static_cast<int32_t>(value)); | |
| break; | |
| case FT_FLOAT: | |
| target =static_cast<double>(static_cast<float>(value)); | |
| break; | |
| case FT_DFLOAT: | |
| target = value; | |
| break; | |
| default: | |
| break; // Don't change | |
| } | |
| } | |
| SDL_FORCE_INLINE void memToValue(float &target, double value, FIELDTYPE ftype) | |
| { | |
| switch(ftype) | |
| { | |
| case FT_BYTE: | |
| target = static_cast<float>(static_cast<uint8_t>(value)); | |
| break; | |
| case FT_WORD: | |
| target = static_cast<float>(static_cast<int16_t>(static_cast<uint16_t>(value))); | |
| break; | |
| case FT_DWORD: | |
| target = static_cast<float>(static_cast<int32_t>(value)); | |
| break; | |
| case FT_FLOAT: | |
| target = static_cast<float>(value); | |
| break; | |
| case FT_DFLOAT: | |
| target = static_cast<double>(static_cast<float>(value)); | |
| break; | |
| default: //Don't change | |
| break; | |
| } | |
| } | |
| SDL_FORCE_INLINE void memToValue(int &target, double value, FIELDTYPE ftype) | |
| { | |
| switch(ftype) | |
| { | |
| case FT_BYTE: | |
| target = static_cast<int32_t>(static_cast<uint8_t>(value)); | |
| break; | |
| case FT_WORD: | |
| target = static_cast<int32_t>(static_cast<int16_t>(static_cast<uint16_t>(value))); | |
| break; | |
| case FT_DWORD: | |
| target = static_cast<int32_t>(value); | |
| break; | |
| case FT_FLOAT: | |
| target = static_cast<int32_t>(static_cast<float>(value)); | |
| break; | |
| case FT_DFLOAT: | |
| target = static_cast<int32_t>(value); | |
| break; | |
| default: //Don't change | |
| break; | |
| } | |
| } | |
| SDL_FORCE_INLINE void memToValue(bool &target, double value, FIELDTYPE ftype) | |
| { | |
| UNUSED(ftype); | |
| target = (value != 0.0); | |
| } | |
| /*----------------------------------------------* | |
| * Read memory value * | |
| *----------------------------------------------*/ | |
| SDL_FORCE_INLINE double valueToMem(double &source, FIELDTYPE ftype) | |
| { | |
| switch(ftype) | |
| { | |
| case FT_BYTE: | |
| return static_cast<double>(static_cast<uint8_t>(source)); | |
| case FT_WORD: | |
| return static_cast<double>(static_cast<int16_t>(static_cast<uint16_t>(source))); | |
| case FT_DWORD: | |
| return static_cast<double>(static_cast<int32_t>(source)); | |
| case FT_FLOAT: | |
| return static_cast<double>(static_cast<float>(source)); | |
| default: | |
| case FT_DFLOAT: | |
| return source; | |
| } | |
| } | |
| SDL_FORCE_INLINE double valueToMem(float &source, FIELDTYPE ftype) | |
| { | |
| switch(ftype) | |
| { | |
| case FT_BYTE: | |
| return static_cast<double>(static_cast<uint8_t>(source)); | |
| case FT_WORD: | |
| return static_cast<double>(static_cast<int16_t>(static_cast<uint16_t>(source))); | |
| case FT_DWORD: | |
| return static_cast<double>(static_cast<int32_t>(source)); | |
| default: | |
| case FT_FLOAT: | |
| case FT_DFLOAT: | |
| return static_cast<double>(source); | |
| } | |
| } | |
| SDL_FORCE_INLINE double valueToMem(int &source, FIELDTYPE ftype) | |
| { | |
| switch(ftype) | |
| { | |
| case FT_BYTE: | |
| return static_cast<double>(static_cast<uint8_t>(source)); | |
| case FT_WORD: | |
| return static_cast<double>(static_cast<int16_t>(source)); | |
| default: | |
| case FT_DWORD: | |
| return static_cast<double>(source); | |
| case FT_FLOAT: | |
| return static_cast<double>(static_cast<float>(source)); | |
| case FT_DFLOAT: | |
| return static_cast<double>(source); | |
| } | |
| } | |
| SDL_FORCE_INLINE double valueToMem(bool &source, FIELDTYPE ftype) | |
| { | |
| UNUSED(ftype); | |
| return source ? 0xFFFF : 0; | |
| } | |
| /*! | |
| * \brief Convert field type into string | |
| * \param ftype Field type | |
| * \return Human-readable string | |
| */ | |
| static const char *FieldtypeToStr(FIELDTYPE ftype) | |
| { | |
| switch(ftype) | |
| { | |
| case FT_BYTE: | |
| return "Uint8"; | |
| case FT_WORD: | |
| return "SInt16"; | |
| case FT_DWORD: | |
| return "SInt32"; | |
| case FT_FLOAT: | |
| return "Float"; | |
| case FT_DFLOAT: | |
| return "Double"; | |
| default: | |
| return "<Invalid>"; | |
| } | |
| } | |
| /*! | |
| * \brief Global memory emulator | |
| */ | |
| class SMBXMemoryEmulator | |
| { | |
| std::unordered_map<size_t, double *> m_df; | |
| std::unordered_map<size_t, float *> m_ff; | |
| std::unordered_map<size_t, int *> m_if; | |
| std::unordered_map<size_t, bool *> m_bf; | |
| std::unordered_map<size_t, std::string *> m_sf; | |
| typedef std::function<double(FIELDTYPE)> Getter; | |
| typedef std::function<void(double,FIELDTYPE)> Setter; | |
| std::unordered_map<size_t, std::pair<Getter, Setter>> m_lff; | |
| enum ValueType | |
| { | |
| VT_UNKNOWN = 0, | |
| VT_DOUBLE, | |
| VT_FLOAT, | |
| VT_INT, | |
| VT_BOOL, | |
| VT_STRING, | |
| VT_LAMBDA | |
| }; | |
| std::unordered_map<int, ValueType> m_type; | |
| void insert(size_t address, int *field) | |
| { | |
| m_if.insert({address, field}); | |
| m_type.insert({address, VT_INT}); | |
| } | |
| void insert(size_t address, double *field) | |
| { | |
| m_df.insert({address, field}); | |
| m_type.insert({address, VT_DOUBLE}); | |
| } | |
| void insert(size_t address, float *field) | |
| { | |
| m_ff.insert({address, field}); | |
| m_type.insert({address, VT_FLOAT}); | |
| } | |
| void insert(size_t address, bool *field) | |
| { | |
| m_bf.insert({address, field}); | |
| m_type.insert({address, VT_BOOL}); | |
| } | |
| void insert(size_t address, std::string *field) | |
| { | |
| m_sf.insert({address, field}); | |
| m_type.insert({address, VT_STRING}); | |
| } | |
| void insert(size_t address, Getter g, Setter s) | |
| { | |
| m_lff.insert({address, {g, s}}); | |
| m_type.insert({address, VT_LAMBDA}); | |
| } | |
| public: | |
| SMBXMemoryEmulator() noexcept | |
| { | |
| buildTable(); | |
| } | |
| void buildTable() | |
| { | |
| insert(0x00B2504C, &TakeScreen); | |
| insert(0x00B250D6, &numLocked); | |
| insert(0x00B250E2, // Pause menu visible | |
| [](FIELDTYPE ftype)->double | |
| { | |
| bool tmp = (GamePaused != PauseCode::None); | |
| return valueToMem(tmp, ftype); | |
| }, | |
| [](double in, FIELDTYPE ftype)->void | |
| { | |
| // FIXME: Verify this, if it needs to be written, try to work around this | |
| pLogWarning("Attempt to write the read-only field at 0x00B250E2 " | |
| "(GamePaused) with value %g as %s", in, FieldtypeToStr(ftype)); | |
| } | |
| ); | |
| insert(0x00B25134, &LevelEditor); | |
| insert(0x00B251E0, &numStars); // HUD star count | |
| insert(0x00B25700, &numWater); | |
| insert(0x00B25724, &StartLevel); | |
| insert(0x00B25728, &NoMap); | |
| insert(0x00B2572A, &RestartLevel); | |
| insert(0x00B257A4, &numTiles); | |
| insert(0x00B257A6, &numScenes); | |
| insert(0x00B258E0, &numWorldPaths); | |
| insert(0x00B258E2, &numWarps); | |
| insert(0x00B25956, &numBlock); | |
| insert(0x00B25958, &numBackground); | |
| insert(0x00B2595A, &numNPCs); // NPC count | |
| insert(0x00B2595E, &numPlayers); // Player Count | |
| insert(0x00B25960, &numWorldLevels); | |
| insert(0x00B25980, &numWorldMusic); | |
| insert(0x00B2C5A8, &Coins); // HUD coins count | |
| insert(0x00B2C5AC, &Lives); // HUD lives count | |
| insert(0x00B2C624, &WorldName); | |
| insert(0x00B2C62C, &PSwitchTime); // P-Switch Timer | |
| insert(0x00B2C62E, &PSwitchStop); // Stopwatch Timer | |
| insert(0x00B2C630, &PSwitchPlayer); // P-Switch/Stopwatch Player | |
| insert(0x00B2C684, &FrameSkip); | |
| insert(0x00B2C87C, &Physics.NPCPSwitch); // P-Switch/Stopwatch Length | |
| insert(0x00B2C880, &MenuCursor); // Current menu choice | |
| insert(0x00B2C882, &MenuMode); // Current menu mode | |
| // insert(0x00B2C884, ???}; // Key Released!!! | |
| insert(0x00B2C894, &BlocksSorted); | |
| insert(0x00B2C8B4, &FreezeNPCs); | |
| insert(0x00B2C8C4, &Cheater); | |
| insert(0x00B2C8E4, &Score); // HUD points count | |
| insert(0x00B2C906, &maxStars); // Max stars at episode | |
| insert(0x00B2D6BC, &SharedCursor.X); // Mouse cursor X | |
| insert(0x00B2D6C4, &SharedCursor.Y); // Mouse cursor Y | |
| insert(0x00B2D6CC, &SharedCursor.Primary); | |
| insert(0x00B2D6D0, &MenuMouseRelease); | |
| insert(0x00B2D6D2, &SharedCursor.Move); | |
| insert(0x00B2D710, &numEvents); | |
| insert(0x00B2D734, &noSound); | |
| } | |
| double getValue(size_t address, FIELDTYPE ftype) | |
| { | |
| if(ftype == FT_INVALID) | |
| { | |
| pLogWarning("MemEmu: Requested value of invalid type: <Global> 0x%x", address); | |
| return 0.0; | |
| } | |
| auto ft = m_type.find(address); | |
| if(ft == m_type.end()) | |
| { | |
| pLogWarning("MemEmu: Unknown %s address to read: <Global> 0x%x", FieldtypeToStr(ftype), address); | |
| return 0.0; | |
| } | |
| switch(ft->second) | |
| { | |
| case VT_DOUBLE: | |
| { | |
| auto dres = m_df.find(address); | |
| if(dres != m_df.end()) | |
| { | |
| if(ftype != FT_DFLOAT) | |
| pLogWarning("MemEmu: Read type missmatched at 0x%x (Double expected, %s actually)", address, FieldtypeToStr(ftype)); | |
| return valueToMem(*dres->second, ftype); | |
| } | |
| break; | |
| } | |
| case VT_FLOAT: | |
| { | |
| auto fres = m_ff.find(address); | |
| if(fres != m_ff.end()) | |
| { | |
| if(ftype != FT_FLOAT) | |
| pLogWarning("MemEmu: Read type missmatched at 0x%x (Float expected, %s actually)", address, FieldtypeToStr(ftype)); | |
| return valueToMem(*fres->second, ftype); | |
| } | |
| break; | |
| } | |
| case VT_INT: | |
| { | |
| auto ires = m_if.find(address); | |
| if(ires != m_if.end()) | |
| { | |
| if(ftype != FT_DWORD && ftype != FT_WORD) | |
| pLogWarning("MemEmu: Read type missmatched at 0x%x (SInt16 or SInt32 expected, %s actually)", address, FieldtypeToStr(ftype)); | |
| return valueToMem(*ires->second, ftype); | |
| } | |
| break; | |
| } | |
| case VT_BOOL: | |
| { | |
| auto bres = m_bf.find(address); | |
| if(bres != m_bf.end()) | |
| { | |
| if(ftype != FT_WORD && ftype != FT_BYTE) | |
| pLogWarning("MemEmu: Read type missmatched at 0x%x (Sint16 or Uint8 as boolean expected, %s actually)", address, FieldtypeToStr(ftype)); | |
| return *bres->second ? 0xffff : 0x0000; | |
| } | |
| break; | |
| } | |
| case VT_LAMBDA: | |
| { | |
| auto lfres = m_lff.find(address); | |
| if(lfres != m_lff.end()) | |
| { | |
| auto &l = lfres->second; | |
| return l.first(ftype); | |
| } | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| return 0.0; | |
| } | |
| void setValue(size_t address, double value, FIELDTYPE ftype) | |
| { | |
| if(ftype == FT_INVALID) | |
| { | |
| pLogWarning("MemEmu: Passed value of invalid type: <Global> 0x%x", address); | |
| return; | |
| } | |
| auto ft = m_type.find(address); | |
| if(ft == m_type.end()) | |
| { | |
| pLogWarning("MemEmu: Unknown %s address to write: 0x%x", FieldtypeToStr(ftype), address); | |
| return; | |
| } | |
| switch(ft->second) | |
| { | |
| case VT_DOUBLE: | |
| { | |
| auto dres = m_df.find(address); | |
| if(dres != m_df.end()) | |
| { | |
| if(ftype != FT_DFLOAT) | |
| pLogWarning("MemEmu: Write type missmatched at 0x%x (Double expected, %s actually)", address, FieldtypeToStr(ftype)); | |
| memToValue(*dres->second, value, ftype); | |
| return; | |
| } | |
| break; | |
| } | |
| case VT_FLOAT: | |
| { | |
| auto fres = m_ff.find(address); | |
| if(fres != m_ff.end()) | |
| { | |
| if(ftype != FT_FLOAT) | |
| pLogWarning("MemEmu: Write type missmatched at 0x%x (Float expected, %s actually)", address, FieldtypeToStr(ftype)); | |
| memToValue(*fres->second, value, ftype); | |
| return; | |
| } | |
| break; | |
| } | |
| case VT_INT: | |
| { | |
| auto ires = m_if.find(address); | |
| if(ires != m_if.end()) | |
| { | |
| if(ftype != FT_DWORD && ftype != FT_WORD) | |
| pLogWarning("MemEmu: Write type missmatched at 0x%x (SInt16 or SInt32 expected, %s actually)", address, FieldtypeToStr(ftype)); | |
| memToValue(*ires->second, value, ftype); | |
| return; | |
| } | |
| break; | |
| } | |
| case VT_BOOL: | |
| { | |
| auto bres = m_bf.find(address); | |
| if(bres != m_bf.end()) | |
| { | |
| if(ftype != FT_WORD && ftype != FT_BYTE) | |
| pLogWarning("MemEmu: Write type missmatched at 0x%x (Sint16 or Uint8 as boolean expected, %s actually)", address, FieldtypeToStr(ftype)); | |
| *bres->second = (value != 0.0); | |
| return; | |
| } | |
| break; | |
| } | |
| case VT_LAMBDA: | |
| { | |
| auto lfres = m_lff.find(address); | |
| if(lfres != m_lff.end()) | |
| { | |
| auto &l = lfres->second; | |
| l.second(value, ftype); | |
| } | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| } | |
| }; | |
| /*! | |
| * \brief Per-Object memory emulator | |
| */ | |
| template<class T, char const *objName, size_t maxAddr> | |
| class SMBXObjectMemoryEmulator | |
| { | |
| protected: | |
| enum ValueType | |
| { | |
| VT_UNKNOWN = 0, | |
| VT_DOUBLE, | |
| VT_FLOAT, | |
| VT_INT, | |
| VT_BOOL, | |
| VT_STRING, | |
| VT_BYTE_HACK | |
| }; | |
| struct Value | |
| { | |
| //! Type of field | |
| ValueType type = VT_UNKNOWN; | |
| //! Base type for byte hacking mode | |
| ValueType baseType = VT_UNKNOWN; | |
| //! Byte offset | |
| int offset = 0; | |
| //! Base address of real value | |
| int baseAddress = 0; | |
| //! Double-type field pointer | |
| double T::* field_d = nullptr; | |
| //! Float-type field pointer | |
| float T::* field_f = nullptr; | |
| //! Int-type field pointer | |
| int T::* field_i = nullptr; | |
| //! Boolean type field pointer | |
| bool T::* field_b = nullptr; | |
| //! String-type field pointer | |
| std::string T::* field_s = nullptr; | |
| }; | |
| //! Basic map of addresses | |
| Value m_type[maxAddr]; | |
| //! Byte map of addresses | |
| Value m_byte[maxAddr]; | |
| void insert(size_t address, int T::*field) | |
| { | |
| Value v; | |
| // Normal field | |
| v.field_i = field; | |
| v.type = VT_INT; | |
| v.baseType = VT_INT; | |
| v.offset = 0; | |
| v.baseAddress = address; | |
| m_type[address] = v; | |
| // Byte hack fields | |
| v.type = VT_BYTE_HACK; | |
| for(int i = 0; i < 2; ++i) | |
| { | |
| v.offset = i; | |
| m_byte[address + i] = v; | |
| if(i > 0) | |
| m_type[address + i] = v; | |
| } | |
| } | |
| void insert(size_t address, double T::*field) | |
| { | |
| Value v; | |
| // Normal field | |
| v.field_d = field; | |
| v.type = VT_DOUBLE; | |
| v.baseType = VT_DOUBLE; | |
| v.offset = 0; | |
| v.baseAddress = address; | |
| m_type[address] = v; | |
| // Byte hack fields | |
| v.type = VT_BYTE_HACK; | |
| for(int i = 0; i < 8; ++i) | |
| { | |
| v.offset = i; | |
| m_byte[address + i] = v; | |
| if(i > 0) | |
| m_type[address + i] = v; | |
| } | |
| } | |
| void insert(size_t address, float T::*field) | |
| { | |
| Value v; | |
| // Normal field | |
| v.field_f = field; | |
| v.type = VT_FLOAT; | |
| v.baseType = VT_FLOAT; | |
| v.offset = 0; | |
| v.baseAddress = address; | |
| m_type[address] = v; | |
| // Byte hack fields | |
| v.type = VT_BYTE_HACK; | |
| for(int i = 0; i < 4; ++i) | |
| { | |
| v.offset = i; | |
| m_byte[address + i] = v; | |
| if(i > 0) | |
| m_type[address + i] = v; | |
| } | |
| } | |
| void insert(size_t address, bool T::*field) | |
| { | |
| Value v; | |
| // Normal field | |
| v.field_b = field; | |
| v.type = VT_BOOL; | |
| v.baseType = VT_BOOL; | |
| v.offset = 0; | |
| v.baseAddress = address; | |
| m_type[address] = v; | |
| } | |
| void insert(size_t address, std::string T::*field) | |
| { | |
| Value v; | |
| // Normal field | |
| v.field_s = field; | |
| v.type = VT_STRING; | |
| v.baseType = VT_STRING; | |
| v.offset = 0; | |
| v.baseAddress = address; | |
| m_type[address] = v; | |
| } | |
| public: | |
| SMBXObjectMemoryEmulator() noexcept | |
| {}; | |
| virtual double getValue(T *obj, size_t address, FIELDTYPE ftype) | |
| { | |
| Value *t = nullptr; | |
| if(address >= maxAddr) | |
| { | |
| pLogWarning("MemEmu: Requested value of out-of-range address: %s 0x%x", objName, address); | |
| return 0.0; | |
| } | |
| if(ftype == FT_INVALID) | |
| { | |
| pLogWarning("MemEmu: Requested value of invalid type: %s 0x%x", objName, address); | |
| return 0.0; | |
| } | |
| if(ftype == FT_BYTE) // byte hacking | |
| { | |
| t = &m_byte[address]; | |
| if(t->type == VT_UNKNOWN) | |
| t = &m_type[address]; | |
| } | |
| else | |
| t = &m_type[address]; | |
| if(t->type == VT_UNKNOWN) | |
| { | |
| pLogWarning("MemEmu: Unknown %s::%s address to read: 0x%x", objName, FieldtypeToStr(ftype), address); | |
| return 0.0; | |
| } | |
| switch(t->type) | |
| { | |
| case VT_DOUBLE: | |
| { | |
| SDL_assert(t->field_d); | |
| if(ftype != FT_DFLOAT) | |
| pLogWarning("MemEmu: Read type missmatched at %s 0x%x (Double expected, %s actually)", objName, address, FieldtypeToStr(ftype)); | |
| return valueToMem(obj->*(t->field_d), ftype); | |
| } | |
| case VT_FLOAT: | |
| { | |
| SDL_assert(t->field_f); | |
| if(ftype != FT_FLOAT) | |
| pLogWarning("MemEmu: Read type missmatched at %s 0x%x (Float expected, %s actually)", objName, address, FieldtypeToStr(ftype)); | |
| return valueToMem(obj->*(t->field_f), ftype); | |
| } | |
| case VT_INT: | |
| { | |
| SDL_assert(t->field_i); | |
| if(ftype != FT_DWORD && ftype != FT_WORD) | |
| pLogWarning("MemEmu: Read type missmatched at %s 0x%x (SInt16 or SInt32 expected, %s actually)", objName, address, FieldtypeToStr(ftype)); | |
| return valueToMem(obj->*(t->field_i), ftype); | |
| } | |
| case VT_BOOL: | |
| { | |
| SDL_assert(t->field_b); | |
| if(ftype != FT_WORD && ftype != FT_BYTE) | |
| pLogWarning("MemEmu: Read type missmatched at %s 0x%x (Sint16 or Uint8 as boolean expected, %s actually)", objName, address, FieldtypeToStr(ftype)); | |
| return valueToMem(obj->*(t->field_b), ftype); | |
| } | |
| case VT_BYTE_HACK: | |
| { | |
| switch(t->baseType) | |
| { | |
| case VT_DOUBLE: | |
| { | |
| auto &bt = m_type[t->baseAddress]; | |
| SDL_assert(bt.type == VT_DOUBLE && bt.field_d); | |
| if(ftype != FT_BYTE) | |
| pLogWarning("MemEmu: Read type missmatched at %s 0x%x (byte expected, %s actually)", objName, address, FieldtypeToStr(ftype)); | |
| return (double)getByteX86(obj->*(bt.field_d), t->offset); | |
| } | |
| case VT_FLOAT: | |
| { | |
| auto &bt = m_type[t->baseAddress]; | |
| SDL_assert(bt.type == VT_FLOAT && bt.field_f); | |
| if(ftype != FT_BYTE) | |
| pLogWarning("MemEmu: Read type missmatched at %s 0x%x (byte expected, %s actually)", objName, address, FieldtypeToStr(ftype)); | |
| return (double)getByteX86(obj->*(bt.field_f), t->offset); | |
| } | |
| case VT_INT: | |
| { | |
| auto &bt = m_type[t->baseAddress]; | |
| SDL_assert(bt.type == VT_INT && bt.field_i); | |
| if(ftype != FT_BYTE) | |
| pLogWarning("MemEmu: Read type missmatched at %s 0x%x (byte expected, %s actually)", objName, address, FieldtypeToStr(ftype)); | |
| int16_t s = static_cast<int16_t>(obj->*(bt.field_i)); | |
| return (double)getByteX86(s, t->offset); | |
| } | |
| default: | |
| break; | |
| } | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| return 0.0; | |
| } | |
| virtual void setValue(T *obj, size_t address, double value, FIELDTYPE ftype) | |
| { | |
| Value *t = nullptr; | |
| if(address >= maxAddr) | |
| { | |
| pLogWarning("MemEmu: Requested value of out-of-range address: %s 0x%x", objName, address); | |
| return; | |
| } | |
| if(ftype == FT_INVALID) | |
| { | |
| pLogWarning("MemEmu: Passed value of invalid type: %s 0x%x", objName, address); | |
| return; | |
| } | |
| if(ftype == FT_BYTE) // byte hacking | |
| { | |
| t = &m_byte[address]; | |
| if(t->type == VT_UNKNOWN) | |
| t = &m_type[address]; | |
| } | |
| else | |
| t = &m_type[address]; | |
| if(t->type == VT_UNKNOWN) | |
| { | |
| pLogWarning("MemEmu: Unknown %s::%s address to write: 0x%x", objName, FieldtypeToStr(ftype), address); | |
| return; | |
| } | |
| switch(t->type) | |
| { | |
| case VT_DOUBLE: | |
| { | |
| SDL_assert(t->field_d); | |
| if(ftype != FT_DFLOAT) | |
| pLogWarning("MemEmu: Write type missmatched at %s 0x%x (Double expected, %s actually)", objName, address, FieldtypeToStr(ftype)); | |
| memToValue(obj->*(t->field_d), value, ftype); | |
| return; | |
| } | |
| case VT_FLOAT: | |
| { | |
| SDL_assert(t->field_f); | |
| if(ftype != FT_FLOAT) | |
| pLogWarning("MemEmu: Write type missmatched at %s 0x%x (Float expected, %s actually)", objName, address, FieldtypeToStr(ftype)); | |
| memToValue(obj->*(t->field_f), value, ftype); | |
| return; | |
| } | |
| case VT_INT: | |
| { | |
| SDL_assert(t->field_i); | |
| if(ftype != FT_DWORD && ftype != FT_WORD) | |
| pLogWarning("MemEmu: Write type missmatched at %s 0x%x (SInt16 or SInt32 expected, %s actually)", objName, address, FieldtypeToStr(ftype)); | |
| memToValue(obj->*(t->field_i), value, ftype); | |
| return; | |
| } | |
| case VT_BOOL: | |
| { | |
| SDL_assert(t->field_b); | |
| if(ftype != FT_WORD && ftype != FT_BYTE) | |
| pLogWarning("MemEmu: Write type missmatched at %s 0x%x (Sint16 or Uint8 as boolean expected, %s actually)", objName, address, FieldtypeToStr(ftype)); | |
| memToValue(obj->*(t->field_b), value, ftype); | |
| return; | |
| } | |
| case VT_BYTE_HACK: | |
| { | |
| switch(t->baseType) | |
| { | |
| case VT_DOUBLE: | |
| { | |
| auto &bt = m_type[t->baseAddress]; | |
| SDL_assert(bt.type == VT_DOUBLE && bt.field_d); | |
| if(ftype != FT_BYTE) | |
| pLogWarning("MemEmu: Write type missmatched at %s 0x%x (byte expected, %s actually)", objName, address, FieldtypeToStr(ftype)); | |
| modifyByteX86(obj->*(bt.field_d), t->offset, (uint8_t)value); | |
| return; | |
| } | |
| case VT_FLOAT: | |
| { | |
| auto &bt = m_type[t->baseAddress]; | |
| SDL_assert(bt.type == VT_FLOAT && bt.field_f); | |
| if(ftype != FT_BYTE) | |
| pLogWarning("MemEmu: Write type missmatched at %s 0x%x (byte expected, %s actually)", objName, address, FieldtypeToStr(ftype)); | |
| modifyByteX86(obj->*(bt.field_f), t->offset, (uint8_t)value); | |
| return; | |
| } | |
| case VT_INT: | |
| { | |
| auto &bt = m_type[t->baseAddress]; | |
| SDL_assert(bt.type == VT_INT && bt.field_i); | |
| if(ftype != FT_BYTE) | |
| pLogWarning("MemEmu: Write type missmatched at %s 0x%x (byte expected, %s actually)", objName, address, FieldtypeToStr(ftype)); | |
| int16_t s = static_cast<int16_t>(obj->*(bt.field_i)); | |
| modifyByteX86(s, t->offset, (uint8_t)value); | |
| obj->*(bt.field_i) = static_cast<int>(s); | |
| return; | |
| } | |
| default: | |
| break; | |
| } | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| } | |
| }; | |
| static constexpr char location_t_name[] = "Location_t"; | |
| typedef SMBXObjectMemoryEmulator<Location_t, location_t_name, 0x31> LocationParent; | |
| class LocationMemory final : public LocationParent | |
| { | |
| public: | |
| LocationMemory() noexcept : LocationParent() | |
| { | |
| buildTable(); | |
| } | |
| void buildTable() | |
| { | |
| insert(0x00, &Location_t::X); | |
| insert(0x08, &Location_t::Y); | |
| insert(0x10, &Location_t::Height); | |
| insert(0x18, &Location_t::Width); | |
| insert(0x20, &Location_t::SpeedX); | |
| insert(0x28, &Location_t::SpeedY); | |
| } | |
| }; | |
| static constexpr char controls_t_name[] = "Controls_t"; | |
| typedef SMBXObjectMemoryEmulator<Controls_t, controls_t_name, 0x16> ControlsParent; | |
| class ControlsMemory final : public ControlsParent | |
| { | |
| public: | |
| ControlsMemory() noexcept : ControlsParent() | |
| { | |
| buildTable(); | |
| } | |
| void buildTable() | |
| { | |
| insert(0x00, &Controls_t::Up); | |
| insert(0x02, &Controls_t::Down); | |
| insert(0x04, &Controls_t::Left); | |
| insert(0x06, &Controls_t::Right); | |
| insert(0x08, &Controls_t::Jump); | |
| insert(0x0A, &Controls_t::AltJump); | |
| insert(0x0C, &Controls_t::Run); | |
| insert(0x0E, &Controls_t::AltRun); | |
| insert(0x10, &Controls_t::Drop); | |
| insert(0x12, &Controls_t::Start); | |
| } | |
| }; | |
| static ControlsMemory s_conMem; | |
| static LocationMemory s_locMem; | |
| static constexpr char playere_t_name[] = "Player_t"; | |
| typedef SMBXObjectMemoryEmulator<Player_t, playere_t_name, 0x186> PlayerParent; | |
| class PlayerMemory final : public PlayerParent | |
| { | |
| public: | |
| PlayerMemory() noexcept : PlayerParent() | |
| { | |
| buildTable(); | |
| } | |
| void buildTable() | |
| { | |
| insert(0x00000000, &Player_t::DoubleJump); | |
| insert(0x00000002, &Player_t::FlySparks); | |
| insert(0x00000004, &Player_t::Driving); | |
| insert(0x00000006, &Player_t::Quicksand); | |
| insert(0x00000008, &Player_t::Bombs); | |
| insert(0x0000000a, &Player_t::Slippy); | |
| insert(0x0000000c, &Player_t::Fairy); | |
| insert(0x0000000e, &Player_t::FairyCD); | |
| insert(0x00000010, &Player_t::FairyTime); | |
| insert(0x00000012, &Player_t::HasKey); | |
| insert(0x00000014, &Player_t::SwordPoke); | |
| insert(0x00000016, &Player_t::Hearts); | |
| insert(0x00000018, &Player_t::CanFloat); | |
| insert(0x0000001a, &Player_t::FloatRelease); | |
| insert(0x0000001c, &Player_t::FloatTime); | |
| insert(0x00000020, &Player_t::FloatSpeed); | |
| insert(0x00000024, &Player_t::FloatDir); | |
| insert(0x00000026, &Player_t::GrabTime); | |
| insert(0x00000028, &Player_t::GrabSpeed); | |
| insert(0x0000002c, &Player_t::VineNPC); | |
| insert(0x00000034, &Player_t::Wet); | |
| insert(0x00000036, &Player_t::WetFrame); | |
| insert(0x00000038, &Player_t::SwimCount); | |
| insert(0x0000003a, &Player_t::NoGravity); | |
| insert(0x0000003c, &Player_t::Slide); | |
| insert(0x0000003e, &Player_t::SlideKill); | |
| insert(0x00000040, &Player_t::Vine); | |
| insert(0x00000042, &Player_t::NoShellKick); | |
| insert(0x00000044, &Player_t::ShellSurf); | |
| insert(0x00000046, &Player_t::StateNPC); | |
| insert(0x00000048, &Player_t::Slope); | |
| insert(0x0000004a, &Player_t::Stoned); | |
| insert(0x0000004c, &Player_t::StonedCD); | |
| insert(0x0000004e, &Player_t::StonedTime); | |
| insert(0x00000050, &Player_t::SpinJump); | |
| insert(0x00000052, &Player_t::SpinFrame); | |
| insert(0x00000054, &Player_t::SpinFireDir); | |
| insert(0x00000056, &Player_t::Multiplier); | |
| insert(0x00000058, &Player_t::SlideCounter); | |
| insert(0x0000005a, &Player_t::ShowWarp); | |
| insert(0x0000005c, &Player_t::GroundPound); | |
| insert(0x0000005e, &Player_t::GroundPound2); | |
| insert(0x00000060, &Player_t::CanPound); | |
| insert(0x00000062, &Player_t::ForceHold); | |
| insert(0x00000064, &Player_t::YoshiYellow); | |
| insert(0x00000066, &Player_t::YoshiBlue); | |
| insert(0x00000068, &Player_t::YoshiRed); | |
| insert(0x0000006a, &Player_t::YoshiWingsFrame); | |
| insert(0x0000006c, &Player_t::YoshiWingsFrameCount); | |
| insert(0x0000006e, &Player_t::YoshiTX); | |
| insert(0x00000070, &Player_t::YoshiTY); | |
| insert(0x00000072, &Player_t::YoshiTFrame); | |
| insert(0x00000074, &Player_t::YoshiTFrameCount); | |
| insert(0x00000076, &Player_t::YoshiBX); | |
| insert(0x00000078, &Player_t::YoshiBY); | |
| insert(0x0000007a, &Player_t::YoshiBFrame); | |
| insert(0x0000007c, &Player_t::YoshiBFrameCount); | |
| //Location_t YoshiTongue; (Between 0x80 and 0xB0) | |
| insert(0x000000b0, &Player_t::YoshiTongueX); | |
| insert(0x000000b4, &Player_t::YoshiTongueLength); | |
| insert(0x000000b6, &Player_t::YoshiTonugeBool); | |
| insert(0x000000b8, &Player_t::YoshiNPC); | |
| insert(0x000000ba, &Player_t::YoshiPlayer); | |
| insert(0x000000bc, &Player_t::Dismount); | |
| insert(0x000000be, &Player_t::NoPlayerCol); | |
| //Location_t Location; (Between 0xC0 and 0xF0) | |
| insert(0x000000f0, &Player_t::Character); | |
| //Controls_t Controls; (Between 0xF2 and 0x105) | |
| insert(0x00000106, &Player_t::Direction); | |
| insert(0x00000108, &Player_t::Mount); | |
| insert(0x0000010a, &Player_t::MountType); | |
| insert(0x0000010c, &Player_t::MountSpecial); | |
| insert(0x0000010e, &Player_t::MountOffsetY); | |
| insert(0x00000110, &Player_t::MountFrame); | |
| insert(0x00000112, &Player_t::State); | |
| insert(0x00000114, &Player_t::Frame); | |
| insert(0x00000118, &Player_t::FrameCount); | |
| insert(0x0000011c, &Player_t::Jump); | |
| insert(0x0000011e, &Player_t::CanJump); | |
| insert(0x00000120, &Player_t::CanAltJump); | |
| insert(0x00000122, &Player_t::Effect); | |
| insert(0x00000124, &Player_t::Effect2); | |
| insert(0x0000012c, &Player_t::DuckRelease); | |
| insert(0x0000012e, &Player_t::Duck); | |
| insert(0x00000130, &Player_t::DropRelease); | |
| insert(0x00000132, &Player_t::StandUp); | |
| insert(0x00000134, &Player_t::StandUp2); | |
| insert(0x00000136, &Player_t::Bumped); | |
| insert(0x00000138, &Player_t::Bumped2); | |
| insert(0x0000013c, &Player_t::Dead); | |
| insert(0x0000013e, &Player_t::TimeToLive); | |
| insert(0x00000140, &Player_t::Immune); | |
| insert(0x00000142, &Player_t::Immune2); | |
| insert(0x00000144, &Player_t::ForceHitSpot3); | |
| insert(0x00000146, &Player_t::Pinched1); | |
| insert(0x00000148, &Player_t::Pinched2); | |
| insert(0x0000014a, &Player_t::Pinched3); | |
| insert(0x0000014c, &Player_t::Pinched4); | |
| insert(0x0000014e, &Player_t::NPCPinched); | |
| insert(0x00000150, &Player_t::m2Speed); | |
| insert(0x00000154, &Player_t::HoldingNPC); | |
| insert(0x00000156, &Player_t::CanGrabNPCs); | |
| insert(0x00000158, &Player_t::HeldBonus); | |
| insert(0x0000015a, &Player_t::Section); | |
| insert(0x0000015c, &Player_t::WarpCD); | |
| insert(0x0000015e, &Player_t::Warp); | |
| insert(0x00000160, &Player_t::FireBallCD); | |
| insert(0x00000162, &Player_t::FireBallCD2); | |
| insert(0x00000164, &Player_t::TailCount); | |
| insert(0x00000168, &Player_t::RunCount); | |
| insert(0x0000016c, &Player_t::CanFly); | |
| insert(0x0000016e, &Player_t::CanFly2); | |
| insert(0x00000170, &Player_t::FlyCount); | |
| insert(0x00000172, &Player_t::RunRelease); | |
| insert(0x00000174, &Player_t::JumpRelease); | |
| insert(0x00000176, &Player_t::StandingOnNPC); | |
| insert(0x00000178, &Player_t::StandingOnTempNPC); | |
| insert(0x0000017a, &Player_t::UnStart); | |
| insert(0x0000017c, &Player_t::mountBump); | |
| insert(0x00000180, &Player_t::SpeedFixY); | |
| } | |
| double getValue(Player_t *obj, size_t address, FIELDTYPE ftype) override | |
| { | |
| if(address >= 0x80 && address < 0xB0) // YoshiTongue | |
| return s_locMem.getValue(&obj->YoshiTongue, address - 0x80, ftype); | |
| else if(address >= 0xC0 && address < 0xF0) // Location | |
| return s_locMem.getValue(&obj->Location, address - 0xC0, ftype); | |
| else if(address >= 0xF2 && address < 0x106) // Controls | |
| return s_conMem.getValue(&obj->Controls, address - 0xF2, ftype); | |
| return PlayerParent::getValue(obj, address, ftype); | |
| } | |
| void setValue(Player_t *obj, size_t address, double value, FIELDTYPE ftype) override | |
| { | |
| if(address >= 0x80 && address < 0xB0) // YoshiTongue | |
| { | |
| s_locMem.setValue(&obj->YoshiTongue, address - 0x80, value, ftype); | |
| return; | |
| } | |
| else if(address >= 0xC0 && address < 0xF0) // Location | |
| { | |
| s_locMem.setValue(&obj->Location, address - 0xC0, value, ftype); | |
| return; | |
| } | |
| else if(address >= 0xF2 && address < 0x106) // Controls | |
| { | |
| s_conMem.setValue(&obj->Controls, address - 0xF2, value, ftype); | |
| return; | |
| } | |
| PlayerParent::setValue(obj, address, value, ftype); | |
| } | |
| }; | |
| static constexpr char npc_t_name[] = "NPC_t"; | |
| typedef SMBXObjectMemoryEmulator<NPC_t, npc_t_name, 0x160> NpcParent; | |
| class NPCMemory final : public NpcParent | |
| { | |
| public: | |
| NPCMemory() noexcept : NpcParent() | |
| { | |
| buildTable(); | |
| } | |
| void buildTable() | |
| { | |
| // Note: strings that became indices have been blocked out temporarily | |
| // (mememu does not support setting strings yet) | |
| // insert(0x00000000, &NPC_t::AttLayer); | |
| insert(0x00000004, &NPC_t::Quicksand); | |
| insert(0x00000006, &NPC_t::RespawnDelay); | |
| insert(0x00000008, &NPC_t::Bouce); | |
| insert(0x0000000a, &NPC_t::Pinched1); | |
| insert(0x0000000c, &NPC_t::Pinched2); | |
| insert(0x0000000e, &NPC_t::Pinched3); | |
| insert(0x00000010, &NPC_t::Pinched4); | |
| insert(0x00000012, &NPC_t::MovingPinched); | |
| insert(0x00000014, &NPC_t::NetTimeout); | |
| insert(0x00000018, &NPC_t::RealSpeedX); | |
| insert(0x0000001c, &NPC_t::Wet); | |
| insert(0x0000001e, &NPC_t::Settings); | |
| insert(0x00000020, &NPC_t::NoLavaSplash); | |
| insert(0x00000022, &NPC_t::Slope); | |
| insert(0x00000024, &NPC_t::Multiplier); | |
| insert(0x00000026, &NPC_t::TailCD); | |
| insert(0x00000028, &NPC_t::Shadow); | |
| // insert(0x0000002c, &NPC_t::TriggerActivate); | |
| // insert(0x00000030, &NPC_t::TriggerDeath); | |
| // insert(0x00000034, &NPC_t::TriggerTalk); | |
| // insert(0x00000038, &NPC_t::TriggerLast); | |
| // insert(0x0000003c, &NPC_t::Layer); | |
| insert(0x00000040, &NPC_t::Hidden); | |
| insert(0x00000042, &NPC_t::Legacy); | |
| insert(0x00000044, &NPC_t::Chat); | |
| insert(0x00000046, &NPC_t::Inert); | |
| insert(0x00000048, &NPC_t::Stuck); | |
| insert(0x0000004a, &NPC_t::DefaultStuck); | |
| // insert(0x0000004c, &NPC_t::Text); | |
| insert(0x00000050, &NPC_t::oldAddBelt); | |
| insert(0x00000054, &NPC_t::PinchCount); | |
| insert(0x00000056, &NPC_t::Pinched); | |
| insert(0x00000058, &NPC_t::PinchedDirection); | |
| insert(0x0000005c, &NPC_t::BeltSpeed); | |
| insert(0x00000060, &NPC_t::standingOnPlayer); | |
| insert(0x00000062, &NPC_t::standingOnPlayerY); | |
| insert(0x00000064, &NPC_t::Generator); | |
| insert(0x00000068, &NPC_t::GeneratorTimeMax); | |
| insert(0x0000006c, &NPC_t::GeneratorTime); | |
| insert(0x00000070, &NPC_t::GeneratorDirection); | |
| insert(0x00000072, &NPC_t::GeneratorEffect); | |
| insert(0x00000074, &NPC_t::GeneratorActive); | |
| insert(0x00000076, &NPC_t::playerTemp); | |
| // insert(0x00000078, &NPC_t::Location); // between 0x78 and 0xA8 | |
| // insert(0x000000a8, &NPC_t::DefaultLocation); // between 0xA8 and 0xD8 | |
| insert(0x000000d8, &NPC_t::DefaultDirection); | |
| insert(0x000000dc, &NPC_t::DefaultType); | |
| insert(0x000000de, &NPC_t::DefaultSpecial); | |
| insert(0x000000e0, &NPC_t::DefaultSpecial2); | |
| insert(0x000000e2, &NPC_t::Type); | |
| insert(0x000000e4, &NPC_t::Frame); | |
| insert(0x000000e8, &NPC_t::FrameCount); | |
| insert(0x000000ec, &NPC_t::Direction); | |
| insert(0x000000f0, &NPC_t::Special); | |
| insert(0x000000f8, &NPC_t::Special2); | |
| insert(0x00000100, &NPC_t::Special3); | |
| insert(0x00000108, &NPC_t::Special4); | |
| insert(0x00000110, &NPC_t::Special5); | |
| insert(0x00000118, &NPC_t::Special6); | |
| insert(0x00000120, &NPC_t::TurnAround); | |
| insert(0x00000122, &NPC_t::Killed); | |
| insert(0x00000124, &NPC_t::Active); | |
| // insert(0x00000126, &NPC_t::Reset); | |
| insert(0x0000012a, &NPC_t::TimeLeft); | |
| insert(0x0000012c, &NPC_t::HoldingPlayer); | |
| insert(0x0000012e, &NPC_t::CantHurt); | |
| insert(0x00000130, &NPC_t::CantHurtPlayer); | |
| insert(0x00000132, &NPC_t::BattleOwner); | |
| insert(0x00000134, &NPC_t::WallDeath); | |
| insert(0x00000136, &NPC_t::Projectile); | |
| insert(0x00000138, &NPC_t::Effect); | |
| insert(0x0000013c, &NPC_t::Effect2); | |
| insert(0x00000144, &NPC_t::Effect3); | |
| insert(0x00000146, &NPC_t::Section); | |
| insert(0x00000148, &NPC_t::Damage); | |
| insert(0x0000014c, &NPC_t::JustActivated); | |
| insert(0x0000014e, &NPC_t::Block); | |
| insert(0x00000150, &NPC_t::tempBlock); | |
| insert(0x00000152, &NPC_t::onWall); | |
| insert(0x00000154, &NPC_t::TurnBackWipe); | |
| insert(0x00000156, &NPC_t::Immune); | |
| } | |
| double getValue(NPC_t *obj, size_t address, FIELDTYPE ftype) override | |
| { | |
| if(address >= 0x78 && address < 0xA8) // Location | |
| return s_locMem.getValue(&obj->Location, address - 0x78, ftype); | |
| else if(address >= 0xA8 && address < 0xD8) // DefaultLocation | |
| return s_locMem.getValue(&obj->DefaultLocation, address - 0xA8, ftype); | |
| else if(address == 0x126) | |
| return obj->Reset[1] ? 0xFFFF : 0; | |
| else if(address == 0x128) | |
| return obj->Reset[2] ? 0xFFFF : 0; | |
| return NpcParent::getValue(obj, address, ftype); | |
| } | |
| void setValue(NPC_t *obj, size_t address, double value, FIELDTYPE ftype) override | |
| { | |
| if(address >= 0x78 && address < 0xA8) // Location | |
| { | |
| s_locMem.setValue(&obj->Location, address - 0x78, value, ftype); | |
| // sync trees for NPC eventually | |
| return; | |
| } | |
| else if(address >= 0xA8 && address < 0xD8) // DefaultLocation | |
| { | |
| s_locMem.setValue(&obj->DefaultLocation, address - 0xA8, value, ftype); | |
| return; | |
| } | |
| else if(address == 0x126) | |
| { | |
| obj->Reset[1] = value != 0; | |
| } | |
| else if(address == 0x128) | |
| { | |
| obj->Reset[2] = value != 0; | |
| } | |
| NpcParent::setValue(obj, address, value, ftype); | |
| #if 0 // Layer sync hook, but setting it is not yet supported. | |
| if(address == 0x3C) | |
| { | |
| int index = obj - &NPC[0]; | |
| if(index >= -128 && index <= maxNPCs) | |
| syncLayers_NPC(index); | |
| } | |
| #endif | |
| } | |
| }; | |
| static SMBXMemoryEmulator s_emu; | |
| static PlayerMemory s_emuPlayer; | |
| static NPCMemory s_emuNPC; | |
| template<typename T, class D> | |
| SDL_FORCE_INLINE void opAdd(D &mem, size_t addr, double o2, FIELDTYPE ftype) | |
| { | |
| double o1 = mem.getValue(addr, ftype); | |
| T res = static_cast<T>(o1) + static_cast<T>(o2); | |
| mem.setValue(addr, static_cast<double>(res), ftype); | |
| } | |
| template<typename T, class D> | |
| SDL_FORCE_INLINE void opSub(D &mem, size_t addr, double o2, FIELDTYPE ftype) | |
| { | |
| double o1 = mem.getValue(addr, ftype); | |
| T res = static_cast<T>(o1) - static_cast<T>(o2); | |
| mem.setValue(addr, static_cast<double>(res), ftype); | |
| } | |
| template<typename T, class D> | |
| SDL_FORCE_INLINE void opMul(D &mem, size_t addr, double o2, FIELDTYPE ftype) | |
| { | |
| double o1 = mem.getValue(addr, ftype); | |
| T res = static_cast<T>(o1) * static_cast<T>(o2); | |
| mem.setValue(addr, static_cast<double>(res), ftype); | |
| } | |
| template<typename T, class D> | |
| SDL_FORCE_INLINE void opDiv(D &mem, size_t addr, double o2, FIELDTYPE ftype) | |
| { | |
| double o1 = mem.getValue(addr, ftype); | |
| T res = static_cast<T>(o1) / static_cast<T>(o2); | |
| mem.setValue(addr, static_cast<double>(res), ftype); | |
| } | |
| template<typename T, class D> | |
| SDL_FORCE_INLINE void opXor(D &mem, size_t addr, double o2, FIELDTYPE ftype) | |
| { | |
| double o1 = mem.getValue(addr, ftype); | |
| T res = static_cast<T>(o1) ^ static_cast<T>(o2); | |
| mem.setValue(addr, static_cast<double>(res), ftype); | |
| } | |
| void MemAssign(size_t address, double value, OPTYPE operation, FIELDTYPE ftype) | |
| { | |
| if(address < GM_BASE || address > GM_END) | |
| { | |
| pLogWarning("MemEmu: MemAssign Requested value of out-of-range global address: 0x%x", address); | |
| return; | |
| } | |
| if(ftype == FT_INVALID) | |
| return; | |
| if(operation == OP_Div && value == 0) | |
| return; | |
| switch(operation) | |
| { | |
| case OP_Assign: | |
| s_emu.setValue(address, value, ftype); | |
| break; | |
| case OP_Add: | |
| { | |
| switch(ftype) | |
| { | |
| case FT_BYTE: | |
| { | |
| opAdd<uint8_t>(s_emu, address, value, ftype); | |
| break; | |
| } | |
| case FT_WORD: | |
| { | |
| opAdd<int16_t>(s_emu, address, value, ftype); | |
| break; | |
| } | |
| case FT_DWORD: | |
| { | |
| opAdd<int32_t>(s_emu, address, value, ftype); | |
| break; | |
| } | |
| case FT_FLOAT: | |
| { | |
| opAdd<float>(s_emu, address, value, ftype); | |
| break; | |
| } | |
| case FT_DFLOAT: | |
| opAdd<double>(s_emu, address, value, ftype); | |
| break; | |
| default: | |
| break; | |
| } | |
| }//OP Add | |
| break; | |
| case OP_Sub: | |
| { | |
| switch(ftype) | |
| { | |
| case FT_BYTE: | |
| { | |
| opSub<uint8_t>(s_emu, address, value, ftype); | |
| break; | |
| } | |
| case FT_WORD: | |
| { | |
| opSub<int16_t>(s_emu, address, value, ftype); | |
| break; | |
| } | |
| case FT_DWORD: | |
| { | |
| opSub<int32_t>(s_emu, address, value, ftype); | |
| break; | |
| } | |
| case FT_FLOAT: | |
| { | |
| opSub<float>(s_emu, address, value, ftype); | |
| break; | |
| } | |
| case FT_DFLOAT: | |
| opSub<double>(s_emu, address, value, ftype); | |
| break; | |
| default: | |
| break; | |
| } | |
| }//OP Sub | |
| break; | |
| case OP_Mult: | |
| { | |
| switch(ftype) | |
| { | |
| case FT_BYTE: | |
| { | |
| opMul<uint8_t>(s_emu, address, value, ftype); | |
| break; | |
| } | |
| case FT_WORD: | |
| { | |
| opMul<int16_t>(s_emu, address, value, ftype); | |
| break; | |
| } | |
| case FT_DWORD: | |
| { | |
| opMul<int32_t>(s_emu, address, value, ftype); | |
| break; | |
| } | |
| case FT_FLOAT: | |
| { | |
| opMul<float>(s_emu, address, value, ftype); | |
| break; | |
| } | |
| case FT_DFLOAT: | |
| opMul<double>(s_emu, address, value, ftype); | |
| break; | |
| default: | |
| break; | |
| } | |
| }//OP Mult | |
| break; | |
| case OP_Div: | |
| { | |
| switch(ftype) | |
| { | |
| case FT_BYTE: | |
| { | |
| opDiv<uint8_t>(s_emu, address, value, ftype); | |
| break; | |
| } | |
| case FT_WORD: | |
| { | |
| opDiv<int16_t>(s_emu, address, value, ftype); | |
| break; | |
| } | |
| case FT_DWORD: | |
| { | |
| opDiv<int32_t>(s_emu, address, value, ftype); | |
| break; | |
| } | |
| case FT_FLOAT: | |
| { | |
| opDiv<float>(s_emu, address, value, ftype); | |
| break; | |
| } | |
| case FT_DFLOAT: | |
| opDiv<double>(s_emu, address, value, ftype); | |
| break; | |
| default: | |
| break; | |
| } | |
| }//OP Div | |
| break; | |
| case OP_XOR: | |
| { | |
| switch(ftype) | |
| { | |
| case FT_BYTE: | |
| { | |
| opXor<uint8_t>(s_emu, address, value, ftype); | |
| break; | |
| } | |
| case FT_WORD: | |
| { | |
| opXor<int16_t>(s_emu, address, value, ftype); | |
| break; | |
| } | |
| case FT_DWORD: | |
| { | |
| opXor<int32_t>(s_emu, address, value, ftype); | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| }//OP XOR | |
| break; | |
| default: | |
| break; | |
| }// switch on op | |
| } | |
| bool CheckMem(size_t address, double value, COMPARETYPE ctype, FIELDTYPE ftype) | |
| { | |
| if(address < GM_BASE || address > GM_END) | |
| { | |
| pLogWarning("MemEmu: CheckMem Requested value of out-of-range global address: 0x%x", address); | |
| return false; | |
| } | |
| double cur = s_emu.getValue(address, ftype); | |
| switch(ctype) | |
| { | |
| case CMPT_EQUALS: | |
| switch(ftype) | |
| { | |
| case FT_BYTE: | |
| return static_cast<uint8_t>(cur) == static_cast<uint8_t>(value); | |
| case FT_WORD: | |
| return static_cast<int16_t>(cur) == static_cast<int16_t>(value); | |
| case FT_DWORD: | |
| return static_cast<int32_t>(cur) == static_cast<int32_t>(value); | |
| case FT_FLOAT: | |
| return fEqual(static_cast<float>(cur), static_cast<float>(value)); | |
| case FT_DFLOAT: | |
| return fEqual(cur, value); | |
| default: | |
| return false; | |
| } | |
| case CMPT_GREATER: | |
| switch(ftype) | |
| { | |
| case FT_BYTE: | |
| return static_cast<uint8_t>(cur) > static_cast<uint8_t>(value); | |
| case FT_WORD: | |
| return static_cast<int16_t>(cur) > static_cast<int16_t>(value); | |
| case FT_DWORD: | |
| return static_cast<int32_t>(cur) > static_cast<int32_t>(value); | |
| case FT_FLOAT: | |
| return static_cast<float>(cur) > static_cast<float>(value); | |
| case FT_DFLOAT: | |
| return cur > value; | |
| default: | |
| return false; | |
| } | |
| case CMPT_LESS: | |
| switch(ftype) | |
| { | |
| case FT_BYTE: | |
| return static_cast<uint8_t>(cur) < static_cast<uint8_t>(value); | |
| case FT_WORD: | |
| return static_cast<int16_t>(cur) < static_cast<int16_t>(value); | |
| case FT_DWORD: | |
| return static_cast<int32_t>(cur) < static_cast<int32_t>(value); | |
| case FT_FLOAT: | |
| return static_cast<float>(cur) < static_cast<float>(value); | |
| case FT_DFLOAT: | |
| return cur < value; | |
| default: | |
| return false; | |
| } | |
| case CMPT_NOTEQ: | |
| switch(ftype) | |
| { | |
| case FT_BYTE: | |
| return static_cast<uint8_t>(cur) != static_cast<uint8_t>(value); | |
| case FT_WORD: | |
| return static_cast<int16_t>(cur) != static_cast<int16_t>(value); | |
| case FT_DWORD: | |
| return static_cast<int32_t>(cur) != static_cast<int32_t>(value); | |
| case FT_FLOAT: | |
| return !fEqual(static_cast<float>(cur), static_cast<float>(value)); | |
| case FT_DFLOAT: | |
| return !fEqual(cur, value); | |
| default: | |
| return false; | |
| } | |
| } | |
| return false; | |
| } | |
| double GetMem(size_t addr, FIELDTYPE ftype) | |
| { | |
| if(addr < GM_BASE || addr > GM_END) | |
| { | |
| pLogWarning("MemEmu: GetMem Requested value of out-of-range global address: 0x%x", addr); | |
| return 0.0; | |
| } | |
| double cur = s_emu.getValue(addr, ftype); | |
| switch(ftype) | |
| { | |
| case FT_BYTE: | |
| return static_cast<double>(static_cast<uint8_t>(cur)); | |
| case FT_WORD: | |
| return static_cast<double>(static_cast<int16_t>(cur)); | |
| case FT_DWORD: | |
| return static_cast<double>(static_cast<int32_t>(cur)); | |
| case FT_FLOAT: | |
| return static_cast<double>(static_cast<float>(cur)); | |
| default: | |
| case FT_DFLOAT: | |
| return cur; | |
| } | |
| } | |
| template<typename T, class D, class U> | |
| SDL_FORCE_INLINE void opAdd(D &mem, U *obj, size_t addr, double o2, FIELDTYPE ftype) | |
| { | |
| double o1 = mem.getValue(obj, addr, ftype); | |
| T res = static_cast<T>(o1) + static_cast<T>(o2); | |
| mem.setValue(obj, addr, static_cast<double>(res), ftype); | |
| } | |
| template<typename T, class D, class U> | |
| SDL_FORCE_INLINE void opSub(D &mem, U *obj, size_t addr, double o2, FIELDTYPE ftype) | |
| { | |
| double o1 = mem.getValue(obj, addr, ftype); | |
| T res = static_cast<T>(o1) - static_cast<T>(o2); | |
| mem.setValue(obj, addr, static_cast<double>(res), ftype); | |
| } | |
| template<typename T, class D, class U> | |
| SDL_FORCE_INLINE void opMul(D &mem, U *obj, size_t addr, double o2, FIELDTYPE ftype) | |
| { | |
| double o1 = mem.getValue(obj, addr, ftype); | |
| T res = static_cast<T>(o1) * static_cast<T>(o2); | |
| mem.setValue(obj, addr, static_cast<double>(res), ftype); | |
| } | |
| template<typename T, class D, class U> | |
| SDL_FORCE_INLINE void opDiv(D &mem, U *obj, size_t addr, double o2, FIELDTYPE ftype) | |
| { | |
| double o1 = mem.getValue(obj, addr, ftype); | |
| T res = static_cast<T>(o1) / static_cast<T>(o2); | |
| mem.setValue(obj, addr, static_cast<double>(res), ftype); | |
| } | |
| template<typename T, class D, class U> | |
| SDL_FORCE_INLINE void opXor(D &mem, U *obj, size_t addr, double o2, FIELDTYPE ftype) | |
| { | |
| double o1 = mem.getValue(obj, addr, ftype); | |
| T res = static_cast<T>(o1) ^ static_cast<T>(o2); | |
| mem.setValue(obj, addr, static_cast<double>(res), ftype); | |
| } | |
| template<class T, class U> | |
| static void MemAssignType(T &mem, U *obj, size_t address, double value, OPTYPE operation, FIELDTYPE ftype) | |
| { | |
| if(ftype == FT_INVALID) | |
| return; | |
| if(operation == OP_Div && value == 0) | |
| return; | |
| switch(operation) | |
| { | |
| case OP_Assign: | |
| mem.setValue(obj, address, value, ftype); | |
| break; | |
| case OP_Add: | |
| { | |
| switch(ftype) | |
| { | |
| case FT_BYTE: | |
| { | |
| opAdd<uint8_t>(mem, obj, address, value, ftype); | |
| break; | |
| } | |
| case FT_WORD: | |
| { | |
| opAdd<int16_t>(mem, obj, address, value, ftype); | |
| break; | |
| } | |
| case FT_DWORD: | |
| { | |
| opAdd<int32_t>(mem, obj, address, value, ftype); | |
| break; | |
| } | |
| case FT_FLOAT: | |
| { | |
| opAdd<float>(mem, obj, address, value, ftype); | |
| break; | |
| } | |
| case FT_DFLOAT: | |
| opAdd<double>(mem, obj, address, value, ftype); | |
| break; | |
| default: | |
| break; | |
| } | |
| }//OP Add | |
| break; | |
| case OP_Sub: | |
| { | |
| switch(ftype) | |
| { | |
| case FT_BYTE: | |
| { | |
| opSub<uint8_t>(mem, obj, address, value, ftype); | |
| break; | |
| } | |
| case FT_WORD: | |
| { | |
| opSub<int16_t>(mem, obj, address, value, ftype); | |
| break; | |
| } | |
| case FT_DWORD: | |
| { | |
| opSub<int32_t>(mem, obj, address, value, ftype); | |
| break; | |
| } | |
| case FT_FLOAT: | |
| { | |
| opSub<float>(mem, obj, address, value, ftype); | |
| break; | |
| } | |
| case FT_DFLOAT: | |
| opSub<double>(mem, obj, address, value, ftype); | |
| break; | |
| default: | |
| break; | |
| } | |
| }//OP Sub | |
| break; | |
| case OP_Mult: | |
| { | |
| switch(ftype) | |
| { | |
| case FT_BYTE: | |
| { | |
| opMul<uint8_t>(mem, obj, address, value, ftype); | |
| break; | |
| } | |
| case FT_WORD: | |
| { | |
| opMul<int16_t>(mem, obj, address, value, ftype); | |
| break; | |
| } | |
| case FT_DWORD: | |
| { | |
| opMul<int32_t>(mem, obj, address, value, ftype); | |
| break; | |
| } | |
| case FT_FLOAT: | |
| { | |
| opMul<float>(mem, obj, address, value, ftype); | |
| break; | |
| } | |
| case FT_DFLOAT: | |
| opMul<double>(mem, obj, address, value, ftype); | |
| break; | |
| default: | |
| break; | |
| } | |
| }//OP Mult | |
| break; | |
| case OP_Div: | |
| { | |
| switch(ftype) | |
| { | |
| case FT_BYTE: | |
| { | |
| opDiv<uint8_t>(mem, obj, address, value, ftype); | |
| break; | |
| } | |
| case FT_WORD: | |
| { | |
| opDiv<int16_t>(mem, obj, address, value, ftype); | |
| break; | |
| } | |
| case FT_DWORD: | |
| { | |
| opDiv<int32_t>(mem, obj, address, value, ftype); | |
| break; | |
| } | |
| case FT_FLOAT: | |
| { | |
| opDiv<float>(mem, obj, address, value, ftype); | |
| break; | |
| } | |
| case FT_DFLOAT: | |
| opDiv<double>(mem, obj, address, value, ftype); | |
| break; | |
| default: | |
| break; | |
| } | |
| }//OP Div | |
| break; | |
| case OP_XOR: | |
| { | |
| switch(ftype) | |
| { | |
| case FT_BYTE: | |
| { | |
| opXor<uint8_t>(mem, obj, address, value, ftype); | |
| break; | |
| } | |
| case FT_WORD: | |
| { | |
| opXor<int16_t>(mem, obj, address, value, ftype); | |
| break; | |
| } | |
| case FT_DWORD: | |
| { | |
| opXor<int32_t>(mem, obj, address, value, ftype); | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| }//OP XOR | |
| break; | |
| default: | |
| break; | |
| }// switch on op | |
| } | |
| template<class T, class U> | |
| static bool ChecmMemType(T &mem, U *obj, size_t offset, double value, COMPARETYPE ctype, FIELDTYPE ftype) | |
| { | |
| double cur = mem.getValue(obj, offset, ftype); | |
| switch(ctype) | |
| { | |
| case CMPT_EQUALS: | |
| switch(ftype) | |
| { | |
| case FT_BYTE: | |
| return static_cast<uint8_t>(cur) == static_cast<uint8_t>(value); | |
| case FT_WORD: | |
| return static_cast<int16_t>(cur) == static_cast<int16_t>(value); | |
| case FT_DWORD: | |
| return static_cast<int32_t>(cur) == static_cast<int32_t>(value); | |
| case FT_FLOAT: | |
| return fEqual(static_cast<float>(cur), static_cast<float>(value)); | |
| case FT_DFLOAT: | |
| return fEqual(cur, value); | |
| default: | |
| return false; | |
| } | |
| case CMPT_GREATER: | |
| switch(ftype) | |
| { | |
| case FT_BYTE: | |
| return static_cast<uint8_t>(cur) > static_cast<uint8_t>(value); | |
| case FT_WORD: | |
| return static_cast<int16_t>(cur) > static_cast<int16_t>(value); | |
| case FT_DWORD: | |
| return static_cast<int32_t>(cur) > static_cast<int32_t>(value); | |
| case FT_FLOAT: | |
| return static_cast<float>(cur) > static_cast<float>(value); | |
| case FT_DFLOAT: | |
| return cur > value; | |
| default: | |
| return false; | |
| } | |
| case CMPT_LESS: | |
| switch(ftype) | |
| { | |
| case FT_BYTE: | |
| return static_cast<uint8_t>(cur) < static_cast<uint8_t>(value); | |
| case FT_WORD: | |
| return static_cast<int16_t>(cur) < static_cast<int16_t>(value); | |
| case FT_DWORD: | |
| return static_cast<int32_t>(cur) < static_cast<int32_t>(value); | |
| case FT_FLOAT: | |
| return static_cast<float>(cur) < static_cast<float>(value); | |
| case FT_DFLOAT: | |
| return cur < value; | |
| default: | |
| return false; | |
| } | |
| case CMPT_NOTEQ: | |
| switch(ftype) | |
| { | |
| case FT_BYTE: | |
| return static_cast<uint8_t>(cur) != static_cast<uint8_t>(value); | |
| case FT_WORD: | |
| return static_cast<int16_t>(cur) != static_cast<int16_t>(value); | |
| case FT_DWORD: | |
| return static_cast<int32_t>(cur) != static_cast<int32_t>(value); | |
| case FT_FLOAT: | |
| return !fEqual(static_cast<float>(cur), static_cast<float>(value)); | |
| case FT_DFLOAT: | |
| return !fEqual(cur, value); | |
| default: | |
| return false; | |
| } | |
| } | |
| return false; | |
| } | |
| // #define DEBUG_MEMEMU_TRACE | |
| void MemAssign(Player_t *obj, size_t address, double value, OPTYPE operation, FIELDTYPE ftype) | |
| { | |
| #ifdef DEBUG_MEMEMU_TRACE | |
| D_pLogDebug("Player mem TRACE: Write 0x%08X, %g, op-%d, ft-%d", address, value, (int)operation, (int)ftype); | |
| #endif | |
| MemAssignType(s_emuPlayer, obj, address, value, operation, ftype); | |
| } | |
| bool CheckMem(Player_t *obj, size_t offset, double value, COMPARETYPE ctype, FIELDTYPE ftype) | |
| { | |
| #ifdef DEBUG_MEMEMU_TRACE | |
| D_pLogDebug("Player mem TRACE: Compare 0x%08X, %g, cp-%d, ft-%d", offset, value, (int)ctype, (int)ftype); | |
| #endif | |
| return ChecmMemType(s_emuPlayer, obj, offset, value, ctype, ftype); | |
| } | |
| double GetMem(Player_t *obj, size_t offset, FIELDTYPE ftype) | |
| { | |
| #ifdef DEBUG_MEMEMU_TRACE | |
| double value = s_emuPlayer.getValue(obj, offset, ftype); | |
| D_pLogDebug("Player mem TRACE: Read 0x%08X, %g, ft-%d", offset, value, (int)ftype); | |
| return value; | |
| #else | |
| return s_emuPlayer.getValue(obj, offset, ftype); | |
| #endif | |
| } | |
| void MemAssign(NPC_t *obj, size_t address, double value, OPTYPE operation, FIELDTYPE ftype) | |
| { | |
| #ifdef DEBUG_MEMEMU_TRACE | |
| D_pLogDebug("NPC mem TRACE: Write 0x%08X, %g, op-%d, ft-%d", address, value, (int)operation, (int)ftype); | |
| #endif | |
| MemAssignType(s_emuNPC, obj, address, value, operation, ftype); | |
| } | |
| bool CheckMem(NPC_t *obj, size_t offset, double value, COMPARETYPE ctype, FIELDTYPE ftype) | |
| { | |
| #ifdef DEBUG_MEMEMU_TRACE | |
| D_pLogDebug("NPC mem TRACE: Compare 0x%08X, %g, cp-%d, ft-%d", offset, value, (int)ctype, (int)ftype); | |
| #endif | |
| return ChecmMemType(s_emuNPC, obj, offset, value, ctype, ftype); | |
| } | |
| double GetMem(NPC_t *obj, size_t offset, FIELDTYPE ftype) | |
| { | |
| #ifdef DEBUG_MEMEMU_TRACE | |
| double value = s_emuNPC.getValue(obj, offset, ftype); | |
| D_pLogDebug("NPC mem TRACE: Read 0x%08X, %g, ft-%d", offset, value, (int)ftype); | |
| return value; | |
| #else | |
| return s_emuNPC.getValue(obj, offset, ftype); | |
| #endif | |
| } |