Merge branch 'master' into translations

This commit is contained in:
Vicki Pfau 2025-02-14 18:17:54 -08:00
commit 7cfea7af77
63 changed files with 1643 additions and 446 deletions

17
CHANGES
View File

@ -1,11 +1,18 @@
0.11.0: (Future)
Features:
- Forwarder support for 3DS and Vita
- Custom border support
- New option to lock the maximum frame size
- Memory access and information logging
- 3DS: Add faster "loose" sync mode, default enabled
- Scripting: New `input` API for getting raw keyboard/mouse/controller state
- Scripting: New `storage` API for saving data for a script, e.g. settings
- Scripting: New `image` and `canvas` APIs for drawing images and displaying on-screen
- Scripting: Debugger integration to allow for breakpoints and watchpoints
- New unlicensed GB mappers: NT (older types 1 and 2), Li Cheng, GGB-81
- New unlicensed GB mappers: NT (older types 1 and 2), Li Cheng, GGB-81, Sintax
- Initial support for bootleg GBA multicarts
- Debugger: Add range watchpoints
- "Headless" frontend for running tests, automation, etc.
Emulation fixes:
- ARM: Add framework for coprocessor support
- GB Serialize: Add missing Pocket Cam state to savestates
@ -16,6 +23,7 @@ Emulation fixes:
- GBA Video: Disable BG target 1 blending when OBJ blending (fixes mgba.io/i/2722)
- GBA Video: Improve emulation of window start/end conditions (fixes mgba.io/i/1945)
Other fixes:
- ARM Debugger: Fix disassembly of ror r0 barrel shift (fixes mgba.io/i/3412)
- Core: Fix inconsistencies with setting game-specific overrides (fixes mgba.io/i/2963)
- Debugger: Fix writing to specific segment in command-line debugger
- FFmpeg: Fix failing to record videos with CRF video (fixes mgba.io/i/3368)
@ -26,10 +34,12 @@ Other fixes:
- mGUI: Load parent directory if last used directory is missing (fixes mgba.io/i/3379)
- Qt: Fix savestate preview sizes with different scales (fixes mgba.io/i/2560)
- Qt: Fix potential crash when configuring shortcuts
- Qt: Fix regression where loading BIOS creates a save file (fixes mgba.io/i/3359)
Misc:
- Core: Handle relative paths for saves, screenshots, etc consistently (fixes mgba.io/i/2826)
- Core: Improve rumble emulation by averaging state over entire frame (fixes mgba.io/i/3232)
- Core: Add MD5 hashing for ROMs
- Core: Add support for specifying an arbitrary portable directory
- GB: Prevent incompatible BIOSes from being used on differing models
- GB Serialize: Add missing savestate support for MBC6 and NT (newer)
- GBA: Improve detection of valid ELF ROMs
@ -48,7 +58,12 @@ Misc:
- Qt: Show maker code and game version in ROM info
- Qt: Show a dummy shader settings tab if shaders aren't supported
- Qt: Allow passing multiple games on command line for multiplayer (closes mgba.io/i/3061)
- Qt: Support building against Qt 6
- Qt: Add shortcuts to increment fast forward speed (mgba.io/i/2903)
- Qt: Enable ROM preloading by default
- Res: Port hq2x and OmniScale shaders from SameBoy
- Res: Port NSO-gba-colors shader (closes mgba.io/i/2834)
- Res: Update gba-colors shader (closes mgba.io/i/2976)
- Scripting: Add `callbacks:oneshot` for single-call callbacks
- Updater: Fix rewriting folders and files on Windows (fixes mgba.io/i/3384)

View File

@ -241,6 +241,10 @@ endif()
if(APPLE)
execute_process(COMMAND xcrun --show-sdk-version OUTPUT_VARIABLE MACOSX_SDK)
if(NOT MACOSX_SDK)
message(WARNING "Could not detect SDK version; defaulting to system version. Is SDKROOT set?")
set(MACOSX_SDK ${CMAKE_SYSTEM_VERSION})
endif()
add_definitions(-D_DARWIN_C_SOURCE)
list(APPEND OS_LIB "-framework Foundation")

View File

@ -73,6 +73,7 @@ The following mappers are partially supported:
- Hitek (missing logo switching)
- GGB-81 (missing logo switching)
- Li Cheng (missing logo switching)
- Sintax (missing logo switching)
### Planned features

268
README_JP.md Normal file
View File

@ -0,0 +1,268 @@
mGBA
====
mGBAは、ゲームボーイアドバンスのゲームを実行するためのエミュレーターです。mGBAの目標は、既存の多くのゲームボーイアドバンスエミュレーターよりも高速かつ正確であり、他のエミュレーターにはない機能を追加することです。また、ゲームボーイおよびゲームボーイカラーのゲームもサポートしています。
最新のニュースとダウンロードは、[mgba.io](https://mgba.io/)で見つけることができます。
[![Build status](https://buildbot.mgba.io/badges/build-win32.svg)](https://buildbot.mgba.io)
[![Translation status](https://hosted.weblate.org/widgets/mgba/-/svg-badge.svg)](https://hosted.weblate.org/engage/mgba)
特徴
--------
- 高精度なゲームボーイアドバンスハードウェアのサポート[<sup>[1]</sup>](#missing)。
- ゲームボーイ/ゲームボーイカラーのハードウェアサポート。
- 高速なエミュレーション。ネットブックなどの低スペックハードウェアでもフルスピードで動作することが知られています。
- 重量級と軽量級のフロントエンドのためのQtおよびSDLポート。
- ローカル(同じコンピュータ)リンクケーブルのサポート。
- フラッシュメモリサイズを含む保存タイプの検出[<sup>[2]</sup>](#flashdetect)。
- モーションセンサーと振動機能を備えたカートリッジのサポート(ゲームコントローラーでのみ使用可能)。
- 設定なしでもリアルタイムクロックのサポート。
- ボクタイゲームのためのソーラーセンサーのサポート。
- ゲームボーイカメラとゲームボーイプリンターのサポート。
- 内蔵BIOS実装と外部BIOSファイルの読み込み機能。
- Luaを使用したスクリプトサポート。
- Tabキーを押し続けることでターボ/早送りサポート。
- バッククォートを押し続けることで巻き戻し。
- 最大10まで設定可能なフレームスキップ。
- スクリーンショットのサポート。
- チートコードのサポート。
- 9つのセーブステートスロット。セーブステートはスクリーンショットとしても表示可能。
- ビデオ、GIF、WebP、およびAPNGの録画。
- e-Readerのサポート。
- キーボードとゲームパッドのリマップ可能なコントロール。
- ZIPおよび7zファイルからの読み込み。
- IPS、UPS、およびBPSパッチのサポート。
- コマンドラインインターフェースとGDBリモートサポートを介したゲームデバッグ、GhidraおよびIDA Proと互換性あり。
- 設定可能なエミュレーションの巻き戻し。
- GameSharkおよびAction Replayスナップショットの読み込みおよびエクスポートのサポート。
- RetroArch/LibretroおよびOpenEmu用のコア。
- [Weblate](https://hosted.weblate.org/engage/mgba)を介した複数の言語のコミュニティ提供の翻訳。
- その他、多くの小さな機能。
#### ゲームボーイマッパー
以下のマッパーが完全にサポートされています:
- MBC1
- MBC1M
- MBC2
- MBC3
- MBC3+RTC
- MBC30
- MBC5
- MBC5+Rumble
- MBC7
- Wisdom Tree非公式
- NT "old type" 1 and 2非公式マルチカート
- NT "new type"非公式MBC5類似
- Pokémon Jade/Diamond非公式
- Sachen MMC1非公式
以下のマッパーが部分的にサポートされています:
- MBC6フラッシュメモリ書き込みサポートなし
- MMM01
- Pocket Cam
- TAMA5RTCサポート不完全
- HuC-1IRサポートなし
- HuC-3IRサポートなし
- Sachen MMC2代替配線サポートなし
- BBDロゴ切り替えなし
- Hitekロゴ切り替えなし
- GGB-81ロゴ切り替えなし
- Li Chengロゴ切り替えなし
### 計画されている機能
- ネットワーク対応のマルチプレイヤーリンクケーブルサポート。
- Dolphin/JOYバスリンクケーブルサポート。
- MP2kオーディオミキシング、ハードウェアより高品質のサウンド。
- ツールアシストランのための再録サポート。
- 包括的なデバッグスイート。
- ワイヤレスアダプターのサポート。
サポートされているプラットフォーム
-------------------
- Windows 7以降
- OS X 10.9Mavericks[<sup>[3]</sup>](#osxver)以降
- Linux
- FreeBSD
- Nintendo 3DS
- Nintendo Switch
- Wii
- PlayStation Vita
他のUnix系プラットフォームOpenBSDなども動作することが知られていますが、テストされておらず、完全にはサポートされていません。
### システム要件
要件は最小限です。Windows Vista以降を実行できるコンピュータであれば、エミュレーションを処理できるはずです。OpenGL 1.1以降のサポートも必要であり、シェーダーや高度な機能にはOpenGL 3.2以降が必要です。
ダウンロード
---------
ダウンロードは公式ウェブサイトの[ダウンロード][downloads]セクションで見つけることができます。ソースコードは[GitHub][source]で見つけることができます。
コントロール
--------
コントロールは設定メニューで設定可能です。多くのゲームコントローラーはデフォルトで自動的にマッピングされるはずです。デフォルトのキーボードコントロールは次のとおりです:
- **A**X
- **B**Z
- **L**A
- **R**S
- **Start**Enter
- **Select**Backspace
コンパイル
---------
コンパイルにはCMake 3.1以降の使用が必要です。GCC、Clang、およびVisual Studio 2019はmGBAのコンパイルに使用できることが知られています。
#### Dockerビルド
ほとんどのプラットフォームでのビルドにはDockerを使用することをお勧めします。いくつかのプラットフォームでmGBAをビルドするために必要なツールチェーンと依存関係を含むいくつかのDockerイメージが提供されています。
注意Windows 10以前の古いWindowsシステムを使用している場合、DockerがVirtualBox共有フォルダーを使用して現在の`mgba`チェックアウトディレクトリをDockerイメージの作業ディレクトリに正しくマッピングするように構成する必要がある場合があります。詳細については、issue [#1985](https://mgba.io/i/1985)を参照してください。)
Dockerイメージを使用してmGBAをビルドするには、mGBAのチェックアウトのルートで次のコマンドを実行します
docker run --rm -it -v ${PWD}:/home/mgba/src mgba/windows:w32
Dockerコンテナを起動した後、ビルド成果物を含む`build-win32`ディレクトリが生成されます。他のプラットフォーム用のDockerイメージに置き換えると、対応する他のディレクトリが生成されます。Docker Hubで利用可能なDockerイメージは次のとおりです
- mgba/3ds
- mgba/switch
- mgba/ubuntu:xenial
- mgba/ubuntu:bionic
- mgba/ubuntu:focal
- mgba/ubuntu:groovy
- mgba/vita
- mgba/wii
- mgba/windows:w32
- mgba/windows:w64
ビルドプロセスを高速化したい場合は、`-e MAKEFLAGS=-jN`フラグを追加して、`N`個のCPUコアでmGBAの並列ビルドを行うことを検討してください。
#### *nixビルド
UnixベースのシステムでCMakeを使用してビルドするには、次のコマンドを実行することをお勧めします
mkdir build
cd build
cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr ..
make
sudo make install
これにより、mGBAがビルドされ、`/usr/bin`および`/usr/lib`にインストールされます。インストールされている依存関係は自動的に検出され、依存関係が見つからない場合に無効になる機能は、`cmake`コマンドを実行した後に警告として表示されます。
macOSを使用している場合、手順は少し異なります。homebrewパッケージマネージャーを使用していると仮定すると、依存関係を取得してビルドするための推奨コマンドは次のとおりです
brew install cmake ffmpeg libzip qt5 sdl2 libedit lua pkg-config
mkdir build
cd build
cmake -DCMAKE_PREFIX_PATH=`brew --prefix qt5` ..
make
macOSでは`make install`を実行しないでください。正しく動作しないためです。
#### Windows開発者ビルド
##### MSYS2
Windowsでの開発用ビルドにはMSYS2を使用することをお勧めします。MSYS2の[ウェブサイト](https://msys2.github.io)に記載されているインストール手順に従ってください。32ビットバージョン「MSYS2 MinGW 32-bit」を実行していることを確認してくださいx86_64用にビルドする場合は64ビットバージョン「MSYS2 MinGW 64-bit」を実行してください。必要な依存関係をインストールするために次の追加コマンド中括弧を含むを実行しますこのコマンドは1100MiB以上のパッケージをダウンロードするため、長時間かかることに注意してください
pacman -Sy --needed base-devel git ${MINGW_PACKAGE_PREFIX}-{cmake,ffmpeg,gcc,gdb,libelf,libepoxy,libzip,lua,pkgconf,qt5,SDL2,ntldd-git}
次のコマンドを実行してソースコードをチェックアウトします:
git clone https://github.com/mgba-emu/mgba.git
最後に、次のコマンドを実行してビルドします:
mkdir -p mgba/build
cd mgba/build
cmake .. -G "MSYS Makefiles"
make -j$(nproc --ignore=1)
このWindows用mGBAビルドは、実行に必要なDLLが分散しているため、配布には適していないことに注意してください。ただし、開発には最適です。ただし、そのようなビルドを配布する必要がある場合たとえば、MSYS2環境がインストールされていないマシンでのテスト用、`cpack -G ZIP`を実行すると、必要なDLLをすべて含むzipファイルが準備されます。
##### Visual Studio
Visual Studioを使用してビルドするには、同様に複雑なセットアップが必要です。まず、[vcpkg](https://github.com/Microsoft/vcpkg)をインストールする必要があります。vcpkgをインストールした後、いくつかの追加パッケージをインストールする必要があります
vcpkg install ffmpeg[vpx,x264] libepoxy libpng libzip lua sdl2 sqlite3
このインストールでは、Nvidiaハードウェアでのハードウェアアクセラレーションビデオエンコーディングはサポートされません。これが重要な場合は、事前にCUDAをインストールし、前のコマンドに`ffmpeg[vpx,x264,nvcodec]`を置き換えます。
Qtもインストールする必要があります。ただし、Qtは合理的な組織ではなく、困窮している会社によって所有および運営されているため、最新バージョンのオフラインオープンソースエディションインストーラーは存在しないため、[旧バージョンのインストーラー](https://download.qt.io/official_releases/qt/5.12/5.12.9/qt-opensource-windows-x86-5.12.9.exe)に戻る必要がありますこれには無用なアカウントの作成が必要ですが、一時的に無効なプロキシを設定するか、ネットワークを無効にすることで回避できます、オンラインインストーラーを使用するいずれにしてもアカウントが必要です、またはvcpkgを使用してビルドする遅い。これらはすべて良い選択肢ではありません。インストーラーを使用する場合は、適用可能なMSVCバージョンをインストールする必要があります。オフラインインストーラーはMSVC 2019をサポートしていないことに注意してください。vcpkgを使用する場合、次のようにインストールする必要があります。特にクアッドコア以下のコンピュータではかなりの時間がかかります
vcpkg install qt5-base qt5-multimedia
次に、Visual Studioを開き、「リポジトリのクローンを作成」を選択し、`https://github.com/mgba-emu/mgba.git`を入力します。Visual Studioがクローンを完了したら、「ファイル」>「CMake」に移動し、チェックアウトされたリポジトリのルートにあるCMakeLists.txtファイルを開きます。そこから、他のVisual Studio CMakeプロジェクトと同様にVisual StudioでmGBAを開発できます。
#### ツールチェーンビルド
devkitARM3DS用、devkitPPCWii用、devkitA64Switch用、またはvitasdkPS Vita用を持っている場合は、次のコマンドを使用してビルドできます
mkdir build
cd build
cmake -DCMAKE_TOOLCHAIN_FILE=../src/platform/3ds/CMakeToolchain.txt ..
make
次のプラットフォーム用に`-DCMAKE_TOOLCHAIN_FILE`パラメータを置き換えます:
- 3DS`../src/platform/3ds/CMakeToolchain.txt`
- Switch`../src/platform/switch/CMakeToolchain.txt`
- Vita`../src/platform/psp2/CMakeToolchain.vitasdk`
- Wii`../src/platform/wii/CMakeToolchain.txt`
### 依存関係
mGBAには厳密な依存関係はありませんが、特定の機能には次のオプションの依存関係が必要です。依存関係が見つからない場合、これらの機能は無効になります。
- Qt 5GUIフロントエンド用。オーディオにはQt MultimediaまたはSDLが必要です。
- SDLより基本的なフロントエンドおよびQtフロントエンドでのゲームパッドサポート用。SDL 2が推奨されますが、1.2もサポートされています。
- zlibおよびlibpngスクリーンショットサポートおよびPNG内セーブステートサポート用。
- libeditコマンドラインデバッガーサポート用。
- ffmpegまたはlibavビデオ、GIF、WebP、およびAPNGの録画用。
- libzipまたはzlibzipファイルに保存されたROMの読み込み用。
- SQLite3ゲームデータベース用。
- libelfELF読み込み用。
- Luaスクリプト用。
- json-cスクリプトの`storage` API用。
SQLite3、libpng、およびzlibはエミュレーターに含まれているため、最初に外部でコンパイルする必要はありません。
脚注
---------
<a name="missing">[1]</a> 現在欠けている機能は次のとおりです
- モード3、4、および5のOBJウィンドウ[バグ#5](http://mgba.io/b/5)
<a name="flashdetect">[2]</a> フラッシュメモリサイズの検出は一部のケースで機能しません。これらは実行時に構成できますが、そのようなケースに遭遇した場合はバグを報告することをお勧めします。
<a name="osxver">[3]</a> 10.9はQtポートにのみ必要です。10.7またはそれ以前のバージョンでQtポートをビルドまたは実行することは可能かもしれませんが、公式にはサポートされていません。SDLポートは10.5で動作することが知られており、古いバージョンでも動作する可能性があります。
[downloads]: http://mgba.io/downloads.html
[source]: https://github.com/mgba-emu/mgba/
著作権
---------
mGBAの著作権は© 2013 2023 Jeffrey Pfauに帰属します。これは[Mozilla Public License version 2.0](https://www.mozilla.org/MPL/2.0/)の下で配布されています。配布されたLICENSEファイルにライセンスのコピーが含まれています。
mGBAには次のサードパーティライブラリが含まれています
- [inih](https://github.com/benhoyt/inih)、著作権© 2009 2020 Ben Hoyt、BSD 3-clauseライセンスの下で使用。
- [LZMA SDK](http://www.7-zip.org/sdk.html)、パブリックドメイン。
- [MurmurHash3](https://github.com/aappleby/smhasher)、Austin Applebyによる実装、パブリックドメイン。
- [getopt for MSVC](https://github.com/skandhurkat/Getopt-for-Visual-Studio/)、パブリックドメイン。
- [SQLite3](https://www.sqlite.org)、パブリックドメイン。
ゲームパブリッシャーであり、商業利用のためにmGBAのライセンスを取得したい場合は、[licensing@mgba.io](mailto:licensing@mgba.io)までメールでお問い合わせください。

View File

@ -20,6 +20,15 @@ enum mCPUComponentType {
CPU_COMPONENT_MAX
};
enum mMemoryAccessSource {
mACCESS_UNKNOWN = 0,
mACCESS_PROGRAM,
mACCESS_DMA,
mACCESS_SYSTEM,
mACCESS_DECOMPRESS,
mACCESS_COPY,
};
struct mCPUComponent {
uint32_t id;
void (*init)(void* cpu, struct mCPUComponent* component);

View File

@ -111,6 +111,7 @@ struct mDebuggerEntryInfo {
uint32_t newValue;
enum mWatchpointType watchType;
enum mWatchpointType accessType;
enum mMemoryAccessSource accessSource;
} wp;
struct {
@ -256,6 +257,7 @@ bool mDebuggerIsShutdown(const struct mDebugger*);
struct mDebuggerModule* mDebuggerCreateModule(enum mDebuggerType type, struct mCore*);
void mDebuggerModuleSetNeedsCallback(struct mDebuggerModule*);
void mDebuggerModuleClearNeedsCallback(struct mDebuggerModule*);
bool mDebuggerLookupIdentifier(struct mDebugger* debugger, const char* name, int32_t* value, int* segment);

View File

@ -50,6 +50,7 @@ enum GBMemoryBankControllerType {
GB_UNL_GGB81 = 0x223,
GB_UNL_SACHEN_MMC1 = 0x230,
GB_UNL_SACHEN_MMC2 = 0x231,
GB_UNL_SINTAX = 0x240,
};
enum GBVideoLayer {

View File

@ -132,6 +132,8 @@ struct ARMMemory {
uint32_t activeNonseqCycles16;
int32_t (*stall)(struct ARMCore*, int32_t wait);
void (*setActiveRegion)(struct ARMCore*, uint32_t address);
enum mMemoryAccessSource accessSource;
};
struct ARMCoprocessor {

View File

@ -23,6 +23,7 @@ struct mDebuggerAccessLogRegion {
uint32_t segmentStart;
mDebuggerAccessLogFlags* block;
mDebuggerAccessLogFlagsEx* blockEx;
ssize_t watchpoint;
};
DECLARE_VECTOR(mDebuggerAccessLogRegionList, struct mDebuggerAccessLogRegion);
@ -41,11 +42,16 @@ void mDebuggerAccessLoggerDeinit(struct mDebuggerAccessLogger*);
bool mDebuggerAccessLoggerOpen(struct mDebuggerAccessLogger*, struct VFile*, int mode);
bool mDebuggerAccessLoggerClose(struct mDebuggerAccessLogger*);
void mDebuggerAccessLoggerStart(struct mDebuggerAccessLogger*);
void mDebuggerAccessLoggerStop(struct mDebuggerAccessLogger*);
int mDebuggerAccessLoggerWatchMemoryBlockId(struct mDebuggerAccessLogger*, size_t id, mDebuggerAccessLogRegionFlags);
int mDebuggerAccessLoggerWatchMemoryBlockName(struct mDebuggerAccessLogger*, const char* internalName, mDebuggerAccessLogRegionFlags);
bool mDebuggerAccessLoggerCreateShadowFile(struct mDebuggerAccessLogger*, int region, struct VFile*, uint8_t fill);
struct mDebuggerAccessLogRegion* mDebuggerAccessLoggerGetRegion(struct mDebuggerAccessLogger*, uint32_t address, int segment, size_t* offset);
CXX_GUARD_END
#endif

View File

@ -261,6 +261,13 @@ struct GBSachenState {
uint8_t baseBank;
};
struct GBSintaxState {
uint8_t mode;
uint8_t xorValues[4];
uint8_t bankNo;
uint8_t romBankXor;
};
union GBMBCState {
struct GBMBC1State mbc1;
struct GBMBC6State mbc6;
@ -274,6 +281,7 @@ union GBMBCState {
struct GBPKJDState pkjd;
struct GBBBDState bbd;
struct GBSachenState sachen;
struct GBSintaxState sintax;
};
struct mRotationSource;

View File

@ -451,6 +451,12 @@ struct GBSerializedState {
uint8_t unmaskedBank;
uint8_t baseBank;
} sachen;
struct {
uint8_t mode;
uint8_t xorValues[4];
uint8_t bankNo;
uint8_t romBankXor;
} sintax;
struct {
uint8_t reserved[16];
} padding;

View File

@ -17,7 +17,7 @@ CXX_GUARD_START
enum GBAIORegisters {
// Video
GBA_REG_DISPCNT = 0x000,
GBA_REG_GREENSWP = 0x002,
GBA_REG_STEREOCNT = 0x002,
GBA_REG_DISPSTAT = 0x004,
GBA_REG_VCOUNT = 0x006,
GBA_REG_BG0CNT = 0x008,

View File

@ -17,6 +17,7 @@ struct GBAVideoProxyRenderer {
struct GBAVideoRenderer d;
struct GBAVideoRenderer* backend;
struct mVideoLogger* logger;
int flushScanline;
};
void GBAVideoProxyRendererCreate(struct GBAVideoProxyRenderer* renderer, struct GBAVideoRenderer* backend, struct mVideoLogger* logger);

View File

@ -110,7 +110,7 @@ struct GBAVideoSoftwareRenderer {
uint16_t bldy;
GBAMosaicControl mosaic;
bool greenswap;
bool stereo;
struct WindowN {
struct GBAVideoWindowRegion h;

View File

@ -45,9 +45,13 @@ enum FlashStateMachine {
FLASH_STATE_CONTINUE = 2,
};
enum FlashManufacturer {
FLASH_MFG_PANASONIC = 0x1B32,
FLASH_MFG_SANYO = 0x1362
enum FlashId {
FLASH_ATMEL_AT29LV512 = 0x3D1F, // 512k
FLASH_MACRONIX_MX29L512 = 0x1CC2, // 512k, unused
FLASH_MACRONIX_MX29L010 = 0x09C2, // 1M
FLASH_PANASONIC_MN63F805MNP = 0x1B32, // 512k, unused
FLASH_SANYO_LE26FV10N1TS = 0x1362, // 1M
FLASH_SST_39LVF512 = 0xD4BF, // 512k
};
enum {

View File

@ -42,7 +42,8 @@ enum {
enum GBAVideoObjMode {
OBJ_MODE_NORMAL = 0,
OBJ_MODE_SEMITRANSPARENT = 1,
OBJ_MODE_OBJWIN = 2
OBJ_MODE_OBJWIN = 2,
OBJ_MODE_STEREO = 3,
};
enum GBAVideoObjShape {
@ -142,6 +143,7 @@ DECL_BITS(GBARegisterDISPSTAT, VcountSetting, 8, 8);
DECL_BITFIELD(GBARegisterBGCNT, uint16_t);
DECL_BITS(GBARegisterBGCNT, Priority, 0, 2);
DECL_BITS(GBARegisterBGCNT, CharBase, 2, 2);
DECL_BITS(GBARegisterBGCNT, StereoMode, 4, 2);
DECL_BIT(GBARegisterBGCNT, Mosaic, 6);
DECL_BIT(GBARegisterBGCNT, 256Color, 7);
DECL_BITS(GBARegisterBGCNT, ScreenBase, 8, 5);

View File

@ -61,6 +61,8 @@ struct SM83Memory {
uint16_t activeMask;
uint16_t activeRegionEnd;
void (*setActiveRegion)(struct SM83Core*, uint16_t address);
enum mMemoryAccessSource accessSource;
};
struct SM83InterruptHandler {

View File

@ -121,6 +121,7 @@ static void _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, en
info.type.wp.newValue = newValue;
info.type.wp.watchType = watchpoint->type;
info.type.wp.accessType = type;
info.type.wp.accessSource = debugger->cpu->memory.accessSource;
info.address = address;
info.segment = 0;
info.width = width;

View File

@ -36,7 +36,7 @@
#define ADDR_MODE_1_ASR ADDR_MODE_1_SHIFT(ASR)
#define ADDR_MODE_1_ROR \
ADDR_MODE_1_SHIFT(ROR) \
if (!info->op3.shifterImm) { \
if ((info->operandFormat & ARM_OPERAND_SHIFT_IMMEDIATE_3) && !info->op3.shifterImm) { \
info->op3.shifterOp = ARM_SHIFT_RRX; \
}

View File

@ -507,6 +507,10 @@ void mCoreThreadClearCrashed(struct mCoreThread* threadContext) {
void mCoreThreadEnd(struct mCoreThread* threadContext) {
MutexLock(&threadContext->impl->stateMutex);
if (threadContext->impl->state == mTHREAD_SHUTDOWN) {
MutexUnlock(&threadContext->impl->stateMutex);
return;
}
_waitOnInterrupt(threadContext->impl);
threadContext->impl->state = mTHREAD_EXITING;
ConditionWake(&threadContext->impl->stateOnThreadCond);

View File

@ -62,74 +62,102 @@ static void _mDebuggerAccessLoggerEntered(struct mDebuggerModule* debugger, enum
break;
}
size_t i;
for (i = 0; i < mDebuggerAccessLogRegionListSize(&logger->regions); ++i) {
struct mDebuggerAccessLogRegion* region = mDebuggerAccessLogRegionListGetPointer(&logger->regions, i);
if (info->address < region->start || info->address >= region->end) {
continue;
}
size_t offset = info->address - region->start;
if (info->segment > 0) {
uint32_t segmentSize = region->end - region->segmentStart;
offset %= segmentSize;
offset += segmentSize * info->segment;
}
size_t offset;
struct mDebuggerAccessLogRegion* region = mDebuggerAccessLoggerGetRegion(logger, info->address, info->segment, &offset);
if (!region) {
return;
}
offset &= -info->width;
if (offset >= region->size) {
continue;
}
offset &= -info->width;
int j;
switch (reason) {
case DEBUGGER_ENTER_WATCHPOINT:
for (j = 0; j < info->width; ++j) {
if (info->type.wp.accessType & WATCHPOINT_WRITE) {
region->block[offset + j] = mDebuggerAccessLogFlagsFillWrite(region->block[offset + j]);
}
if (info->type.wp.accessType & WATCHPOINT_READ) {
region->block[offset + j] = mDebuggerAccessLogFlagsFillRead(region->block[offset + j]);
}
}
switch (info->width) {
case 1:
region->block[offset] = mDebuggerAccessLogFlagsFillAccess8(region->block[offset]);
break;
case 2:
region->block[offset] = mDebuggerAccessLogFlagsFillAccess16(region->block[offset]);
region->block[offset + 1] = mDebuggerAccessLogFlagsFillAccess16(region->block[offset + 1]);
break;
case 4:
region->block[offset] = mDebuggerAccessLogFlagsFillAccess32(region->block[offset]);
region->block[offset + 1] = mDebuggerAccessLogFlagsFillAccess32(region->block[offset + 1]);
region->block[offset + 2] = mDebuggerAccessLogFlagsFillAccess32(region->block[offset + 2]);
region->block[offset + 3] = mDebuggerAccessLogFlagsFillAccess32(region->block[offset + 3]);
break;
case 8:
region->block[offset] = mDebuggerAccessLogFlagsFillAccess64(region->block[offset]);
region->block[offset + 1] = mDebuggerAccessLogFlagsFillAccess64(region->block[offset + 1]);
region->block[offset + 2] = mDebuggerAccessLogFlagsFillAccess64(region->block[offset + 2]);
region->block[offset + 3] = mDebuggerAccessLogFlagsFillAccess64(region->block[offset + 3]);
region->block[offset + 4] = mDebuggerAccessLogFlagsFillAccess64(region->block[offset + 4]);
region->block[offset + 5] = mDebuggerAccessLogFlagsFillAccess64(region->block[offset + 5]);
region->block[offset + 6] = mDebuggerAccessLogFlagsFillAccess64(region->block[offset + 6]);
region->block[offset + 7] = mDebuggerAccessLogFlagsFillAccess64(region->block[offset + 7]);
break;
}
mDebuggerAccessLogFlags flags = 0;
mDebuggerAccessLogFlagsEx flagsEx = 0;
switch (reason) {
case DEBUGGER_ENTER_WATCHPOINT:
switch (info->type.wp.accessSource) {
case mACCESS_PROGRAM:
flagsEx = mDebuggerAccessLogFlagsExFillAccessProgram(flagsEx);
break;
case DEBUGGER_ENTER_ILLEGAL_OP:
region->block[offset] = mDebuggerAccessLogFlagsFillExecute(region->block[offset]);
case mACCESS_DMA:
flagsEx = mDebuggerAccessLogFlagsExFillAccessDMA(flagsEx);
break;
case mACCESS_SYSTEM:
flagsEx = mDebuggerAccessLogFlagsExFillAccessSystem(flagsEx);
break;
case mACCESS_DECOMPRESS:
flagsEx = mDebuggerAccessLogFlagsExFillAccessDecompress(flagsEx);
break;
case mACCESS_COPY:
flagsEx = mDebuggerAccessLogFlagsExFillAccessCopy(flagsEx);
break;
case mACCESS_UNKNOWN:
break;
}
if (info->type.wp.accessType & WATCHPOINT_WRITE) {
flags = mDebuggerAccessLogFlagsFillWrite(flags);
}
if (info->type.wp.accessType & WATCHPOINT_READ) {
flags = mDebuggerAccessLogFlagsFillRead(flags);
}
switch (info->width) {
case 1:
region->block[offset] = flags | mDebuggerAccessLogFlagsFillAccess8(region->block[offset]);
if (region->blockEx) {
uint16_t ex;
LOAD_16LE(ex, 0, &region->blockEx[offset]);
ex = mDebuggerAccessLogFlagsExFillErrorIllegalOpcode(ex);
STORE_16LE(ex, 0, &region->blockEx[offset]);
region->blockEx[offset] |= flagsEx;
}
break;
default:
case 2:
region->block[offset] = flags | mDebuggerAccessLogFlagsFillAccess16(region->block[offset]);
region->block[offset + 1] = flags | mDebuggerAccessLogFlagsFillAccess16(region->block[offset + 1]);
if (region->blockEx) {
region->blockEx[offset] |= flagsEx;
region->blockEx[offset + 1] |= flagsEx;
}
break;
case 4:
region->block[offset] = flags | mDebuggerAccessLogFlagsFillAccess32(region->block[offset]);
region->block[offset + 1] = flags | mDebuggerAccessLogFlagsFillAccess32(region->block[offset + 1]);
region->block[offset + 2] = flags | mDebuggerAccessLogFlagsFillAccess32(region->block[offset + 2]);
region->block[offset + 3] = flags | mDebuggerAccessLogFlagsFillAccess32(region->block[offset + 3]);
if (region->blockEx) {
region->blockEx[offset] |= flagsEx;
region->blockEx[offset + 1] |= flagsEx;
region->blockEx[offset + 2] |= flagsEx;
region->blockEx[offset + 3] |= flagsEx;
}
break;
case 8:
region->block[offset] = flags | mDebuggerAccessLogFlagsFillAccess64(region->block[offset]);
region->block[offset + 1] = flags | mDebuggerAccessLogFlagsFillAccess64(region->block[offset + 1]);
region->block[offset + 2] = flags | mDebuggerAccessLogFlagsFillAccess64(region->block[offset + 2]);
region->block[offset + 3] = flags | mDebuggerAccessLogFlagsFillAccess64(region->block[offset + 3]);
region->block[offset + 4] = flags | mDebuggerAccessLogFlagsFillAccess64(region->block[offset + 4]);
region->block[offset + 5] = flags | mDebuggerAccessLogFlagsFillAccess64(region->block[offset + 5]);
region->block[offset + 6] = flags | mDebuggerAccessLogFlagsFillAccess64(region->block[offset + 6]);
region->block[offset + 7] = flags | mDebuggerAccessLogFlagsFillAccess64(region->block[offset + 7]);
if (region->blockEx) {
region->blockEx[offset] |= flagsEx;
region->blockEx[offset + 1] |= flagsEx;
region->blockEx[offset + 2] |= flagsEx;
region->blockEx[offset + 3] |= flagsEx;
region->blockEx[offset + 4] |= flagsEx;
region->blockEx[offset + 5] |= flagsEx;
region->blockEx[offset + 6] |= flagsEx;
region->blockEx[offset + 7] |= flagsEx;
}
break;
}
break;
case DEBUGGER_ENTER_ILLEGAL_OP:
region->block[offset] = mDebuggerAccessLogFlagsFillExecute(region->block[offset]);
if (region->blockEx) {
uint16_t ex;
LOAD_16LE(ex, 0, &region->blockEx[offset]);
ex = mDebuggerAccessLogFlagsExFillErrorIllegalOpcode(ex);
STORE_16LE(ex, 0, &region->blockEx[offset]);
}
break;
default:
break;
}
}
@ -139,34 +167,22 @@ static void _mDebuggerAccessLoggerCallback(struct mDebuggerModule* debugger) {
struct mDebuggerInstructionInfo info;
logger->d.p->platform->nextInstructionInfo(logger->d.p->platform, &info);
size_t offset;
struct mDebuggerAccessLogRegion* region = mDebuggerAccessLoggerGetRegion(logger, info.address, info.segment, &offset);
if (!region) {
return;
}
size_t i;
for (i = 0; i < mDebuggerAccessLogRegionListSize(&logger->regions); ++i) {
struct mDebuggerAccessLogRegion* region = mDebuggerAccessLogRegionListGetPointer(&logger->regions, i);
if (info.address < region->start || info.address >= region->end) {
continue;
}
size_t offset = info.address - region->start;
if (info.segment > 0) {
uint32_t segmentSize = region->end - region->segmentStart;
offset %= segmentSize;
offset += segmentSize * info.segment;
}
for (i = 0; i < info.width; ++i) {
uint16_t ex = 0;
region->block[offset + i] = mDebuggerAccessLogFlagsFillExecute(region->block[offset + i]);
region->block[offset + i] |= info.flags[i];
if (offset >= region->size) {
continue;
}
size_t j;
for (j = 0; j < info.width; ++j) {
uint16_t ex = 0;
region->block[offset + j] = mDebuggerAccessLogFlagsFillExecute(region->block[offset + j]);
region->block[offset + j] |= info.flags[j];
if (region->blockEx) {
LOAD_16LE(ex, 0, &region->blockEx[offset + j]);
ex |= info.flagsEx[j];
STORE_16LE(ex, 0, &region->blockEx[offset + j]);
}
if (region->blockEx) {
LOAD_16LE(ex, 0, &region->blockEx[offset + i]);
ex |= info.flagsEx[i];
STORE_16LE(ex, 0, &region->blockEx[offset + i]);
}
}
}
@ -240,13 +256,18 @@ static bool _setupRegion(struct mDebuggerAccessLogger* logger, struct mDebuggerA
return false;
}
struct mWatchpoint wp = {
.segment = -1,
.minAddress = region->start,
.maxAddress = region->end,
.type = WATCHPOINT_RW,
};
logger->d.p->platform->setWatchpoint(logger->d.p->platform, &logger->d, &wp);
if (region->watchpoint < 0) {
struct mWatchpoint wp = {
.segment = -1,
.minAddress = region->start,
.maxAddress = region->end,
.type = WATCHPOINT_RW,
};
region->watchpoint = logger->d.p->platform->setWatchpoint(logger->d.p->platform, &logger->d, &wp);
}
if (region->watchpoint < 0) {
return false;
}
mDebuggerModuleSetNeedsCallback(&logger->d);
return true;
}
@ -290,7 +311,7 @@ static bool mDebuggerAccessLoggerLoad(struct mDebuggerAccessLogger* logger) {
LOAD_32LE(region->end, 0, &info->end);
LOAD_32LE(region->size, 0, &info->size);
LOAD_32LE(region->segmentStart, 0, &info->segmentStart);
if (!_setupRegion(logger, region, info)) {
if (!_mapRegion(logger, region, info)) {
mDebuggerAccessLogRegionListClear(&logger->regions);
return false;
}
@ -335,6 +356,30 @@ bool mDebuggerAccessLoggerOpen(struct mDebuggerAccessLogger* logger, struct VFil
return loaded;
}
void mDebuggerAccessLoggerStart(struct mDebuggerAccessLogger* logger) {
size_t i;
for (i = 0; i < logger->mapped->header.nRegions; ++i) {
struct mDebuggerAccessLogRegionInfo* info = &logger->mapped->regionInfo[i];
struct mDebuggerAccessLogRegion* region = mDebuggerAccessLogRegionListGetPointer(&logger->regions, i);
if (!_setupRegion(logger, region, info)) {
return;
}
}
}
void mDebuggerAccessLoggerStop(struct mDebuggerAccessLogger* logger) {
size_t i;
for (i = 0; i < logger->mapped->header.nRegions; ++i) {
struct mDebuggerAccessLogRegion* region = mDebuggerAccessLogRegionListGetPointer(&logger->regions, i);
if (region->watchpoint < 0) {
continue;
}
logger->d.p->platform->clearBreakpoint(logger->d.p->platform, region->watchpoint);
region->watchpoint = -1;
}
mDebuggerModuleClearNeedsCallback(&logger->d);
}
static int _mDebuggerAccessLoggerWatchMemoryBlock(struct mDebuggerAccessLogger* logger, const struct mCoreMemoryBlock* block, mDebuggerAccessLogRegionFlags flags) {
if (mDebuggerAccessLogRegionListSize(&logger->regions) >= logger->mapped->header.regionCapacity) {
return -1;
@ -406,6 +451,7 @@ static int _mDebuggerAccessLoggerWatchMemoryBlock(struct mDebuggerAccessLogger*
region->size = block->size;
region->segmentStart = block->segmentStart;
region->block = (mDebuggerAccessLogFlags*) ((uintptr_t) logger->backing + fileEnd);
region->watchpoint = -1;
struct mDebuggerAccessLogRegionInfo* info = &logger->mapped->regionInfo[id];
STORE_32LE(region->start, 0, &info->start);
@ -434,6 +480,7 @@ bool mDebuggerAccessLoggerClose(struct mDebuggerAccessLogger* logger) {
if (!logger->backing) {
return true;
}
mDebuggerAccessLoggerStop(logger);
mDebuggerAccessLogRegionListClear(&logger->regions);
logger->backing->unmap(logger->backing, logger->mapped, logger->backing->size(logger->backing));
logger->mapped = NULL;
@ -518,3 +565,28 @@ bool mDebuggerAccessLoggerCreateShadowFile(struct mDebuggerAccessLogger* logger,
}
return true;
}
struct mDebuggerAccessLogRegion* mDebuggerAccessLoggerGetRegion(struct mDebuggerAccessLogger* logger, uint32_t address, int segment, size_t* offsetOut) {
size_t i;
for (i = 0; i < mDebuggerAccessLogRegionListSize(&logger->regions); ++i) {
struct mDebuggerAccessLogRegion* region = mDebuggerAccessLogRegionListGetPointer(&logger->regions, i);
if (address < region->start || address >= region->end) {
continue;
}
size_t offset = address - region->start;
if (segment > 0) {
uint32_t segmentSize = region->end - region->segmentStart;
offset %= segmentSize;
offset += segmentSize * segment;
}
if (offset >= region->size) {
continue;
}
if (offsetOut) {
*offsetOut = offset;
}
return region;
}
return NULL;
}

View File

@ -320,3 +320,8 @@ void mDebuggerModuleSetNeedsCallback(struct mDebuggerModule* debugger) {
debugger->needsCallback = true;
mDebuggerUpdatePaused(debugger->p);
}
void mDebuggerModuleClearNeedsCallback(struct mDebuggerModule* debugger) {
debugger->needsCallback = false;
mDebuggerUpdatePaused(debugger->p);
}

View File

@ -109,7 +109,7 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t
.nStates = 2
};
*GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) {
.title = "Fast forward mute",
.title = "Mute while fast forwarding",
.data = GUI_V_S("fastForwardMute"),
.submenu = 0,
.state = false,
@ -341,7 +341,7 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t
mCoreConfigSetUIntValue(&runner->config, item->data.v.s, v->v.u);
break;
case GUI_VARIANT_INT:
mCoreConfigSetUIntValue(&runner->config, item->data.v.s, v->v.i);
mCoreConfigSetIntValue(&runner->config, item->data.v.s, v->v.i);
break;
case GUI_VARIANT_FLOAT:
mCoreConfigSetFloatValue(&runner->config, item->data.v.s, v->v.f);

View File

@ -702,6 +702,7 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) {
mCoreConfigGetIntValue(&runner->config, "showOSD", &showOSD);
mCoreConfigGetIntValue(&runner->config, "mute", &mute);
mCoreConfigGetIntValue(&runner->config, "fastForwardMute", &fastForwardMute);
runner->core->reloadConfigOption(runner->core, "threadedVideo.flushScanline", &runner->config);
#ifdef M_CORE_GB
if (runner->core->platform(runner->core) == mPLATFORM_GB) {
runner->core->reloadConfigOption(runner->core, "gb.pal", &runner->config);

View File

@ -113,7 +113,7 @@ static struct {
{"M161", GB_MBC_AUTODETECT}, // TODO
{"BBD", GB_UNL_BBD},
{"HITK", GB_UNL_HITEK},
{"SNTX", GB_MBC_AUTODETECT}, // TODO
{"SNTX", GB_UNL_SINTAX},
{"NTO1", GB_UNL_NT_OLD_1},
{"NTO2", GB_UNL_NT_OLD_2},
{"NTN", GB_UNL_NT_NEW},
@ -128,6 +128,8 @@ static struct {
{"NGHK", GB_MBC_AUTODETECT}, // TODO
{"GB81", GB_UNL_GGB81},
{"TPP1", GB_MBC_AUTODETECT}, // TODO
{"VF01", GB_MBC_AUTODETECT}, // TODO
{"SKL8", GB_MBC_AUTODETECT}, // TODO
{NULL, GB_MBC_AUTODETECT},
};
@ -223,6 +225,12 @@ static enum GBMemoryBankControllerType _detectUnlMBC(const uint8_t* mem, size_t
return GB_UNL_LI_CHENG;
}
break;
case 0x6c1dcf2d:
case 0x99e3449d:
if (mem[0x7FFF] != 0x01) { // Make sure we're not using a "fixed" version
return GB_UNL_SINTAX;
}
break;
}
if (mem[0x104] == 0xCE && mem[0x144] == 0xED && mem[0x114] == 0x66) {
@ -504,6 +512,14 @@ void GBMBCInit(struct GB* gb) {
gb->memory.sramAccess = true;
}
break;
case GB_UNL_SINTAX:
gb->memory.mbcWrite = _GBSintax;
gb->memory.mbcRead = _GBSintaxRead;
gb->memory.mbcReadBank1 = true;
if (gb->sramSize) {
gb->memory.sramAccess = true;
}
break;
}
gb->memory.currentBank = 1;
@ -559,6 +575,9 @@ void GBMBCReset(struct GB* gb) {
GBMBCSwitchBank0(gb, gb->memory.romSize / GB_SIZE_CART_BANK0 - 2);
GBMBCSwitchBank(gb, gb->memory.romSize / GB_SIZE_CART_BANK0 - 1);
break;
case GB_UNL_SINTAX:
gb->memory.mbcState.sintax.mode = 0xF;
break;
default:
break;
}

View File

@ -38,6 +38,7 @@ void _GBHitek(struct GB* gb, uint16_t address, uint8_t value);
void _GBLiCheng(struct GB* gb, uint16_t address, uint8_t value);
void _GBGGB81(struct GB* gb, uint16_t address, uint8_t value);
void _GBSachen(struct GB* gb, uint16_t address, uint8_t value);
void _GBSintax(struct GB* gb, uint16_t address, uint8_t value);
uint8_t _GBMBC2Read(struct GBMemory*, uint16_t address);
uint8_t _GBMBC6Read(struct GBMemory*, uint16_t address);
@ -54,6 +55,7 @@ uint8_t _GBHitekRead(struct GBMemory*, uint16_t address);
uint8_t _GBGGB81Read(struct GBMemory*, uint16_t address);
uint8_t _GBSachenMMC1Read(struct GBMemory*, uint16_t address);
uint8_t _GBSachenMMC2Read(struct GBMemory*, uint16_t address);
uint8_t _GBSintaxRead(struct GBMemory*, uint16_t address);
void _GBMBCLatchRTC(struct mRTCSource* rtc, uint8_t* rtcRegs, time_t* rtcLastLatch);
void _GBMBCAppendSaveSuffix(struct GB* gb, const void* buffer, size_t size);

View File

@ -500,3 +500,102 @@ uint8_t _GBSachenMMC2Read(struct GBMemory* memory, uint16_t address) {
return 0xFF;
}
}
static const uint8_t _sintaxReordering[16][8] = {
{ 2, 1, 4, 3, 6, 5, 0, 7 },
{ 3, 2, 5, 4, 7, 6, 1, 0 },
{ 0, 1, 2, 3, 4, 5, 6, 7 }, // unknown
{ 0, 1, 2, 3, 4, 5, 6, 7 }, // unknown
{ 0, 1, 2, 3, 4, 5, 6, 7 }, // unknown
{ 4, 5, 2, 3, 0, 1, 6, 7 },
{ 0, 1, 2, 3, 4, 5, 6, 7 }, // unknown
{ 6, 7, 4, 5, 1, 3, 0, 2 },
{ 0, 1, 2, 3, 4, 5, 6, 7 }, // unknown
{ 7, 6, 1, 0, 3, 2, 5, 4 },
{ 0, 1, 2, 3, 4, 5, 6, 7 }, // unknown
{ 5, 4, 7, 6, 1, 0, 3, 2 },
{ 0, 1, 2, 3, 4, 5, 6, 7 }, // unknown
{ 2, 3, 4, 5, 6, 7, 0, 1 },
{ 0, 1, 2, 3, 4, 5, 6, 7 }, // unknown
{ 0, 1, 2, 3, 4, 5, 6, 7 },
};
void _GBSintax(struct GB* gb, uint16_t address, uint8_t value) {
struct GBSintaxState* state = &gb->memory.mbcState.sintax;
if (address >= 0x2000 && address < 0x3000) {
state->bankNo = value;
value = _reorderBits(value, _sintaxReordering[state->mode]);
state->romBankXor = state->xorValues[state->bankNo & 0x3];
}
if ((address & 0xF0F0) == 0x5010) {
// contrary to previous belief it IS possible to change the mode after setting it initially
// The reason Metal Max was breaking is because it only recognises writes to 5x1x
// and that game writes to a bunch of other 5xxx addresses before battles
state->mode = value & 0xF;
mLOG(GB_MBC, DEBUG, "Sintax bank reorder mode: %X", state->mode);
switch (state->mode) {
// Supported modes
case 0x00: // Lion King, Golden Sun
case 0x01: // Langrisser
case 0x05: // Maple Story, Pokemon Platinum
case 0x07: // Bynasty Warriors 5
case 0x09: // ???
case 0x0B: // Shaolin Legend
case 0x0D: // Older games
case 0x0F: // Default mode, no reordering
break;
default:
mLOG(GB_MBC, DEBUG, "Bank reorder mode unsupported - %X", state->mode);
break;
}
_GBSintax(gb, 0x2000, state->bankNo); // fake a bank switch to select the correct bank
return;
}
if (address >= 0x7000 && address < 0x8000) {
int xorNo = (address & 0x00F0) >> 4;
switch (xorNo) {
case 2:
state->xorValues[0] = value;
mLOG(GB_MBC, DEBUG, "Sintax XOR 0: %X", value);
break;
case 3:
state->xorValues[1] = value;
mLOG(GB_MBC, DEBUG, "Sintax XOR 1: %X", value);
break;
case 4:
state->xorValues[2] = value;
mLOG(GB_MBC, DEBUG, "Sintax XOR 2: %X", value);
break;
case 5:
state->xorValues[3] = value;
mLOG(GB_MBC, DEBUG, "Sintax XOR 3: %X", value);
break;
}
// xor is applied immediately to the current bank
state->romBankXor = state->xorValues[state->bankNo & 0x3];
}
_GBMBC5(gb, address, value);
}
uint8_t _GBSintaxRead(struct GBMemory* memory, uint16_t address) {
struct GBSintaxState* state = &memory->mbcState.sintax;
switch (address >> 13) {
case 0x2:
case 0x3:
return memory->romBank[address & (GB_SIZE_CART_BANK0 - 1)] ^ state->romBankXor;
case 0x5:
if (memory->sramAccess && memory->sram) {
return memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)];
}
return 0xFF;
default:
return 0xFF;
}
}

View File

@ -148,6 +148,7 @@ void GBMemoryInit(struct GB* gb) {
cpu->memory.store8 = GBStore8;
cpu->memory.currentSegment = GBCurrentSegment;
cpu->memory.setActiveRegion = GBSetActiveRegion;
cpu->memory.accessSource = mACCESS_UNKNOWN;
gb->memory.wram = 0;
gb->memory.wramBank = 0;
@ -205,6 +206,7 @@ void GBMemoryReset(struct GB* gb) {
gb->memory.hdmaDest = 0;
gb->memory.isHdma = false;
gb->cpu->memory.accessSource = mACCESS_UNKNOWN;
gb->memory.dmaEvent.context = gb;
gb->memory.dmaEvent.name = "GB DMA";
@ -576,10 +578,13 @@ void _GBMemoryDMAService(struct mTiming* timing, void* context, uint32_t cyclesL
struct GB* gb = context;
int dmaRemaining = gb->memory.dmaRemaining;
gb->memory.dmaRemaining = 0;
enum mMemoryAccessSource oldAccess = gb->cpu->memory.accessSource;
gb->cpu->memory.accessSource = mACCESS_DMA;
uint8_t b = GBLoad8(gb->cpu, gb->memory.dmaSource);
// TODO: Can DMA write OAM during modes 2-3?
gb->video.oam.raw[gb->memory.dmaDest] = b;
gb->video.renderer->writeOAM(gb->video.renderer, gb->memory.dmaDest);
gb->cpu->memory.accessSource = oldAccess;
++gb->memory.dmaSource;
++gb->memory.dmaDest;
gb->memory.dmaRemaining = dmaRemaining - 1;
@ -591,8 +596,11 @@ void _GBMemoryDMAService(struct mTiming* timing, void* context, uint32_t cyclesL
void _GBMemoryHDMAService(struct mTiming* timing, void* context, uint32_t cyclesLate) {
struct GB* gb = context;
gb->cpuBlocked = true;
enum mMemoryAccessSource oldAccess = gb->cpu->memory.accessSource;
gb->cpu->memory.accessSource = mACCESS_DMA;
uint8_t b = gb->cpu->memory.load8(gb->cpu, gb->memory.hdmaSource);
gb->cpu->memory.store8(gb->cpu, gb->memory.hdmaDest, b);
gb->cpu->memory.accessSource = oldAccess;
++gb->memory.hdmaSource;
++gb->memory.hdmaDest;
--gb->memory.hdmaRemaining;
@ -832,6 +840,12 @@ void GBMemorySerialize(const struct GB* gb, struct GBSerializedState* state) {
state->memory.sachen.unmaskedBank = memory->mbcState.sachen.unmaskedBank;
state->memory.sachen.baseBank = memory->mbcState.sachen.baseBank;
break;
case GB_UNL_SINTAX:
state->memory.sintax.mode = memory->mbcState.sintax.mode;
memcpy(state->memory.sintax.xorValues, memory->mbcState.sintax.xorValues, sizeof(state->memory.sintax.xorValues));
state->memory.sintax.bankNo = memory->mbcState.sintax.bankNo;
state->memory.sintax.romBankXor = memory->mbcState.sintax.romBankXor;
break;
default:
break;
}
@ -1000,6 +1014,12 @@ void GBMemoryDeserialize(struct GB* gb, const struct GBSerializedState* state) {
memory->mbcState.sachen.baseBank = state->memory.sachen.baseBank;
GBMBCSwitchBank0(gb, memory->mbcState.sachen.baseBank & memory->mbcState.sachen.mask);
break;
case GB_UNL_SINTAX:
memory->mbcState.sintax.mode = state->memory.sintax.mode;
memcpy(memory->mbcState.sintax.xorValues, state->memory.sintax.xorValues, sizeof(memory->mbcState.sintax.xorValues));
memory->mbcState.sintax.bankNo = state->memory.sintax.bankNo;
memory->mbcState.sintax.romBankXor = state->memory.sintax.romBankXor;
break;
default:
break;
}

View File

@ -174,6 +174,8 @@ static void _BgAffineSet(struct GBA* gba) {
int destination = cpu->gprs[1];
float a, b, c, d;
float rx, ry;
enum mMemoryAccessSource oldAccess = cpu->memory.accessSource;
cpu->memory.accessSource = mACCESS_SYSTEM;
while (i--) {
// [ sx 0 0 ] [ cos(theta) -sin(theta) 0 ] [ 1 0 cx - ox ] [ A B rx ]
// [ 0 sy 0 ] * [ sin(theta) cos(theta) 0 ] * [ 0 1 cy - oy ] = [ C D ry ]
@ -205,6 +207,7 @@ static void _BgAffineSet(struct GBA* gba) {
cpu->memory.store32(cpu, destination + 12, ry * 256, 0);
destination += 16;
}
cpu->memory.accessSource = oldAccess;
}
static void _ObjAffineSet(struct GBA* gba) {
@ -216,6 +219,8 @@ static void _ObjAffineSet(struct GBA* gba) {
int destination = cpu->gprs[1];
int diff = cpu->gprs[3];
float a, b, c, d;
enum mMemoryAccessSource oldAccess = cpu->memory.accessSource;
cpu->memory.accessSource = mACCESS_SYSTEM;
while (i--) {
// [ sx 0 ] [ cos(theta) -sin(theta) ] [ A B ]
// [ 0 sy ] * [ sin(theta) cos(theta) ] = [ C D ]
@ -237,6 +242,7 @@ static void _ObjAffineSet(struct GBA* gba) {
cpu->memory.store16(cpu, destination + diff * 3, d * 256, 0);
destination += diff * 4;
}
cpu->memory.accessSource = oldAccess;
}
static void _MidiKey2Freq(struct GBA* gba) {
@ -244,7 +250,10 @@ static void _MidiKey2Freq(struct GBA* gba) {
int oldRegion = gba->memory.activeRegion;
gba->memory.activeRegion = GBA_REGION_BIOS;
enum mMemoryAccessSource oldAccess = cpu->memory.accessSource;
cpu->memory.accessSource = mACCESS_SYSTEM;
uint32_t key = cpu->memory.load32(cpu, cpu->gprs[0] + 4, 0);
cpu->memory.accessSource = oldAccess;
gba->memory.activeRegion = oldRegion;
cpu->gprs[0] = key / exp2f((180.f - cpu->gprs[1] - cpu->gprs[2] / 256.f) / 12.f);
@ -624,6 +633,8 @@ static void _unLz77(struct GBA* gba, int width) {
uint32_t source = cpu->gprs[0];
uint32_t dest = cpu->gprs[1];
int cycles = 20;
enum mMemoryAccessSource oldAccess = cpu->memory.accessSource;
cpu->memory.accessSource = mACCESS_DECOMPRESS;
int remaining = (cpu->memory.load32(cpu, source, &cycles) & 0xFFFFFF00) >> 8;
// We assume the signature byte (0x10) is correct
int blockheader = 0; // Some compilers warn if this isn't set, even though it's trivially provably always set
@ -698,6 +709,7 @@ static void _unLz77(struct GBA* gba, int width) {
blocksRemaining = 8;
}
}
cpu->memory.accessSource = oldAccess;
cpu->gprs[0] = source;
cpu->gprs[1] = dest;
cpu->gprs[3] = 0;
@ -713,6 +725,8 @@ static void _unHuffman(struct GBA* gba) {
struct ARMCore* cpu = gba->cpu;
uint32_t source = cpu->gprs[0] & 0xFFFFFFFC;
uint32_t dest = cpu->gprs[1];
enum mMemoryAccessSource oldAccess = cpu->memory.accessSource;
cpu->memory.accessSource = mACCESS_DECOMPRESS;
uint32_t header = cpu->memory.load32(cpu, source, 0);
int remaining = header >> 8;
unsigned bits = header & 0xF;
@ -722,6 +736,7 @@ static void _unHuffman(struct GBA* gba) {
}
if (32 % bits || bits == 1) {
mLOG(GBA_BIOS, STUB, "Unimplemented unaligned Huffman");
cpu->memory.accessSource = oldAccess;
return;
}
// We assume the signature byte (0x20) is correct
@ -773,6 +788,7 @@ static void _unHuffman(struct GBA* gba) {
}
}
}
cpu->memory.accessSource = oldAccess;
cpu->gprs[0] = source;
cpu->gprs[1] = dest;
}
@ -780,6 +796,8 @@ static void _unHuffman(struct GBA* gba) {
static void _unRl(struct GBA* gba, int width) {
struct ARMCore* cpu = gba->cpu;
uint32_t source = cpu->gprs[0];
enum mMemoryAccessSource oldAccess = cpu->memory.accessSource;
cpu->memory.accessSource = mACCESS_DECOMPRESS;
int remaining = (cpu->memory.load32(cpu, source & 0xFFFFFFFC, 0) & 0xFFFFFF00) >> 8;
int padding = (4 - remaining) & 0x3;
// We assume the signature byte (0x30) is correct
@ -846,6 +864,7 @@ static void _unRl(struct GBA* gba, int width) {
++dest;
}
}
cpu->memory.accessSource = oldAccess;
cpu->gprs[0] = source;
cpu->gprs[1] = dest;
}
@ -854,6 +873,8 @@ static void _unFilter(struct GBA* gba, int inwidth, int outwidth) {
struct ARMCore* cpu = gba->cpu;
uint32_t source = cpu->gprs[0] & 0xFFFFFFFC;
uint32_t dest = cpu->gprs[1];
enum mMemoryAccessSource oldAccess = cpu->memory.accessSource;
cpu->memory.accessSource = mACCESS_DECOMPRESS;
uint32_t header = cpu->memory.load32(cpu, source, 0);
int remaining = header >> 8;
// We assume the signature nybble (0x8) is correct
@ -888,6 +909,7 @@ static void _unFilter(struct GBA* gba, int inwidth, int outwidth) {
old = new;
source += inwidth;
}
cpu->memory.accessSource = oldAccess;
cpu->gprs[0] = source;
cpu->gprs[1] = dest;
}
@ -897,6 +919,8 @@ static void _unBitPack(struct GBA* gba) {
uint32_t source = cpu->gprs[0];
uint32_t dest = cpu->gprs[1];
uint32_t info = cpu->gprs[2];
enum mMemoryAccessSource oldAccess = cpu->memory.accessSource;
cpu->memory.accessSource = mACCESS_DECOMPRESS;
unsigned sourceLen = cpu->memory.load16(cpu, info, 0);
unsigned sourceWidth = cpu->memory.load8(cpu, info + 2, 0);
unsigned destWidth = cpu->memory.load8(cpu, info + 3, 0);
@ -908,6 +932,7 @@ static void _unBitPack(struct GBA* gba) {
break;
default:
mLOG(GBA_BIOS, GAME_ERROR, "Bad BitUnPack source width: %u", sourceWidth);
cpu->memory.accessSource = oldAccess;
return;
}
switch (destWidth) {
@ -920,6 +945,7 @@ static void _unBitPack(struct GBA* gba) {
break;
default:
mLOG(GBA_BIOS, GAME_ERROR, "Bad BitUnPack destination width: %u", destWidth);
cpu->memory.accessSource = oldAccess;
return;
}
uint32_t bias = cpu->memory.load32(cpu, info + 4, 0);
@ -949,6 +975,7 @@ static void _unBitPack(struct GBA* gba) {
dest += 4;
}
}
cpu->memory.accessSource = oldAccess;
cpu->gprs[0] = source;
cpu->gprs[1] = dest;
}

View File

@ -479,6 +479,14 @@ static void _GBACoreReloadConfigOption(struct mCore* core, const char* option, c
GBAVideoAssociateRenderer(&gba->video, renderer);
}
}
#ifndef MINIMAL_CORE
if (strcmp("threadedVideo.flushScanline", option) == 0) {
int flushScanline = -1;
mCoreConfigGetIntValue(config, "threadedVideo.flushScanline", &flushScanline);
gbacore->proxyRenderer.flushScanline = flushScanline;
}
#endif
}
static void _GBACoreSetOverride(struct mCore* core, const void* override) {
@ -730,6 +738,10 @@ static void _GBACoreReset(struct mCore* core) {
if (renderer && core->videoLogger) {
GBAVideoProxyRendererCreate(&gbacore->proxyRenderer, renderer, core->videoLogger);
renderer = &gbacore->proxyRenderer.d;
int flushScanline = -1;
mCoreConfigGetIntValue(&core->config, "threadedVideo.flushScanline", &flushScanline);
gbacore->proxyRenderer.flushScanline = flushScanline;
}
#endif
if (renderer) {

View File

@ -248,10 +248,12 @@ void GBADMAService(struct GBA* gba, int number, struct GBADMA* info) {
uint32_t dest = info->nextDest;
uint32_t sourceRegion = source >> BASE_OFFSET;
uint32_t destRegion = dest >> BASE_OFFSET;
enum mMemoryAccessSource oldAccess = cpu->memory.accessSource;
int32_t cycles = 2;
gba->cpuBlocked = true;
gba->performingDMA = 1 | (number << 1);
cpu->memory.accessSource = mACCESS_DMA;
if (info->count == info->nextCount) {
if (width == 4) {
@ -315,6 +317,7 @@ void GBADMAService(struct GBA* gba, int number, struct GBADMA* info) {
--info->nextCount;
gba->performingDMA = 0;
cpu->memory.accessSource = oldAccess;
int i;
for (i = 0; i < 4; ++i) {

View File

@ -75,6 +75,7 @@ void GBAVideoProxyRendererCreate(struct GBAVideoProxyRenderer* renderer, struct
logger->vramSize = GBA_SIZE_VRAM;
logger->oamSize = GBA_SIZE_OAM;
renderer->flushScanline = -1;
renderer->backend = backend;
}
@ -359,6 +360,9 @@ void GBAVideoProxyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t o
void GBAVideoProxyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer;
if (proxyRenderer->flushScanline == y) {
mVideoLoggerRendererFlush(proxyRenderer->logger);
}
if (!proxyRenderer->logger->block) {
_copyExtraState(proxyRenderer);
proxyRenderer->backend->drawScanline(proxyRenderer->backend, y);
@ -375,7 +379,9 @@ void GBAVideoProxyRendererFinishFrame(struct GBAVideoRenderer* renderer) {
proxyRenderer->backend->finishFrame(proxyRenderer->backend);
}
mVideoLoggerRendererFinishFrame(proxyRenderer->logger);
mVideoLoggerRendererFlush(proxyRenderer->logger);
if (proxyRenderer->flushScanline < 0) {
mVideoLoggerRendererFlush(proxyRenderer->logger);
}
}
static void GBAVideoProxyRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels) {

View File

@ -934,7 +934,7 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
}
// Fall through
case GBA_REG_DISPCNT:
case GBA_REG_GREENSWP:
case GBA_REG_STEREOCNT:
case GBA_REG_DISPSTAT:
case GBA_REG_VCOUNT:
case GBA_REG_BG0CNT:

View File

@ -82,6 +82,7 @@ void GBAMemoryInit(struct GBA* gba) {
cpu->memory.activeSeqCycles16 = 0;
cpu->memory.activeNonseqCycles32 = 0;
cpu->memory.activeNonseqCycles16 = 0;
cpu->memory.accessSource = mACCESS_UNKNOWN;
gba->memory.biosPrefetch = 0;
gba->memory.agbPrintProtect = 0;
@ -132,6 +133,7 @@ void GBAMemoryReset(struct GBA* gba) {
gba->memory.prefetch = false;
gba->memory.lastPrefetchedPc = 0;
gba->cpu->memory.accessSource = mACCESS_UNKNOWN;
if (!gba->memory.wram || !gba->memory.iwram) {
GBAMemoryDeinit(gba);
@ -299,22 +301,27 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
memory->activeRegion = newRegion;
switch (newRegion) {
case GBA_REGION_BIOS:
cpu->memory.accessSource = mACCESS_SYSTEM;
cpu->memory.activeRegion = memory->bios;
cpu->memory.activeMask = GBA_SIZE_BIOS - 1;
break;
case GBA_REGION_EWRAM:
cpu->memory.accessSource = mACCESS_PROGRAM;
cpu->memory.activeRegion = memory->wram;
cpu->memory.activeMask = GBA_SIZE_EWRAM - 1;
break;
case GBA_REGION_IWRAM:
cpu->memory.accessSource = mACCESS_PROGRAM;
cpu->memory.activeRegion = memory->iwram;
cpu->memory.activeMask = GBA_SIZE_IWRAM - 1;
break;
case GBA_REGION_PALETTE_RAM:
cpu->memory.accessSource = mACCESS_PROGRAM;
cpu->memory.activeRegion = (uint32_t*) gba->video.palette;
cpu->memory.activeMask = GBA_SIZE_PALETTE_RAM - 1;
break;
case GBA_REGION_VRAM:
cpu->memory.accessSource = mACCESS_PROGRAM;
if (address & 0x10000) {
cpu->memory.activeRegion = (uint32_t*) &gba->video.vram[0x8000];
cpu->memory.activeMask = 0x00007FFF;
@ -324,6 +331,7 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
}
break;
case GBA_REGION_OAM:
cpu->memory.accessSource = mACCESS_PROGRAM;
cpu->memory.activeRegion = (uint32_t*) gba->video.oam.raw;
cpu->memory.activeMask = GBA_SIZE_OAM - 1;
break;
@ -333,6 +341,7 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
case GBA_REGION_ROM1_EX:
case GBA_REGION_ROM2:
case GBA_REGION_ROM2_EX:
cpu->memory.accessSource = mACCESS_PROGRAM;
cpu->memory.activeRegion = memory->rom;
cpu->memory.activeMask = memory->romMask;
if ((address & (GBA_SIZE_ROM0 - 1)) < memory->romSize) {
@ -345,6 +354,7 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
}
// Fall through
default:
cpu->memory.accessSource = mACCESS_UNKNOWN;
memory->activeRegion = -1;
cpu->memory.activeRegion = (uint32_t*) _deadbeef;
cpu->memory.activeMask = 0;

View File

@ -136,7 +136,7 @@ static void GBAVideoSoftwareRendererReset(struct GBAVideoRenderer* renderer) {
softwareRenderer->oamMax = 0;
softwareRenderer->mosaic = 0;
softwareRenderer->greenswap = false;
softwareRenderer->stereo = false;
softwareRenderer->nextY = 0;
softwareRenderer->objOffsetX = 0;
@ -195,8 +195,8 @@ static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRender
softwareRenderer->dispcnt = value;
GBAVideoSoftwareRendererUpdateDISPCNT(softwareRenderer);
break;
case GBA_REG_GREENSWP:
softwareRenderer->greenswap = value & 1;
case GBA_REG_STEREOCNT:
softwareRenderer->stereo = value & 1;
break;
case GBA_REG_BG0CNT:
value &= 0xDFFF;
@ -705,7 +705,7 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render
}
int x;
if (softwareRenderer->greenswap) {
if (softwareRenderer->stereo) {
for (x = 0; x < GBA_VIDEO_HORIZONTAL_PIXELS; x += 4) {
row[x] = softwareRenderer->row[x] & (M_COLOR_RED | M_COLOR_BLUE);
row[x] |= softwareRenderer->row[x + 1] & M_COLOR_GREEN;

View File

@ -372,11 +372,11 @@ uint8_t GBASavedataReadFlash(struct GBASavedata* savedata, uint16_t address) {
if (savedata->command == FLASH_COMMAND_ID) {
if (savedata->type == GBA_SAVEDATA_FLASH512) {
if (address < 2) {
return FLASH_MFG_PANASONIC >> (address * 8);
return FLASH_PANASONIC_MN63F805MNP >> (address * 8);
}
} else if (savedata->type == GBA_SAVEDATA_FLASH1M) {
if (address < 2) {
return FLASH_MFG_SANYO >> (address * 8);
return FLASH_SANYO_LE26FV10N1TS >> (address * 8);
}
}
}

View File

@ -128,7 +128,7 @@ void GBAVideoAssociateRenderer(struct GBAVideo* video, struct GBAVideoRenderer*
video->renderer->init(video->renderer);
video->renderer->reset(video->renderer);
renderer->writeVideoRegister(renderer, GBA_REG_DISPCNT, video->p->memory.io[GBA_REG(DISPCNT)]);
renderer->writeVideoRegister(renderer, GBA_REG_GREENSWP, video->p->memory.io[GBA_REG(GREENSWP)]);
renderer->writeVideoRegister(renderer, GBA_REG_STEREOCNT, video->p->memory.io[GBA_REG(STEREOCNT)]);
int address;
for (address = GBA_REG_BG0CNT; address < 0x56; address += 2) {
if (address == 0x4E) {

View File

@ -273,6 +273,7 @@ static void _resetCamera(struct m3DSImageSource* imageSource) {
static void _setup(struct mGUIRunner* runner) {
if (core2) {
mCoreConfigSetDefaultIntValue(&runner->config, "threadedVideo", 1);
mCoreConfigSetDefaultIntValue(&runner->config, "threadedVideo.flushScanline", 0);
mCoreLoadForeignConfig(runner->core, &runner->config);
}
@ -968,6 +969,22 @@ int main(int argc, char* argv[]) {
{ .id = 0 }
},
.configExtra = (struct GUIMenuItem[]) {
#ifdef M_CORE_GBA
{
.title = "Sync",
.data = GUI_V_S("threadedVideo.flushScanline"),
.state = 0,
.validStates = (const char*[]) {
"Loose (faster, can tear)", "Strict (slower, less input lag)"
},
.stateMappings = (const struct GUIVariant[]) {
GUI_V_I(0),
GUI_V_I(-1),
},
.nStates = 2
},
#endif
{
.title = "Screen mode",
.data = GUI_V_S("screenMode"),

View File

@ -54,7 +54,7 @@ class GBA(Core):
class GBAMemory(Memory):
def __init__(self, core, romSize=lib.SIZE_CART0):
def __init__(self, core, romSize=lib.GBA_SIZE_ROM0):
super(GBAMemory, self).__init__(core, 0x100000000)
self.bios = Memory(core, lib.GBA_SIZE_BIOS, lib.GBA_BASE_BIOS)
@ -64,11 +64,10 @@ class GBAMemory(Memory):
self.palette = Memory(core, lib.GBA_SIZE_PALETTE_RAM, lib.GBA_BASE_PALETTE_RAM)
self.vram = Memory(core, lib.GBA_SIZE_VRAM, lib.GBA_BASE_VRAM)
self.oam = Memory(core, lib.GBA_SIZE_OAM, lib.GBA_BASE_OAM)
self.cart0 = Memory(core, romSize, lib.BASE_CART0)
self.cart1 = Memory(core, romSize, lib.BASE_CART1)
self.cart2 = Memory(core, romSize, lib.BASE_CART2)
self.cart = self.cart0
self.rom = self.cart0
self.rom0 = Memory(core, romSize, lib.GBA_BASE_ROM0)
self.rom1 = Memory(core, romSize, lib.GBA_BASE_ROM1)
self.rom2 = Memory(core, romSize, lib.GBA_BASE_ROM2)
self.rom = self.rom0
self.sram = Memory(core, lib.GBA_SIZE_SRAM, lib.GBA_BASE_SRAM)

View File

@ -262,6 +262,7 @@ if(ENABLE_DEBUGGERS)
DebuggerConsole.cpp
DebuggerConsoleController.cpp
MemoryAccessLogController.cpp
MemoryAccessLogModel.cpp
MemoryAccessLogView.cpp)
endif()

View File

@ -149,6 +149,7 @@ ConfigController::ConfigController(QObject* parent)
mCoreConfigSetDefaultIntValue(&m_config, "sgb.borders", 1);
mCoreConfigSetDefaultIntValue(&m_config, "gb.colors", GB_COLORS_CGB);
#endif
mCoreConfigSetDefaultIntValue(&m_config, "preload", true);
mCoreConfigMap(&m_config, &m_opts);
mSubParserGraphicsInit(&m_subparsers[0], &m_graphicsOpts);

View File

@ -305,7 +305,7 @@ void CoreController::loadConfig(ConfigController* config) {
m_fastForwardMute = config->getOption("fastForwardMute", -1).toInt();
mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "volume");
mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "mute");
m_preload = config->getOption("preload").toInt();
m_preload = config->getOption("preload", true).toInt();
QSize sizeBefore = screenDimensions();
m_activeBuffer.resize(256 * 224 * sizeof(mColor));
@ -1267,6 +1267,9 @@ void CoreController::finishFrame() {
}
void CoreController::updatePlayerSave() {
if (m_saveBlocked) {
return;
}
int savePlayerId = m_multiplayer->saveId(this);
QString saveSuffix;

View File

@ -176,6 +176,7 @@ public slots:
void scanCards(const QStringList&);
void replaceGame(const QString&);
void yankPak();
void blockSave() { m_saveBlocked = true; }
void addKey(int key);
void clearKey(int key);
@ -262,7 +263,8 @@ private:
QString m_savePath;
bool m_patched = false;
bool m_preload = false;
bool m_preload = true;
bool m_saveBlocked = false;
uint32_t m_crc32;
QString m_internalTitle;

View File

@ -169,6 +169,7 @@ CoreController* CoreManager::loadBIOS(int platform, const QString& path) {
mDirectorySetAttachBase(&core->dirs, VDirOpen(bytes.constData()));
CoreController* cc = new CoreController(core);
cc->blockSave();
if (m_multiplayer) {
cc->setMultiplayerController(m_multiplayer);
}

View File

@ -36,7 +36,7 @@ signals:
private:
const mCoreConfig* m_config = nullptr;
MultiplayerController* m_multiplayer = nullptr;
bool m_preload = false;
bool m_preload = true;
};
}

View File

@ -44,6 +44,7 @@ static const QList<GBMemoryBankControllerType> s_mbcList{
GB_UNL_LI_CHENG,
GB_UNL_SACHEN_MMC1,
GB_UNL_SACHEN_MMC2,
GB_UNL_SINTAX,
};
static QMap<GBModel, QString> s_gbModelNames;
@ -102,6 +103,7 @@ QString GameBoy::mbcName(GBMemoryBankControllerType mbc) {
s_mbcNames[GB_UNL_LI_CHENG] = tr("Li Cheng");
s_mbcNames[GB_UNL_SACHEN_MMC1] = tr("Sachen (MMC1)");
s_mbcNames[GB_UNL_SACHEN_MMC2] = tr("Sachen (MMC2)");
s_mbcNames[GB_UNL_SINTAX] = tr("Sintax");
}
return s_mbcNames[mbc];

View File

@ -60,7 +60,7 @@ const QList<IOViewer::RegisterDescription>& IOViewer::registerDescriptions(mPlat
{ tr("Enable Window 1"), 14 },
{ tr("Enable OBJ Window"), 15 },
});
// 0x04000002: Green swap
// 0x04000002: STEREOCNT
regGBA.append({
{ tr("Swap green components"), 0 },
});

View File

@ -10,8 +10,14 @@
#include "utils.h"
#include "VFileDevice.h"
#include <mgba-util/math.h>
using namespace QGBA;
int MemoryAccessLogController::Flags::count() const {
return popcount32(flags) + popcount32(flagsEx);
}
MemoryAccessLogController::MemoryAccessLogController(CoreController* controller, QObject* parent)
: QObject(parent)
, m_controller(controller)
@ -39,6 +45,17 @@ bool MemoryAccessLogController::canExport() const {
return m_regionMapping.contains("cart0");
}
MemoryAccessLogController::Flags MemoryAccessLogController::flagsForAddress(uint32_t addresss, int segment) {
uint32_t offset = cacheRegion(addresss, segment);
if (!m_cachedRegion) {
return { 0, 0 };
}
return {
m_cachedRegion->blockEx ? m_cachedRegion->blockEx[offset] : mDebuggerAccessLogFlagsEx{},
m_cachedRegion->block ? m_cachedRegion->block[offset] : mDebuggerAccessLogFlags{},
};
}
void MemoryAccessLogController::updateRegion(const QString& internalName, bool checked) {
if (checked) {
m_watchedRegions += internalName;
@ -48,7 +65,9 @@ void MemoryAccessLogController::updateRegion(const QString& internalName, bool c
if (!m_active) {
return;
}
m_regionMapping[internalName] = mDebuggerAccessLoggerWatchMemoryBlockName(&m_logger, internalName.toUtf8().constData(), activeFlags());
if (checked && !m_regionMapping.contains(internalName)) {
m_regionMapping[internalName] = mDebuggerAccessLoggerWatchMemoryBlockName(&m_logger, internalName.toUtf8().constData(), activeFlags());
}
emit regionMappingChanged(internalName, checked);
}
@ -57,6 +76,38 @@ void MemoryAccessLogController::setFile(const QString& path) {
}
void MemoryAccessLogController::start(bool loadExisting, bool logExtra) {
if (!m_loaded) {
load(loadExisting);
}
if (!m_loaded) {
return;
}
CoreController::Interrupter interrupter(m_controller);
mDebuggerAccessLoggerStart(&m_logger);
m_logExtra = logExtra;
m_active = true;
for (const auto& region : m_watchedRegions) {
m_regionMapping[region] = mDebuggerAccessLoggerWatchMemoryBlockName(&m_logger, region.toUtf8().constData(), activeFlags());
}
emit loggingChanged(true);
}
void MemoryAccessLogController::stop() {
if (!m_active) {
return;
}
CoreController::Interrupter interrupter(m_controller);
mDebuggerAccessLoggerStop(&m_logger);
emit loggingChanged(false);
interrupter.resume();
m_active = false;
}
void MemoryAccessLogController::load(bool loadExisting) {
if (m_loaded) {
return;
}
int flags = O_CREAT | O_RDWR;
if (!loadExisting) {
flags |= O_TRUNC;
@ -66,7 +117,6 @@ void MemoryAccessLogController::start(bool loadExisting, bool logExtra) {
LOG(QT, ERROR) << tr("Failed to open memory log file");
return;
}
m_logExtra = logExtra;
mDebuggerAccessLoggerInit(&m_logger);
CoreController::Interrupter interrupter(m_controller);
@ -76,25 +126,22 @@ void MemoryAccessLogController::start(bool loadExisting, bool logExtra) {
LOG(QT, ERROR) << tr("Failed to open memory log file");
return;
}
m_active = true;
emit loggingChanged(true);
for (const auto& region : m_watchedRegions) {
m_regionMapping[region] = mDebuggerAccessLoggerWatchMemoryBlockName(&m_logger, region.toUtf8().constData(), activeFlags());
}
interrupter.resume();
emit loaded(true);
m_loaded = true;
}
void MemoryAccessLogController::stop() {
if (!m_active) {
void MemoryAccessLogController::unload() {
if (m_active) {
stop();
}
if (m_active) {
return;
}
CoreController::Interrupter interrupter(m_controller);
m_controller->detachDebuggerModule(&m_logger.d);
mDebuggerAccessLoggerDeinit(&m_logger);
emit loggingChanged(false);
interrupter.resume();
m_active = false;
emit loaded(false);
m_loaded = false;
}
mDebuggerAccessLogRegionFlags MemoryAccessLogController::activeFlags() const {
@ -116,3 +163,27 @@ void MemoryAccessLogController::exportFile(const QString& filename) {
mDebuggerAccessLoggerCreateShadowFile(&m_logger, m_regionMapping[QString("cart0")], vf, 0);
vf->close(vf);
}
uint32_t MemoryAccessLogController::cacheRegion(uint32_t address, int segment) {
if (m_cachedRegion && (address < m_cachedRegion->start || address >= m_cachedRegion->end)) {
m_cachedRegion = nullptr;
}
if (!m_cachedRegion) {
m_cachedRegion = mDebuggerAccessLoggerGetRegion(&m_logger, address, segment, nullptr);
}
if (!m_cachedRegion) {
return 0;
}
size_t offset = address - m_cachedRegion->start;
if (segment > 0) {
uint32_t segmentSize = m_cachedRegion->end - m_cachedRegion->segmentStart;
offset %= segmentSize;
offset += segmentSize * segment;
}
if (offset >= m_cachedRegion->size) {
m_cachedRegion = nullptr;
return cacheRegion(address, segment);
}
return offset;
}

View File

@ -12,6 +12,7 @@
#include "CoreController.h"
#include <mgba/debugger/debugger.h>
#include <mgba/internal/debugger/access-logger.h>
namespace QGBA {
@ -25,6 +26,16 @@ public:
QString internalName;
};
struct Flags {
mDebuggerAccessLogFlagsEx flagsEx;
mDebuggerAccessLogFlags flags;
int count() const;
bool operator==(const Flags& other) const { return flags == other.flags && flagsEx == other.flagsEx; }
bool operator!=(const Flags& other) const { return flags != other.flags || flagsEx != other.flagsEx; }
operator bool() const { return flags || flagsEx; }
};
MemoryAccessLogController(CoreController* controller, QObject* parent = nullptr);
~MemoryAccessLogController();
@ -34,8 +45,11 @@ public:
bool canExport() const;
mPlatform platform() const { return m_controller->platform(); }
Flags flagsForAddress(uint32_t address, int segment = -1);
QString file() const { return m_path; }
bool active() const { return m_active; }
bool isLoaded() const { return m_loaded; }
public slots:
void updateRegion(const QString& internalName, bool enable);
@ -44,9 +58,13 @@ public slots:
void start(bool loadExisting, bool logExtra);
void stop();
void load(bool loadExisting);
void unload();
void exportFile(const QString& filename);
signals:
void loaded(bool loaded);
void loggingChanged(bool active);
void regionMappingChanged(const QString& internalName, bool active);
@ -58,9 +76,12 @@ private:
QHash<QString, int> m_regionMapping;
QVector<Region> m_regions;
struct mDebuggerAccessLogger m_logger{};
bool m_loaded = false;
bool m_active = false;
mDebuggerAccessLogRegion* m_cachedRegion = nullptr;
mDebuggerAccessLogRegionFlags activeFlags() const;
uint32_t cacheRegion(uint32_t address, int segment);
};
}

View File

@ -0,0 +1,301 @@
/* Copyright (c) 2013-2025 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "MemoryAccessLogModel.h"
#include <limits>
using namespace QGBA;
MemoryAccessLogModel::MemoryAccessLogModel(std::weak_ptr<MemoryAccessLogController> controller, mPlatform platform)
: m_controller(controller)
, m_platform(platform)
{
}
QVariant MemoryAccessLogModel::data(const QModelIndex& index, int role) const {
if (role != Qt::DisplayRole) {
return {};
}
if (index.column() != 0) {
return {};
}
int blockIndex = -1;
int flagIndex = -1;
QModelIndex parent = index.parent();
if (!parent.isValid()) {
blockIndex = index.row();
} else {
blockIndex = parent.row();
flagIndex = index.row();
}
if (blockIndex < 0 || blockIndex >= m_cachedBlocks.count()) {
return {};
}
const Block& block = m_cachedBlocks[blockIndex];
if (flagIndex < 0) {
if (m_platform == mPLATFORM_GB) {
if (m_segment < 0) {
return QString("$%1 $%2")
.arg(QString("%0").arg(block.region.first, 4, 16, QChar('0')).toUpper())
.arg(QString("%0").arg(block.region.second, 4, 16, QChar('0')).toUpper());
} else {
return QString("$%1:%3 $%2:%4")
.arg(QString("%0").arg(m_segment, 2, 16, QChar('0')).toUpper())
.arg(QString("%0").arg(m_segment, 2, 16, QChar('0')).toUpper())
.arg(QString("%0").arg(block.region.first, 4, 16, QChar('0')).toUpper())
.arg(QString("%0").arg(block.region.second, 4, 16, QChar('0')).toUpper());
}
} else {
return QString("0x%1 0x%2")
.arg(QString("%0").arg(block.region.first, 8, 16, QChar('0')).toUpper())
.arg(QString("%0").arg(block.region.second, 8, 16, QChar('0')).toUpper());
}
}
for (int i = 0; i < 8; ++i) {
if (!(block.flags.flags & (1 << i))) {
continue;
}
if (flagIndex == 0) {
switch (i) {
case 0:
return tr("Data read");
case 1:
return tr("Data written");
case 2:
return tr("Code executed");
case 3:
return tr("Code aborted");
case 4:
return tr("8-bit access");
case 5:
return tr("16-bit access");
case 6:
return tr("32-bit access");
case 7:
return tr("64-bit access");
default:
Q_UNREACHABLE();
}
}
--flagIndex;
}
for (int i = 0; i < 16; ++i) {
if (!(block.flags.flagsEx & (1 << i))) {
continue;
}
if (flagIndex == 0) {
switch (i) {
case 0:
return tr("Accessed by instruction");
case 1:
return tr("Accessed by DMA");
case 2:
return tr("Accessed by BIOS");
case 3:
return tr("Compressed data");
case 4:
return tr("Accessed by memory copy");
case 5:
return tr("(Unknown extra bit 5)");
case 6:
return tr("(Unknown extra bit 6)");
case 7:
return tr("(Unknown extra bit 7)");
case 8:
return tr("Invalid instruction");
case 9:
return tr("Invalid read");
case 10:
return tr("Invalid write");
case 11:
return tr("Invalid executable address");
case 12:
return tr("(Private bit 0)");
case 13:
return tr("(Private bit 1)");
case 14:
switch (m_platform) {
case mPLATFORM_GBA:
return tr("ARM code");
case mPLATFORM_GB:
return tr("Instruction opcode");
default:
return tr("(Private bit 2)");
}
case 15:
switch (m_platform) {
case mPLATFORM_GBA:
return tr("Thumb code");
case mPLATFORM_GB:
return tr("Instruction operand");
default:
return tr("(Private bit 3)");
}
default:
Q_UNREACHABLE();
}
}
--flagIndex;
}
return tr("(Unknown)");
}
QModelIndex MemoryAccessLogModel::index(int row, int column, const QModelIndex& parent) const {
if (column != 0) {
return {};
}
if (parent.isValid()) {
return createIndex(row, 0, parent.row());
}
return createIndex(row, 0, std::numeric_limits<quintptr>::max());
}
QModelIndex MemoryAccessLogModel::parent(const QModelIndex& index) const {
if (!index.isValid()) {
return {};
}
quintptr row = index.internalId();
if (row >= std::numeric_limits<uint32_t>::max()) {
return {};
}
return createIndex(row, 0, std::numeric_limits<quintptr>::max());
}
int MemoryAccessLogModel::rowCount(const QModelIndex& parent) const {
int blockIndex = -1;
if (!parent.isValid()) {
return m_cachedBlocks.count();
} else if (parent.column() != 0) {
return 0;
} else if (parent.parent().isValid()) {
return 0;
} else {
blockIndex = parent.row();
}
if (blockIndex < 0 || blockIndex >= m_cachedBlocks.count()) {
return 0;
}
const Block& block = m_cachedBlocks[blockIndex];
return block.flags.count();
}
void MemoryAccessLogModel::updateSelection(uint32_t start, uint32_t end) {
std::shared_ptr<MemoryAccessLogController> controller = m_controller.lock();
if (!controller) {
return;
}
QVector<Block> newBlocks;
uint32_t lastStart = start;
auto lastFlags = controller->flagsForAddress(m_base + start, m_segment);
for (uint32_t address = start; address < end; ++address) {
auto flags = controller->flagsForAddress(m_base + address, m_segment);
if (flags == lastFlags) {
continue;
}
if (lastFlags) {
newBlocks.append({ lastFlags, qMakePair(lastStart, address) });
}
lastFlags = flags;
lastStart = address;
}
if (lastFlags) {
newBlocks.append({ lastFlags, qMakePair(lastStart, end) });
}
if (m_cachedBlocks.count() == 0 || newBlocks.count() == 0) {
beginResetModel();
m_cachedBlocks = newBlocks;
endResetModel();
return;
}
QPair<int, int> changed{ -1, -1 };
for (int i = 0; i < m_cachedBlocks.count() && i < newBlocks.count(); ++i) {
const Block& oldBlock = m_cachedBlocks.at(i);
const Block& newBlock = newBlocks.at(i);
if (oldBlock != newBlock) {
changed = qMakePair(i, m_cachedBlocks.count());
break;
}
}
if (m_cachedBlocks.count() > newBlocks.count()) {
beginRemoveRows({}, newBlocks.count(), m_cachedBlocks.count());
m_cachedBlocks.resize(newBlocks.count());
endRemoveRows();
changed.second = newBlocks.count();
}
if (m_cachedBlocks.count() < newBlocks.count()) {
beginInsertRows({}, m_cachedBlocks.count(), newBlocks.count());
if (changed.first < 0) {
// Only new rows
m_cachedBlocks = newBlocks;
endInsertRows();
return;
}
}
if (changed.first < 0) {
// No changed rows, though some might have been removed
return;
}
for (int i = 0; i < m_cachedBlocks.count() && i < newBlocks.count(); ++i) {
const Block& oldBlock = m_cachedBlocks.at(i);
const Block& newBlock = newBlocks.at(i);
if (oldBlock.flags != newBlock.flags) {
int oldFlags = oldBlock.flags.count();
int newFlags = newBlock.flags.count();
if (oldFlags > newFlags) {
beginRemoveRows(createIndex(i, 0, std::numeric_limits<quintptr>::max()), newFlags, oldFlags);
} else if (oldFlags < newFlags) {
beginInsertRows(createIndex(i, 0, std::numeric_limits<quintptr>::max()), oldFlags, newFlags);
}
m_cachedBlocks[i] = newBlock;
emit dataChanged(createIndex(0, 0, i), createIndex(std::min(oldFlags, newFlags), 0, i));
if (oldFlags > newFlags) {
endRemoveRows();
} else if (oldFlags < newFlags) {
endInsertRows();
}
}
}
emit dataChanged(createIndex(changed.first, 0, std::numeric_limits<quintptr>::max()),
createIndex(changed.second, 0, std::numeric_limits<quintptr>::max()));
if (m_cachedBlocks.count() < newBlocks.count()) {
m_cachedBlocks = newBlocks;
endInsertRows();
}
}
void MemoryAccessLogModel::setSegment(int segment) {
if (m_segment == segment) {
return;
}
beginResetModel();
m_segment = segment;
m_cachedBlocks.clear();
endResetModel();
}
void MemoryAccessLogModel::setRegion(uint32_t base, uint32_t, bool useSegments) {
if (m_base == base) {
return;
}
beginResetModel();
m_segment = useSegments ? 0 : -1;
m_cachedBlocks.clear();
endResetModel();
}

View File

@ -0,0 +1,55 @@
/* Copyright (c) 2013-2025 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#pragma once
#include <QAbstractItemModel>
#include <QVector>
#include "MemoryAccessLogController.h"
struct mCheatDevice;
struct mCheatSet;
namespace QGBA {
class MemoryAccessLogModel : public QAbstractItemModel {
Q_OBJECT
public:
MemoryAccessLogModel(std::weak_ptr<MemoryAccessLogController> controller, mPlatform platform);
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
virtual QModelIndex index(int row, int column, const QModelIndex& parent) const override;
virtual QModelIndex parent(const QModelIndex& index) const override;
virtual int columnCount(const QModelIndex& = QModelIndex()) const override { return 1; }
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
public slots:
void updateSelection(uint32_t start, uint32_t end);
void setSegment(int segment);
void setRegion(uint32_t base, uint32_t segmentSize, bool useSegments);
private:
struct Block {
MemoryAccessLogController::Flags flags;
QPair<uint32_t, uint32_t> region;
bool operator==(const Block& other) const { return flags == other.flags && region == other.region; }
bool operator!=(const Block& other) const { return flags != other.flags || region != other.region; }
};
int flagCount(int index) const;
std::weak_ptr<MemoryAccessLogController> m_controller;
mPlatform m_platform;
uint32_t m_base = 0;
int m_segment = -1;
QVector<Block> m_cachedBlocks;
};
}

View File

@ -26,8 +26,10 @@ MemoryAccessLogView::MemoryAccessLogView(std::weak_ptr<MemoryAccessLogController
connect(m_ui.exportButton, &QAbstractButton::clicked, this, &MemoryAccessLogView::exportFile);
connect(controllerPtr.get(), &MemoryAccessLogController::regionMappingChanged, this, &MemoryAccessLogView::updateRegion);
connect(controllerPtr.get(), &MemoryAccessLogController::loggingChanged, this, &MemoryAccessLogView::handleStartStop);
connect(controllerPtr.get(), &MemoryAccessLogController::loaded, this, &MemoryAccessLogView::handleLoadUnload);
bool active = controllerPtr->active();
bool loaded = controllerPtr->isLoaded();
auto watchedRegions = controllerPtr->watchedRegions();
QVBoxLayout* regionBox = static_cast<QVBoxLayout*>(m_ui.regionBox->layout());
@ -46,11 +48,14 @@ MemoryAccessLogView::MemoryAccessLogView(std::weak_ptr<MemoryAccessLogController
});
}
handleLoadUnload(loaded);
handleStartStop(active);
}
void MemoryAccessLogView::updateRegion(const QString& internalName, bool) {
m_regionBoxes[internalName]->setEnabled(false);
void MemoryAccessLogView::updateRegion(const QString& internalName, bool checked) {
if (checked) {
m_regionBoxes[internalName]->setEnabled(false);
}
}
void MemoryAccessLogView::start() {
@ -68,10 +73,23 @@ void MemoryAccessLogView::stop() {
return;
}
controllerPtr->stop();
for (const auto& region : controllerPtr->watchedRegions()) {
m_regionBoxes[region]->setEnabled(true);
}
void MemoryAccessLogView::load() {
std::shared_ptr<MemoryAccessLogController> controllerPtr = m_controller.lock();
if (!controllerPtr) {
return;
}
m_ui.exportButton->setEnabled(false);
controllerPtr->setFile(m_ui.filename->text());
controllerPtr->load(m_ui.loadExisting->isChecked());
}
void MemoryAccessLogView::unload() {
std::shared_ptr<MemoryAccessLogController> controllerPtr = m_controller.lock();
if (!controllerPtr) {
return;
}
controllerPtr->unload();
}
void MemoryAccessLogView::selectFile() {
@ -110,12 +128,26 @@ void MemoryAccessLogView::handleStartStop(bool start) {
m_regionBoxes[region]->setChecked(true);
}
if (watchedRegions.contains(QString("cart0"))) {
m_ui.exportButton->setEnabled(start);
}
m_ui.start->setDisabled(start);
m_ui.stop->setEnabled(start);
m_ui.filename->setDisabled(start);
m_ui.browse->setDisabled(start);
m_ui.unload->setDisabled(start || !controllerPtr->isLoaded());
}
void MemoryAccessLogView::handleLoadUnload(bool load) {
std::shared_ptr<MemoryAccessLogController> controllerPtr = m_controller.lock();
if (!controllerPtr) {
return;
}
m_ui.filename->setText(controllerPtr->file());
if (load && controllerPtr->canExport()) {
m_ui.exportButton->setEnabled(true);
} else if (!load) {
m_ui.exportButton->setEnabled(false);
}
m_ui.load->setDisabled(load);
m_ui.unload->setEnabled(load);
m_ui.filename->setDisabled(load);
m_ui.browse->setDisabled(load);
}

View File

@ -34,9 +34,13 @@ private slots:
void start();
void stop();
void load();
void unload();
void exportFile();
void handleStartStop(bool start);
void handleLoadUnload(bool load);
private:
Ui::MemoryAccessLogView m_ui;

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>385</width>
<height>380</height>
<width>311</width>
<height>387</height>
</rect>
</property>
<property name="windowTitle">
@ -22,25 +22,25 @@
<property name="title">
<string>Log file</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QGridLayout" name="gridLayout_2" columnstretch="3,1,2">
<item row="0" column="0" colspan="2">
<widget class="QLineEdit" name="filename"/>
</item>
<item row="0" column="1">
<item row="0" column="2">
<widget class="QPushButton" name="browse">
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<item row="1" column="0" colspan="3">
<widget class="QCheckBox" name="logExtra">
<property name="text">
<string>Log additional information (uses 3× space)</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<item row="2" column="0" colspan="3">
<widget class="QCheckBox" name="loadExisting">
<property name="text">
<string>Load existing file if present</string>
@ -50,6 +50,23 @@
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QPushButton" name="load">
<property name="text">
<string>Load</string>
</property>
</widget>
</item>
<item row="3" column="1" colspan="2">
<widget class="QPushButton" name="unload">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Unload</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -124,9 +141,43 @@
</hint>
</hints>
</connection>
<connection>
<sender>load</sender>
<signal>clicked()</signal>
<receiver>QGBA::MemoryAccessLogView</receiver>
<slot>load()</slot>
<hints>
<hint type="sourcelabel">
<x>81</x>
<y>152</y>
</hint>
<hint type="destinationlabel">
<x>192</x>
<y>189</y>
</hint>
</hints>
</connection>
<connection>
<sender>unload</sender>
<signal>clicked()</signal>
<receiver>QGBA::MemoryAccessLogView</receiver>
<slot>unload()</slot>
<hints>
<hint type="sourcelabel">
<x>226</x>
<y>152</y>
</hint>
<hint type="destinationlabel">
<x>192</x>
<y>189</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>start()</slot>
<slot>stop()</slot>
<slot>load()</slot>
<slot>unload()</slot>
</slots>
</ui>

View File

@ -7,6 +7,7 @@
#include "MemoryView.h"
#include "CoreController.h"
#include "MemoryAccessLogView.h"
#include "MemoryDump.h"
#include <mgba/core/core.h>
@ -107,6 +108,9 @@ QValidator::State IntValidator::validate(QString& input, int&) const {
MemoryView::MemoryView(std::shared_ptr<CoreController> controller, QWidget* parent)
: QWidget(parent)
, m_controller(controller)
#ifdef ENABLE_DEBUGGERS
, m_malModel(controller->memoryAccessLogController(), controller->platform())
#endif
{
m_ui.setupUi(this);
@ -133,20 +137,10 @@ MemoryView::MemoryView(std::shared_ptr<CoreController> controller, QWidget* pare
}
}
connect(m_ui.width8, &QAbstractButton::clicked, [this]() {
m_ui.hexfield->setAlignment(1);
m_sintValidator.setWidth(1);
m_uintValidator.setWidth(1);
});
connect(m_ui.width16, &QAbstractButton::clicked, [this]() {
m_ui.hexfield->setAlignment(2);
m_sintValidator.setWidth(2);
m_uintValidator.setWidth(2);
});
connect(m_ui.width32, &QAbstractButton::clicked, [this]() {
m_ui.hexfield->setAlignment(4);
m_sintValidator.setWidth(4);
m_uintValidator.setWidth(4);
connect(m_ui.width, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [this](int index) {
m_ui.hexfield->setAlignment(1 << index);
m_sintValidator.setWidth(1 << index);
m_uintValidator.setWidth(1 << index);
});
connect(m_ui.setAddress, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
this, static_cast<void (MemoryView::*)(uint32_t)>(&MemoryView::jumpToAddress));
@ -199,6 +193,22 @@ MemoryView::MemoryView(std::shared_ptr<CoreController> controller, QWidget* pare
}
update();
});
#ifdef ENABLE_DEBUGGERS
connect(m_ui.hexfield, &MemoryModel::selectionChanged, &m_malModel, &MemoryAccessLogModel::updateSelection);
connect(m_ui.segments, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
&m_malModel, &MemoryAccessLogModel::setSegment);
connect(m_ui.accessLoggerButton, &QAbstractButton::clicked, this, [this]() {
std::weak_ptr<MemoryAccessLogController> controller = m_controller->memoryAccessLogController();
MemoryAccessLogView* view = new MemoryAccessLogView(controller);
connect(m_controller.get(), &CoreController::stopping, view, &QWidget::close);
view->setAttribute(Qt::WA_DeleteOnClose);
view->show();
});
m_ui.accessLog->setModel(&m_malModel);
#else
m_ui.accessLog->hide();
#endif
}
void MemoryView::setIndex(int index) {
@ -216,6 +226,10 @@ void MemoryView::setIndex(int index) {
m_ui.segmentColon->setVisible(info.maxSegment > 0);
m_ui.segments->setMaximum(info.maxSegment);
m_ui.hexfield->setRegion(info.start, info.end - info.start, info.shortName);
#ifdef ENABLE_DEBUGGERS
m_malModel.setRegion(info.start, info.segmentStart - info.start, info.maxSegment > 0);
#endif
}
void MemoryView::setSegment(int segment) {
@ -258,7 +272,7 @@ void MemoryView::updateStatus() {
mCore* core = m_controller->thread()->core;
QByteArray selection(m_ui.hexfield->serialize());
QString text(m_ui.hexfield->decodeText(selection));
m_ui.stringVal->setText(text);
m_ui.stringVal->setPlainText(text);
if (m_selection.first & (align - 1) || m_selection.second - m_selection.first != align) {
m_ui.sintVal->clear();

View File

@ -8,6 +8,7 @@
#include <QValidator>
#include "MemoryModel.h"
#include "MemoryAccessLogModel.h"
#include "ui_MemoryView.h"
@ -54,6 +55,10 @@ private:
std::shared_ptr<CoreController> m_controller;
QPair<uint32_t, uint32_t> m_region;
QPair<uint32_t, uint32_t> m_selection;
#ifdef ENABLE_DEBUGGERS
MemoryAccessLogModel m_malModel;
#endif
};
}

View File

@ -6,167 +6,14 @@
<rect>
<x>0</x>
<y>0</y>
<width>874</width>
<height>900</height>
<width>708</width>
<height>549</height>
</rect>
</property>
<property name="windowTitle">
<string>Memory</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QComboBox" name="regions"/>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Inspect Address:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="segments">
<property name="minimum">
<number>-1</number>
</property>
<property name="maximum">
<number>0</number>
</property>
<property name="displayIntegerBase">
<number>16</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="segmentColon">
<property name="text">
<string notr="true">:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="setAddress">
<property name="accelerated">
<bool>true</bool>
</property>
<property name="prefix">
<string notr="true">0x</string>
</property>
<property name="maximum">
<number>268435455</number>
</property>
<property name="singleStep">
<number>16</number>
</property>
<property name="displayIntegerBase">
<number>16</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Set Alignment:</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QRadioButton" name="width8">
<property name="text">
<string>&amp;1 Byte</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QRadioButton" name="width16">
<property name="text">
<string>&amp;2 Bytes</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QRadioButton" name="width32">
<property name="text">
<string>&amp;4 Bytes</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<layout class="QHBoxLayout" name="horizontalLayout_7" stretch="1,0">
<item>
<widget class="QGBA::MemoryModel" name="hexfield" native="true">
<property name="sizePolicy">
@ -175,127 +22,296 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<layout class="QVBoxLayout" name="sidebarLayout" stretch="0,0,0,1,0,1">
<item>
<widget class="QComboBox" name="regions"/>
</item>
<item>
<layout class="QHBoxLayout" name="addressLayout">
<item>
<widget class="QLabel" name="label_4">
<widget class="QLabel" name="address">
<property name="text">
<string>Unsigned Integer:</string>
<string>Address:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="uintVal">
<property name="maxLength">
<number>10</number>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="readOnly">
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QSpinBox" name="segments">
<property name="minimum">
<number>-1</number>
</property>
<property name="maximum">
<number>0</number>
</property>
<property name="displayIntegerBase">
<number>16</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="segmentColon">
<property name="text">
<string notr="true">:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="setAddress">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="accelerated">
<bool>true</bool>
</property>
<property name="prefix">
<string notr="true">0x</string>
</property>
<property name="maximum">
<number>268435455</number>
</property>
<property name="singleStep">
<number>16</number>
</property>
<property name="displayIntegerBase">
<number>16</number>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QHBoxLayout" name="alignmentLayout">
<item>
<widget class="QLabel" name="label_3">
<widget class="QLabel" name="alignmentLabel">
<property name="text">
<string>Signed Integer:</string>
<string>Alignment:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="sintVal">
<property name="maxLength">
<number>11</number>
<widget class="QComboBox" name="width">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="readOnly">
<bool>true</bool>
<item>
<property name="text">
<string>1 Byte</string>
</property>
</item>
<item>
<property name="text">
<string>2 Bytes</string>
</property>
</item>
<item>
<property name="text">
<string>4 Bytes</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="data">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string/>
</property>
<layout class="QGridLayout" name="gridLayout_9">
<item row="0" column="0">
<widget class="QLabel" name="sintLabel">
<property name="text">
<string>Signed:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="sintVal">
<property name="maxLength">
<number>11</number>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="placeholderText">
<string notr="true"/>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="uintLabel">
<property name="text">
<string>Unsigned:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="uintVal">
<property name="maxLength">
<number>10</number>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="placeholderText">
<string notr="true"/>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="stringLabel">
<property name="text">
<string>String:</string>
</property>
</widget>
</item>
<item row="2" column="1" alignment="Qt::AlignRight">
<widget class="QPushButton" name="loadTBL">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Load TBL</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QPlainTextEdit" name="stringVal">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="plainText">
<string notr="true"/>
</property>
<property name="placeholderText">
<string notr="true"/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QGridLayout" name="buttons">
<item row="0" column="0">
<widget class="QPushButton" name="copy">
<property name="text">
<string>Copy Selection</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="save">
<property name="text">
<string>Save Selection</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="paste">
<property name="text">
<string>Paste</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="saveRange">
<property name="text">
<string>Save Range</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QPushButton" name="load">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Load</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>String:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="stringVal">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="loadTBL">
<property name="text">
<string>Load TBL</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QPushButton" name="copy">
<property name="text">
<string>Copy Selection</string>
<widget class="QGroupBox" name="verticalGroupBox_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="paste">
<property name="text">
<string>Paste</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_7">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="save">
<property name="text">
<string>Save Selection</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="saveRange">
<property name="text">
<string>Save Range</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="load">
<property name="text">
<string>Load</string>
<property name="title">
<string>Selected address accesses</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTreeView" name="accessLog">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="textElideMode">
<enum>Qt::ElideNone</enum>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<widget class="QPushButton" name="accessLoggerButton">
<property name="text">
<string>Logging configuration</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
@ -310,23 +326,6 @@
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>regions</tabstop>
<tabstop>segments</tabstop>
<tabstop>setAddress</tabstop>
<tabstop>width8</tabstop>
<tabstop>width16</tabstop>
<tabstop>width32</tabstop>
<tabstop>sintVal</tabstop>
<tabstop>uintVal</tabstop>
<tabstop>stringVal</tabstop>
<tabstop>loadTBL</tabstop>
<tabstop>copy</tabstop>
<tabstop>paste</tabstop>
<tabstop>save</tabstop>
<tabstop>saveRange</tabstop>
<tabstop>load</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@ -437,9 +437,9 @@ void SettingsView::setShaderSelector(ShaderSelector* shaderSelector) {
QObject::disconnect(m_shader, nullptr, this, nullptr);
}
m_shader = shaderSelector;
QObject::connect(this, &SettingsView::saveSettingsRequested, m_shader, &ShaderSelector::saveSettings);
QObject::connect(m_ui.buttonBox, &QDialogButtonBox::rejected, m_shader, &ShaderSelector::revert);
if (shaderSelector) {
QObject::connect(this, &SettingsView::saveSettingsRequested, m_shader, &ShaderSelector::saveSettings);
QObject::connect(m_ui.buttonBox, &QDialogButtonBox::rejected, m_shader, &ShaderSelector::revert);
addPage(tr("Shaders"), m_shader, Page::SHADERS);
} else {
addPage(tr("Shaders"), m_dummyShader, Page::SHADERS);
@ -747,7 +747,7 @@ void SettingsView::reloadConfig() {
loadSetting("patchPath", m_ui.patchPath);
loadSetting("cheatsPath", m_ui.cheatsPath);
loadSetting("showLibrary", m_ui.showLibrary);
loadSetting("preload", m_ui.preload);
loadSetting("preload", m_ui.preload, true);
loadSetting("showFps", m_ui.showFps, true);
loadSetting("cheatAutoload", m_ui.cheatAutoload, true);
loadSetting("cheatAutosave", m_ui.cheatAutosave, true);

View File

@ -7,10 +7,12 @@
#include <QDialog>
#include <QMap>
#include <QPointer>
#include <QTimer>
#include "ColorPicker.h"
#include "LogConfigModel.h"
#include "ShaderSelector.h"
#include <mgba/core/core.h>
@ -25,7 +27,6 @@ namespace QGBA {
class ConfigController;
class InputController;
class ShortcutController;
class ShaderSelector;
class SettingsView : public QDialog {
Q_OBJECT
@ -80,7 +81,7 @@ private:
ConfigController* m_controller;
InputController* m_input;
ShaderSelector* m_shader = nullptr;
QPointer<ShaderSelector> m_shader;
QLabel* m_dummyShader;
LogConfigModel m_logModel;
QTimer m_checkTimer;

View File

@ -1263,6 +1263,9 @@
<property name="text">
<string>Preload entire ROM into memory</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="10" column="1">

View File

@ -972,7 +972,12 @@ void Window::gameStopped() {
#endif
}
m_controller.reset();
std::shared_ptr<CoreController> controller;
m_controller.swap(controller);
QTimer::singleShot(0, this, [controller]() {
// Destroy the controller after everything else has cleaned up
Q_UNUSED(controller);
});
detachWidget();
updateTitle();
@ -2155,11 +2160,6 @@ void Window::setController(CoreController* controller, const QString& fname) {
connect(m_controller.get(), &CoreController::started, this, &Window::gameStarted);
connect(m_controller.get(), &CoreController::started, GBAApp::app(), &GBAApp::suspendScreensaver);
connect(m_controller.get(), &CoreController::stopping, this, &Window::gameStopped);
{
connect(m_controller.get(), &CoreController::stopping, [this]() {
m_controller.reset();
});
}
connect(m_controller.get(), &CoreController::stopping, GBAApp::app(), &GBAApp::resumeScreensaver);
connect(m_controller.get(), &CoreController::paused, this, &Window::updateFrame);

View File

@ -130,6 +130,7 @@ int main(int argc, char** argv) {
mDebuggerAccessLoggerInit(&accessLog);
mDebuggerAttachModule(&debugger, &accessLog.d);
mDebuggerAccessLoggerOpen(&accessLog, vf, O_RDWR);
mDebuggerAccessLoggerStart(&accessLog);
hasDebugger = true;
}

View File

@ -61,6 +61,7 @@ static void _checkWatchpoints(struct SM83Debugger* debugger, uint16_t address, e
info.type.wp.newValue = newValue;
info.type.wp.watchType = watchpoint->type;
info.type.wp.accessType = type;
info.type.wp.accessSource = debugger->cpu->memory.accessSource;
info.address = address;
info.segment = debugger->originalMemory.currentSegment(debugger->cpu, address);
info.width = 1;