Add Gambatte GB/GBC emulator, Call/SMS anti-spam blocklist, and Lua/bc/dc/Yabasic

This commit is contained in:
Luxferre
2026-10-26 15:17:55 +02:00
parent b7f0dc08c9
commit 6db14be799
113 changed files with 20006 additions and 237 deletions
+661 -160
View File
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-only
// CallLog DWM Plugin - Preloaded resident X11 call log viewer & dialer launcher (240x320)
// CallLog DWM Plugin - Preloaded resident X11 call log viewer, anti-spam blocklist & dialer launcher (240x320)
#include <X11/Xatom.h>
#include <X11/Xft/Xft.h>
@@ -37,6 +37,20 @@ void handleSignal(int) {
gRunning = 0;
}
enum class ViewMode {
Log = 0,
Options = 1,
Blocklist = 2,
AddBlock = 3,
};
struct OptionEntry {
int id;
std::string icon;
std::string label;
bool danger = false;
};
int calllog_run_main(int argc, char** argv) {
gRunning = 1;
bool isPreload = false;
@@ -116,13 +130,29 @@ int calllog_run_main(int argc, char** argv) {
XftColor& colRed = theme.colRed;
ContactBook book;
BlockList blockList;
std::vector<CallLogItem> allLogs;
std::vector<CallLogItem> blockedLogs;
std::vector<CallLogItem> filtered;
FileStamp contactsStamp;
FileStamp logStamp;
FileStamp blockFileStamp;
FileStamp blockLogStamp;
int filterTab = 0; // 0 = All, 1 = Missed, 2 = In, 3 = Out
constexpr int kNumTabs = 5;
const char* kTabLabels[kNumTabs] = {"All", "Miss", "In", "Out", "Spam"};
ViewMode mode = ViewMode::Log;
int filterTab = 0; // 0 = All, 1 = Missed, 2 = In, 3 = Out, 4 = Spam/Blocked
int selected = 0;
int optionSelected = 0;
int blockSelected = 0;
std::vector<OptionEntry> options;
std::string addBlockInput;
std::string toastMsg;
int64_t toastUntilMs = 0;
bool visible = false;
int64_t openStartMs = 0;
int64_t startupIgnoreUntilMs = 0;
@@ -130,13 +160,32 @@ int calllog_run_main(int argc, char** argv) {
int softkeyPending = 0;
int64_t softkeyDeadlineMs = 0;
auto showToast = [&](const std::string& msg) {
toastMsg = msg;
toastUntilMs = nowMs() + 1800;
};
auto applyFilter = [&]() {
filtered.clear();
for (const auto& item : allLogs) {
if (filterTab == 1 && item.direction != CallDirection::Missed) continue;
if (filterTab == 2 && item.direction != CallDirection::Incoming) continue;
if (filterTab == 3 && item.direction != CallDirection::Outgoing) continue;
filtered.push_back(item);
if (filterTab == 4) {
for (auto item : blockedLogs) {
item.isBlockedNumber = blockList.isBlocked(item.number);
filtered.push_back(item);
}
for (auto item : allLogs) {
if (blockList.isBlocked(item.number)) {
item.isBlockedNumber = true;
filtered.push_back(item);
}
}
} else {
for (auto item : allLogs) {
if (filterTab == 1 && item.direction != CallDirection::Missed) continue;
if (filterTab == 2 && item.direction != CallDirection::Incoming) continue;
if (filterTab == 3 && item.direction != CallDirection::Outgoing) continue;
item.isBlockedNumber = blockList.isBlocked(item.number);
filtered.push_back(item);
}
}
if (selected >= static_cast<int>(filtered.size())) {
selected = std::max(0, static_cast<int>(filtered.size()) - 1);
@@ -144,21 +193,60 @@ int calllog_run_main(int argc, char** argv) {
};
bool logsLoadedOnce = false;
auto reloadLogsIfNeeded = [&]() -> bool {
auto reloadLogsIfNeeded = [&](bool force = false) -> bool {
bool cChanged = contactsStamp.checkAndUpdate(getContactsFilePath());
bool lChanged = logStamp.checkAndUpdate(getModemLogFilePath());
if (cChanged || lChanged || !logsLoadedOnce) {
bool bChanged = blockFileStamp.checkAndUpdate(getBlockFilePath());
bool blChanged = blockLogStamp.checkAndUpdate(getBlockedModemLogFilePath());
if (force || cChanged || lChanged || bChanged || blChanged || !logsLoadedOnce) {
logsLoadedOnce = true;
book.load();
blockList.load();
allLogs = loadCallLog(book);
blockedLogs = loadBlockedCallLog(book);
applyFilter();
if (blockSelected >= static_cast<int>(blockList.entries().size())) {
blockSelected = std::max(0, static_cast<int>(blockList.entries().size()) - 1);
}
return true;
}
return false;
};
reloadLogsIfNeeded();
applyFilter();
reloadLogsIfNeeded(true);
auto buildOptions = [&]() {
options.clear();
bool hasSel = (!filtered.empty() && selected >= 0 &&
selected < static_cast<int>(filtered.size()));
if (hasSel) {
const CallLogItem& item = filtered[selected];
bool isBlk = blockList.isBlocked(item.number);
options.push_back({1, kIconPhone, "Call Number", false});
options.push_back({2, kIconPhoneLog, "Open in Dialer", false});
options.push_back({3, kIconSms, "Send SMS", false});
if (isBlk) {
options.push_back({4, kIconCheck, "Unblock Number", false});
} else {
options.push_back({4, kIconBan, "Block Number (Spam)", true});
}
options.push_back({5, kIconUser, "Contacts", false});
} else {
options.push_back({2, kIconPhoneLog, "Open Dialer", false});
}
std::string blkCount =
"Blocklist (" + std::to_string(blockList.entries().size()) + ")";
options.push_back({6, kIconShield, blkCount, false});
options.push_back({7, kIconPlus, "Block a Number...", false});
if (filterTab == 4) {
if (!blockedLogs.empty()) {
options.push_back({8, kIconTrash, "Clear Spam Log", true});
}
} else if (!allLogs.empty()) {
options.push_back({9, kIconTrash, "Clear Call Log", true});
}
optionSelected = 0;
};
auto drawPango = [&](PangoLayout* layout, XftColor* color, int x, int y, int h,
const std::string& text, int maxW = -1) {
@@ -191,8 +279,6 @@ int calllog_run_main(int argc, char** argv) {
constexpr int kListH = kWinHeight - kHeaderH - kSoftbarH;
constexpr int kVisibleRows = 5;
const char* kTabLabels[4] = {"All", "Missed", "In", "Out"};
auto render = [&]() {
XSetForeground(dpy, gc, theme.spec.bgMain);
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kWinHeight);
@@ -200,108 +286,270 @@ int calllog_run_main(int argc, char** argv) {
XSetForeground(dpy, gc, theme.spec.bgHeader);
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kHeaderH);
for (int t = 0; t < 4; ++t) {
int tx = (t * kWinWidth) / 4;
int tw = ((t + 1) * kWinWidth) / 4 - tx;
bool active = (t == filterTab);
if (active) {
XSetForeground(dpy, gc, theme.spec.selBg);
XFillRectangle(dpy, pix, gc, tx, 0, tw, kHeaderH - 2);
XSetForeground(dpy, gc, theme.spec.accent);
XFillRectangle(dpy, pix, gc, tx, kHeaderH - 3, tw, 3);
}
std::string lbl(kTabLabels[t]);
int lw = pangoWidth(layoutSmall, lbl);
drawPango(layoutSmall, active ? &colWhite : &colMuted,
tx + (tw - lw) / 2, 0, kHeaderH - 2, lbl);
}
std::string softLeft = "Options";
std::string softCenter = "CALL";
std::string softRight = "Close";
XSetForeground(dpy, gc, theme.spec.border);
XFillRectangle(dpy, pix, gc, 0, kHeaderH - 1, kWinWidth, 1);
if (filtered.empty()) {
drawPango(layoutIcon, &colMuted, 112, kListY + 60, 24, kIconPhoneLog);
drawPango(layoutMain, &colMuted, 52, kListY + 90, 28, "No call history", 160);
drawPango(layoutSmall, &colMuted, 24, kListY + 120, 20,
"Press 0-9 or Left Softkey to dial", 200);
} else {
int total = static_cast<int>(filtered.size());
int scrollOffset = 0;
if (selected >= kVisibleRows) {
scrollOffset = selected - kVisibleRows + 1;
}
if (scrollOffset > std::max(0, total - kVisibleRows)) {
scrollOffset = std::max(0, total - kVisibleRows);
}
for (int i = 0; i < kVisibleRows; ++i) {
int idx = scrollOffset + i;
if (idx >= total) {
break;
if (mode == ViewMode::Log) {
for (int t = 0; t < kNumTabs; ++t) {
int tx = (t * kWinWidth) / kNumTabs;
int tw = ((t + 1) * kWinWidth) / kNumTabs - tx;
bool active = (t == filterTab);
if (active) {
XSetForeground(dpy, gc, theme.spec.selBg);
XFillRectangle(dpy, pix, gc, tx, 0, tw, kHeaderH - 2);
XSetForeground(dpy, gc, (t == 4) ? theme.spec.danger : theme.spec.accent);
XFillRectangle(dpy, pix, gc, tx, kHeaderH - 3, tw, 3);
}
int ry = kListY + (i * kListH) / kVisibleRows;
int nextRy = kListY + ((i + 1) * kListH) / kVisibleRows;
int rowH = nextRy - ry;
bool isSel = (idx == selected);
std::string lbl(kTabLabels[t]);
int lw = pangoWidth(layoutSmall, lbl);
XftColor* tabCol = active ? ((t == 4) ? &colRed : &colWhite) : &colMuted;
drawPango(layoutSmall, tabCol, tx + (tw - lw) / 2, 0, kHeaderH - 2, lbl);
}
XSetForeground(dpy, gc,
isSel ? theme.spec.selBg : ((i % 2 == 0) ? theme.spec.bgAltEven : theme.spec.bgAltOdd));
XSetForeground(dpy, gc, theme.spec.border);
XFillRectangle(dpy, pix, gc, 0, kHeaderH - 1, kWinWidth, 1);
if (filtered.empty()) {
drawPango(layoutIcon, &colMuted, 112, kListY + 54, 24,
(filterTab == 4) ? kIconShield : kIconPhoneLog);
std::string emptyTitle =
(filterTab == 4) ? "No blocked calls" : "No call history";
int etw = pangoWidth(layoutMain, emptyTitle);
drawPango(layoutMain, &colMuted, std::max(8, (kWinWidth - etw) / 2),
kListY + 84, 26, emptyTitle, 224);
std::string emptySub =
(filterTab == 4)
? ("Blocklist: " + std::to_string(blockList.entries().size()) +
" number(s)")
: "Press 0-9 to dial or Options";
int esw = pangoWidth(layoutSmall, emptySub);
drawPango(layoutSmall, &colMuted, std::max(8, (kWinWidth - esw) / 2),
kListY + 114, 20, emptySub, 224);
} else {
int total = static_cast<int>(filtered.size());
int scrollOffset = 0;
if (selected >= kVisibleRows) {
scrollOffset = selected - kVisibleRows + 1;
}
if (scrollOffset > std::max(0, total - kVisibleRows)) {
scrollOffset = std::max(0, total - kVisibleRows);
}
for (int i = 0; i < kVisibleRows; ++i) {
int idx = scrollOffset + i;
if (idx >= total) {
break;
}
int ry = kListY + (i * kListH) / kVisibleRows;
int nextRy = kListY + ((i + 1) * kListH) / kVisibleRows;
int rowH = nextRy - ry;
bool isSel = (idx == selected);
XSetForeground(
dpy, gc,
isSel ? theme.spec.selBg
: ((i % 2 == 0) ? theme.spec.bgAltEven : theme.spec.bgAltOdd));
XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH);
if (isSel) {
XSetForeground(dpy, gc, theme.spec.accent);
XFillRectangle(dpy, pix, gc, 0, ry, 4, rowH);
}
const CallLogItem& item = filtered[idx];
const char* dirIcon = kIconInCall;
XftColor* dirCol = &colGreen;
const char* dirName = "In";
if (item.direction == CallDirection::Blocked) {
dirIcon = kIconBan;
dirCol = &colRed;
dirName = "Blocked";
} else if (item.direction == CallDirection::Outgoing) {
dirIcon = kIconOutCall;
dirCol = &colBlue;
dirName = "Out";
} else if (item.direction == CallDirection::Missed) {
dirIcon = kIconMissedCall;
dirCol = &colRed;
dirName = "Missed";
}
drawPango(layoutIcon, dirCol, 8, ry, rowH, dirIcon);
std::string rightBadge = formatDuration(item.durationSec);
XftColor* badgeCol = isSel ? &colYellow : &colGreen;
if (item.isBlockedNumber) {
rightBadge = "BLK";
badgeCol = &colRed;
}
int durW = 0;
if (!rightBadge.empty()) {
durW = pangoWidth(layoutSmall, rightBadge);
drawPango(layoutSmall, badgeCol, kWinWidth - durW - 6, ry + 3, 22,
rightBadge);
}
std::string primary =
item.contactName.empty() ? item.number : item.contactName;
XftColor* priCol =
((item.direction == CallDirection::Missed ||
item.direction == CallDirection::Blocked ||
item.isBlockedNumber) &&
!isSel)
? &colRed
: (isSel ? &colWhite : &colLight);
int priMaxW = (durW > 0) ? (kWinWidth - 36 - durW - 6) : 200;
drawPango(layoutMain, priCol, 30, ry + 3, 22, primary, priMaxW);
int timeW = pangoWidth(layoutSmall, item.displayTime);
drawPango(layoutSmall, isSel ? &colWhite : &colMuted,
kWinWidth - timeW - 6, ry + 24, 19, item.displayTime);
std::string secondary =
!item.contactName.empty() ? item.number : std::string(dirName);
int secMaxW = std::max(60, kWinWidth - 36 - timeW - 8);
drawPango(layoutSmall, isSel ? &colYellow : &colMuted, 30, ry + 24,
19, secondary, secMaxW);
}
if (total > kVisibleRows) {
int sbH = std::max(16, (kListH * kVisibleRows) / total);
int sbY = kListY +
((kListH - sbH) * scrollOffset) / (total - kVisibleRows);
XSetForeground(dpy, gc, theme.spec.divider);
XFillRectangle(dpy, pix, gc, kWinWidth - 3, sbY, 3, sbH);
}
}
} else if (mode == ViewMode::Options) {
drawPango(layoutIcon, &colYellow, 8, 0, kHeaderH, kIconList);
drawPango(layoutMain, &colWhite, 28, 0, kHeaderH, "Call Log Options", 200);
XSetForeground(dpy, gc, theme.spec.border);
XFillRectangle(dpy, pix, gc, 0, kHeaderH - 1, kWinWidth, 1);
constexpr int kOptVis = 6;
int total = static_cast<int>(options.size());
int scrollOffset = 0;
if (optionSelected >= kOptVis) {
scrollOffset = optionSelected - kOptVis + 1;
}
int rowH = kListH / kOptVis;
for (int i = 0; i < kOptVis; ++i) {
int idx = scrollOffset + i;
if (idx >= total) break;
int ry = kListY + i * rowH;
bool isSel = (idx == optionSelected);
XSetForeground(
dpy, gc,
isSel ? theme.spec.selBg
: ((i % 2 == 0) ? theme.spec.bgAltEven : theme.spec.bgAltOdd));
XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH);
if (isSel) {
XSetForeground(dpy, gc, theme.spec.accent);
XSetForeground(dpy, gc,
options[idx].danger ? theme.spec.danger : theme.spec.accent);
XFillRectangle(dpy, pix, gc, 0, ry, 4, rowH);
}
const CallLogItem& item = filtered[idx];
const char* dirIcon = kIconInCall;
XftColor* dirCol = &colGreen;
const char* dirName = "In";
if (item.direction == CallDirection::Outgoing) {
dirIcon = kIconOutCall;
dirCol = &colBlue;
dirName = "Out";
} else if (item.direction == CallDirection::Missed) {
dirIcon = kIconMissedCall;
dirCol = &colRed;
dirName = "Missed";
}
drawPango(layoutIcon, dirCol, 8, ry, rowH, dirIcon);
std::string dur = formatDuration(item.durationSec);
int durW = 0;
if (!dur.empty()) {
durW = pangoWidth(layoutSmall, dur);
drawPango(layoutSmall, isSel ? &colYellow : &colGreen,
kWinWidth - durW - 6, ry + 3, 22, dur);
}
std::string primary = item.contactName.empty() ? item.number : item.contactName;
XftColor* priCol = (item.direction == CallDirection::Missed && !isSel)
? &colRed
: (isSel ? &colWhite : &colLight);
int priMaxW = (durW > 0) ? (kWinWidth - 36 - durW - 6) : 200;
drawPango(layoutMain, priCol, 30, ry + 3, 22, primary, priMaxW);
int timeW = pangoWidth(layoutSmall, item.displayTime);
drawPango(layoutSmall, isSel ? &colWhite : &colMuted,
kWinWidth - timeW - 6, ry + 24, 19, item.displayTime);
std::string secondary =
!item.contactName.empty() ? item.number : std::string(dirName);
int secMaxW = std::max(60, kWinWidth - 36 - timeW - 8);
drawPango(layoutSmall, isSel ? &colYellow : &colMuted, 30, ry + 24, 19,
secondary, secMaxW);
XftColor* icCol = options[idx].danger
? &colRed
: (isSel ? &colYellow : &colBlue);
XftColor* txCol = options[idx].danger
? &colRed
: (isSel ? &colWhite : &colLight);
drawPango(layoutIcon, icCol, 10, ry, rowH, options[idx].icon);
drawPango(layoutMain, txCol, 34, ry, rowH, options[idx].label,
kWinWidth - 42);
}
softLeft = "Select";
softCenter = "OK";
softRight = "Back";
} else if (mode == ViewMode::Blocklist) {
drawPango(layoutIcon, &colRed, 8, 0, kHeaderH, kIconBan);
std::string hdr =
"Blocklist (" + std::to_string(blockList.entries().size()) + ")";
drawPango(layoutMain, &colWhite, 28, 0, kHeaderH, hdr, 204);
XSetForeground(dpy, gc, theme.spec.border);
XFillRectangle(dpy, pix, gc, 0, kHeaderH - 1, kWinWidth, 1);
if (total > kVisibleRows) {
int sbH = std::max(16, (kListH * kVisibleRows) / total);
int sbY = kListY +
((kListH - sbH) * scrollOffset) / (total - kVisibleRows);
XSetForeground(dpy, gc, theme.spec.divider);
XFillRectangle(dpy, pix, gc, kWinWidth - 3, sbY, 3, sbH);
const auto& entries = blockList.entries();
if (entries.empty()) {
drawPango(layoutIcon, &colMuted, 112, kListY + 54, 24, kIconShield);
drawPango(layoutMain, &colMuted, 44, kListY + 84, 26,
"Blocklist is empty", 180);
drawPango(layoutSmall, &colMuted, 22, kListY + 114, 20,
"Left Softkey: Block a Number", 204);
} else {
int total = static_cast<int>(entries.size());
int scrollOffset = 0;
if (blockSelected >= kVisibleRows) {
scrollOffset = blockSelected - kVisibleRows + 1;
}
for (int i = 0; i < kVisibleRows; ++i) {
int idx = scrollOffset + i;
if (idx >= total) break;
int ry = kListY + (i * kListH) / kVisibleRows;
int nextRy = kListY + ((i + 1) * kListH) / kVisibleRows;
int rowH = nextRy - ry;
bool isSel = (idx == blockSelected);
XSetForeground(
dpy, gc,
isSel ? theme.spec.selBg
: ((i % 2 == 0) ? theme.spec.bgAltEven : theme.spec.bgAltOdd));
XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH);
if (isSel) {
XSetForeground(dpy, gc, theme.spec.danger);
XFillRectangle(dpy, pix, gc, 0, ry, 4, rowH);
}
const auto& e = entries[idx];
std::string cname = book.resolveName(e.number);
std::string topLine = cname.empty() ? e.number : cname;
std::string subLine =
cname.empty() ? e.label : (e.number + " (" + e.label + ")");
drawPango(layoutIcon, &colRed, 8, ry, rowH, kIconBan);
drawPango(layoutMain, isSel ? &colWhite : &colLight, 30, ry + 3, 22,
topLine, kWinWidth - 38);
drawPango(layoutSmall, isSel ? &colYellow : &colMuted, 30, ry + 24,
19, subLine, kWinWidth - 38);
}
}
softLeft = "Add New";
softCenter = entries.empty() ? "" : "Unblock";
softRight = "Back";
} else if (mode == ViewMode::AddBlock) {
drawPango(layoutIcon, &colRed, 8, 0, kHeaderH, kIconBan);
drawPango(layoutMain, &colWhite, 28, 0, kHeaderH, "Block Phone Number", 204);
XSetForeground(dpy, gc, theme.spec.border);
XFillRectangle(dpy, pix, gc, 0, kHeaderH - 1, kWinWidth, 1);
drawPango(layoutSmall, &colMuted, 12, kListY + 18, 20,
"Enter number or prefix (e.g. +1800*):", kWinWidth - 24);
XSetForeground(dpy, gc, theme.spec.bgAltOdd);
XFillRectangle(dpy, pix, gc, 12, kListY + 44, kWinWidth - 24, 34);
XSetForeground(dpy, gc, theme.spec.accent);
XDrawRectangle(dpy, pix, gc, 12, kListY + 44, kWinWidth - 25, 33);
std::string disp = addBlockInput + "_";
drawPango(layoutMain, &colWhite, 18, kListY + 47, 28, disp, kWinWidth - 36);
drawPango(layoutSmall, &colMuted, 12, kListY + 90, 20,
"0-9 / * / #: Digits #: + prefix", kWinWidth - 24);
drawPango(layoutSmall, &colMuted, 12, kListY + 112, 20,
"Left/Down: Backspace", kWinWidth - 24);
softLeft = "Block";
softCenter = "SAVE";
softRight = "Cancel";
}
if (!toastMsg.empty() && nowMs() < toastUntilMs) {
int tw = std::min(kWinWidth - 16, pangoWidth(layoutSmall, toastMsg) + 20);
int tx = (kWinWidth - tw) / 2;
int ty = kWinHeight - kSoftbarH - 28;
XSetForeground(dpy, gc, theme.spec.selBg);
XFillRectangle(dpy, pix, gc, tx, ty, tw, 24);
XSetForeground(dpy, gc, theme.spec.accent);
XDrawRectangle(dpy, pix, gc, tx, ty, tw - 1, 23);
int textW = pangoWidth(layoutSmall, toastMsg);
drawPango(layoutSmall, &colYellow, tx + (tw - textW) / 2, ty + 2, 20,
toastMsg, tw - 8);
}
int sbY = kWinHeight - kSoftbarH;
@@ -310,15 +558,21 @@ int calllog_run_main(int argc, char** argv) {
XSetForeground(dpy, gc, theme.spec.border);
XFillRectangle(dpy, pix, gc, 0, sbY, kWinWidth, 1);
drawPango(layoutMain, &colLight, 6, sbY + 1, kSoftbarH - 1, "Dialer");
std::string centerLabel = "CALL";
int cw = pangoWidth(layoutMain, centerLabel);
drawPango(layoutMain, filtered.empty() ? &colMuted : &colGreen,
(kWinWidth - cw) / 2, sbY + 1, kSoftbarH - 1, centerLabel);
std::string rightLabel = "Close";
int rw = pangoWidth(layoutMain, rightLabel);
drawPango(layoutMain, &colLight, 6, sbY + 1, kSoftbarH - 1, softLeft);
if (!softCenter.empty()) {
int cw = pangoWidth(layoutMain, softCenter);
XftColor* cCol = &colGreen;
if (mode == ViewMode::Log && filtered.empty()) {
cCol = &colMuted;
} else if (mode == ViewMode::Blocklist) {
cCol = &colRed;
}
drawPango(layoutMain, cCol, (kWinWidth - cw) / 2, sbY + 1, kSoftbarH - 1,
softCenter);
}
int rw = pangoWidth(layoutMain, softRight);
drawPango(layoutMain, &colLight, kWinWidth - rw - 6, sbY + 1,
kSoftbarH - 1, rightLabel);
kSoftbarH - 1, softRight);
if (visible) {
XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0);
@@ -326,9 +580,124 @@ int calllog_run_main(int argc, char** argv) {
}
};
auto executeOption = [&](int optId) {
bool hasSel = (!filtered.empty() && selected >= 0 &&
selected < static_cast<int>(filtered.size()));
std::string selNum = hasSel ? filtered[selected].number : "";
std::string selName = hasSel ? filtered[selected].contactName : "";
switch (optId) {
case 1: { // Call Number
if (!selNum.empty()) {
mode = ViewMode::Log;
writeSoftkeySuppress(nowMs() + 350);
XUngrabKeyboard(dpy, CurrentTime);
XUnmapWindow(dpy, win);
XFlush(dpy);
::unlink(activePidPath.c_str());
visible = false;
placePhoneCall(selNum);
}
break;
}
case 2: { // Open in Dialer
mode = ViewMode::Log;
XUngrabKeyboard(dpy, CurrentTime);
XUnmapWindow(dpy, win);
XFlush(dpy);
::unlink(activePidPath.c_str());
visible = false;
if (!sendPluginCommand("dialer",
selNum.empty() ? "OPEN" : ("OPEN " + selNum))) {
if (selNum.empty()) {
runAsyncArgv("xdialer");
} else {
runAsyncArgv("xdialer", selNum.c_str());
}
}
break;
}
case 3: { // Send SMS
if (!selNum.empty()) {
mode = ViewMode::Log;
XUngrabKeyboard(dpy, CurrentTime);
XUnmapWindow(dpy, win);
XFlush(dpy);
::unlink(activePidPath.c_str());
visible = false;
if (!sendPluginCommand("sms", "OPEN " + selNum)) {
runAsyncArgv("xsms", selNum.c_str());
}
}
break;
}
case 4: { // Block / Unblock Number
if (!selNum.empty()) {
if (blockList.isBlocked(selNum)) {
blockList.unblockNumber(selNum);
showToast("Unblocked " + selNum);
} else {
blockList.blockNumber(selNum, selName.empty() ? "Call Log" : selName);
showToast("Blocked " + selNum);
}
reloadLogsIfNeeded(true);
}
mode = ViewMode::Log;
render();
break;
}
case 5: { // Contacts
mode = ViewMode::Log;
XUngrabKeyboard(dpy, CurrentTime);
XUnmapWindow(dpy, win);
XFlush(dpy);
::unlink(activePidPath.c_str());
visible = false;
if (!sendPluginCommand("contacts", "OPEN")) {
runAsyncArgv("xcontacts");
}
break;
}
case 6: { // Manage Blocklist
blockList.load();
blockSelected = 0;
mode = ViewMode::Blocklist;
render();
break;
}
case 7: { // Block a Number...
addBlockInput = selNum;
mode = ViewMode::AddBlock;
render();
break;
}
case 8: { // Clear Spam Log
clearCallLogFile(getBlockedModemLogFilePath());
reloadLogsIfNeeded(true);
showToast("Cleared blocked call log");
mode = ViewMode::Log;
render();
break;
}
case 9: { // Clear Call Log
clearCallLogFile(getModemLogFilePath());
reloadLogsIfNeeded(true);
showToast("Cleared call history");
mode = ViewMode::Log;
render();
break;
}
default:
mode = ViewMode::Log;
render();
break;
}
};
// Pre-warm Pango/FreeType/Xrender glyph caches into offscreen Pixmap at startup
drawPango(layoutIcon, &colGreen, 0, 0, 20,
std::string(kIconInCall) + kIconOutCall + kIconMissedCall + kIconPhoneLog);
std::string(kIconInCall) + kIconOutCall + kIconMissedCall +
kIconPhoneLog + kIconBan + kIconShield);
render();
XSync(dpy, False);
@@ -344,6 +713,7 @@ int calllog_run_main(int argc, char** argv) {
initialSoftkeysReleased = (querySoftkeysDown(dpy) == 0);
softkeyPending = 0;
softkeyDeadlineMs = 0;
mode = ViewMode::Log;
visible = true;
XMapRaised(dpy, win);
if (!themeChanged) {
@@ -392,7 +762,8 @@ int calllog_run_main(int argc, char** argv) {
visible = false;
softkeyPending = 0;
softkeyDeadlineMs = 0;
if (filterTab != 0 || selected != 0) {
if (mode != ViewMode::Log || filterTab != 0 || selected != 0) {
mode = ViewMode::Log;
filterTab = 0;
selected = 0;
applyFilter();
@@ -422,6 +793,11 @@ int calllog_run_main(int argc, char** argv) {
}
}
if (visible && !toastMsg.empty() && nowMs() >= toastUntilMs) {
toastMsg.clear();
render();
}
if (visible && !initialSoftkeysReleased && querySoftkeysDown(dpy) == 0) {
initialSoftkeysReleased = true;
}
@@ -485,6 +861,128 @@ int calllog_run_main(int argc, char** argv) {
continue;
}
if (mode == ViewMode::AddBlock) {
char dialCh = mapDialKey(sym, ev.xkey.keycode, ev.xkey.state);
if (dialCh == '#') {
if (addBlockInput.empty()) {
addBlockInput.push_back('+');
} else {
addBlockInput.push_back('#');
}
render();
continue;
} else if (dialCh != '\0') {
addBlockInput.push_back(dialCh);
render();
continue;
}
if (sym == XK_BackSpace || sym == XK_Left || sym == XK_Down) {
if (!addBlockInput.empty()) {
addBlockInput.pop_back();
render();
}
continue;
} else if (sym == XK_Escape) {
mode = ViewMode::Blocklist;
render();
continue;
} else if (sym == XK_Return || sym == XK_KP_Enter) {
std::string num = trimStr(addBlockInput);
if (!normalizePhone(num).empty()) {
blockList.blockNumber(num, "Manual");
reloadLogsIfNeeded(true);
showToast("Blocked " + num);
}
mode = ViewMode::Blocklist;
render();
continue;
}
continue;
}
if (mode == ViewMode::Options) {
switch (sym) {
case XK_Escape:
case XK_BackSpace:
case XK_Left:
mode = ViewMode::Log;
render();
break;
case XK_Up:
case XK_KP_Up:
case XK_k:
if (!options.empty()) {
optionSelected =
(optionSelected + static_cast<int>(options.size()) - 1) %
static_cast<int>(options.size());
render();
}
break;
case XK_Down:
case XK_KP_Down:
case XK_j:
if (!options.empty()) {
optionSelected =
(optionSelected + 1) % static_cast<int>(options.size());
render();
}
break;
case XK_Return:
case XK_KP_Enter:
if (!options.empty() && optionSelected >= 0 &&
optionSelected < static_cast<int>(options.size())) {
executeOption(options[optionSelected].id);
}
break;
default:
break;
}
continue;
}
if (mode == ViewMode::Blocklist) {
switch (sym) {
case XK_Escape:
case XK_BackSpace:
case XK_Left:
mode = ViewMode::Log;
render();
break;
case XK_Up:
case XK_KP_Up:
case XK_k:
if (!blockList.entries().empty()) {
int n = static_cast<int>(blockList.entries().size());
blockSelected = (blockSelected + n - 1) % n;
render();
}
break;
case XK_Down:
case XK_KP_Down:
case XK_j:
if (!blockList.entries().empty()) {
int n = static_cast<int>(blockList.entries().size());
blockSelected = (blockSelected + 1) % n;
render();
}
break;
case XK_Return:
case XK_KP_Enter:
if (!blockList.entries().empty() && blockSelected >= 0 &&
blockSelected < static_cast<int>(blockList.entries().size())) {
std::string num = blockList.entries()[blockSelected].number;
blockList.unblockNumber(num);
reloadLogsIfNeeded(true);
showToast("Unblocked " + num);
render();
}
break;
default:
break;
}
continue;
}
char dialCh = mapDialKey(sym, ev.xkey.keycode, ev.xkey.state);
if (dialCh != '\0') {
std::string arg(1, dialCh);
@@ -505,7 +1003,7 @@ int calllog_run_main(int argc, char** argv) {
case XK_Left:
case XK_KP_Left:
case XK_h:
filterTab = (filterTab + 3) % 4;
filterTab = (filterTab + kNumTabs - 1) % kNumTabs;
selected = 0;
applyFilter();
render();
@@ -513,7 +1011,7 @@ int calllog_run_main(int argc, char** argv) {
case XK_Right:
case XK_KP_Right:
case XK_l:
filterTab = (filterTab + 1) % 4;
filterTab = (filterTab + 1) % kNumTabs;
selected = 0;
applyFilter();
render();
@@ -549,9 +1047,16 @@ int calllog_run_main(int argc, char** argv) {
hideWindow(true);
placePhoneCall(num);
} else if (filtered.empty()) {
hideWindow(false);
if (!sendPluginCommand("dialer", "OPEN")) {
runAsyncArgv("xdialer");
if (filterTab == 4) {
blockList.load();
blockSelected = 0;
mode = ViewMode::Blocklist;
render();
} else {
hideWindow(false);
if (!sendPluginCommand("dialer", "OPEN")) {
runAsyncArgv("xdialer");
}
}
}
break;
@@ -572,22 +1077,40 @@ int calllog_run_main(int argc, char** argv) {
runAsyncArgv("xdrawer");
}
} else if (comb == 1) {
std::string arg;
if (!filtered.empty() && selected >= 0 &&
selected < static_cast<int>(filtered.size())) {
arg = filtered[selected].number;
}
hideWindow(false);
if (!sendPluginCommand("dialer", arg.empty() ? "OPEN" : ("OPEN " + arg))) {
if (arg.empty()) {
runAsyncArgv("xdialer");
} else {
runAsyncArgv("xdialer", arg.c_str());
if (mode == ViewMode::Log) {
buildOptions();
mode = ViewMode::Options;
render();
} else if (mode == ViewMode::Options) {
if (!options.empty() && optionSelected >= 0 &&
optionSelected < static_cast<int>(options.size())) {
executeOption(options[optionSelected].id);
}
} else if (mode == ViewMode::Blocklist) {
addBlockInput.clear();
mode = ViewMode::AddBlock;
render();
} else if (mode == ViewMode::AddBlock) {
std::string num = trimStr(addBlockInput);
if (!normalizePhone(num).empty()) {
blockList.blockNumber(num, "Manual");
reloadLogsIfNeeded(true);
showToast("Blocked " + num);
}
mode = ViewMode::Blocklist;
render();
}
} else if (comb == 2) {
writeSoftkeySuppress(nowMs() + 350);
hideWindow(true);
if (mode == ViewMode::AddBlock) {
mode = ViewMode::Blocklist;
render();
} else if (mode == ViewMode::Options || mode == ViewMode::Blocklist) {
mode = ViewMode::Log;
render();
} else {
writeSoftkeySuppress(nowMs() + 350);
hideWindow(true);
}
}
}
@@ -653,46 +1176,24 @@ int calllog_on_global_key(const DwmHostApi* host, KeySym keysym,
}
return 1;
}
}
return 0;
}
int calllog_on_idle_key(const DwmHostApi* host, KeySym keysym,
unsigned int keycode, unsigned int state) {
if (!host || !host->is_idle_desktop || !host->is_idle_desktop() || state != 0) {
return 0;
}
if (keysym == XF86XK_Phone || keysym == XF86XK_PickupPhone || keycode == 177) {
if (isCallRingingFast()) {
if (!sendPluginCommand("callscreen", "ACCEPT")) {
runAsyncArgv("xcallscreen", "accept");
}
return 1;
}
if (isCallActiveOrRingingFast()) {
if (!sendPluginCommand("callscreen", "OPEN")) {
runAsyncArgv("xcallscreen");
}
return 1;
}
if (host->spawn_plugin) {
host->spawn_plugin("calllog", nullptr);
if (!sendPluginCommand("calllog", "OPEN")) {
runAsyncArgv("xcalllog");
}
return 1;
}
return 0;
}
} // namespace
} // namespace
extern "C" __attribute__((visibility("default"))) const DwmPlugin dwm_plugin_entry = {
DWM_PLUGIN_API_VERSION,
"calllog",
"Keypad-driven call log viewer & dialer launcher (opens on idle Call key)",
"Preloaded resident X11 call log viewer, anti-spam blocklist & dialer launcher",
calllog_plugin_init,
nullptr,
nullptr,
calllog_on_idle_key,
nullptr,
calllog_on_global_key,
calllog_run_main
};
+13
View File
@@ -118,6 +118,10 @@ bool isMediaExt(const std::string& ext) {
ext == "avi";
}
bool isGameBoyExt(const std::string& ext) {
return ext == "gb" || ext == "gbc";
}
bool isLikelyTextFile(const std::string& path, off_t size) {
if (size == 0) return true;
if (size > 256 * 1024) return false;
@@ -612,6 +616,8 @@ int runFilesApp(int argc, char** argv) {
} else if (isAudioExt(ext)) {
actionsList.push_back({1, kIconMusic, "Play in Music Player"});
actionsList.push_back({15, kIconBell, "Set as Ringtone"});
} else if (isGameBoyExt(ext)) {
actionsList.push_back({1, kIconGamepad, "Play in Game Boy Emulator"});
} else {
actionsList.push_back({1, kIconEye, "Open / View"});
}
@@ -689,6 +695,10 @@ int runFilesApp(int argc, char** argv) {
} else if (isAudioExt(ext)) {
openInMusicPlayer(item.fullPath);
return;
} else if (isGameBoyExt(ext)) {
hideWindow();
runAsyncArgv("gambatte", item.fullPath.c_str());
return;
} else if (isMediaExt(ext)) {
hideWindow();
runAsyncArgv("sxmo_open.sh", item.fullPath.c_str());
@@ -832,6 +842,9 @@ int runFilesApp(int argc, char** argv) {
} else if (isMediaExt(ext)) {
ic = kIconMusic;
icCol = &colBlue;
} else if (isGameBoyExt(ext)) {
ic = kIconGamepad;
icCol = &colYellow;
} else if (it.isExec || ext == "sh") {
ic = kIconTerm;
icCol = &colOrange;
+351 -17
View File
@@ -93,6 +93,9 @@ constexpr const char* kIconToggleOff = "\xef\x88\x84"; // U+F204 toggle off
constexpr const char* kIconKey = "\xef\x82\x84"; // U+F084 key / PIN
constexpr const char* kIconSim = "\xf3\xb0\x99\x95"; // U+F0655 nf-md-sim
constexpr const char* kIconBell = "\xef\x83\xb3"; // U+F0F3 bell / ringtone
constexpr const char* kIconBan = "\xef\x81\x9e"; // U+F05E ban / block
constexpr const char* kIconShield = "\xef\x84\xb2"; // U+F132 shield / anti-spam
constexpr const char* kIconGamepad = "\xef\x84\x9b"; // U+F11B gamepad / emulator
inline int64_t nowMs() {
struct timespec ts {};
@@ -947,10 +950,247 @@ class ContactBook {
std::unordered_map<std::string, std::string> suffixMap_;
};
inline std::string getBlockFilePath() {
const char* env = std::getenv("SXMO_BLOCKFILE");
if (env && env[0]) {
return env;
}
const char* xdg = std::getenv("XDG_CONFIG_HOME");
if (xdg && xdg[0]) {
return std::string(xdg) + "/sxmo/block.tsv";
}
const char* home = std::getenv("HOME");
return std::string(home && home[0] ? home : "/home/user") +
"/.config/sxmo/block.tsv";
}
inline std::string getBlockDirPath() {
const char* env = std::getenv("SXMO_BLOCKDIR");
if (env && env[0]) {
return env;
}
const char* xdg = std::getenv("XDG_DATA_HOME");
if (xdg && xdg[0]) {
return std::string(xdg) + "/sxmo/block";
}
const char* home = std::getenv("HOME");
return std::string(home && home[0] ? home : "/home/user") +
"/.local/share/sxmo/block";
}
inline std::string getBlockedModemLogFilePath() {
return getBlockDirPath() + "/modemlog.tsv";
}
struct BlockEntry {
std::string number;
std::string normNumber;
std::string label;
};
class BlockList {
public:
void load() {
entries_.clear();
exactSet_.clear();
suffixSet_.clear();
std::ifstream bf(getBlockFilePath());
std::string line;
while (std::getline(bf, line)) {
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
if (line.empty()) {
continue;
}
size_t tab = line.find('\t');
std::string num = trimStr(tab == std::string::npos ? line : line.substr(0, tab));
std::string lbl = tab == std::string::npos ? "" : trimStr(line.substr(tab + 1));
std::string norm = normalizePhone(num);
if (norm.empty()) {
continue;
}
BlockEntry e;
e.number = num;
e.normNumber = norm;
e.label = lbl.empty() ? "Blocked" : lbl;
entries_.push_back(e);
exactSet_.insert(norm);
std::string sfx = phoneSuffixKey(norm);
if (sfx.size() >= 7) {
suffixSet_.insert(sfx);
}
}
}
bool isBlocked(const std::string& rawNumber) const {
std::string norm = normalizePhone(rawNumber);
if (norm.empty()) {
return false;
}
if (exactSet_.count(norm) > 0) {
return true;
}
std::string sfx = phoneSuffixKey(norm);
if (sfx.size() >= 7 && suffixSet_.count(sfx) > 0) {
return true;
}
for (const auto& e : entries_) {
if (!e.normNumber.empty() && e.normNumber.back() == '*' &&
e.normNumber.size() > 1) {
std::string prefix = e.normNumber.substr(0, e.normNumber.size() - 1);
if (norm.rfind(prefix, 0) == 0) {
return true;
}
}
}
return false;
}
bool blockNumber(const std::string& rawNumber,
const std::string& labelRaw = "") {
std::string num = trimStr(rawNumber);
std::string norm = normalizePhone(num);
if (norm.empty()) {
return false;
}
if (num.find('\t') != std::string::npos ||
num.find('\n') != std::string::npos) {
num = norm;
}
std::string label = trimStr(labelRaw);
for (char& ch : label) {
if (ch == '\t' || ch == '\r' || ch == '\n') {
ch = ' ';
}
}
if (label.empty()) {
label = "Blocked";
}
std::string path = getBlockFilePath();
ContactBook::ensureParentDir(path);
std::vector<std::pair<std::string, std::string>> rows;
{
std::ifstream in(path);
std::string line;
while (std::getline(in, line)) {
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
if (line.empty()) continue;
size_t tab = line.find('\t');
std::string rNum = trimStr(tab == std::string::npos ? line : line.substr(0, tab));
std::string rLbl = tab == std::string::npos ? "Blocked" : trimStr(line.substr(tab + 1));
if (!normalizePhone(rNum).empty()) {
rows.push_back({rNum, rLbl.empty() ? "Blocked" : rLbl});
}
}
}
std::string sfx = phoneSuffixKey(norm);
bool updated = false;
for (auto& r : rows) {
std::string rNorm = normalizePhone(r.first);
std::string rSfx = phoneSuffixKey(rNorm);
if (rNorm == norm || (sfx.size() >= 7 && rSfx == sfx)) {
r.first = num;
if (!labelRaw.empty()) {
r.second = label;
}
updated = true;
break;
}
}
if (!updated) {
rows.push_back({num, label});
}
std::ofstream out(path, std::ios::trunc);
if (!out.is_open()) {
return false;
}
for (const auto& r : rows) {
out << r.first << "\t" << r.second << "\n";
}
out.close();
load();
return true;
}
bool unblockNumber(const std::string& rawNumber) {
std::string norm = normalizePhone(rawNumber);
if (norm.empty()) {
return false;
}
std::string sfx = phoneSuffixKey(norm);
std::string path = getBlockFilePath();
std::vector<std::pair<std::string, std::string>> rows;
bool removed = false;
{
std::ifstream in(path);
std::string line;
while (std::getline(in, line)) {
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
if (line.empty()) continue;
size_t tab = line.find('\t');
std::string rNum = trimStr(tab == std::string::npos ? line : line.substr(0, tab));
std::string rLbl = tab == std::string::npos ? "Blocked" : trimStr(line.substr(tab + 1));
std::string rNorm = normalizePhone(rNum);
if (rNorm.empty()) continue;
std::string rSfx = phoneSuffixKey(rNorm);
if (rNorm == norm || (sfx.size() >= 7 && rSfx == sfx)) {
removed = true;
continue;
}
rows.push_back({rNum, rLbl.empty() ? "Blocked" : rLbl});
}
}
if (!removed) {
return false;
}
ContactBook::ensureParentDir(path);
std::ofstream out(path, std::ios::trunc);
if (!out.is_open()) {
return false;
}
for (const auto& r : rows) {
out << r.first << "\t" << r.second << "\n";
}
out.close();
load();
return true;
}
bool clearAll() {
std::string path = getBlockFilePath();
ContactBook::ensureParentDir(path);
std::ofstream out(path, std::ios::trunc);
if (!out.is_open()) {
return false;
}
out.close();
load();
return true;
}
const std::vector<BlockEntry>& entries() const { return entries_; }
private:
std::vector<BlockEntry> entries_;
std::unordered_set<std::string> exactSet_;
std::unordered_set<std::string> suffixSet_;
};
enum class CallDirection {
Incoming = 0,
Outgoing = 1,
Missed = 2,
Blocked = 3,
};
struct CallLogItem {
@@ -959,6 +1199,7 @@ struct CallLogItem {
std::string contactName;
std::string displayTime;
int durationSec = 0;
bool isBlockedNumber = false;
};
inline time_t parseIsoTime(const std::string& iso) {
@@ -999,8 +1240,10 @@ inline std::string formatDuration(int sec) {
return buf;
}
inline std::vector<CallLogItem> loadCallLog(const ContactBook& book) {
std::ifstream lf(getModemLogFilePath());
inline std::vector<CallLogItem> loadCallLogFromFile(const ContactBook& book,
const std::string& logPath,
bool forceBlockedDir = false) {
std::ifstream lf(logPath);
std::string line;
struct ActiveSession {
@@ -1022,7 +1265,7 @@ inline std::vector<CallLogItem> loadCallLog(const ContactBook& book) {
return;
}
CallLogItem item;
item.direction = cur.dir;
item.direction = forceBlockedDir ? CallDirection::Blocked : cur.dir;
item.number = cur.number;
item.contactName = book.resolveName(cur.number);
item.displayTime = formatShortTime(cur.startTimeStr);
@@ -1053,10 +1296,24 @@ inline std::vector<CallLogItem> loadCallLog(const ContactBook& book) {
time_t epoch = parseIsoTime(ts);
if (evt == "call_ring") {
if (evt == "call_blocked") {
if (cur.active && normalizePhone(cur.number) == normalizePhone(num)) {
cur.dir = CallDirection::Blocked;
flushSession();
} else {
flushSession();
CallLogItem item;
item.direction = CallDirection::Blocked;
item.number = num;
item.contactName = book.resolveName(num);
item.displayTime = formatShortTime(ts);
item.durationSec = 0;
history.push_back(item);
}
} else if (evt == "call_ring") {
flushSession();
cur.active = true;
cur.dir = CallDirection::Missed;
cur.dir = forceBlockedDir ? CallDirection::Blocked : CallDirection::Missed;
cur.number = num;
cur.startTimeStr = ts;
cur.startEpoch = epoch;
@@ -1088,12 +1345,12 @@ inline std::vector<CallLogItem> loadCallLog(const ContactBook& book) {
}
} else if (evt == "call_missed") {
if (cur.active && normalizePhone(cur.number) == normalizePhone(num)) {
cur.dir = CallDirection::Missed;
cur.dir = forceBlockedDir ? CallDirection::Blocked : CallDirection::Missed;
flushSession();
} else {
flushSession();
CallLogItem item;
item.direction = CallDirection::Missed;
item.direction = forceBlockedDir ? CallDirection::Blocked : CallDirection::Missed;
item.number = num;
item.contactName = book.resolveName(num);
item.displayTime = formatShortTime(ts);
@@ -1105,10 +1362,10 @@ inline std::vector<CallLogItem> loadCallLog(const ContactBook& book) {
if (cur.active && normalizePhone(cur.number) == normalizePhone(num)) {
if (evt == "call_discard" && !cur.answered &&
cur.dir != CallDirection::Outgoing) {
cur.dir = CallDirection::Missed;
cur.dir = forceBlockedDir ? CallDirection::Blocked : CallDirection::Missed;
}
CallLogItem item;
item.direction = cur.dir;
item.direction = forceBlockedDir ? CallDirection::Blocked : cur.dir;
item.number = cur.number;
item.contactName = book.resolveName(cur.number);
item.displayTime = formatShortTime(cur.startTimeStr);
@@ -1126,6 +1383,44 @@ inline std::vector<CallLogItem> loadCallLog(const ContactBook& book) {
return history;
}
inline std::vector<CallLogItem> loadCallLog(const ContactBook& book) {
return loadCallLogFromFile(book, getModemLogFilePath(), false);
}
inline std::vector<CallLogItem> loadBlockedCallLog(const ContactBook& book) {
return loadCallLogFromFile(book, getBlockedModemLogFilePath(), true);
}
inline bool clearCallLogFile(const std::string& logPath) {
std::ifstream in(logPath);
if (!in.is_open()) {
return true;
}
std::vector<std::string> keep;
std::string line;
while (std::getline(in, line)) {
if (line.empty()) continue;
size_t t1 = line.find('\t');
if (t1 == std::string::npos) continue;
size_t t2 = line.find('\t', t1 + 1);
if (t2 == std::string::npos) continue;
std::string evt = trimStr(line.substr(t1 + 1, t2 - t1 - 1));
if (evt.rfind("call_", 0) == 0 || evt == "ring_muted") {
continue;
}
keep.push_back(line);
}
in.close();
std::ofstream out(logPath, std::ios::trunc);
if (!out.is_open()) {
return false;
}
for (const auto& l : keep) {
out << l << "\n";
}
return true;
}
inline void placePhoneCall(const std::string& rawNumber) {
std::string num = normalizePhone(rawNumber);
if (num.empty()) {
@@ -1285,6 +1580,7 @@ struct SmsThread {
time_t lastEpoch = 0;
bool lastIncoming = true;
bool hasDraft = false;
bool isBlocked = false;
int messageCount = 0;
};
@@ -1319,9 +1615,12 @@ inline bool parseSmsHeaderLine(const std::string& line,
return false;
}
inline std::vector<SmsMessage> loadSmsThreadMessages(const std::string& number) {
inline std::vector<SmsMessage> loadSmsThreadMessages(
const std::string& number,
const std::string& customBaseDir = "") {
std::vector<SmsMessage> msgs;
std::string path = getModemLogDirPath() + "/" + number + "/sms.txt";
std::string baseDir = customBaseDir.empty() ? getModemLogDirPath() : customBaseDir;
std::string path = baseDir + "/" + number + "/sms.txt";
std::ifstream in(path);
if (!in.is_open()) {
return msgs;
@@ -1424,11 +1723,13 @@ inline void removeSmsDraft(const std::string& number) {
::unlink(path.c_str());
}
inline bool deleteSmsThread(const std::string& number) {
inline bool deleteSmsThread(const std::string& number,
const std::string& customBaseDir = "") {
if (number.empty()) {
return false;
}
std::string dir = getModemLogDirPath() + "/" + number;
std::string baseDir = customBaseDir.empty() ? getModemLogDirPath() : customBaseDir;
std::string dir = baseDir + "/" + number;
std::string smsPath = dir + "/sms.txt";
std::string draftPath = dir + "/draft.txt";
::unlink(smsPath.c_str());
@@ -1437,9 +1738,42 @@ inline bool deleteSmsThread(const std::string& number) {
return true;
}
inline std::vector<SmsThread> loadSmsThreads(const ContactBook& book) {
inline bool moveSmsThreadBetweenDirs(const std::string& number,
const std::string& fromBaseDir,
const std::string& toBaseDir) {
if (number.empty()) {
return false;
}
std::string srcPath = fromBaseDir + "/" + number + "/sms.txt";
std::ifstream in(srcPath);
if (!in.is_open()) {
return false;
}
std::ostringstream ss;
ss << in.rdbuf();
in.close();
std::string content = ss.str();
if (content.empty()) {
return false;
}
std::string dstPath = toBaseDir + "/" + number + "/sms.txt";
ContactBook::ensureParentDir(dstPath);
std::ofstream out(dstPath, std::ios::app);
if (!out.is_open()) {
return false;
}
out << content;
out.close();
deleteSmsThread(number, fromBaseDir);
return true;
}
inline std::vector<SmsThread> loadSmsThreads(
const ContactBook& book,
const std::string& customBaseDir = "") {
std::vector<SmsThread> threads;
std::string baseDir = getModemLogDirPath();
bool isSpamDir = !customBaseDir.empty();
std::string baseDir = isSpamDir ? customBaseDir : getModemLogDirPath();
DIR* d = ::opendir(baseDir.c_str());
if (!d) {
return threads;
@@ -1459,13 +1793,13 @@ inline std::vector<SmsThread> loadSmsThreads(const ContactBook& book) {
std::string smsPath = subDir + "/sms.txt";
std::string draftPath = subDir + "/draft.txt";
bool hasSms = (::access(smsPath.c_str(), R_OK) == 0);
std::string draft = loadSmsDraft(name);
std::string draft = isSpamDir ? "" : loadSmsDraft(name);
bool hasDraft = !draft.empty();
if (!hasSms && !hasDraft) {
continue;
}
auto msgs = loadSmsThreadMessages(name);
auto msgs = loadSmsThreadMessages(name, baseDir);
if (msgs.empty() && !hasDraft) {
continue;
}
+315 -36
View File
@@ -28,6 +28,8 @@ enum class ViewMode {
PickContact = 3,
Options = 4,
ConfirmDelete = 5,
Blocklist = 6,
AddBlock = 7,
};
enum class TextCaseMode {
@@ -171,6 +173,9 @@ int runSmsApp(int argc, char** argv) {
auto& colRed = pt.colRed;
ContactBook book;
BlockList blockList;
std::vector<SmsThread> inboxThreads;
std::vector<SmsThread> spamThreads;
std::vector<SmsThread> allThreads;
std::vector<SmsThread> filteredThreads;
std::vector<SmsMessage> threadMessages;
@@ -178,7 +183,10 @@ int runSmsApp(int argc, char** argv) {
FileStamp contactsStamp;
FileStamp modemLogStamp;
FileStamp blockFileStamp;
FileStamp blockLogStamp;
bool loadedOnce = false;
bool viewingSpamFolder = false;
ViewMode mode = ViewMode::Threads;
ViewMode prevMode = ViewMode::Threads;
@@ -188,6 +196,8 @@ int runSmsApp(int argc, char** argv) {
int selectedOption = 0;
int selectedPicker = 0;
int confirmSelected = 0;
int blockSelected = 0;
std::string addBlockInput;
std::string activeNumber;
std::string composeRecipient;
@@ -216,10 +226,15 @@ int runSmsApp(int argc, char** argv) {
int id;
std::string icon;
std::string label;
bool danger = false;
};
std::vector<OptionEntry> optionsList;
auto updateFilter = [&]() {
allThreads = viewingSpamFolder ? spamThreads : inboxThreads;
for (auto& t : allThreads) {
t.isBlocked = viewingSpamFolder || blockList.isBlocked(t.number);
}
filteredThreads.clear();
if (searchQuery.empty()) {
filteredThreads = allThreads;
@@ -247,13 +262,22 @@ int runSmsApp(int argc, char** argv) {
auto reloadAllData = [&](bool force = false) {
bool cChanged = contactsStamp.checkAndUpdate(getContactsFilePath());
bool lChanged = modemLogStamp.checkAndUpdate(getModemLogFilePath());
if (force || cChanged || lChanged || !loadedOnce) {
bool bChanged = blockFileStamp.checkAndUpdate(getBlockFilePath());
bool blChanged = blockLogStamp.checkAndUpdate(getBlockedModemLogFilePath());
if (force || cChanged || lChanged || bChanged || blChanged || !loadedOnce) {
loadedOnce = true;
book.load();
allThreads = loadSmsThreads(book);
blockList.load();
inboxThreads = loadSmsThreads(book, getModemLogDirPath());
spamThreads = loadSmsThreads(book, getBlockDirPath());
updateFilter();
if (blockSelected >= static_cast<int>(blockList.entries().size())) {
blockSelected = std::max(0, static_cast<int>(blockList.entries().size()) - 1);
}
if (!activeNumber.empty()) {
threadMessages = loadSmsThreadMessages(activeNumber);
std::string curBase =
viewingSpamFolder ? getBlockDirPath() : getModemLogDirPath();
threadMessages = loadSmsThreadMessages(activeNumber, curBase);
if (selectedMsg >= static_cast<int>(threadMessages.size())) {
selectedMsg =
std::max(0, static_cast<int>(threadMessages.size()) - 1);
@@ -266,7 +290,9 @@ int runSmsApp(int argc, char** argv) {
auto openThreadView = [&](const std::string& num) {
activeNumber = trimStr(num);
threadMessages = loadSmsThreadMessages(activeNumber);
std::string curBase =
viewingSpamFolder ? getBlockDirPath() : getModemLogDirPath();
threadMessages = loadSmsThreadMessages(activeNumber, curBase);
selectedMsg = std::max(0, static_cast<int>(threadMessages.size()) - 1);
mode = ViewMode::ThreadView;
};
@@ -289,22 +315,55 @@ int runSmsApp(int argc, char** argv) {
auto buildOptions = [&]() {
optionsList.clear();
if (mode == ViewMode::Threads) {
optionsList.push_back({1, kIconPlus, "New Message"});
optionsList.push_back({1, kIconPlus, "New Message", false});
if (!filteredThreads.empty()) {
optionsList.push_back({2, kIconEye, "Open Conversation"});
optionsList.push_back({3, kIconSend, "Reply to Selected"});
optionsList.push_back({4, kIconPhone, "Call Contact"});
optionsList.push_back({5, kIconUser, "Open in Contacts"});
optionsList.push_back({6, kIconTrash, "Delete Conversation"});
const auto& selTh = filteredThreads[selectedThread];
bool isBlk = blockList.isBlocked(selTh.number);
optionsList.push_back({2, kIconEye, "Open Conversation", false});
optionsList.push_back({3, kIconSend, "Reply to Selected", false});
optionsList.push_back({4, kIconPhone, "Call Contact", false});
if (isBlk || viewingSpamFolder) {
optionsList.push_back({8, kIconCheck, "Unblock & Restore", false});
} else {
optionsList.push_back({8, kIconBan, "Block Number (Spam)", true});
}
optionsList.push_back({5, kIconUser, "Open in Contacts", false});
optionsList.push_back({6, kIconTrash, "Delete Conversation", true});
}
if (viewingSpamFolder) {
optionsList.push_back(
{9, kIconSms,
"Switch to Inbox (" + std::to_string(inboxThreads.size()) + ")",
false});
} else {
optionsList.push_back(
{9, kIconShield,
"Spam Folder (" + std::to_string(spamThreads.size()) + ")",
false});
}
optionsList.push_back(
{10, kIconBan,
"Manage Blocklist (" + std::to_string(blockList.entries().size()) + ")",
false});
optionsList.push_back({11, kIconPlus, "Block a Number...", false});
} else if (mode == ViewMode::ThreadView) {
optionsList.push_back({3, kIconSend, "Reply / Compose"});
optionsList.push_back({4, kIconPhone, "Call Contact"});
bool isBlk = blockList.isBlocked(activeNumber);
optionsList.push_back({3, kIconSend, "Reply / Compose", false});
optionsList.push_back({4, kIconPhone, "Call Contact", false});
if (!threadMessages.empty()) {
optionsList.push_back({7, kIconCopy, "Forward Selected Msg"});
optionsList.push_back({7, kIconCopy, "Forward Selected Msg", false});
}
optionsList.push_back({5, kIconUser, "Save / View Contact"});
optionsList.push_back({6, kIconTrash, "Delete Conversation"});
if (isBlk || viewingSpamFolder) {
optionsList.push_back({8, kIconCheck, "Unblock & Restore", false});
} else {
optionsList.push_back({8, kIconBan, "Block Number (Spam)", true});
}
optionsList.push_back({5, kIconUser, "Save / View Contact", false});
optionsList.push_back({6, kIconTrash, "Delete Conversation", true});
optionsList.push_back(
{10, kIconBan,
"Manage Blocklist (" + std::to_string(blockList.entries().size()) + ")",
false});
}
selectedOption = 0;
};
@@ -462,9 +521,13 @@ int runSmsApp(int argc, char** argv) {
XDrawLine(dpy, pix, gc, 0, 37, kWinWidth, 37);
if (searchQuery.empty()) {
drawPango(layoutIcon, &colYellow, 8, 2, 34, kIconSms);
drawPango(layoutMain, &colWhite, 30, 2, 34, "Messages", 145);
std::string cnt = "[" + std::to_string(allThreads.size()) + "]";
drawPango(layoutIcon, viewingSpamFolder ? &colRed : &colYellow, 8, 2,
34, viewingSpamFolder ? kIconBan : kIconSms);
std::string hdrTitle =
viewingSpamFolder ? "Spam / Blocked" : "Messages";
drawPango(layoutMain, viewingSpamFolder ? &colRed : &colWhite, 30, 2,
34, hdrTitle, 145);
std::string cnt = "< [" + std::to_string(allThreads.size()) + "] >";
pango_layout_set_width(layoutSmall, -1);
pango_layout_set_text(layoutSmall, cnt.c_str(),
static_cast<int>(cnt.size()));
@@ -489,9 +552,15 @@ int runSmsApp(int argc, char** argv) {
if (filteredThreads.empty()) {
drawPango(layoutMain, &colMuted, 16, 110, 24,
searchQuery.empty() ? "No messages yet" : "No matches", 208);
searchQuery.empty()
? (viewingSpamFolder ? "No blocked messages"
: "No messages yet")
: "No matches",
208);
drawPango(layoutSmall, &colBlue, 16, 138, 20,
"Press Left Softkey for New SMS", 208);
viewingSpamFolder ? "Left/Right: Switch to Inbox"
: "Left Softkey: Options / Blocklist",
208);
} else {
int scroll = 0;
if (selectedThread >= visibleRows) {
@@ -515,17 +584,22 @@ int runSmsApp(int argc, char** argv) {
XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH - 1);
}
const char* ic = th.hasDraft
? kIconEdit
: (th.lastIncoming ? kIconInCall : kIconOutCall);
XftColor* icCol = th.hasDraft
? &colYellow
: (th.lastIncoming ? &colGreen : &colBlue);
const char* ic =
th.isBlocked
? kIconBan
: (th.hasDraft ? kIconEdit
: (th.lastIncoming ? kIconInCall : kIconOutCall));
XftColor* icCol =
th.isBlocked
? &colRed
: (th.hasDraft ? &colYellow
: (th.lastIncoming ? &colGreen : &colBlue));
drawPango(layoutIcon, icCol, 6, ry + 1, 20, ic);
std::string title =
th.contactName.empty() ? th.number : th.contactName;
drawPango(layoutMain, &colWhite, 26, ry + 1, 20, title, 128);
drawPango(layoutMain, th.isBlocked ? &colRed : &colWhite, 26, ry + 1,
20, title, 128);
if (!th.lastDisplayTime.empty()) {
pango_layout_set_width(layoutSmall, -1);
@@ -807,18 +881,89 @@ int runSmsApp(int argc, char** argv) {
const int listY = 38;
const int rowH = 36;
for (size_t i = 0; i < optionsList.size(); ++i) {
int ry = listY + static_cast<int>(i) * rowH;
bool isSel = (static_cast<int>(i) == selectedOption);
const int visRows = 6;
int scroll = 0;
if (selectedOption >= visRows) {
scroll = selectedOption - visRows + 1;
}
for (int i = 0; i < visRows; ++i) {
int idx = scroll + i;
if (idx >= static_cast<int>(optionsList.size())) break;
int ry = listY + i * rowH;
bool isSel = (idx == selectedOption);
XSetForeground(dpy, gc, isSel ? pt.spec.selBg : pt.spec.bgMain);
XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH - 1);
drawPango(layoutIcon, isSel ? &colYellow : &colBlue, 10, ry, rowH,
optionsList[i].icon);
drawPango(layoutMain, &colWhite, 34, ry, rowH, optionsList[i].label,
198);
XftColor* icCol =
optionsList[idx].danger ? &colRed : (isSel ? &colYellow : &colBlue);
XftColor* txCol = optionsList[idx].danger ? &colRed : &colWhite;
drawPango(layoutIcon, icCol, 10, ry, rowH, optionsList[idx].icon);
drawPango(layoutMain, txCol, 34, ry, rowH, optionsList[idx].label, 198);
}
drawSoftkeyBar("Select", "Select", "Back");
} else if (mode == ViewMode::Blocklist) {
XSetForeground(dpy, gc, pt.spec.bgHeader);
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 34);
drawPango(layoutIcon, &colRed, 8, 0, 34, kIconBan);
std::string hdr =
"Blocklist (" + std::to_string(blockList.entries().size()) + ")";
drawPango(layoutMain, &colWhite, 30, 0, 34, hdr, 200);
const auto& entries = blockList.entries();
if (entries.empty()) {
drawPango(layoutIcon, &colMuted, 112, 94, 24, kIconShield);
drawPango(layoutMain, &colMuted, 44, 124, 24, "Blocklist is empty",
180);
drawPango(layoutSmall, &colBlue, 22, 152, 20,
"Left Softkey: Block a Number", 204);
} else {
const int listY = 36;
const int rowH = 38;
const int visRows = 6;
int scroll = 0;
if (blockSelected >= visRows) {
scroll = blockSelected - visRows + 1;
}
for (int i = 0; i < visRows; ++i) {
int idx = scroll + i;
if (idx >= static_cast<int>(entries.size())) break;
const auto& e = entries[idx];
int ry = listY + i * rowH;
bool isSel = (idx == blockSelected);
XSetForeground(dpy, gc, isSel ? pt.spec.selBg : pt.spec.bgMain);
XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH - 1);
std::string cname = book.resolveName(e.number);
std::string topLine = cname.empty() ? e.number : cname;
std::string subLine =
cname.empty() ? e.label : (e.number + " (" + e.label + ")");
drawPango(layoutIcon, &colRed, 8, ry + 1, 20, kIconBan);
drawPango(layoutMain, &colWhite, 28, ry + 1, 20, topLine, 204);
drawPango(layoutSmall, isSel ? &colLight : &colMuted, 28, ry + 20, 16,
subLine, 204);
}
}
drawSoftkeyBar("Add New", entries.empty() ? "" : "Unblock", "Back");
} else if (mode == ViewMode::AddBlock) {
XSetForeground(dpy, gc, pt.spec.bgHeader);
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 34);
drawPango(layoutIcon, &colRed, 8, 0, 34, kIconBan);
drawPango(layoutMain, &colWhite, 30, 0, 34, "Block Phone Number", 200);
drawPango(layoutSmall, &colMuted, 12, 54, 20,
"Enter number or prefix (e.g. +1800*):", 216);
XSetForeground(dpy, gc, pt.spec.bgInputFocus);
XFillRectangle(dpy, pix, gc, 12, 80, kWinWidth - 24, 34);
XSetForeground(dpy, gc, pt.spec.accentAlt);
XDrawRectangle(dpy, pix, gc, 12, 80, kWinWidth - 25, 33);
std::string disp = addBlockInput + "|";
drawPango(layoutMain, &colWhite, 18, 83, 28, disp, kWinWidth - 36);
drawPango(layoutSmall, &colMuted, 12, 124, 20,
"0-9 / * / #: Digits #: + prefix", 216);
drawPango(layoutSmall, &colMuted, 12, 146, 20,
"BackSpace/Left: Delete digit", 216);
drawSoftkeyBar("Block", "Save", "Cancel");
} else if (mode == ViewMode::ConfirmDelete) {
XSetForeground(dpy, gc, pt.spec.bgHeader);
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 36);
@@ -997,6 +1142,49 @@ int runSmsApp(int argc, char** argv) {
} else if (optId == 7 && !threadMessages.empty()) {
std::string body = threadMessages[selectedMsg].text;
openComposeView("", body);
} else if (optId == 8) {
std::string target = (prevMode == ViewMode::ThreadView)
? activeNumber
: (!filteredThreads.empty()
? filteredThreads[selectedThread].number
: "");
if (!target.empty()) {
if (blockList.isBlocked(target) || viewingSpamFolder) {
blockList.unblockNumber(target);
moveSmsThreadBetweenDirs(target, getBlockDirPath(),
getModemLogDirPath());
std::lock_guard<std::mutex> lk(sendMutex);
statusBanner = "Unblocked & restored " + target;
statusBannerUntilMs = nowMs() + 2000;
} else {
std::string cname = book.resolveName(target);
blockList.blockNumber(target, cname.empty() ? "SMS Spam" : cname);
moveSmsThreadBetweenDirs(target, getModemLogDirPath(),
getBlockDirPath());
std::lock_guard<std::mutex> lk(sendMutex);
statusBanner = "Blocked " + target;
statusBannerUntilMs = nowMs() + 2000;
}
reloadAllData(true);
}
mode = ViewMode::Threads;
} else if (optId == 9) {
viewingSpamFolder = !viewingSpamFolder;
selectedThread = 0;
reloadAllData(true);
mode = ViewMode::Threads;
} else if (optId == 10) {
blockList.load();
blockSelected = 0;
mode = ViewMode::Blocklist;
} else if (optId == 11) {
std::string target = (prevMode == ViewMode::ThreadView)
? activeNumber
: (!filteredThreads.empty()
? filteredThreads[selectedThread].number
: "");
addBlockInput = target;
mode = ViewMode::AddBlock;
}
};
@@ -1010,6 +1198,10 @@ int runSmsApp(int argc, char** argv) {
if (!searchQuery.empty()) {
popUtf8Char(searchQuery);
updateFilter();
} else if (viewingSpamFolder) {
viewingSpamFolder = false;
selectedThread = 0;
updateFilter();
} else {
hideWindow();
return;
@@ -1066,10 +1258,40 @@ int runSmsApp(int argc, char** argv) {
} else {
mode = prevMode;
}
} else if (mode == ViewMode::Blocklist) {
if (isLeft) {
addBlockInput.clear();
mode = ViewMode::AddBlock;
} else {
reloadAllData(true);
mode = prevMode;
}
} else if (mode == ViewMode::AddBlock) {
if (isLeft) {
std::string num = trimStr(addBlockInput);
if (!normalizePhone(num).empty()) {
blockList.blockNumber(num, "SMS Blocklist");
moveSmsThreadBetweenDirs(num, getModemLogDirPath(),
getBlockDirPath());
reloadAllData(true);
std::lock_guard<std::mutex> lk(sendMutex);
statusBanner = "Blocked " + num;
statusBannerUntilMs = nowMs() + 2000;
}
mode = ViewMode::Blocklist;
} else {
if (!addBlockInput.empty()) {
popUtf8Char(addBlockInput);
} else {
mode = ViewMode::Blocklist;
}
}
} else if (mode == ViewMode::ConfirmDelete) {
if (isLeft) {
if (confirmSelected == 1) {
deleteSmsThread(activeNumber);
deleteSmsThread(activeNumber, viewingSpamFolder
? getBlockDirPath()
: getModemLogDirPath());
reloadAllData(true);
mode = ViewMode::Threads;
} else {
@@ -1189,7 +1411,13 @@ int runSmsApp(int argc, char** argv) {
}
if (mode == ViewMode::Threads) {
if (sym == XK_Up || sym == XK_KP_Up) {
if (sym == XK_Left || sym == XK_KP_Left || sym == XK_Right ||
sym == XK_KP_Right) {
viewingSpamFolder = !viewingSpamFolder;
selectedThread = 0;
updateFilter();
drawUI();
} else if (sym == XK_Up || sym == XK_KP_Up) {
if (!filteredThreads.empty()) {
selectedThread =
(selectedThread - 1 + static_cast<int>(filteredThreads.size())) %
@@ -1346,6 +1574,57 @@ int runSmsApp(int argc, char** argv) {
} else if (sym == XK_Return || sym == XK_KP_Enter) {
handleSoftkeyAction(true);
}
} else if (mode == ViewMode::Blocklist) {
if (sym == XK_Up || sym == XK_KP_Up) {
if (!blockList.entries().empty()) {
int n = static_cast<int>(blockList.entries().size());
blockSelected = (blockSelected + n - 1) % n;
drawUI();
}
} else if (sym == XK_Down || sym == XK_KP_Down) {
if (!blockList.entries().empty()) {
int n = static_cast<int>(blockList.entries().size());
blockSelected = (blockSelected + 1) % n;
drawUI();
}
} else if (sym == XK_Return || sym == XK_KP_Enter) {
if (!blockList.entries().empty() && blockSelected >= 0 &&
blockSelected < static_cast<int>(blockList.entries().size())) {
std::string num = blockList.entries()[blockSelected].number;
blockList.unblockNumber(num);
moveSmsThreadBetweenDirs(num, getBlockDirPath(),
getModemLogDirPath());
reloadAllData(true);
{
std::lock_guard<std::mutex> lk(sendMutex);
statusBanner = "Unblocked " + num;
statusBannerUntilMs = nowMs() + 1800;
}
drawUI();
}
}
} else if (mode == ViewMode::AddBlock) {
if (sym == XK_Left || sym == XK_KP_Left) {
if (!addBlockInput.empty()) {
popUtf8Char(addBlockInput);
drawUI();
}
} else if (sym == XK_Return || sym == XK_KP_Enter) {
handleSoftkeyAction(true);
} else {
char d = mapDialKey(sym, kc, ev.xkey.state);
if (d == '#') {
if (addBlockInput.empty()) {
addBlockInput.push_back('+');
} else {
addBlockInput.push_back('#');
}
drawUI();
} else if (d != '\0') {
addBlockInput.push_back(d);
drawUI();
}
}
} else if (mode == ViewMode::ConfirmDelete) {
if (sym == XK_Up || sym == XK_Down || sym == XK_KP_Up ||
sym == XK_KP_Down) {
@@ -0,0 +1,39 @@
/***************************************************************************
* Copyright (C) 2008 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef ARRAY_H
#define ARRAY_H
#include <cstddef>
#include "uncopyable.h"
template<typename T>
class Array : Uncopyable {
T *a;
std::size_t sz;
public:
explicit Array(const std::size_t size = 0) : a(size ? new T[size] : 0), sz(size) {}
~Array() { delete []a; }
void reset(const std::size_t size = 0) { delete []a; a = size ? new T[size] : 0; sz = size; }
std::size_t size() const { return sz; }
T * get() const { return a; }
operator T*() const { return a; }
};
#endif
+29
View File
@@ -0,0 +1,29 @@
/***************************************************************************
* Copyright (C) 2009 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef UNCOPYABLE_H
#define UNCOPYABLE_H
class Uncopyable {
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
public:
Uncopyable() {}
};
#endif
+162
View File
@@ -0,0 +1,162 @@
/***************************************************************************
* Copyright (C) 2007 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef GAMBATTE_H
#define GAMBATTE_H
#include "inputgetter.h"
#ifdef HAVE_NETWORK
#include "serial_io.h"
#endif
#include "gbint.h"
#include <string>
#include <cstddef>
namespace gambatte {
#if defined(VIDEO_RGB565) || defined(VIDEO_ABGR1555)
typedef uint16_t video_pixel_t;
#else
typedef uint_least32_t video_pixel_t;
#endif
enum { BG_PALETTE = 0, SP1_PALETTE = 1, SP2_PALETTE = 2 };
class GB {
public:
GB();
~GB();
enum LoadFlag {
FORCE_DMG = 1, /**< Treat the ROM as not having CGB support regardless of what its header advertises. */
GBA_CGB = 2, /**< Use GBA intial CPU register values when in CGB mode. */
MULTICART_COMPAT = 4, /**< Use heuristics to detect and support some multicart MBCs disguised as MBC1. */
FORCE_CGB = 8
};
int load(const void *romdata, unsigned size, unsigned flags = 0);
/** Emulates until at least 'samples' stereo sound samples are produced in the supplied buffer,
* or until a video frame has been drawn.
*
* There are 35112 stereo sound samples in a video frame.
* May run for uptil 2064 stereo samples too long.
* EDIT: Due to internal emulator bugs, may in fact run for
* an arbitrary number of samples...
* A stereo sample consists of two native endian 2s complement 16-bit PCM samples,
* with the left sample preceding the right one. Usually casting soundBuf to/from
* short* is OK and recommended. The reason for not using a short* in the interface
* is to avoid implementation-defined behaviour without compromising performance.
*
* Returns early when a new video frame has finished drawing in the video buffer,
* such that the caller may update the video output before the frame is overwritten.
* The return value indicates whether a new video frame has been drawn, and the
* exact time (in number of samples) at which it was drawn.
*
* @param videoBuf 160x144 RGB32 (native endian) video frame buffer or 0
* @param pitch distance in number of pixels (not bytes) from the start of one line to the next in videoBuf.
* @param soundBuf buffer with space >= samples + 2064
* @param soundBufSize actual size of soundBuf buffer
* @param samples in: number of stereo samples to produce, out: actual number of samples produced
* @return sample number at which the video frame was produced. -1 means no frame was produced.
*/
long runFor(gambatte::video_pixel_t *videoBuf, int pitch,
gambatte::uint_least32_t *soundBuf, std::size_t soundBufSize, unsigned &samples);
/** Reset to initial state.
* Equivalent to reloading a ROM image, or turning a Game Boy Color off and on again.
*/
void reset();
/** @param palNum 0 <= palNum < 3. One of BG_PALETTE, SP1_PALETTE and SP2_PALETTE.
* @param colorNum 0 <= colorNum < 4
*/
void setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned rgb32);
/** Sets the callback used for getting input state. */
void setInputGetter(InputGetter *getInput);
/** Sets the callback used for getting the bootloader data. */
void setBootloaderGetter(bool (*getter)(void *userdata, bool isgbc, uint8_t *data, uint32_t buf_size));
#ifdef HAVE_NETWORK
/** Sets the callback used for transferring serial data. */
void setSerialIO(SerialIO *serial_io);
#endif
/** Sets the directory used for storing save data. The default is the same directory as the ROM Image file. */
void setSaveDir(const std::string &sdir);
void *savedata_ptr();
unsigned savedata_size();
void *rtcdata_ptr();
unsigned rtcdata_size();
/** Returns true if the currently loaded ROM image is treated as having CGB support. */
bool isCgb() const;
/** Returns true if a ROM image is loaded. */
bool isLoaded() const;
void saveState(void *data);
/* Load a savestate previously produced by saveState. The size
* parameter must match the buffer length so the parser can
* reject malformed states without reading past the end of the
* buffer. Returns true on success. */
bool loadState(const void *data, size_t size);
size_t stateSize() const;
void setColorCorrection(bool enable);
void setColorCorrectionMode(unsigned colorCorrectionMode);
void setColorCorrectionBrightness(float colorCorrectionBrightness);
void setDarkFilterLevel(unsigned darkFilterLevel);
video_pixel_t gbcToRgb32(const unsigned bgr15);
/** Set Game Genie codes to apply to currently loaded ROM image. Cleared on ROM load.
* @param codes Game Genie codes in format HHH-HHH-HHH;HHH-HHH-HHH;... where H is [0-9]|[A-F]
*/
void setGameGenie(const std::string &codes);
/** Set Game Shark codes to apply to currently loaded ROM image. Cleared on ROM load.
* @param codes Game Shark codes in format 01HHHHHH;01HHHHHH;... where H is [0-9]|[A-F]
*/
void setGameShark(const std::string &codes);
void clearCheats();
#ifdef __LIBRETRO__
void *vram_ptr() const;
void *rambank0_ptr() const;
void *rambank1_ptr() const;
void *rambank2_ptr() const;
void *bankedram_ptr() const;
void *rombank0_ptr() const;
void *rombank1_ptr() const;
void *zeropage_ptr() const;
void *oamram_ptr() const;
#endif
private:
struct Priv;
Priv *const p_;
void loadState(const std::string &filepath, bool osdMessage);
GB(const GB &);
GB & operator=(const GB &);
};
}
#endif
+16
View File
@@ -0,0 +1,16 @@
#ifndef GAMBATTE_LOG_H
#define GAMBATTE_LOG_H
enum retro_log_level {
RETRO_LOG_DEBUG = 0,
RETRO_LOG_INFO,
RETRO_LOG_WARN,
RETRO_LOG_ERROR
};
static inline void gambatte_log(enum retro_log_level level, const char *format, ...) {
(void)level;
(void)format;
}
#endif
+62
View File
@@ -0,0 +1,62 @@
/***************************************************************************
* Copyright (C) 2007 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef GAMBATTE_INT_H
#define GAMBATTE_INT_H
#ifdef HAVE_CSTDINT
#include <cstdint>
namespace gambatte {
using std::uint_least32_t;
using std::uint_least16_t;
}
#elif defined(HAVE_STDINT_H)
#include <stdint.h>
namespace gambatte {
using ::uint_least32_t;
using ::uint_least16_t;
}
#else
namespace gambatte {
#ifdef CHAR_LEAST_32
typedef unsigned char uint_least32_t;
#elif defined(SHORT_LEAST_32)
typedef unsigned short uint_least32_t;
#elif defined(INT_LEAST_32)
typedef unsigned uint_least32_t;
#else
typedef unsigned long uint_least32_t;
#endif
#ifdef CHAR_LEAST_16
typedef unsigned char uint_least16_t;
#else
typedef unsigned short uint_least16_t;
#endif
}
#endif
#endif
+33
View File
@@ -0,0 +1,33 @@
/***************************************************************************
* Copyright (C) 2007 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef GAMBATTE_INPUTGETTER_H
#define GAMBATTE_INPUTGETTER_H
namespace gambatte {
class InputGetter {
public:
enum { A = 0x01, B = 0x02, SELECT = 0x04, START = 0x08, RIGHT = 0x10, LEFT = 0x20, UP = 0x40, DOWN = 0x80 };
virtual ~InputGetter() {};
/** @return A|B|SELECT|START|RIGHT|LEFT|UP|DOWN if those buttons are pressed. */
virtual unsigned operator()() = 0;
};
}
#endif
+113
View File
@@ -0,0 +1,113 @@
#include <stdint.h>
#include <cstring>
#include <string>
#include "bootloader.h"
namespace gambatte {
Bootloader::Bootloader()
: addrspace_start(NULL)
, bootloadersize(0)
, has_called_FF50(false)
, get_raw_bootloader_data(NULL)
, using_bootloader(false)
{
/* bootromswapspace and rombackup are intentionally left
* uninitialized for performance (0x900 bytes each); they
* are written before any read in load(). All scalar members
* are now initialized so observers (e.g. choosebank() called
* before a successful load) cannot read garbage. */
}
void Bootloader::patch_gbc_to_gba_mode() {
/*moves one jump over another and puts ld b,0x01 into the original position*/
uint16_t patchloc = 0xF2;
uint8_t patch[0x7] = {0xCD,0xD0,0x05/*<-call systemsetup*/,0x06,0x01/*<-ld b,0x1*/,0x00/*<-nop*/,0x00/*<-nop*/};
std::memcpy(bootromswapspace + patchloc, patch, 0x7);
}
void Bootloader::load(bool isgbc, bool isgba) {
if (get_raw_bootloader_data == NULL) {
using_bootloader = false;
return;
}
//the gba only uses the gbc bios
if (isgba)
isgbc = true;
bool bootloaderavail = get_raw_bootloader_data((void*)this, isgbc, bootromswapspace, 0x900/*buf_size*/);
if (!bootloaderavail) {
using_bootloader = false;
return;
}
if (isgbc)
bootloadersize = 0x900;
else
bootloadersize = 0x100;
if (isgba)//patch bootloader to fake gba mode
patch_gbc_to_gba_mode();
//backup rom segment that is shared with bootloader
std::memcpy(rombackup, (uint8_t*)addrspace_start, bootloadersize);
//put back cartridge data in a 256 byte window of the bios that is not mapped(GBC only)
if (isgbc)
std::memcpy(bootromswapspace + 0x100, rombackup + 0x100, 0x100);
//put bootloader in main memory
std::memcpy((uint8_t*)addrspace_start, bootromswapspace, bootloadersize);
using_bootloader = true;
}
void Bootloader::reset() {
bootloadersize = 0;
has_called_FF50 = false;
addrspace_start = NULL;
using_bootloader = false;
}
void Bootloader::set_bootloader_getter(bool (*getter)(void* userdata, bool isgbc, uint8_t* data, uint32_t buf_size)) {
get_raw_bootloader_data = getter;
}
void Bootloader::set_address_space_start(void* start) {
addrspace_start = start;
}
void Bootloader::choosebank(bool inbootloader) {
//inbootloader = (state.mem.ioamhram.get()[0x150] != 0xFF);//do not uncomment this is just for reference
if (using_bootloader) {
//switching from game to bootloader with savestate
if (inbootloader && has_called_FF50)
uncall_FF50();
//switching from bootloader to game with savestate
else if (!inbootloader && !has_called_FF50)
call_FF50();
//switching from game to game or bootloader to bootloader needs no changes
}
}
void Bootloader::call_FF50() {
if (!has_called_FF50 && using_bootloader) {
//put rom back in main memory when bootloader has finished
std::memcpy((uint8_t*)addrspace_start, rombackup, bootloadersize);
has_called_FF50 = true;
}
}
//this is a developer function only,a real gameboy can never undo calling 0xFF50,this function is for savestate functionality
void Bootloader::uncall_FF50() {
std::memcpy((uint8_t*)addrspace_start, bootromswapspace, bootloadersize);
has_called_FF50 = false;
}
}
+44
View File
@@ -0,0 +1,44 @@
#ifndef BOOTLOADER_H
#define BOOTLOADER_H
#include <stdint.h>
#include <string>
namespace gambatte {
class Bootloader {
private:
uint8_t bootromswapspace[0x900];
uint8_t rombackup[0x900];
void* addrspace_start;
unsigned int bootloadersize;
bool has_called_FF50;
//Set this to NULL or return false if you want to ignore bootloaders completely
//(initial value is NULL and so the bootloader is disabled by default)
bool (*get_raw_bootloader_data)(void* userdata, bool isgbc, uint8_t* data, uint32_t buf_size);
void patch_gbc_to_gba_mode();
void uncall_FF50();
public:
bool using_bootloader;
Bootloader();
void load(bool isgbc,bool isgba);
void reset();
void set_bootloader_getter(bool (*getter)(void* userdata, bool isgbc, uint8_t* data, uint32_t buf_size));
void set_address_space_start(void* start);
void choosebank(bool inbootloader);
void call_FF50();
};
}
#endif
+10
View File
@@ -0,0 +1,10 @@
#ifndef COUNTERDEF_H
#define COUNTERDEF_H
namespace gambatte {
enum { disabled_time = 0xfffffffful };
}
#endif
File diff suppressed because it is too large Load Diff
+114
View File
@@ -0,0 +1,114 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef CPU_H
#define CPU_H
#include "gambatte.h"
#include "gambatte-memory.h"
#include "savestate.h"
namespace gambatte {
#ifdef HAVE_NETWORK
class SerialIO;
#endif
class CPU {
public:
CPU();
long runFor(unsigned long cycles);
void setStatePtrs(SaveState &state);
void saveState(SaveState &state);
void loadState(SaveState const &state);
#if 0
void loadSavedata() { mem_.loadSavedata(); }
void saveSavedata() { mem_.saveSavedata(); }
#endif
#ifdef __LIBRETRO__
void *savedata_ptr() { return mem_.savedata_ptr(); }
unsigned savedata_size() { return mem_.savedata_size(); }
void *rtcdata_ptr() { return mem_.rtcdata_ptr(); }
unsigned rtcdata_size() { return mem_.rtcdata_size(); }
void clearCheats() { mem_.clearCheats(); }
void *vram_ptr() const { return mem_.vram_ptr(); }
void *rambank0_ptr() const { return mem_.rambank0_ptr(); }
void *rambank1_ptr() const { return mem_.rambank1_ptr(); }
void *rambank2_ptr() const { return mem_.rambank2_ptr(); }
void *bankedram_ptr() const { return mem_.bankedram_ptr(); }
void *rombank0_ptr() const { return mem_.rombank0_ptr(); }
void *rombank1_ptr() const { return mem_.rombank1_ptr(); }
void *zeropage_ptr() const { return mem_.zeropage_ptr(); }
void *oamram_ptr() const { return mem_.oamram_ptr(); }
#endif
void setVideoBuffer(video_pixel_t *videoBuf, std::ptrdiff_t pitch) {
mem_.setVideoBuffer(videoBuf, pitch);
}
void setInputGetter(InputGetter *getInput) {
mem_.setInputGetter(getInput);
}
#ifdef HAVE_NETWORK
void setSerialIO(SerialIO *serial_io) {
mem_.setSerialIO(serial_io);
}
#endif
void setSaveDir(std::string const &sdir) {
mem_.setSaveDir(sdir);
}
std::string const saveBasePath() const {
return mem_.saveBasePath();
}
int load(const void *romdata, unsigned int romsize, unsigned int forceModel, bool multicartCompat) {
return mem_.loadROM(romdata, romsize, forceModel, multicartCompat);
}
#if 0
bool loaded() const { return mem_.loaded(); }
#endif
void setSoundBuffer(uint_least32_t *buf, std::size_t size) { mem_.setSoundBuffer(buf, size); }
std::size_t fillSoundBuffer() { return mem_.fillSoundBuffer(cycleCounter_); }
bool isCgb() const { return mem_.isCgb(); }
void setDmgPaletteColor(int palNum, int colorNum, unsigned long rgb32) {
mem_.setDmgPaletteColor(palNum, colorNum, rgb32);
}
void setGameGenie(std::string const &codes) { mem_.setGameGenie(codes); }
void setGameShark(std::string const &codes) { mem_.setGameShark(codes); }
Memory mem_;
private:
unsigned long cycleCounter_;
unsigned short pc_;
unsigned short sp;
unsigned hf1, hf2, zf, cf;
unsigned char a_, b, c, d, e, /*f,*/ h, l;
bool skip_;
void process(unsigned long cycles);
};
}
#endif
File diff suppressed because it is too large Load Diff
+226
View File
@@ -0,0 +1,226 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef MEMORY_H
#define MEMORY_H
#include "mem/cartridge.h"
#include "interrupter.h"
#include "bootloader.h"
#include "sound.h"
#include "tima.h"
#include "video.h"
namespace gambatte {
class InputGetter;
#ifdef HAVE_NETWORK
class SerialIO;
#endif
class Memory {
public:
Bootloader bootloader;
explicit Memory(Interrupter const &interrupter);
bool loaded() const { return cart_.loaded(); }
void setStatePtrs(SaveState &state);
unsigned long saveState(SaveState &state, unsigned long cc);
void loadState(SaveState const &state);
#ifdef __LIBRETRO__
void *savedata_ptr() { return cart_.savedata_ptr(); }
unsigned savedata_size() { return cart_.savedata_size(); }
void *rtcdata_ptr() { return cart_.rtcdata_ptr(); }
unsigned rtcdata_size() { return cart_.rtcdata_size(); }
void display_setColorCorrection(bool enable) { lcd_.setColorCorrection(enable); }
void display_setColorCorrectionMode(unsigned colorCorrectionMode) { lcd_.setColorCorrectionMode(colorCorrectionMode); }
void display_setColorCorrectionBrightness(float colorCorrectionBrightness) { lcd_.setColorCorrectionBrightness(colorCorrectionBrightness); }
void display_setDarkFilterLevel(unsigned darkFilterLevel) { lcd_.setDarkFilterLevel(darkFilterLevel); }
video_pixel_t display_gbcToRgb32(const unsigned bgr15) { return lcd_.gbcToRgb32(bgr15); }
void clearCheats() { cart_.clearCheats(); interrupter_.clearCheats(); }
void *vram_ptr() const { return cart_.vramdata(); }
void *rambank0_ptr() const { return cart_.wramdata(0); }
void *rambank1_ptr() const { return cart_.wramdata(0) + 0x1000; }
void *rambank2_ptr() const { return cart_.wramdata(0) + 0x2000; }
void *bankedram_ptr() const { return cart_.wramdata(1); }
void *rombank0_ptr() const { return cart_.romdata(0); }
void *rombank1_ptr() const { return cart_.romdata(0) + 0x4000; }
void *zeropage_ptr() const { return (void*)(ioamhram_ + 0x0180); }
void *oamram_ptr() const { return (void*)ioamhram_; }
#else
void loadSavedata() { cart_.loadSavedata(); }
void saveSavedata() { cart_.saveSavedata(); }
#endif
std::string const saveBasePath() const { return cart_.saveBasePath(); }
unsigned long stop(unsigned long cycleCounter);
bool isCgb() const { return lcd_.isCgb(); }
bool ime() const { return intreq_.ime(); }
bool halted() const { return intreq_.halted(); }
unsigned long nextEventTime() const { return intreq_.minEventTime(); }
bool isActive() const { return intreq_.eventTime(intevent_end) != disabled_time; }
long cyclesSinceBlit(unsigned long cc) const
{
if (cc < intreq_.eventTime(intevent_blit))
return -1;
unsigned is_doublespeed = (unsigned)isDoubleSpeed();
return (cc - intreq_.eventTime(intevent_blit)) >> is_doublespeed;
}
void halt() { intreq_.halt(); }
void ei(unsigned long cycleCounter) { if (!ime()) { intreq_.ei(cycleCounter); } }
void di() { intreq_.di(); }
unsigned ff_read(unsigned p, unsigned long cc) {
return p < 0x80 ? nontrivial_ff_read(p, cc) : ioamhram_[p + 0x100];
}
unsigned read(unsigned p, unsigned long cc) {
/* Sachen MMC1 carts need to count CPU reads of 0x0100..0x01FF
* so the mapper can leave its boot-time locked state
* between the bootstrap's logo-display pass (48 cart
* reads of 0x0104..0x0133) and its logo-verify pass
* (another 48 reads of the same range). The counter
* pointer is null for every other cartridge, so the
* branch below is a single predictable null-check on
* the hot path. The unlock fires AFTER the 48th locked
* read has returned its byte, so the display pass sees a
* full Sachen-logo sequence and the very next read
* (first byte of the verify pass) sees the unlocked
* Nintendo bytes. */
const unsigned char *const rm = cart_.rmem(p >> 12);
const unsigned value = rm ? rm[p] : nontrivial_read(p, cc);
if (sachenLockCounter_ && (p & 0xFF00) == 0x0100) {
if (*sachenLockCounter_ < 48) {
if (++*sachenLockCounter_ == 48)
cart_.onSachenUnlock();
}
}
return value;
}
void write(unsigned p, unsigned data, unsigned long cc) {
if (cart_.wmem(p >> 12)) {
cart_.wmem(p >> 12)[p] = data;
} else
nontrivial_write(p, data, cc);
}
void ff_write(unsigned p, unsigned data, unsigned long cc) {
if (p - 0x80u < 0x7Fu) {
ioamhram_[p + 0x100] = data;
} else
nontrivial_ff_write(p, data, cc);
}
#ifdef HAVE_NETWORK
void startSerialTransfer(unsigned long cycleCounter, unsigned char data, bool fastCgb);
#endif
unsigned long event(unsigned long cycleCounter);
unsigned long resetCounters(unsigned long cycleCounter);
void setSaveDir(std::string const &dir) { cart_.setSaveDir(dir); }
void setInputGetter(InputGetter *getInput) { getInput_ = getInput; }
#ifdef HAVE_NETWORK
void setSerialIO(SerialIO* serial_io) { serial_io_ = serial_io; }
#endif
void setEndtime(unsigned long cc, unsigned long inc);
void setSoundBuffer(uint_least32_t *buf, std::size_t size) { psg_.setBuffer(buf, size); }
std::size_t fillSoundBuffer(unsigned long cc);
void setVideoBuffer(video_pixel_t *videoBuf, std::ptrdiff_t pitch) {
lcd_.setVideoBuffer(videoBuf, pitch);
}
void setDmgPaletteColor(int palNum, int colorNum, unsigned long rgb32) {
lcd_.setDmgPaletteColor(palNum, colorNum, rgb32);
}
void setGameGenie(std::string const &codes) { cart_.setGameGenie(codes); }
void setGameShark(std::string const &codes) { interrupter_.setGameShark(codes); }
#ifdef HAVE_NETWORK
void checkSerial(unsigned long cc);
#endif
void updateInput();
int loadROM(const void *romdata, unsigned int romsize, unsigned int forceModel, const bool multicartCompat);
/* Forwarders for unlicensed-mapper hooks. Currently only Sachen
* MMC1 carts use them; for every other cartridge type these are
* no-ops and isSachen() returns false. Called by
* GB::Priv::full_init after the bootloader has been installed
* (or skipped). */
bool isSachen() const { return cart_.isSachen(); }
void sachenLockSetup(bool bootloaderUsed) {
cart_.sachenLockSetup(bootloaderUsed);
/* Pick up the MBC's lock-counter address now that the cart
* has decided whether to enter the locked state. For a
* Sachen cart, sachenLockCounterPtr() returns a real
* address; for any other cart it returns null and the read
* fast path stays in single-branch territory. */
sachenLockCounter_ = cart_.sachenLockCounterPtr();
}
void onSachenUnlock() { cart_.onSachenUnlock(); }
private:
Cartridge cart_;
unsigned char ioamhram_[0x200];
/* Pointer to the Sachen MBC's read counter, or null for any
* non-Sachen cart. When non-null, Memory::read bumps it on
* every CPU read of 0x0100..0x01FF and signals the cart on
* the 48th such read so the mapper can leave its locked
* state between the bootstrap's display and verify passes. */
unsigned char *sachenLockCounter_;
#ifdef HAVE_NETWORK
unsigned char serialize_value_;
bool serialize_is_fastcgb_;
SerialIO *serial_io_;
#endif
InputGetter *getInput_;
unsigned long divLastUpdate_;
unsigned long lastOamDmaUpdate_;
InterruptRequester intreq_;
Tima tima_;
LCD lcd_;
PSG psg_;
Interrupter interrupter_;
unsigned short dmaSource_;
unsigned short dmaDestination_;
unsigned char oamDmaPos_;
unsigned char serialCnt_;
bool blanklcd_;
void decEventCycles(IntEventId eventId, unsigned long dec);
void oamDmaInitSetup();
void updateOamDma(unsigned long cycleCounter);
void startOamDma(unsigned long cycleCounter);
void endOamDma(unsigned long cycleCounter);
unsigned char const * oamDmaSrcPtr() const;
unsigned nontrivial_ff_read(unsigned p, unsigned long cycleCounter);
unsigned nontrivial_read(unsigned p, unsigned long cycleCounter);
void nontrivial_ff_write(unsigned p, unsigned data, unsigned long cycleCounter);
void nontrivial_write(unsigned p, unsigned data, unsigned long cycleCounter);
void updateSerial(unsigned long cc);
void updateTimaIrq(unsigned long cc);
void updateIrqs(unsigned long cc);
bool isDoubleSpeed() const { return lcd_.isDoubleSpeed(); }
};
}
#endif
+243
View File
@@ -0,0 +1,243 @@
/***************************************************************************
* Copyright (C) 2007 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "gambatte.h"
#include "cpu.h"
#include "savestate.h"
#include "statesaver.h"
#include "initstate.h"
#include "bootloader.h"
#include <sstream>
#include <cstring>
namespace gambatte {
struct GB::Priv {
CPU cpu;
int stateNo;
bool gbaCgbMode;
Priv() : stateNo(1), gbaCgbMode(false) {}
void full_init(bool clearSram = true);
};
GB::GB() : p_(new Priv) {}
GB::~GB() {
delete p_;
}
long GB::runFor(gambatte::video_pixel_t *const videoBuf, const int pitch,
gambatte::uint_least32_t *const soundBuf, std::size_t soundBufSize, unsigned &samples) {
p_->cpu.setVideoBuffer(videoBuf, pitch);
p_->cpu.setSoundBuffer(soundBuf, soundBufSize);
const long cyclesSinceBlit = p_->cpu.runFor(samples * 2);
samples = p_->cpu.fillSoundBuffer();
return cyclesSinceBlit < 0 ? cyclesSinceBlit : static_cast<long>(samples) - (cyclesSinceBlit >> 1);
}
void GB::Priv::full_init(bool const clearSram) {
SaveState state;
cpu.setStatePtrs(state);
setInitState(state, cpu.isCgb(), gbaCgbMode, clearSram);
cpu.mem_.bootloader.reset();
cpu.mem_.bootloader.set_address_space_start((void*)cpu.rombank0_ptr());
cpu.mem_.bootloader.load(cpu.isCgb(), gbaCgbMode);
if (cpu.mem_.bootloader.using_bootloader) {
uint8_t *ioamhram = (uint8_t*)state.mem.ioamhram.get();
uint8_t serialctrl = (cpu.isCgb() || gbaCgbMode) ? 0x7C : 0x7E;
state.cpu.pc = 0x0000;
// the hw registers must be zeroed out to prevent the logo from being garbled
std::memset((void*)(ioamhram + 0x100), 0x00, 0x100);
//init values taken from SameBoy
ioamhram[0x100] = 0xCF;//joypad initial value
ioamhram[0x102] = serialctrl;//serialctrl
ioamhram[0x148] = 0xFC;//object palette 0
ioamhram[0x149] = 0xFC;//object palette 1
}
cpu.loadState(state);
/* Sachen MMC1 carts need their lock-state overlay installed
* (or their VRAM logo injected) AFTER the generic state
* restore has finished, so the MBC's loadState handler can't
* tear down the overlay based on the default-initialized
* SaveState fields. For every non-Sachen cart this is a
* no-op; for Sachen carts it also wires the read-count
* unlock trigger into the Memory hot path. */
cpu.mem_.sachenLockSetup(cpu.mem_.bootloader.using_bootloader);
}
void GB::reset() {
/* Battery-backed SRAM and RTC must persist across a soft reset
* (matching real-hardware behavior; the cart battery keeps
* volatile SRAM alive while the console is power-cycled). */
p_->full_init(false);
}
void GB::setInputGetter(InputGetter *getInput) {
p_->cpu.setInputGetter(getInput);
}
void GB::setBootloaderGetter(bool (*getter)(void* userdata, bool isgbc, uint8_t* data, uint32_t max_size)) {
p_->cpu.mem_.bootloader.set_bootloader_getter(getter);
}
#ifdef HAVE_NETWORK
void GB::setSerialIO(SerialIO *serial_io) {
p_->cpu.setSerialIO(serial_io);
}
#endif
void *GB::savedata_ptr() { return p_->cpu.savedata_ptr(); }
unsigned GB::savedata_size() { return p_->cpu.savedata_size(); }
void *GB::rtcdata_ptr() { return p_->cpu.rtcdata_ptr(); }
unsigned GB::rtcdata_size() { return p_->cpu.rtcdata_size(); }
int GB::load(const void *romdata, unsigned romsize, const unsigned flags) {
const int failed = p_->cpu.load(romdata, romsize, flags & (FORCE_DMG | FORCE_CGB), flags & MULTICART_COMPAT);
if (!failed) {
p_->gbaCgbMode = flags & GBA_CGB;
p_->full_init();
p_->stateNo = 1;
}
return failed;
}
bool GB::isCgb() const {
return p_->cpu.isCgb();
}
bool GB::isLoaded() const {
/* Previously this unconditionally returned true. The
* underlying Cartridge::loaded() reports whether an MBC has
* been instantiated, which is a faithful "is a ROM loaded"
* signal, so route through to it. */
return p_->cpu.mem_.loaded();
}
void GB::setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned rgb32) {
p_->cpu.setDmgPaletteColor(palNum, colorNum, rgb32);
}
bool GB::loadState(const void *data, size_t size) {
SaveState state;
p_->cpu.setStatePtrs(state);
if (StateSaver::loadState(state, data, size)) {
p_->cpu.loadState(state);
p_->cpu.mem_.bootloader.choosebank(state.mem.ioamhram.get()[0x150] != 0xFF);
return true;
}
return false;
}
void GB::saveState(void *data) {
SaveState state;
p_->cpu.setStatePtrs(state);
p_->cpu.saveState(state);
StateSaver::saveState(state, data);
}
size_t GB::stateSize() const {
SaveState state;
p_->cpu.setStatePtrs(state);
p_->cpu.saveState(state);
return StateSaver::stateSize(state);
}
void GB::setColorCorrection(bool enable) {
p_->cpu.mem_.display_setColorCorrection(enable);
}
void GB::setColorCorrectionMode(unsigned colorCorrectionMode) {
p_->cpu.mem_.display_setColorCorrectionMode(colorCorrectionMode);
}
void GB::setColorCorrectionBrightness(float colorCorrectionBrightness) {
p_->cpu.mem_.display_setColorCorrectionBrightness(colorCorrectionBrightness);
}
void GB::setDarkFilterLevel(unsigned darkFilterLevel) {
p_->cpu.mem_.display_setDarkFilterLevel(darkFilterLevel);
}
video_pixel_t GB::gbcToRgb32(const unsigned bgr15) {
return p_->cpu.mem_.display_gbcToRgb32(bgr15);
}
void GB::setGameGenie(const std::string &codes) {
p_->cpu.setGameGenie(codes);
}
void GB::setGameShark(const std::string &codes) {
p_->cpu.setGameShark(codes);
}
void GB::clearCheats() {
p_->cpu.clearCheats();
}
#ifdef __LIBRETRO__
void *GB::vram_ptr() const {
return p_->cpu.vram_ptr();
}
void *GB::rambank0_ptr() const {
return p_->cpu.rambank0_ptr();
}
void *GB::rambank1_ptr() const {
return p_->cpu.rambank1_ptr();
}
void *GB::rambank2_ptr() const {
return p_->cpu.rambank2_ptr();
}
void *GB::bankedram_ptr() const {
return p_->cpu.bankedram_ptr();
}
void *GB::rombank0_ptr() const {
return p_->cpu.rombank0_ptr();
}
void *GB::rombank1_ptr() const {
return p_->cpu.rombank1_ptr();
}
void *GB::zeropage_ptr() const {
return p_->cpu.zeropage_ptr();
}
void *GB::oamram_ptr() const {
return p_->cpu.oamram_ptr();
}
#endif
}
File diff suppressed because it is too large Load Diff
+31
View File
@@ -0,0 +1,31 @@
/***************************************************************************
* Copyright (C) 2008 by Sindre Aam�s *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef INITSTATE_H
#define INITSTATE_H
namespace gambatte {
/* clearSram defaults to true to preserve the historical behavior
* for callers that load a fresh cart (uninitialized SRAM should
* read as 0xFF). Pass false from a soft-reset path so battery-
* backed cartridge RAM survives the reset, matching real
* hardware behavior. */
void setInitState(struct SaveState &state, bool cgb, bool gbaCgbMode, bool clearSram = true);
}
#endif
+51
View File
@@ -0,0 +1,51 @@
/***************************************************************************
* Copyright (C) 2007 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef INSERTION_SORT_H
#define INSERTION_SORT_H
#include <functional>
template<typename T, class Less>
void insertionSort(T *const start, T *const end, Less less) {
if (start >= end)
return;
T *a = start;
while (++a < end) {
const T e = *a;
T *b = a;
while (b != start && less(e, *(b - 1))) {
*b = *(b - 1);
b = b - 1;
}
*b = e;
}
}
template<typename T>
inline void insertionSort(T *const start, T *const end) {
insertionSort(start, end, std::less<T>());
}
#endif /*INSERTION_SORT_H*/
+85
View File
@@ -0,0 +1,85 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "interrupter.h"
#include "gambatte-memory.h"
namespace gambatte {
Interrupter::Interrupter(unsigned short &sp, unsigned short &pc)
: sp_(sp)
, pc_(pc)
{
}
unsigned long Interrupter::interrupt(unsigned const address, unsigned long cc, Memory &memory) {
cc += 8;
sp_ = (sp_ - 1) & 0xFFFF;
memory.write(sp_, pc_ >> 8, cc);
cc += 4;
sp_ = (sp_ - 1) & 0xFFFF;
memory.write(sp_, pc_ & 0xFF, cc);
pc_ = address;
cc += 8;
if (address == 0x40 && !gsCodes_.empty())
applyVblankCheats(cc, memory);
return cc;
}
static int asHex(char c) {
/* Accept both uppercase and lowercase A-F. The previous
* implementation produced silently-wrong values for
* lowercase letters, leading to no-op cheats when users
* typed codes in lowercase. */
if (c >= 'a' && c <= 'f') return c - 'a' + 0xA;
if (c >= 'A' && c <= 'F') return c - 'A' + 0xA;
return c - '0';
}
void Interrupter::setGameShark(std::string const &codes) {
std::string code;
for (std::size_t pos = 0; pos < codes.length(); pos += code.length() + 1) {
code = codes.substr(pos, codes.find(';', pos) - pos);
if (code.length() >= 8) {
GsCode gs;
gs.type = asHex(code[0]) << 4 | asHex(code[1]);
gs.value = (asHex(code[2]) << 4 | asHex(code[3])) & 0xFF;
gs.address = ( asHex(code[4]) << 4
| asHex(code[5])
| asHex(code[6]) << 12
| asHex(code[7]) << 8) & 0xFFFF;
gsCodes_.push_back(gs);
}
}
}
void Interrupter::clearCheats() {
gsCodes_.clear();
}
void Interrupter::applyVblankCheats(unsigned long const cc, Memory &memory) {
for (std::size_t i = 0, size = gsCodes_.size(); i < size; ++i) {
if (gsCodes_[i].type == 0x01)
memory.write(gsCodes_[i].address, gsCodes_[i].value, cc);
}
}
}
+52
View File
@@ -0,0 +1,52 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef INTERRUPTER_H
#define INTERRUPTER_H
#include <string>
#include <vector>
namespace gambatte {
struct GsCode {
unsigned short address;
unsigned char value;
unsigned char type;
};
class Memory;
class Interrupter {
public:
Interrupter(unsigned short &sp, unsigned short &pc);
unsigned long interrupt(unsigned address, unsigned long cycleCounter, Memory &memory);
void setGameShark(std::string const &codes);
void clearCheats();
private:
unsigned short &sp_;
unsigned short &pc_;
std::vector<GsCode> gsCodes_;
void applyVblankCheats(unsigned long cc, Memory &mem);
};
}
#endif
@@ -0,0 +1,117 @@
//
// Copyright (C) 2010 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "interruptrequester.h"
#include "savestate.h"
namespace gambatte {
InterruptRequester::InterruptRequester()
: eventTimes_(disabled_time)
, minIntTime_(0)
, ifreg_(0)
, iereg_(0)
{
}
void InterruptRequester::saveState(SaveState &state) const {
state.mem.minIntTime = minIntTime_;
state.mem.IME = ime();
state.mem.halted = halted();
}
void InterruptRequester::loadState(SaveState const &state) {
minIntTime_ = state.mem.minIntTime;
ifreg_ = state.mem.ioamhram.get()[0x10F];
iereg_ = state.mem.ioamhram.get()[0x1FF] & 0x1F;
intFlags_.set(state.mem.IME, state.mem.halted);
eventTimes_.setValue<intevent_interrupts>(intFlags_.imeOrHalted() && pendingIrqs()
? minIntTime_
: static_cast<unsigned long>(disabled_time));
}
void InterruptRequester::resetCc(unsigned long oldCc, unsigned long newCc) {
minIntTime_ = minIntTime_ < oldCc ? 0 : minIntTime_ - (oldCc - newCc);
if (eventTimes_.value(intevent_interrupts) != disabled_time)
eventTimes_.setValue<intevent_interrupts>(minIntTime_);
}
void InterruptRequester::ei(unsigned long cc) {
intFlags_.setIme();
minIntTime_ = cc + 1;
if (pendingIrqs())
eventTimes_.setValue<intevent_interrupts>(minIntTime_);
}
void InterruptRequester::di() {
intFlags_.unsetIme();
if (!intFlags_.imeOrHalted())
eventTimes_.setValue<intevent_interrupts>(disabled_time);
}
void InterruptRequester::halt() {
intFlags_.setHalted();
if (pendingIrqs())
eventTimes_.setValue<intevent_interrupts>(minIntTime_);
}
void InterruptRequester::unhalt() {
intFlags_.unsetHalted();
if (!intFlags_.imeOrHalted())
eventTimes_.setValue<intevent_interrupts>(disabled_time);
}
void InterruptRequester::flagIrq(unsigned bit) {
ifreg_ |= bit;
if (intFlags_.imeOrHalted() && pendingIrqs())
eventTimes_.setValue<intevent_interrupts>(minIntTime_);
}
void InterruptRequester::ackIrq(unsigned bit) {
ifreg_ ^= bit;
di();
}
void InterruptRequester::setIereg(unsigned iereg) {
iereg_ = iereg & 0x1F;
if (intFlags_.imeOrHalted()) {
eventTimes_.setValue<intevent_interrupts>(pendingIrqs()
? minIntTime_
: static_cast<unsigned long>(disabled_time));
}
}
void InterruptRequester::setIfreg(unsigned ifreg) {
ifreg_ = ifreg;
if (intFlags_.imeOrHalted()) {
eventTimes_.setValue<intevent_interrupts>(pendingIrqs()
? minIntTime_
: static_cast<unsigned long>(disabled_time));
}
}
}
@@ -0,0 +1,97 @@
//
// Copyright (C) 2010 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef INTERRUPT_REQUESTER_H
#define INTERRUPT_REQUESTER_H
#include "counterdef.h"
#include "minkeeper.h"
namespace gambatte {
struct SaveState;
enum IntEventId { intevent_unhalt,
intevent_end,
intevent_blit,
intevent_serial,
intevent_oam,
intevent_dma,
intevent_tima,
intevent_video,
intevent_interrupts, intevent_last = intevent_interrupts };
class InterruptRequester {
public:
InterruptRequester();
void saveState(SaveState &) const;
void loadState(SaveState const &);
void resetCc(unsigned long oldCc, unsigned long newCc);
unsigned ifreg() const { return ifreg_; }
unsigned pendingIrqs() const { return ifreg_ & iereg_; }
bool ime() const { return intFlags_.ime(); }
bool halted() const { return intFlags_.halted(); }
void ei(unsigned long cc);
void di();
void halt();
void unhalt();
void flagIrq(unsigned bit);
void ackIrq(unsigned bit);
void setIereg(unsigned iereg);
void setIfreg(unsigned ifreg);
IntEventId minEventId() const { return static_cast<IntEventId>(eventTimes_.min()); }
unsigned long minEventTime() const { return eventTimes_.minValue(); }
template<IntEventId id> void setEventTime(unsigned long value) { eventTimes_.setValue<id>(value); }
void setEventTime(IntEventId id, unsigned long value) { eventTimes_.setValue(id, value); }
unsigned long eventTime(IntEventId id) const { return eventTimes_.value(id); }
private:
class IntFlags {
public:
IntFlags() : flags_(0) {}
bool ime() const { return flags_ & flag_ime; }
bool halted() const { return flags_ & flag_halted; }
bool imeOrHalted() const { return flags_; }
void setIme() { flags_ |= flag_ime; }
void unsetIme() { flags_ &= ~flag_ime; }
void setHalted() { flags_ |= flag_halted; }
void unsetHalted() { flags_ &= ~flag_halted; }
void set(bool ime, bool halted) { flags_ = halted * flag_halted + ime * flag_ime; }
private:
unsigned char flags_;
enum { flag_ime = 1, flag_halted = 2 };
};
MinKeeper<intevent_last + 1> eventTimes_;
unsigned long minIntTime_;
unsigned ifreg_;
unsigned iereg_;
IntFlags intFlags_;
};
inline void flagHdmaReq(InterruptRequester &intreq) { intreq.setEventTime<intevent_dma>(0); }
inline void flagGdmaReq(InterruptRequester &intreq) { intreq.setEventTime<intevent_dma>(1); }
inline void ackDmaReq(InterruptRequester &intreq) { intreq.setEventTime<intevent_dma>(disabled_time); }
inline bool hdmaReqFlagged(InterruptRequester const &intreq) { return intreq.eventTime(intevent_dma) == 0; }
inline bool gdmaReqFlagged(InterruptRequester const &intreq) { return intreq.eventTime(intevent_dma) == 1; }
}
#endif
File diff suppressed because it is too large Load Diff
+226
View File
@@ -0,0 +1,226 @@
/***************************************************************************
* Copyright (C) 2007-2010 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef CARTRIDGE_H
#define CARTRIDGE_H
#include "memptrs.h"
#include "rtc.h"
#include "huc3.h"
#include "savestate.h"
#include <memory>
#include <string>
#include <vector>
namespace gambatte
{
class Mbc
{
public:
virtual ~Mbc() {}
virtual void romWrite(unsigned P, unsigned data) {};
virtual void saveState(SaveState::Mem &ss) const {};
virtual void loadState(const SaveState::Mem &ss) {};
virtual bool isAddressWithinAreaRombankCanBeMappedTo(unsigned address, unsigned rombank) const = 0;
/* Called once when the boot ROM hands control to the cartridge
* (write of 1 to 0xFF50). No MBC currently implements this --
* the Sachen MMC1 now unlocks on read-count, not on FF50 --
* but the hook is kept for any future MBC that needs to
* sync with end-of-bootstrap. Default is a no-op so every
* other MBC is unaffected. */
virtual void bootloaderFinished() {}
/* Sachen MMC1 only: pointer to the mapper's lock-progress
* counter (a single unsigned char shared with Memory) and
* the unlock entry point. Default null/no-op for every
* other MBC. */
virtual unsigned char *lockCounterPtr() { return 0; }
virtual void unlock() {}
};
class Cartridge
{
public:
Cartridge() : isSachen_(false) {}
void setStatePtrs(SaveState &);
void saveState(SaveState &) const;
void loadState(const SaveState &);
bool loaded() const { return mbc.get(); }
const unsigned char * rmem(unsigned area) const
{
return memptrs_.rmem(area);
}
unsigned char * wmem(unsigned area) const
{
return memptrs_.wmem(area);
}
unsigned char * vramdata() const
{
return memptrs_.vramdata();
}
unsigned char * romdata(unsigned area) const
{
return memptrs_.romdata(area);
}
unsigned char * wramdata(unsigned area) const
{
return memptrs_.wramdata(area);
}
const unsigned char * rdisabledRam() const
{
return memptrs_.rdisabledRam();
}
const unsigned char * rsrambankptr() const
{
return memptrs_.rsrambankptr();
}
unsigned char * wsrambankptr() const
{
return memptrs_.wsrambankptr();
}
unsigned char * vrambankptr() const
{
return memptrs_.vrambankptr();
}
OamDmaSrc oamDmaSrc() const
{
return memptrs_.oamDmaSrc();
}
void setVrambank(unsigned bank)
{
memptrs_.setVrambank(bank);
}
void setWrambank(unsigned bank)
{
memptrs_.setWrambank(bank);
}
void setOamDmaSrc(OamDmaSrc oamDmaSrc)
{
memptrs_.setOamDmaSrc(oamDmaSrc);
}
void mbcWrite(unsigned addr, unsigned data) { mbc->romWrite(addr, data); }
bool isCgb() const
{
return gambatte::isCgb(memptrs_);
}
void rtcWrite(unsigned data)
{
rtc_.write(data);
}
unsigned char rtcRead() const
{
return *rtc_.getActive();
}
const std::string saveBasePath() const;
void setSaveDir(const std::string &dir);
int loadROM(const void *romdata, unsigned int romsize, unsigned int forceModel, bool multicartCompat);
void setGameGenie(const std::string &codes);
void clearCheats();
bool isHuC3() const { return huc3_.isHuC3(); }
unsigned char HuC3Read(unsigned p, unsigned long const cc) { return huc3_.read(p, cc); }
void HuC3Write(unsigned p, unsigned data) { huc3_.write(p, data); }
/* Sachen MMC1 (unlicensed mapper used by Sachen 4-in-1 etc.):
* the cartridge header is bit-scrambled and a "locked" boot
* state requires hooks outside the normal MBC write path.
* isSachen() returns true for Sachen MMC1 carts.
* sachenLockSetup(haveBoot) is called once after the bootloader
* has been installed (or skipped) to
* overlay the locked header bytes
* when the bootstrap will run, or to
* inject the Sachen logo into VRAM
* when running without a bootstrap.
* sachenLockCounterPtr() returns a pointer to the mapper's
* "cart reads in 0x0100..0x01FF"
* counter, or null for non-Sachen
* carts. Memory::read bumps it and
* calls onSachenUnlock() when the
* bootstrap finishes its logo
* display pass.
* onSachenUnlock() drops the locked state mid-boot
* so the bootstrap's verify pass
* sees the unscrambled Nintendo
* logo bytes and succeeds.
* onBootloaderFinished() is a no-op for the new Sachen
* implementation (lock now drops
* on read-count, not on 0xFF50)
* but is kept for any future MBC
* that needs the hook.
* For every non-Sachen cart these functions are no-ops. */
bool isSachen() const { return isSachen_; }
void sachenLockSetup(bool bootloaderUsed);
unsigned char *sachenLockCounterPtr();
void onSachenUnlock();
void onBootloaderFinished() { if (mbc.get()) mbc->bootloaderFinished(); }
void *savedata_ptr();
unsigned savedata_size();
// Not endian-safe at all, but hey.
void *rtcdata_ptr();
unsigned rtcdata_size();
private:
struct AddrData
{
unsigned long addr;
unsigned char data;
AddrData(unsigned long addr, unsigned data) : addr(addr), data(data)
{
}
};
MemPtrs memptrs_;
Rtc rtc_;
HuC3Chip huc3_;
/* True only when the loaded ROM was identified as a Sachen
* MMC1 unlicensed cart via the scrambled-logo checksum check
* in loadROM. Used to route bootloader/VRAM hooks. */
bool isSachen_;
#if __cplusplus >= 201103L
std::unique_ptr<Mbc> mbc;
#else
std::auto_ptr<Mbc> mbc;
#endif
std::vector<AddrData> ggUndoList_;
void applyGameGenie(const std::string &code);
};
}
#endif
@@ -0,0 +1,93 @@
#include "cartridge.h"
#include "../savestate.h"
#include <cstring>
#include <fstream>
namespace gambatte
{
static bool hasBattery(unsigned char headerByte0x147)
{
switch (headerByte0x147)
{
case 0x03:
case 0x06:
case 0x09:
case 0x0F:
case 0x10:
case 0x13:
case 0x1B:
case 0x1E:
case 0xFE:
case 0xFF:
return true;
default:
return false;
}
}
static bool hasRtc(unsigned headerByte0x147)
{
switch (headerByte0x147)
{
case 0xFE: // huc3
case 0x0F:
case 0x10:
return true;
default:
return false;
}
}
void *Cartridge::savedata_ptr()
{
/* Sachen MMC1 carts have no SRAM, and the byte at 0x0147 is
* the descrambled cartridge type which has no Nintendo
* meaning anyway -- skip the hasBattery() check entirely so
* we never hand the frontend a bogus save pointer. */
if (isSachen_)
return 0;
// Check ROM header for battery.
if (hasBattery(memptrs_.romdata()[0x147]))
return memptrs_.rambankdata();
return 0;
}
unsigned Cartridge::savedata_size()
{
if (isSachen_)
return 0;
if (hasBattery(memptrs_.romdata()[0x147]))
return memptrs_.rambankdataend() - memptrs_.rambankdata();
return 0;
}
void *Cartridge::rtcdata_ptr()
{
if (isSachen_)
return 0;
if (hasRtc(memptrs_.romdata()[0x147])) {
if (isHuC3()) {
return &huc3_.getBaseTime();
} else {
return &rtc_.getBaseTime();
}
}
return 0;
}
unsigned Cartridge::rtcdata_size()
{
if (isSachen_)
return 0;
if (hasRtc(memptrs_.romdata()[0x147])) {
if (isHuC3()) {
return sizeof(huc3_.getBaseTime());
} else {
return sizeof(rtc_.getBaseTime());
}
}
return 0;
}
}
+189
View File
@@ -0,0 +1,189 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
//
#include "huc3.h"
#include "../savestate.h"
#include "gambatte_log.h"
namespace gambatte {
HuC3Chip::HuC3Chip()
: baseTime_(0)
, haltTime_(0)
, dataTime_(0)
, writingTime_(0)
, ramValue_(0)
, shift_(0)
, ramflag_(0)
, modeflag_(HUC3_NONE)
, irBaseCycle_(0)
, enabled_(false)
, halted_(false)
, irReceivingPulse_(false)
{
}
void HuC3Chip::doLatch() {
uint64_t tmp = (halted_ ? haltTime_ : std::time(0)) - baseTime_;
unsigned minute = (tmp / 60) % 1440;
unsigned day = (tmp / 86400) & 0xFFF;
dataTime_ = (day << 12) | minute;
}
void HuC3Chip::saveState(SaveState &state) const {
state.huc3.baseTime = baseTime_;
state.huc3.haltTime = haltTime_;
state.huc3.dataTime = dataTime_;
state.huc3.writingTime = writingTime_;
state.huc3.ramValue = ramValue_;
state.huc3.shift = shift_;
state.huc3.halted = halted_;
state.huc3.modeflag = modeflag_;
state.huc3.irBaseCycle = irBaseCycle_;
state.huc3.irReceivingPulse = irReceivingPulse_;
}
void HuC3Chip::loadState(SaveState const &state) {
baseTime_ = state.huc3.baseTime;
haltTime_ = state.huc3.haltTime;
dataTime_ = state.huc3.dataTime;
ramValue_ = state.huc3.ramValue;
shift_ = state.huc3.shift;
halted_ = state.huc3.halted;
modeflag_ = state.huc3.modeflag;
writingTime_ = state.huc3.writingTime;
irBaseCycle_ = state.huc3.irBaseCycle;
irReceivingPulse_ = state.huc3.irReceivingPulse;
}
unsigned char HuC3Chip::read(unsigned p, unsigned long const cc) {
// should only reach here with ramflag = 0B-0E
if(ramflag_ == 0x0E) {
// INFRARED
if(!irReceivingPulse_) {
irReceivingPulse_ = true;
irBaseCycle_ = cc;
}
unsigned long cyclesSinceStart = cc - irBaseCycle_;
unsigned char modulation = (cyclesSinceStart/105) & 1; // 4194304 Hz CPU, 40000 Hz remote signal
unsigned long timeUs = cyclesSinceStart*36/151; // actually *1000000/4194304
// sony protocol
if(timeUs < 10000) {
// initialization allowance
return 0;
}
else if(timeUs < 10000 + 2400) {
// initial mark
return modulation;
}
else if(timeUs < 10000 + 2400 + 600) {
// initial space
return 0;
}
else {
// send data
timeUs -= 13000;
// write 20 bits (any 20 seem to do)
unsigned int data = 0xFFFFF;
for(unsigned long mask = 1UL << (20-1); mask; mask >>= 1) {
unsigned int markTime = (data & mask) ? 1200 : 600;
if(timeUs < markTime) { return modulation; }
timeUs -= markTime;
if(timeUs < 600) { return 0; }
timeUs -= 600;
}
return 0;
}
}
if(ramflag_ < 0x0B || ramflag_ > 0x0D) {
gambatte_log(RETRO_LOG_ERROR, "<HuC3> error, hit huc3 read with ramflag=%02X\n", ramflag_);
return 0xFF;
}
if(ramflag_ == 0x0D) return 1;
else return ramValue_;
}
void HuC3Chip::write(unsigned p, unsigned data) {
// as above
if(ramflag_ == 0x0B) {
// command
switch(data & 0xF0) {
case 0x10:
// read time
doLatch();
if(modeflag_ == HUC3_READ) {
ramValue_ = (dataTime_ >> shift_) & 0x0F;
shift_ += 4;
if(shift_ > 24) shift_ = 0;
}
break;
case 0x30:
// write time
if(modeflag_ == HUC3_WRITE) {
if(shift_ == 0) writingTime_ = 0;
if(shift_ < 24) {
writingTime_ |= (data & 0x0F) << shift_;
shift_ += 4;
if(shift_ == 24) {
updateTime();
modeflag_ = HUC3_READ;
}
}
}
break;
case 0x40:
// some kind of mode shift
switch(data & 0x0F) {
case 0x0:
// shift reset?
shift_ = 0;
break;
case 0x3:
// write time?
modeflag_ = HUC3_WRITE;
shift_ = 0;
break;
case 0x7:
modeflag_ = HUC3_READ;
shift_ = 0;
break;
// others are unimplemented so far
}
break;
case 0x50:
// ???
break;
case 0x60:
modeflag_ = HUC3_READ; // ???
break;
}
}
// do nothing for 0C/0D yet
}
void HuC3Chip::updateTime() {
unsigned minute = (writingTime_ & 0xFFF) % 1440;
unsigned day = (writingTime_ & 0xFFF000) >> 12;
baseTime_ = std::time(0) - minute*60 - day*86400;
haltTime_ = baseTime_;
}
}
+79
View File
@@ -0,0 +1,79 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
//
#ifndef HuC3Chip_H
#define HuC3Chip_H
enum
{
HUC3_READ = 0,
HUC3_WRITE = 1,
HUC3_NONE = 2
};
#include <ctime>
#include <stdint.h>
namespace gambatte {
struct SaveState;
class HuC3Chip {
public:
HuC3Chip();
uint64_t baseTime() const { return baseTime_; }
void setBaseTime(uint64_t baseTime) { baseTime_ = baseTime; }
uint64_t& getBaseTime()
{
return baseTime_;
}
void saveState(SaveState &state) const;
void loadState(SaveState const &state);
void setRamflag(unsigned char ramflag) { ramflag_ = ramflag; irReceivingPulse_ = false; }
bool isHuC3() const { return enabled_; }
void set(bool enabled) {
enabled_ = enabled;
}
unsigned char read(unsigned p, unsigned long const cc);
void write(unsigned p, unsigned data);
private:
uint64_t baseTime_;
uint64_t haltTime_;
unsigned dataTime_;
unsigned writingTime_;
unsigned char ramValue_;
unsigned char shift_;
unsigned char ramflag_;
unsigned char modeflag_;
unsigned long irBaseCycle_;
bool enabled_;
bool halted_;
bool irReceivingPulse_;
void doLatch();
void updateTime();
};
}
#endif
+168
View File
@@ -0,0 +1,168 @@
/***************************************************************************
* Copyright (C) 2007-2010 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "memptrs.h"
#include <algorithm>
#include <cstring>
namespace gambatte
{
MemPtrs::MemPtrs()
: rmem_()
, wmem_()
, romdata_()
, wramdata_()
, vrambankptr_(0)
, rsrambankptr_(0)
, wsrambankptr_(0)
,memchunk_(0)
, rambankdata_(0)
, wramdataend_(0)
, oamDmaSrc_(oam_dma_src_off)
{
}
MemPtrs::~MemPtrs()
{
delete []memchunk_;
}
void MemPtrs::reset(const unsigned rombanks, const unsigned rambanks, const unsigned wrambanks)
{
delete []memchunk_;
memchunk_ = new unsigned char[
0x4000
+ rombanks * 0x4000ul + 0x4000
+ rambanks * 0x2000ul
+ wrambanks * 0x1000ul
+ 0x4000];
romdata_[0] = romdata();
rambankdata_ = romdata_[0] + rombanks * 0x4000ul + 0x4000;
wramdata_[0] = rambankdata_ + rambanks * 0x2000ul;
wramdataend_ = wramdata_[0] + wrambanks * 0x1000ul;
std::memset(rdisabledRamw(), 0xFF, 0x2000);
oamDmaSrc_ = oam_dma_src_off;
rmem_[0x3] = rmem_[0x2] = rmem_[0x1] = rmem_[0x0] = romdata_[0];
rmem_[0xC] = wmem_[0xC] = wramdata_[0] - 0xC000;
rmem_[0xE] = wmem_[0xE] = wramdata_[0] - 0xE000;
setRombank(1);
setRambank(0, 0);
setVrambank(0);
setWrambank(1);
}
void MemPtrs::setRombank0(const unsigned bank)
{
romdata_[0] = romdata() + bank * 0x4000ul;
rmem_[0x3] = rmem_[0x2] = rmem_[0x1] = rmem_[0x0] = romdata_[0];
disconnectOamDmaAreas();
}
void MemPtrs::setRombank(const unsigned bank)
{
romdata_[1] = romdata() + bank * 0x4000ul - 0x4000;
rmem_[0x7] = rmem_[0x6] = rmem_[0x5] = rmem_[0x4] = romdata_[1];
disconnectOamDmaAreas();
}
void MemPtrs::setRambank(const unsigned flags, const unsigned rambank)
{
unsigned char *const srambankptr = (flags & RTC_EN)
? 0
: (rambankdata() != rambankdataend()
? rambankdata_ + rambank * 0x2000ul - 0xA000 : wdisabledRam() - 0xA000);
rsrambankptr_ = (flags & READ_EN) && srambankptr != wdisabledRam() - 0xA000 ? srambankptr : rdisabledRamw() - 0xA000;
wsrambankptr_ = (flags & WRITE_EN) ? srambankptr : wdisabledRam() - 0xA000;
rmem_[0xB] = rmem_[0xA] = rsrambankptr_;
wmem_[0xB] = wmem_[0xA] = wsrambankptr_;
disconnectOamDmaAreas();
}
void MemPtrs::setWrambank(const unsigned bank)
{
wramdata_[1] = wramdata_[0] + ((bank & 0x07) ? (bank & 0x07) : 1) * 0x1000;
rmem_[0xD] = wmem_[0xD] = wramdata_[1] - 0xD000;
disconnectOamDmaAreas();
}
void MemPtrs::setOamDmaSrc(const OamDmaSrc oamDmaSrc)
{
rmem_[0x3] = rmem_[0x2] = rmem_[0x1] = rmem_[0x0] = romdata_[0];
rmem_[0x7] = rmem_[0x6] = rmem_[0x5] = rmem_[0x4] = romdata_[1];
rmem_[0xB] = rmem_[0xA] = rsrambankptr_;
wmem_[0xB] = wmem_[0xA] = wsrambankptr_;
rmem_[0xC] = wmem_[0xC] = wramdata_[0] - 0xC000;
rmem_[0xD] = wmem_[0xD] = wramdata_[1] - 0xD000;
rmem_[0xE] = wmem_[0xE] = wramdata_[0] - 0xE000;
oamDmaSrc_ = oamDmaSrc;
disconnectOamDmaAreas();
}
void MemPtrs::disconnectOamDmaAreas()
{
if (isCgb(*this))
{
switch (oamDmaSrc_)
{
case oam_dma_src_rom: // fall through
case oam_dma_src_sram:
case oam_dma_src_invalid:
std::fill(rmem_, rmem_ + 8, static_cast<unsigned char *>(0));
rmem_[0xB] = rmem_[0xA] = 0;
wmem_[0xB] = wmem_[0xA] = 0;
break;
case oam_dma_src_vram:
break;
case oam_dma_src_wram:
rmem_[0xE] = rmem_[0xD] = rmem_[0xC] = 0;
wmem_[0xE] = wmem_[0xD] = wmem_[0xC] = 0;
break;
case oam_dma_src_off:
break;
}
}
else
{
switch (oamDmaSrc_)
{
case oam_dma_src_rom: // fall through
case oam_dma_src_sram:
case oam_dma_src_wram:
case oam_dma_src_invalid:
std::fill(rmem_, rmem_ + 8, static_cast<unsigned char *>(0));
rmem_[0xB] = rmem_[0xA] = 0;
wmem_[0xB] = wmem_[0xA] = 0;
rmem_[0xE] = rmem_[0xD] = rmem_[0xC] = 0;
wmem_[0xE] = wmem_[0xD] = wmem_[0xC] = 0;
break;
case oam_dma_src_vram:
break;
case oam_dma_src_off:
break;
}
}
}
}
+159
View File
@@ -0,0 +1,159 @@
/***************************************************************************
* Copyright (C) 2007-2010 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef MEMPTRS_H
#define MEMPTRS_H
namespace gambatte
{
enum OamDmaSrc { oam_dma_src_rom,
oam_dma_src_sram,
oam_dma_src_vram,
oam_dma_src_wram,
oam_dma_src_invalid,
oam_dma_src_off, };
class MemPtrs
{
public:
enum RamFlag
{
READ_EN = 1,
WRITE_EN = 2,
RTC_EN = 4
};
MemPtrs();
~MemPtrs();
void reset(unsigned rombanks, unsigned rambanks, unsigned wrambanks);
const unsigned char * rmem(unsigned area) const
{
return rmem_[area];
}
unsigned char * wmem(unsigned area) const
{
return wmem_[area];
}
unsigned char * vramdata() const
{
return rambankdata_ - 0x4000;
}
unsigned char * vramdataend() const
{
return rambankdata_;
}
unsigned char * romdata() const
{
return memchunk_ + 0x4000;
}
unsigned char * romdata(unsigned area) const
{
return romdata_[area];
}
unsigned char * romdataend() const
{
return rambankdata_ - 0x4000;
}
unsigned char * wramdata(unsigned area) const
{
return wramdata_[area];
}
unsigned char * wramdataend() const
{
return wramdataend_;
}
unsigned char * rambankdata() const
{
return rambankdata_;
}
unsigned char * rambankdataend() const
{
return wramdata_[0];
}
const unsigned char * rdisabledRam() const
{
return rdisabledRamw();
}
const unsigned char * rsrambankptr() const
{
return rsrambankptr_;
}
unsigned char * wsrambankptr() const
{
return wsrambankptr_;
}
unsigned char * vrambankptr() const
{
return vrambankptr_;
}
OamDmaSrc oamDmaSrc() const
{
return oamDmaSrc_;
}
void setRombank0(unsigned bank);
void setRombank(unsigned bank);
void setRambank(unsigned ramFlags, unsigned rambank);
void setVrambank(unsigned bank) { vrambankptr_ = vramdata() + bank * 0x2000ul - 0x8000; }
void setWrambank(unsigned bank);
void setOamDmaSrc(OamDmaSrc oamDmaSrc);
private:
unsigned char *romdata_[2];
unsigned char *wramdata_[2];
const unsigned char *rmem_[0x10];
unsigned char *wmem_[0x10];
unsigned char *vrambankptr_;
unsigned char *rsrambankptr_;
unsigned char *wsrambankptr_;
unsigned char *memchunk_;
unsigned char *rambankdata_;
unsigned char *wramdataend_;
OamDmaSrc oamDmaSrc_;
MemPtrs(const MemPtrs &);
MemPtrs & operator=(const MemPtrs &);
void disconnectOamDmaAreas();
unsigned char * rdisabledRamw() const { return wramdataend_ ; }
unsigned char * wdisabledRam() const { return wramdataend_ + 0x2000; }
};
inline bool isCgb(const MemPtrs &memptrs)
{
return memptrs.wramdataend() - memptrs.wramdata(0) == 0x8000;
}
}
#endif
+170
View File
@@ -0,0 +1,170 @@
/***************************************************************************
* Copyright (C) 2007 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "rtc.h"
#include "../savestate.h"
namespace gambatte
{
Rtc::Rtc()
: activeData_(NULL),
activeSet_(NULL),
baseTime_(0),
haltTime_(0),
index_(5),
dataDh_(0),
dataDl_(0),
dataH_(0),
dataM_(0),
dataS_(0),
enabled_(false),
lastLatchData_(false)
{
}
void Rtc::doLatch()
{
uint64_t tmp = ((dataDh_ & 0x40) ? haltTime_ : std::time(0)) - baseTime_;
while (tmp > 0x1FF * 86400)
{
baseTime_ += 0x1FF * 86400;
tmp -= 0x1FF * 86400;
dataDh_ |= 0x80;
}
dataDl_ = (tmp / 86400) & 0xFF;
dataDh_ &= 0xFE;
dataDh_ |= ((tmp / 86400) & 0x100) >> 8;
tmp %= 86400;
dataH_ = tmp / 3600;
tmp %= 3600;
dataM_ = tmp / 60;
tmp %= 60;
dataS_ = tmp;
}
void Rtc::doSwapActive()
{
if (!enabled_ || index_ > 4)
{
activeData_ = NULL;
activeSet_ = NULL;
}
else switch (index_)
{
case 0x00:
activeData_ = &dataS_;
activeSet_ = &Rtc::setS;
break;
case 0x01:
activeData_ = &dataM_;
activeSet_ = &Rtc::setM;
break;
case 0x02:
activeData_ = &dataH_;
activeSet_ = &Rtc::setH;
break;
case 0x03:
activeData_ = &dataDl_;
activeSet_ = &Rtc::setDl;
break;
case 0x04:
activeData_ = &dataDh_;
activeSet_ = &Rtc::setDh;
break;
}
}
void Rtc::saveState(SaveState &state) const
{
state.rtc.baseTime = baseTime_;
state.rtc.haltTime = haltTime_;
state.rtc.dataDh = dataDh_;
state.rtc.dataDl = dataDl_;
state.rtc.dataH = dataH_;
state.rtc.dataM = dataM_;
state.rtc.dataS = dataS_;
state.rtc.lastLatchData = lastLatchData_;
}
void Rtc::loadState(const SaveState &state)
{
baseTime_ = state.rtc.baseTime;
haltTime_ = state.rtc.haltTime;
dataDh_ = state.rtc.dataDh;
dataDl_ = state.rtc.dataDl;
dataH_ = state.rtc.dataH;
dataM_ = state.rtc.dataM;
dataS_ = state.rtc.dataS;
lastLatchData_ = state.rtc.lastLatchData;
doSwapActive();
}
void Rtc::setDh(const unsigned new_dh)
{
const uint64_t unixtime = (dataDh_ & 0x40) ? haltTime_ : std::time(0);
const uint64_t old_highdays = ((unixtime - baseTime_) / 86400) & 0x100;
baseTime_ += old_highdays * 86400;
baseTime_ -= ((new_dh & 0x1) << 8) * 86400;
if ((dataDh_ ^ new_dh) & 0x40)
{
if (new_dh & 0x40)
haltTime_ = std::time(0);
else
baseTime_ += std::time(0) - haltTime_;
}
}
void Rtc::setDl(const unsigned new_lowdays)
{
const uint64_t unixtime = (dataDh_ & 0x40) ? haltTime_ : std::time(0);
const uint64_t old_lowdays = ((unixtime - baseTime_) / 86400) & 0xFF;
baseTime_ += old_lowdays * 86400;
baseTime_ -= new_lowdays * 86400;
}
void Rtc::setH(const unsigned new_hours)
{
const uint64_t unixtime = (dataDh_ & 0x40) ? haltTime_ : std::time(0);
const uint64_t old_hours = ((unixtime - baseTime_) / 3600) % 24;
baseTime_ += old_hours * 3600;
baseTime_ -= new_hours * 3600;
}
void Rtc::setM(const unsigned new_minutes)
{
const uint64_t unixtime = (dataDh_ & 0x40) ? haltTime_ : std::time(0);
const uint64_t old_minutes = ((unixtime - baseTime_) / 60) % 60;
baseTime_ += old_minutes * 60;
baseTime_ -= new_minutes * 60;
}
void Rtc::setS(const unsigned new_seconds)
{
const uint64_t unixtime = (dataDh_ & 0x40) ? haltTime_ : std::time(0);
baseTime_ += (unixtime - baseTime_) % 60;
baseTime_ -= new_seconds;
}
}
+104
View File
@@ -0,0 +1,104 @@
/***************************************************************************
* Copyright (C) 2007 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef RTC_H
#define RTC_H
#include <ctime>
#include <stdint.h>
namespace gambatte
{
struct SaveState;
class Rtc
{
public:
Rtc();
const unsigned char* getActive() const
{
return activeData_;
}
uint64_t& getBaseTime()
{
return baseTime_;
}
void setBaseTime(const uint64_t baseTime)
{
baseTime_ = baseTime;
}
void latch(const unsigned data)
{
if (!lastLatchData_ && data == 1)
doLatch();
lastLatchData_ = data;
}
void saveState(SaveState &state) const;
void loadState(const SaveState &state);
void set(const bool enabled, unsigned bank)
{
bank &= 0xF;
bank -= 8;
this->enabled_ = enabled;
this->index_ = bank;
doSwapActive();
}
void write(const unsigned data)
{
(this->*activeSet_)(data);
*activeData_ = data;
}
private:
unsigned char *activeData_;
void (Rtc::*activeSet_)(unsigned);
uint64_t baseTime_;
uint64_t haltTime_;
unsigned char index_;
unsigned char dataDh_;
unsigned char dataDl_;
unsigned char dataH_;
unsigned char dataM_;
unsigned char dataS_;
bool enabled_;
bool lastLatchData_;
void doLatch();
void doSwapActive();
void setDh(unsigned new_dh);
void setDl(unsigned new_lowdays);
void setH(unsigned new_hours);
void setM(unsigned new_minutes);
void setS(unsigned new_seconds);
};
}
#endif
+157
View File
@@ -0,0 +1,157 @@
/***************************************************************************
* Copyright (C) 2009 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef MINKEEPER_H
#define MINKEEPER_H
#include <algorithm>
namespace MinKeeperUtil
{
template<int n> struct CeiledLog2 { enum { RESULT = 1 + CeiledLog2<(n + 1) / 2>::RESULT }; };
template<> struct CeiledLog2<1> { enum { RESULT = 0 }; };
template<int v, int n> struct RoundedDiv2n { enum { RESULT = RoundedDiv2n<(v + 1) / 2, n - 1>::RESULT }; };
template<int v> struct RoundedDiv2n<v,1> { enum { RESULT = v }; };
template<template<int> class T, int n> struct Sum { enum { RESULT = T<n-1>::RESULT + Sum<T, n-1>::RESULT }; };
template<template<int> class T> struct Sum<T,0> { enum { RESULT = 0 }; };
}
// Keeps track of minimum value identified by id as values change.
// Higher ids prioritized (as min value) if values are equal. Can easily be reversed by swapping < for <=.
// Higher ids can be faster to change when the number of ids isn't a power of 2.
// Thus the ones that change more frequently should have higher ids if priority allows it.
template<int ids>
class MinKeeper
{
enum { LEVELS = MinKeeperUtil::CeiledLog2<ids>::RESULT };
template<int l> struct Num { enum { RESULT = MinKeeperUtil::RoundedDiv2n<ids, LEVELS + 1 - l>::RESULT }; };
template<int l> struct Sum { enum { RESULT = MinKeeperUtil::Sum<Num, l>::RESULT }; };
template<int id, int level>
struct UpdateValue {
enum { P = Sum<level-1>::RESULT + id };
enum { C0 = Sum<level>::RESULT + id * 2 };
static void updateValue(MinKeeper<ids> *const s)
{
// GCC 4.3 generates better code with the ternary operator on i386.
s->a[P] = (id * 2 + 1 == Num<level>::RESULT || s->values[s->a[C0]] < s->values[s->a[C0 + 1]]) ? s->a[C0] : s->a[C0 + 1];
UpdateValue<id / 2, level - 1>::updateValue(s);
}
};
template<int id>
struct UpdateValue<id,0>
{
static void updateValue(MinKeeper<ids> *const s)
{
s->minValue_ = s->values[s->a[0]];
}
};
template<int id, int dummy> struct FillLut {
static void fillLut(MinKeeper<ids> *const s)
{
s->updateValueLut[id] = updateValue<id>;
FillLut<id-1,dummy>::fillLut(s);
}
};
template<int dummy> struct FillLut<-1,dummy> {
static void fillLut(MinKeeper<ids> *const)
{
}
};
unsigned long values[ids];
unsigned long minValue_;
void (*updateValueLut[Num<LEVELS-1>::RESULT])(MinKeeper<ids>*const);
int a[Sum<LEVELS>::RESULT];
template<int id> static void updateValue(MinKeeper<ids> *const s);
public:
MinKeeper(unsigned long initValue = 0xFFFFFFFF);
int min() const { return a[0]; }
unsigned long minValue() const { return minValue_; }
template<int id>
void setValue(const unsigned long cnt)
{
values[id] = cnt;
updateValue<id / 2>(this);
}
void setValue(const int id, const unsigned long cnt)
{
values[id] = cnt;
updateValueLut[id >> 1](this);
}
unsigned long value(const int id) const { return values[id]; }
};
template<int ids>
MinKeeper<ids>::MinKeeper(const unsigned long initValue)
{
std::fill(values, values + ids, initValue);
for (int i = 0; i < Num<LEVELS-1>::RESULT; ++i)
{
a[Sum<LEVELS-1>::RESULT + i] = (i * 2 + 1 == ids || values[i * 2] < values[i * 2 + 1]) ? i * 2 : i * 2 + 1;
}
int n = Num<LEVELS-1>::RESULT;
int off = Sum<LEVELS-1>::RESULT;
while (off)
{
const int pn = (n + 1) >> 1;
const int poff = off - pn;
for (int i = 0; i < pn; ++i)
{
a[poff + i] = (i * 2 + 1 == n ||
values[a[off + i * 2]] < values[a[off + i * 2 + 1]]) ?
a[off + i * 2] : a[off + i * 2 + 1];
}
off = poff;
n = pn;
}
minValue_ = values[a[0]];
FillLut<Num<LEVELS-1>::RESULT-1,0>::fillLut(this);
}
template<int ids>
template<int id>
void MinKeeper<ids>::updateValue(MinKeeper<ids> *const s)
{
s->a[Sum<LEVELS-1>::RESULT + id] = (id * 2 + 1 == ids || s->values[id * 2] < s->values[id * 2 + 1]) ? id * 2 : id * 2 + 1;
UpdateValue<id / 2, LEVELS-1>::updateValue(s);
}
#endif
+229
View File
@@ -0,0 +1,229 @@
//
// Copyright (C) 2008 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef SAVESTATE_H
#define SAVESTATE_H
#include <stddef.h>
#include <cstddef>
namespace gambatte {
class SaverList;
struct SaveState {
template<typename T>
class Ptr {
public:
Ptr() : ptr(0), size_(0) {}
T* get() const { return ptr; }
std::size_t size() const { return size_; }
void set(T *p, std::size_t size) { ptr = p; size_ = size; }
friend class SaverList;
friend void setInitState(SaveState &, bool, bool, bool);
private:
T *ptr;
std::size_t size_;
};
struct CPU {
unsigned long cycleCounter;
unsigned short pc;
unsigned short sp;
unsigned char a;
unsigned char b;
unsigned char c;
unsigned char d;
unsigned char e;
unsigned char f;
unsigned char h;
unsigned char l;
bool skip;
} cpu;
struct Mem {
Ptr<unsigned char> vram;
Ptr<unsigned char> sram;
Ptr<unsigned char> wram;
Ptr<unsigned char> ioamhram;
unsigned long divLastUpdate;
unsigned long timaLastUpdate;
unsigned long tmatime;
unsigned long nextSerialtime;
unsigned long lastOamDmaUpdate;
unsigned long minIntTime;
unsigned long unhaltTime;
unsigned short rombank;
unsigned short dmaSource;
unsigned short dmaDestination;
unsigned char rambank;
unsigned char oamDmaPos;
unsigned char HuC3RAMflag;
unsigned char sachenOuterMask;
/* Sachen MMC1: count of CPU reads of 0x0100..0x01FF observed
* while the cartridge is in its boot-time locked state.
* Preserved so a savestate captured mid-bootstrap restores
* the cart to exactly the right point in its 48-reads-to-
* unlock sequence; without it, a mid-boot save would resume
* with count=0 and unlock 48 reads too late, breaking the
* bootstrap's verify pass. Unused (and zero) for every
* non-Sachen cartridge. */
unsigned char sachenLockCount;
#ifdef HAVE_NETWORK
unsigned char serialize_value;
bool serialize_is_fastcgb;
#endif
bool IME;
bool halted;
bool enableRam;
bool rambankMode;
bool hdmaTransfer;
} mem;
struct PPU {
Ptr<unsigned char> bgpData;
Ptr<unsigned char> objpData;
//SpriteMapper::OamReader
Ptr<unsigned char> oamReaderBuf;
Ptr<bool> oamReaderSzbuf;
unsigned char dmgPalette[8 * 3];
unsigned long videoCycles;
unsigned long enableDisplayM0Time;
unsigned short lastM0Time;
unsigned short nextM0Irq;
unsigned short tileword;
unsigned short ntileword;
unsigned char spAttribList[10];
unsigned char spByte0List[10];
unsigned char spByte1List[10];
unsigned char winYPos;
unsigned char xpos;
unsigned char endx;
unsigned char reg0;
unsigned char reg1;
unsigned char attrib;
unsigned char nattrib;
unsigned char state;
unsigned char nextSprite;
unsigned char currentSprite;
unsigned char lyc;
unsigned char m0lyc;
unsigned char oldWy;
unsigned char winDrawState;
unsigned char wscx;
bool weMaster;
bool pendingLcdstatIrq;
} ppu;
struct SPU {
struct Duty {
unsigned long nextPosUpdate;
unsigned char nr3;
unsigned char pos;
bool high;
};
struct Env {
unsigned long counter;
unsigned char volume;
};
struct LCounter {
unsigned long counter;
unsigned short lengthCounter;
};
struct {
struct {
unsigned long counter;
unsigned short shadow;
unsigned char nr0;
bool negging;
} sweep;
Duty duty;
Env env;
LCounter lcounter;
unsigned char nr4;
bool master;
} ch1;
struct {
Duty duty;
Env env;
LCounter lcounter;
unsigned char nr4;
bool master;
} ch2;
struct {
Ptr<unsigned char> waveRam;
LCounter lcounter;
unsigned long waveCounter;
unsigned long lastReadTime;
unsigned char nr3;
unsigned char nr4;
unsigned char wavePos;
unsigned char sampleBuf;
bool master;
} ch3;
struct {
struct {
unsigned long counter;
unsigned short reg;
} lfsr;
Env env;
LCounter lcounter;
unsigned char nr4;
bool master;
} ch4;
unsigned long cycleCounter;
} spu;
struct RTC {
unsigned long baseTime;
unsigned long haltTime;
unsigned char dataDh;
unsigned char dataDl;
unsigned char dataH;
unsigned char dataM;
unsigned char dataS;
bool lastLatchData;
} rtc;
struct HuC3 {
unsigned long baseTime;
unsigned long haltTime;
unsigned long dataTime;
unsigned long writingTime;
unsigned long irBaseCycle;
bool halted;
unsigned char shift;
unsigned char ramValue;
unsigned char modeflag;
bool irReceivingPulse;
} huc3;
};
}
#endif
+35
View File
@@ -0,0 +1,35 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef SERIAL_IO_H
#define SERIAL_IO_H
namespace gambatte {
class SerialIO
{
public:
virtual ~SerialIO() {};
virtual bool check(unsigned char out, unsigned char& in, bool& fastCgb) = 0;
virtual unsigned char send(unsigned char data, bool fastCgb) = 0;
};
}
#endif
+206
View File
@@ -0,0 +1,206 @@
/***************************************************************************
* Copyright (C) 2007 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "sound.h"
#include "savestate.h"
#include <cstring>
#include <algorithm>
/*
Frame Sequencer
Step Length Ctr Vol Env Sweep
- - - - - - - - - - - - - - - - - - - -
0 Clock - Clock
S 1 - Clock -
2 Clock - -
3 - - -
4 Clock - Clock
5 - - -
6 Clock - -
7 - - -
- - - - - - - - - - - - - - - - - - - -
Rate 256 Hz 64 Hz 128 Hz
S) start step on sound power on.
*/
namespace gambatte
{
PSG::PSG()
: buffer_(0)
, bufferSize_(0)
, bufferPos_(0)
, lastUpdate_(0)
, soVol_(0)
, rsum_(0x8000) // initialize to 0x8000 to prevent borrows from high word, xor away later
, enabled_(false)
{
}
void PSG::init(const bool cgb)
{
ch1_.init(cgb);
ch3_.init(cgb);
}
void PSG::reset()
{
ch1_.reset();
ch2_.reset();
ch3_.reset();
ch4_.reset();
}
void PSG::setStatePtrs(SaveState &state)
{
ch3_.setStatePtrs(state);
}
void PSG::saveState(SaveState &state)
{
ch1_.saveState(state);
ch2_.saveState(state);
ch3_.saveState(state);
ch4_.saveState(state);
}
void PSG::loadState(const SaveState &state)
{
ch1_.loadState(state);
ch2_.loadState(state);
ch3_.loadState(state);
ch4_.loadState(state);
lastUpdate_ = state.cpu.cycleCounter;
setSoVolume(state.mem.ioamhram.get()[0x124]);
mapSo(state.mem.ioamhram.get()[0x125]);
enabled_ = state.mem.ioamhram.get()[0x126] >> 7 & 1;
}
void PSG::accumulateChannels(const unsigned long cycles)
{
uint_least32_t *const buf = buffer_ + bufferPos_;
std::memset(buf, 0, cycles * sizeof(uint_least32_t));
ch1_.update(buf, soVol_, cycles);
ch2_.update(buf, soVol_, cycles);
ch3_.update(buf, soVol_, cycles);
ch4_.update(buf, soVol_, cycles);
}
void PSG::generateSamples(unsigned long const cycleCounter, bool const doubleSpeed)
{
unsigned long cycles = (cycleCounter - lastUpdate_) >> (1 + doubleSpeed);
if (cycles + bufferPos_ > bufferSize_)
cycles = (bufferSize_ > bufferPos_) ? (bufferSize_ - bufferPos_) : 0;
lastUpdate_ += cycles << (1 + doubleSpeed);
if (cycles)
accumulateChannels(cycles);
bufferPos_ += cycles;
}
void PSG::resetCounter(unsigned long newCc, unsigned long oldCc, bool doubleSpeed)
{
generateSamples(oldCc, doubleSpeed);
lastUpdate_ = newCc - (oldCc - lastUpdate_);
}
size_t PSG::fillBuffer()
{
uint_least32_t sum = rsum_;
uint_least32_t *b = buffer_;
unsigned n = bufferPos_;
if (unsigned n2 = n >> 3)
{
n -= n2 << 3;
do
{
sum += b[0];
b[0] = sum ^ 0x8000;
sum += b[1];
b[1] = sum ^ 0x8000;
sum += b[2];
b[2] = sum ^ 0x8000;
sum += b[3];
b[3] = sum ^ 0x8000;
sum += b[4];
b[4] = sum ^ 0x8000;
sum += b[5];
b[5] = sum ^ 0x8000;
sum += b[6];
b[6] = sum ^ 0x8000;
sum += b[7];
b[7] = sum ^ 0x8000;
b += 8;
} while (--n2);
}
while (n--)
{
sum += *b;
/* xor away the initial rsum value of 0x8000 (which prevents
* borrows from the high word) from the low word */
*b++ = sum ^ 0x8000;
}
rsum_ = sum;
return bufferPos_;
}
#ifdef WORDS_BIGENDIAN
static const unsigned long so1Mul = 0x00000001;
static const unsigned long so2Mul = 0x00010000;
#else
static const unsigned long so1Mul = 0x00010000;
static const unsigned long so2Mul = 0x00000001;
#endif
void PSG::setSoVolume(const unsigned nr50)
{
soVol_ = (((nr50 & 0x7) + 1) * so1Mul
+ ((nr50 >> 4 & 0x7) + 1) * so2Mul) * 64;
}
void PSG::mapSo(const unsigned nr51)
{
const unsigned long tmp = nr51 * so1Mul + (nr51 >> 4) * so2Mul;
ch1_.setSo((tmp & 0x00010001) * 0xFFFF);
ch2_.setSo((tmp >> 1 & 0x00010001) * 0xFFFF);
ch3_.setSo((tmp >> 2 & 0x00010001) * 0xFFFF);
ch4_.setSo((tmp >> 3 & 0x00010001) * 0xFFFF);
}
unsigned PSG::getStatus() const
{
return ch1_.isActive()
| ch2_.isActive() << 1
| ch3_.isActive() << 2
| ch4_.isActive() << 3;
}
}
+95
View File
@@ -0,0 +1,95 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef SOUND_H
#define SOUND_H
#include <stddef.h>
#include <cstddef>
#include "sound/channel1.h"
#include "sound/channel2.h"
#include "sound/channel3.h"
#include "sound/channel4.h"
namespace gambatte {
class PSG {
public:
PSG();
void init(bool cgb);
void reset();
void setStatePtrs(SaveState &state);
void saveState(SaveState &state);
void loadState(SaveState const &state);
void generateSamples(unsigned long cycleCounter, bool doubleSpeed);
void resetCounter(unsigned long newCc, unsigned long oldCc, bool doubleSpeed);
std::size_t fillBuffer();
void setBuffer(uint_least32_t *buf, std::size_t size) { buffer_ = buf; bufferSize_ = size; bufferPos_ = 0; }
bool isEnabled() const { return enabled_; }
void setEnabled(bool value) { enabled_ = value; }
void setNr10(unsigned data) { ch1_.setNr0(data); }
void setNr11(unsigned data) { ch1_.setNr1(data); }
void setNr12(unsigned data) { ch1_.setNr2(data); }
void setNr13(unsigned data) { ch1_.setNr3(data); }
void setNr14(unsigned data) { ch1_.setNr4(data); }
void setNr21(unsigned data) { ch2_.setNr1(data); }
void setNr22(unsigned data) { ch2_.setNr2(data); }
void setNr23(unsigned data) { ch2_.setNr3(data); }
void setNr24(unsigned data) { ch2_.setNr4(data); }
void setNr30(unsigned data) { ch3_.setNr0(data); }
void setNr31(unsigned data) { ch3_.setNr1(data); }
void setNr32(unsigned data) { ch3_.setNr2(data); }
void setNr33(unsigned data) { ch3_.setNr3(data); }
void setNr34(unsigned data) { ch3_.setNr4(data); }
unsigned waveRamRead(unsigned index) const { return ch3_.waveRamRead(index); }
void waveRamWrite(unsigned index, unsigned data) { ch3_.waveRamWrite(index, data); }
void setNr41(unsigned data) { ch4_.setNr1(data); }
void setNr42(unsigned data) { ch4_.setNr2(data); }
void setNr43(unsigned data) { ch4_.setNr3(data); }
void setNr44(unsigned data) { ch4_.setNr4(data); }
void setSoVolume(unsigned nr50);
void mapSo(unsigned nr51);
unsigned getStatus() const;
private:
Channel1 ch1_;
Channel2 ch2_;
Channel3 ch3_;
Channel4 ch4_;
uint_least32_t *buffer_;
std::size_t bufferSize_;
std::size_t bufferPos_;
unsigned long lastUpdate_;
unsigned long soVol_;
uint_least32_t rsum_;
bool enabled_;
void accumulateChannels(unsigned long cycles);
};
}
#endif
+262
View File
@@ -0,0 +1,262 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "channel1.h"
#include "../savestate.h"
#include <algorithm>
namespace gambatte {
Channel1::SweepUnit::SweepUnit(MasterDisabler &disabler, DutyUnit &dutyUnit)
: disableMaster_(disabler)
, dutyUnit_(dutyUnit)
, shadow_(0)
, nr0_(0)
, negging_(false)
, cgb_(false)
{
}
unsigned Channel1::SweepUnit::calcFreq() {
unsigned freq = shadow_ >> (nr0_ & 0x07);
if (nr0_ & 0x08) {
freq = shadow_ - freq;
negging_ = true;
} else
freq = shadow_ + freq;
if (freq & 2048)
disableMaster_();
return freq;
}
void Channel1::SweepUnit::event() {
unsigned long const period = nr0_ >> 4 & 0x07;
if (period) {
unsigned const freq = calcFreq();
if (!(freq & 2048) && (nr0_ & 0x07)) {
shadow_ = freq;
dutyUnit_.setFreq(freq, counter_);
calcFreq();
}
counter_ += period << 14;
} else
counter_ += 8ul << 14;
}
void Channel1::SweepUnit::nr0Change(unsigned newNr0) {
if (negging_ && !(newNr0 & 0x08))
disableMaster_();
nr0_ = newNr0;
}
void Channel1::SweepUnit::nr4Init(unsigned long const cc) {
negging_ = false;
shadow_ = dutyUnit_.freq();
unsigned const period = nr0_ >> 4 & 0x07;
unsigned const shift = nr0_ & 0x07;
if (period | shift)
counter_ = ((((cc + 2 + cgb_ * 2) >> 14) + (period ? period : 8)) << 14) + 2;
else
counter_ = counter_disabled;
if (shift)
calcFreq();
}
void Channel1::SweepUnit::reset() {
counter_ = counter_disabled;
}
void Channel1::SweepUnit::saveState(SaveState &state) const {
state.spu.ch1.sweep.counter = counter_;
state.spu.ch1.sweep.shadow = shadow_;
state.spu.ch1.sweep.nr0 = nr0_;
state.spu.ch1.sweep.negging = negging_;
}
void Channel1::SweepUnit::loadState(SaveState const &state) {
counter_ = std::max(state.spu.ch1.sweep.counter, state.spu.cycleCounter);
shadow_ = state.spu.ch1.sweep.shadow;
nr0_ = state.spu.ch1.sweep.nr0;
negging_ = state.spu.ch1.sweep.negging;
}
Channel1::Channel1()
: staticOutputTest_(*this, dutyUnit_)
, disableMaster_(master_, dutyUnit_)
, lengthCounter_(disableMaster_, 0x3F)
, envelopeUnit_(staticOutputTest_)
, sweepUnit_(disableMaster_, dutyUnit_)
, nextEventUnit_(0)
, cycleCounter_(0)
, soMask_(0)
, prevOut_(0)
, nr4_(0)
, master_(false)
{
setEvent();
}
void Channel1::setEvent() {
nextEventUnit_ = &sweepUnit_;
if (envelopeUnit_.counter() < nextEventUnit_->counter())
nextEventUnit_ = &envelopeUnit_;
if (lengthCounter_.counter() < nextEventUnit_->counter())
nextEventUnit_ = &lengthCounter_;
}
void Channel1::setNr0(unsigned data) {
sweepUnit_.nr0Change(data);
setEvent();
}
void Channel1::setNr1(unsigned data) {
lengthCounter_.nr1Change(data, nr4_, cycleCounter_);
dutyUnit_.nr1Change(data, cycleCounter_);
setEvent();
}
void Channel1::setNr2(unsigned data) {
if (envelopeUnit_.nr2Change(data))
disableMaster_();
else
staticOutputTest_(cycleCounter_);
setEvent();
}
void Channel1::setNr3(unsigned data) {
dutyUnit_.nr3Change(data, cycleCounter_);
setEvent();
}
void Channel1::setNr4(unsigned const data) {
lengthCounter_.nr4Change(nr4_, data, cycleCounter_);
nr4_ = data;
dutyUnit_.nr4Change(data, cycleCounter_);
if (data & 0x80) { // init-bit
nr4_ &= 0x7F;
master_ = !envelopeUnit_.nr4Init(cycleCounter_);
sweepUnit_.nr4Init(cycleCounter_);
staticOutputTest_(cycleCounter_);
}
setEvent();
}
void Channel1::setSo(unsigned long soMask) {
soMask_ = soMask;
staticOutputTest_(cycleCounter_);
setEvent();
}
void Channel1::reset() {
// cycleCounter >> 12 & 7 represents the frame sequencer position.
cycleCounter_ &= 0xFFF;
cycleCounter_ += ~(cycleCounter_ + 2) << 1 & 0x1000;
dutyUnit_.reset();
envelopeUnit_.reset();
sweepUnit_.reset();
setEvent();
}
void Channel1::init(bool cgb) {
sweepUnit_.init(cgb);
}
void Channel1::saveState(SaveState &state) {
sweepUnit_.saveState(state);
dutyUnit_.saveState(state.spu.ch1.duty, cycleCounter_);
envelopeUnit_.saveState(state.spu.ch1.env);
lengthCounter_.saveState(state.spu.ch1.lcounter);
state.spu.cycleCounter = cycleCounter_;
state.spu.ch1.nr4 = nr4_;
state.spu.ch1.master = master_;
}
void Channel1::loadState(SaveState const &state) {
sweepUnit_.loadState(state);
dutyUnit_.loadState(state.spu.ch1.duty, state.mem.ioamhram.get()[0x111],
state.spu.ch1.nr4, state.spu.cycleCounter);
envelopeUnit_.loadState(state.spu.ch1.env, state.mem.ioamhram.get()[0x112],
state.spu.cycleCounter);
lengthCounter_.loadState(state.spu.ch1.lcounter, state.spu.cycleCounter);
cycleCounter_ = state.spu.cycleCounter;
nr4_ = state.spu.ch1.nr4;
master_ = state.spu.ch1.master;
}
void Channel1::update(uint_least32_t *buf, unsigned long const soBaseVol, unsigned long cycles) {
unsigned long const outBase = envelopeUnit_.dacIsOn() ? soBaseVol & soMask_ : 0;
unsigned long const outLow = outBase * (0 - 15ul);
unsigned long const endCycles = cycleCounter_ + cycles;
for (;;) {
unsigned long const outHigh = master_
? outBase * (envelopeUnit_.getVolume() * 2 - 15ul)
: outLow;
unsigned long const nextMajorEvent = std::min(nextEventUnit_->counter(), endCycles);
unsigned long out = dutyUnit_.isHighState() ? outHigh : outLow;
while (dutyUnit_.counter() <= nextMajorEvent) {
*buf = out - prevOut_;
prevOut_ = out;
buf += dutyUnit_.counter() - cycleCounter_;
cycleCounter_ = dutyUnit_.counter();
dutyUnit_.event();
out = dutyUnit_.isHighState() ? outHigh : outLow;
}
if (cycleCounter_ < nextMajorEvent) {
*buf = out - prevOut_;
prevOut_ = out;
buf += nextMajorEvent - cycleCounter_;
cycleCounter_ = nextMajorEvent;
}
if (nextEventUnit_->counter() == nextMajorEvent) {
nextEventUnit_->event();
setEvent();
} else
break;
}
if (cycleCounter_ >= SoundUnit::counter_max) {
dutyUnit_.resetCounters(cycleCounter_);
lengthCounter_.resetCounters(cycleCounter_);
envelopeUnit_.resetCounters(cycleCounter_);
sweepUnit_.resetCounters(cycleCounter_);
cycleCounter_ -= SoundUnit::counter_max;
}
}
}
+92
View File
@@ -0,0 +1,92 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef SOUND_CHANNEL1_H
#define SOUND_CHANNEL1_H
#include "duty_unit.h"
#include "envelope_unit.h"
#include "gbint.h"
#include "length_counter.h"
#include "master_disabler.h"
#include "static_output_tester.h"
namespace gambatte {
struct SaveState;
class Channel1 {
public:
Channel1();
void setNr0(unsigned data);
void setNr1(unsigned data);
void setNr2(unsigned data);
void setNr3(unsigned data);
void setNr4(unsigned data);
void setSo(unsigned long soMask);
bool isActive() const { return master_; }
void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles);
void reset();
void init(bool cgb);
void saveState(SaveState &state);
void loadState(SaveState const &state);
private:
class SweepUnit : public SoundUnit {
public:
SweepUnit(MasterDisabler &disabler, DutyUnit &dutyUnit);
virtual void event();
void nr0Change(unsigned newNr0);
void nr4Init(unsigned long cycleCounter);
void reset();
void init(bool cgb) { cgb_ = cgb; }
void saveState(SaveState &state) const;
void loadState(SaveState const &state);
private:
MasterDisabler &disableMaster_;
DutyUnit &dutyUnit_;
unsigned short shadow_;
unsigned char nr0_;
bool negging_;
bool cgb_;
unsigned calcFreq();
};
friend class StaticOutputTester<Channel1, DutyUnit>;
StaticOutputTester<Channel1, DutyUnit> staticOutputTest_;
DutyMasterDisabler disableMaster_;
LengthCounter lengthCounter_;
DutyUnit dutyUnit_;
EnvelopeUnit envelopeUnit_;
SweepUnit sweepUnit_;
SoundUnit *nextEventUnit_;
unsigned long cycleCounter_;
unsigned long soMask_;
unsigned long prevOut_;
unsigned char nr4_;
bool master_;
void setEvent();
};
}
#endif
+160
View File
@@ -0,0 +1,160 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "channel2.h"
#include "../savestate.h"
#include <algorithm>
namespace gambatte {
Channel2::Channel2()
: staticOutputTest_(*this, dutyUnit_)
, disableMaster_(master_, dutyUnit_)
, lengthCounter_(disableMaster_, 0x3F)
, envelopeUnit_(staticOutputTest_)
, cycleCounter_(0)
, soMask_(0)
, prevOut_(0)
, nr4_(0)
, master_(false)
{
setEvent();
}
void Channel2::setEvent() {
nextEventUnit = &envelopeUnit_;
if (lengthCounter_.counter() < nextEventUnit->counter())
nextEventUnit = &lengthCounter_;
}
void Channel2::setNr1(unsigned data) {
lengthCounter_.nr1Change(data, nr4_, cycleCounter_);
dutyUnit_.nr1Change(data, cycleCounter_);
setEvent();
}
void Channel2::setNr2(unsigned data) {
if (envelopeUnit_.nr2Change(data))
disableMaster_();
else
staticOutputTest_(cycleCounter_);
setEvent();
}
void Channel2::setNr3(unsigned data) {
dutyUnit_.nr3Change(data, cycleCounter_);
setEvent();
}
void Channel2::setNr4(unsigned const data) {
lengthCounter_.nr4Change(nr4_, data, cycleCounter_);
nr4_ = data;
if (data & 0x80) { // init-bit
nr4_ &= 0x7F;
master_ = !envelopeUnit_.nr4Init(cycleCounter_);
staticOutputTest_(cycleCounter_);
}
dutyUnit_.nr4Change(data, cycleCounter_);
setEvent();
}
void Channel2::setSo(unsigned long soMask) {
soMask_ = soMask;
staticOutputTest_(cycleCounter_);
setEvent();
}
void Channel2::reset() {
// cycleCounter >> 12 & 7 represents the frame sequencer position.
cycleCounter_ &= 0xFFF;
cycleCounter_ += ~(cycleCounter_ + 2) << 1 & 0x1000;
dutyUnit_.reset();
envelopeUnit_.reset();
setEvent();
}
void Channel2::saveState(SaveState &state) {
dutyUnit_.saveState(state.spu.ch2.duty, cycleCounter_);
envelopeUnit_.saveState(state.spu.ch2.env);
lengthCounter_.saveState(state.spu.ch2.lcounter);
state.spu.ch2.nr4 = nr4_;
state.spu.ch2.master = master_;
}
void Channel2::loadState(SaveState const &state) {
dutyUnit_.loadState(state.spu.ch2.duty, state.mem.ioamhram.get()[0x116],
state.spu.ch2.nr4, state.spu.cycleCounter);
envelopeUnit_.loadState(state.spu.ch2.env, state.mem.ioamhram.get()[0x117],
state.spu.cycleCounter);
lengthCounter_.loadState(state.spu.ch2.lcounter, state.spu.cycleCounter);
cycleCounter_ = state.spu.cycleCounter;
nr4_ = state.spu.ch2.nr4;
master_ = state.spu.ch2.master;
}
void Channel2::update(uint_least32_t *buf, unsigned long const soBaseVol, unsigned long cycles) {
unsigned long const outBase = envelopeUnit_.dacIsOn() ? soBaseVol & soMask_ : 0;
unsigned long const outLow = outBase * (0 - 15ul);
unsigned long const endCycles = cycleCounter_ + cycles;
for (;;) {
unsigned long const outHigh = master_
? outBase * (envelopeUnit_.getVolume() * 2 - 15ul)
: outLow;
unsigned long const nextMajorEvent = std::min(nextEventUnit->counter(), endCycles);
unsigned long out = dutyUnit_.isHighState() ? outHigh : outLow;
while (dutyUnit_.counter() <= nextMajorEvent) {
*buf += out - prevOut_;
prevOut_ = out;
buf += dutyUnit_.counter() - cycleCounter_;
cycleCounter_ = dutyUnit_.counter();
dutyUnit_.event();
out = dutyUnit_.isHighState() ? outHigh : outLow;
}
if (cycleCounter_ < nextMajorEvent) {
*buf += out - prevOut_;
prevOut_ = out;
buf += nextMajorEvent - cycleCounter_;
cycleCounter_ = nextMajorEvent;
}
if (nextEventUnit->counter() == nextMajorEvent) {
nextEventUnit->event();
setEvent();
} else
break;
}
if (cycleCounter_ >= SoundUnit::counter_max) {
dutyUnit_.resetCounters(cycleCounter_);
lengthCounter_.resetCounters(cycleCounter_);
envelopeUnit_.resetCounters(cycleCounter_);
cycleCounter_ -= SoundUnit::counter_max;
}
}
}
+66
View File
@@ -0,0 +1,66 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef SOUND_CHANNEL2_H
#define SOUND_CHANNEL2_H
#include "duty_unit.h"
#include "envelope_unit.h"
#include "gbint.h"
#include "length_counter.h"
#include "static_output_tester.h"
namespace gambatte {
struct SaveState;
class Channel2 {
public:
Channel2();
void setNr1(unsigned data);
void setNr2(unsigned data);
void setNr3(unsigned data);
void setNr4(unsigned data);
void setSo(unsigned long soMask);
bool isActive() const { return master_; }
void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles);
void reset();
void saveState(SaveState &state);
void loadState(SaveState const &state);
private:
friend class StaticOutputTester<Channel2, DutyUnit>;
StaticOutputTester<Channel2, DutyUnit> staticOutputTest_;
DutyMasterDisabler disableMaster_;
LengthCounter lengthCounter_;
DutyUnit dutyUnit_;
EnvelopeUnit envelopeUnit_;
SoundUnit *nextEventUnit;
unsigned long cycleCounter_;
unsigned long soMask_;
unsigned long prevOut_;
unsigned char nr4_;
bool master_;
void setEvent();
};
}
#endif
+211
View File
@@ -0,0 +1,211 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "channel3.h"
#include "../savestate.h"
#include <algorithm>
#include <cstring>
static inline unsigned toPeriod(unsigned nr3, unsigned nr4) {
return 0x800 - ((nr4 << 8 & 0x700) | nr3);
}
namespace gambatte {
Channel3::Channel3()
: disableMaster_(master_, waveCounter_)
, lengthCounter_(disableMaster_, 0xFF)
, cycleCounter_(0)
, soMask_(0)
, prevOut_(0)
, waveCounter_(SoundUnit::counter_disabled)
, lastReadTime_(0)
, nr0_(0)
, nr3_(0)
, nr4_(0)
, wavePos_(0)
, rshift_(4)
, sampleBuf_(0)
, master_(false)
, cgb_(false)
{
}
void Channel3::setNr0(unsigned data) {
nr0_ = data & 0x80;
if (!(data & 0x80))
disableMaster_();
}
void Channel3::setNr2(unsigned data) {
rshift_ = (data >> 5 & 3U) - 1;
if (rshift_ > 3)
rshift_ = 4;
}
void Channel3::setNr4(unsigned const data) {
lengthCounter_.nr4Change(nr4_, data, cycleCounter_);
nr4_ = data & 0x7F;
if (data & nr0_/* & 0x80*/) {
if (!cgb_ && waveCounter_ == cycleCounter_ + 1) {
unsigned const pos = ((wavePos_ + 1) & 0x1F) >> 1;
if (pos < 4)
waveRam_[0] = waveRam_[pos];
else
std::memcpy(waveRam_, waveRam_ + (pos & ~3), 4);
}
master_ = true;
wavePos_ = 0;
lastReadTime_ = waveCounter_ = cycleCounter_ + toPeriod(nr3_, data) + 3;
}
}
void Channel3::setSo(unsigned long soMask) {
soMask_ = soMask;
}
void Channel3::reset() {
// cycleCounter >> 12 & 7 represents the frame sequencer position.
cycleCounter_ &= 0xFFF;
cycleCounter_ += ~(cycleCounter_ + 2) << 1 & 0x1000;
sampleBuf_ = 0;
}
void Channel3::init(bool cgb) {
cgb_ = cgb;
}
void Channel3::setStatePtrs(SaveState &state) {
state.spu.ch3.waveRam.set(waveRam_, sizeof waveRam_);
}
void Channel3::saveState(SaveState &state) const {
lengthCounter_.saveState(state.spu.ch3.lcounter);
state.spu.ch3.waveCounter = waveCounter_;
state.spu.ch3.lastReadTime = lastReadTime_;
state.spu.ch3.nr3 = nr3_;
state.spu.ch3.nr4 = nr4_;
state.spu.ch3.wavePos = wavePos_;
state.spu.ch3.sampleBuf = sampleBuf_;
state.spu.ch3.master = master_;
}
void Channel3::loadState(SaveState const &state) {
lengthCounter_.loadState(state.spu.ch3.lcounter, state.spu.cycleCounter);
cycleCounter_ = state.spu.cycleCounter;
waveCounter_ = std::max(state.spu.ch3.waveCounter, state.spu.cycleCounter);
lastReadTime_ = state.spu.ch3.lastReadTime;
nr3_ = state.spu.ch3.nr3;
nr4_ = state.spu.ch3.nr4;
wavePos_ = state.spu.ch3.wavePos & 0x1F;
sampleBuf_ = state.spu.ch3.sampleBuf;
master_ = state.spu.ch3.master;
nr0_ = state.mem.ioamhram.get()[0x11A] & 0x80;
setNr2(state.mem.ioamhram.get()[0x11C]);
}
void Channel3::updateWaveCounter(unsigned long const cc) {
if (cc >= waveCounter_) {
unsigned const period = toPeriod(nr3_, nr4_);
unsigned long const periods = (cc - waveCounter_) / period;
lastReadTime_ = waveCounter_ + periods * period;
waveCounter_ = lastReadTime_ + period;
wavePos_ += periods + 1;
wavePos_ &= 0x1F;
sampleBuf_ = waveRam_[wavePos_ >> 1];
}
}
void Channel3::update(uint_least32_t *buf, unsigned long const soBaseVol, unsigned long cycles) {
unsigned long const outBase = nr0_/* & 0x80*/ ? soBaseVol & soMask_ : 0;
if (outBase && rshift_ != 4) {
unsigned long const endCycles = cycleCounter_ + cycles;
for (;;) {
unsigned long const nextMajorEvent =
std::min(lengthCounter_.counter(), endCycles);
unsigned long out = master_
? ((sampleBuf_ >> (~wavePos_ << 2 & 4) & 0xF) >> rshift_) * 2 - 15ul
: 0 - 15ul;
out *= outBase;
while (waveCounter_ <= nextMajorEvent) {
*buf += out - prevOut_;
prevOut_ = out;
buf += waveCounter_ - cycleCounter_;
cycleCounter_ = waveCounter_;
lastReadTime_ = waveCounter_;
waveCounter_ += toPeriod(nr3_, nr4_);
++wavePos_;
wavePos_ &= 0x1F;
sampleBuf_ = waveRam_[wavePos_ >> 1];
out = ((sampleBuf_ >> (~wavePos_ << 2 & 4) & 0xF) >> rshift_) * 2 - 15ul;
out *= outBase;
}
if (cycleCounter_ < nextMajorEvent) {
*buf += out - prevOut_;
prevOut_ = out;
buf += nextMajorEvent - cycleCounter_;
cycleCounter_ = nextMajorEvent;
}
if (lengthCounter_.counter() == nextMajorEvent) {
lengthCounter_.event();
} else
break;
}
} else {
unsigned long const out = outBase * (0 - 15ul);
*buf += out - prevOut_;
prevOut_ = out;
cycleCounter_ += cycles;
while (lengthCounter_.counter() <= cycleCounter_) {
updateWaveCounter(lengthCounter_.counter());
lengthCounter_.event();
}
updateWaveCounter(cycleCounter_);
}
if (cycleCounter_ >= SoundUnit::counter_max) {
lengthCounter_.resetCounters(cycleCounter_);
if (waveCounter_ != SoundUnit::counter_disabled)
waveCounter_ -= SoundUnit::counter_max;
lastReadTime_ -= SoundUnit::counter_max;
cycleCounter_ -= SoundUnit::counter_max;
}
}
}
+105
View File
@@ -0,0 +1,105 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef SOUND_CHANNEL3_H
#define SOUND_CHANNEL3_H
#include "gbint.h"
#include "length_counter.h"
#include "master_disabler.h"
namespace gambatte {
struct SaveState;
class Channel3 {
public:
Channel3();
bool isActive() const { return master_; }
void reset();
void init(bool cgb);
void setStatePtrs(SaveState &state);
void saveState(SaveState &state) const;
void loadState(const SaveState &state);
void setNr0(unsigned data);
void setNr1(unsigned data) { lengthCounter_.nr1Change(data, nr4_, cycleCounter_); }
void setNr2(unsigned data);
void setNr3(unsigned data) { nr3_ = data; }
void setNr4(unsigned data);
void setSo(unsigned long soMask);
void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles);
unsigned waveRamRead(unsigned index) const {
if (master_) {
if (!cgb_ && cycleCounter_ != lastReadTime_)
return 0xFF;
index = wavePos_ >> 1;
}
return waveRam_[index];
}
void waveRamWrite(unsigned index, unsigned data) {
if (master_) {
if (!cgb_ && cycleCounter_ != lastReadTime_)
return;
index = wavePos_ >> 1;
}
waveRam_[index] = data;
}
private:
class Ch3MasterDisabler : public MasterDisabler {
public:
Ch3MasterDisabler(bool &m, unsigned long &wC) : MasterDisabler(m), waveCounter_(wC) {}
virtual void operator()() {
MasterDisabler::operator()();
waveCounter_ = SoundUnit::counter_disabled;
}
private:
unsigned long &waveCounter_;
};
unsigned char waveRam_[0x10];
Ch3MasterDisabler disableMaster_;
LengthCounter lengthCounter_;
unsigned long cycleCounter_;
unsigned long soMask_;
unsigned long prevOut_;
unsigned long waveCounter_;
unsigned long lastReadTime_;
unsigned char nr0_;
unsigned char nr3_;
unsigned char nr4_;
unsigned char wavePos_;
unsigned char rshift_;
unsigned char sampleBuf_;
bool master_;
bool cgb_;
void updateWaveCounter(unsigned long cc);
};
}
#endif
+261
View File
@@ -0,0 +1,261 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "channel4.h"
#include "../savestate.h"
#include <algorithm>
static unsigned long toPeriod(unsigned const nr3) {
unsigned s = (nr3 >> 4) + 3;
unsigned r = nr3 & 7;
if (!r) {
r = 1;
--s;
}
return r << s;
}
namespace gambatte {
Channel4::Lfsr::Lfsr()
: backupCounter_(counter_disabled)
, reg_(0x7FFF)
, nr3_(0)
, master_(false)
{
}
void Channel4::Lfsr::updateBackupCounter(unsigned long const cc) {
if (backupCounter_ <= cc) {
unsigned long const period = toPeriod(nr3_);
unsigned long periods = (cc - backupCounter_) / period + 1;
backupCounter_ += periods * period;
if (master_ && nr3_ < 0xE0) {
if (nr3_ & 8) {
while (periods > 6) {
unsigned const xored = (reg_ << 1 ^ reg_) & 0x7E;
reg_ = (reg_ >> 6 & ~0x7E) | xored | xored << 8;
periods -= 6;
}
unsigned const xored = ((reg_ ^ reg_ >> 1) << (7 - periods)) & 0x7F;
reg_ = (reg_ >> periods & ~(0x80 - (0x80 >> periods))) | xored | xored << 8;
} else {
while (periods > 15) {
reg_ = reg_ ^ reg_ >> 1;
periods -= 15;
}
reg_ = reg_ >> periods | (((reg_ ^ reg_ >> 1) << (15 - periods)) & 0x7FFF);
}
}
}
}
void Channel4::Lfsr::reviveCounter(unsigned long cc) {
updateBackupCounter(cc);
counter_ = backupCounter_;
}
inline void Channel4::Lfsr::event() {
if (nr3_ < 0xE0) {
unsigned const shifted = reg_ >> 1;
unsigned const xored = (reg_ ^ shifted) & 1;
reg_ = shifted | xored << 14;
if (nr3_ & 8)
reg_ = (reg_ & ~0x40) | xored << 6;
}
counter_ += toPeriod(nr3_);
backupCounter_ = counter_;
}
void Channel4::Lfsr::nr3Change(unsigned newNr3, unsigned long cc) {
updateBackupCounter(cc);
nr3_ = newNr3;
counter_ = cc;
}
void Channel4::Lfsr::nr4Init(unsigned long cc) {
disableMaster();
updateBackupCounter(cc);
master_ = true;
backupCounter_ += 4;
counter_ = backupCounter_;
}
void Channel4::Lfsr::reset(unsigned long cc) {
nr3_ = 0;
disableMaster();
backupCounter_ = cc + toPeriod(nr3_);
}
void Channel4::Lfsr::resetCounters(unsigned long oldCc) {
updateBackupCounter(oldCc);
backupCounter_ -= counter_max;
SoundUnit::resetCounters(oldCc);
}
void Channel4::Lfsr::saveState(SaveState &state, unsigned long cc) {
updateBackupCounter(cc);
state.spu.ch4.lfsr.counter = backupCounter_;
state.spu.ch4.lfsr.reg = reg_;
}
void Channel4::Lfsr::loadState(SaveState const &state) {
counter_ = backupCounter_ = std::max(state.spu.ch4.lfsr.counter, state.spu.cycleCounter);
reg_ = state.spu.ch4.lfsr.reg;
master_ = state.spu.ch4.master;
nr3_ = state.mem.ioamhram.get()[0x122];
}
Channel4::Channel4()
: staticOutputTest_(*this, lfsr_)
, disableMaster_(master_, lfsr_)
, lengthCounter_(disableMaster_, 0x3F)
, envelopeUnit_(staticOutputTest_)
, nextEventUnit_(0)
, cycleCounter_(0)
, soMask_(0)
, prevOut_(0)
, nr4_(0)
, master_(false)
{
setEvent();
}
void Channel4::setEvent() {
nextEventUnit_ = &envelopeUnit_;
if (lengthCounter_.counter() < nextEventUnit_->counter())
nextEventUnit_ = &lengthCounter_;
}
void Channel4::setNr1(unsigned data) {
lengthCounter_.nr1Change(data, nr4_, cycleCounter_);
setEvent();
}
void Channel4::setNr2(unsigned data) {
if (envelopeUnit_.nr2Change(data))
disableMaster_();
else
staticOutputTest_(cycleCounter_);
setEvent();
}
void Channel4::setNr4(unsigned const data) {
lengthCounter_.nr4Change(nr4_, data, cycleCounter_);
nr4_ = data;
if (data & 0x80) { // init-bit
nr4_ &= 0x7F;
master_ = !envelopeUnit_.nr4Init(cycleCounter_);
if (master_)
lfsr_.nr4Init(cycleCounter_);
staticOutputTest_(cycleCounter_);
}
setEvent();
}
void Channel4::setSo(unsigned long soMask) {
soMask_ = soMask;
staticOutputTest_(cycleCounter_);
setEvent();
}
void Channel4::reset() {
// cycleCounter >> 12 & 7 represents the frame sequencer position.
cycleCounter_ &= 0xFFF;
cycleCounter_ += ~(cycleCounter_ + 2) << 1 & 0x1000;
lfsr_.reset(cycleCounter_);
envelopeUnit_.reset();
setEvent();
}
void Channel4::saveState(SaveState &state) {
lfsr_.saveState(state, cycleCounter_);
envelopeUnit_.saveState(state.spu.ch4.env);
lengthCounter_.saveState(state.spu.ch4.lcounter);
state.spu.ch4.nr4 = nr4_;
state.spu.ch4.master = master_;
}
void Channel4::loadState(SaveState const &state) {
lfsr_.loadState(state);
envelopeUnit_.loadState(state.spu.ch4.env, state.mem.ioamhram.get()[0x121],
state.spu.cycleCounter);
lengthCounter_.loadState(state.spu.ch4.lcounter, state.spu.cycleCounter);
cycleCounter_ = state.spu.cycleCounter;
nr4_ = state.spu.ch4.nr4;
master_ = state.spu.ch4.master;
}
void Channel4::update(uint_least32_t *buf, unsigned long const soBaseVol, unsigned long cycles) {
unsigned long const outBase = envelopeUnit_.dacIsOn() ? soBaseVol & soMask_ : 0;
unsigned long const outLow = outBase * (0 - 15ul);
unsigned long const endCycles = cycleCounter_ + cycles;
for (;;) {
unsigned long const outHigh = outBase * (envelopeUnit_.getVolume() * 2 - 15ul);
unsigned long const nextMajorEvent = std::min(nextEventUnit_->counter(), endCycles);
unsigned long out = lfsr_.isHighState() ? outHigh : outLow;
while (lfsr_.counter() <= nextMajorEvent) {
*buf += out - prevOut_;
prevOut_ = out;
buf += lfsr_.counter() - cycleCounter_;
cycleCounter_ = lfsr_.counter();
lfsr_.event();
out = lfsr_.isHighState() ? outHigh : outLow;
}
if (cycleCounter_ < nextMajorEvent) {
*buf += out - prevOut_;
prevOut_ = out;
buf += nextMajorEvent - cycleCounter_;
cycleCounter_ = nextMajorEvent;
}
if (nextEventUnit_->counter() == nextMajorEvent) {
nextEventUnit_->event();
setEvent();
} else
break;
}
if (cycleCounter_ >= SoundUnit::counter_max) {
lengthCounter_.resetCounters(cycleCounter_);
lfsr_.resetCounters(cycleCounter_);
envelopeUnit_.resetCounters(cycleCounter_);
cycleCounter_ -= SoundUnit::counter_max;
}
}
}
+99
View File
@@ -0,0 +1,99 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef SOUND_CHANNEL4_H
#define SOUND_CHANNEL4_H
#include "envelope_unit.h"
#include "gbint.h"
#include "length_counter.h"
#include "master_disabler.h"
#include "static_output_tester.h"
namespace gambatte {
struct SaveState;
class Channel4 {
public:
Channel4();
void setNr1(unsigned data);
void setNr2(unsigned data);
void setNr3(unsigned data) { lfsr_.nr3Change(data, cycleCounter_); }
void setNr4(unsigned data);
void setSo(unsigned long soMask);
bool isActive() const { return master_; }
void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles);
void reset();
void saveState(SaveState &state);
void loadState(SaveState const &state);
private:
class Lfsr : public SoundUnit {
public:
Lfsr();
virtual void event();
virtual void resetCounters(unsigned long oldCc);
bool isHighState() const { return ~reg_ & 1; }
void nr3Change(unsigned newNr3, unsigned long cc);
void nr4Init(unsigned long cc);
void reset(unsigned long cc);
void saveState(SaveState &state, unsigned long cc);
void loadState(SaveState const &state);
void disableMaster() { killCounter(); master_ = false; reg_ = 0x7FFF; }
void killCounter() { counter_ = counter_disabled; }
void reviveCounter(unsigned long cc);
private:
unsigned long backupCounter_;
unsigned short reg_;
unsigned char nr3_;
bool master_;
void updateBackupCounter(unsigned long cc);
};
class Ch4MasterDisabler : public MasterDisabler {
public:
Ch4MasterDisabler(bool &m, Lfsr &lfsr) : MasterDisabler(m), lfsr_(lfsr) {}
virtual void operator()() { MasterDisabler::operator()(); lfsr_.disableMaster(); }
private:
Lfsr &lfsr_;
};
friend class StaticOutputTester<Channel4, Lfsr>;
StaticOutputTester<Channel4, Lfsr> staticOutputTest_;
Ch4MasterDisabler disableMaster_;
LengthCounter lengthCounter_;
EnvelopeUnit envelopeUnit_;
Lfsr lfsr_;
SoundUnit *nextEventUnit_;
unsigned long cycleCounter_;
unsigned long soMask_;
unsigned long prevOut_;
unsigned char nr4_;
bool master_;
void setEvent();
};
}
#endif
+158
View File
@@ -0,0 +1,158 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "duty_unit.h"
#include <algorithm>
static inline bool toOutState(unsigned duty, unsigned pos) {
return 0x7EE18180 >> (duty * 8 + pos) & 1;
}
static inline unsigned toPeriod(unsigned freq) {
return (2048 - freq) * 2;
}
namespace gambatte {
DutyUnit::DutyUnit()
: nextPosUpdate_(counter_disabled)
, period_(4096)
, pos_(0)
, duty_(0)
, inc_(0)
, high_(false)
, enableEvents_(true)
{
}
void DutyUnit::updatePos(unsigned long const cc) {
if (cc >= nextPosUpdate_) {
unsigned long const inc = (cc - nextPosUpdate_) / period_ + 1;
nextPosUpdate_ += period_ * inc;
pos_ += inc;
pos_ &= 7;
high_ = toOutState(duty_, pos_);
}
}
void DutyUnit::setCounter() {
static unsigned char const nextStateDistance[4 * 8] = {
7, 6, 5, 4, 3, 2, 1, 1,
1, 6, 5, 4, 3, 2, 1, 2,
1, 4, 3, 2, 1, 4, 3, 2,
1, 6, 5, 4, 3, 2, 1, 2
};
if (enableEvents_ && nextPosUpdate_ != counter_disabled) {
unsigned const npos = (pos_ + 1) & 7;
counter_ = nextPosUpdate_;
inc_ = nextStateDistance[duty_ * 8 + npos];
if (toOutState(duty_, npos) == high_) {
counter_ += period_ * inc_;
inc_ = nextStateDistance[duty_ * 8 + ((npos + inc_) & 7)];
}
} else
counter_ = counter_disabled;
}
void DutyUnit::setFreq(unsigned newFreq, unsigned long cc) {
updatePos(cc);
period_ = toPeriod(newFreq);
setCounter();
}
void DutyUnit::event() {
static unsigned char const inc[] = {
1, 7,
2, 6,
4, 4,
6, 2,
};
high_ ^= true;
counter_ += inc_ * period_;
inc_ = inc[duty_ * 2 + high_];
}
void DutyUnit::nr1Change(unsigned newNr1, unsigned long cc) {
updatePos(cc);
duty_ = newNr1 >> 6;
setCounter();
}
void DutyUnit::nr3Change(unsigned newNr3, unsigned long cc) {
setFreq((freq() & 0x700) | newNr3, cc);
}
void DutyUnit::nr4Change(unsigned const newNr4, unsigned long const cc) {
setFreq((newNr4 << 8 & 0x700) | (freq() & 0xFF), cc);
if (newNr4 & 0x80) {
nextPosUpdate_ = (cc & ~1ul) + period_ + 4;
setCounter();
}
}
void DutyUnit::reset() {
pos_ = 0;
high_ = false;
nextPosUpdate_ = counter_disabled;
setCounter();
}
void DutyUnit::saveState(SaveState::SPU::Duty &dstate, unsigned long const cc) {
updatePos(cc);
setCounter();
dstate.nextPosUpdate = nextPosUpdate_;
dstate.nr3 = freq() & 0xFF;
dstate.pos = pos_;
dstate.high = high_;
}
void DutyUnit::loadState(SaveState::SPU::Duty const &dstate,
unsigned const nr1, unsigned const nr4, unsigned long const cc) {
nextPosUpdate_ = std::max(dstate.nextPosUpdate, cc);
pos_ = dstate.pos & 7;
high_ = dstate.high;
duty_ = nr1 >> 6;
period_ = toPeriod((nr4 << 8 & 0x700) | dstate.nr3);
enableEvents_ = true;
setCounter();
}
void DutyUnit::resetCounters(unsigned long const oldCc) {
if (nextPosUpdate_ == counter_disabled)
return;
updatePos(oldCc);
nextPosUpdate_ -= counter_max;
setCounter();
}
void DutyUnit::killCounter() {
enableEvents_ = false;
setCounter();
}
void DutyUnit::reviveCounter(unsigned long const cc) {
updatePos(cc);
enableEvents_ = true;
setCounter();
}
}
+72
View File
@@ -0,0 +1,72 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef DUTY_UNIT_H
#define DUTY_UNIT_H
#include "sound_unit.h"
#include "master_disabler.h"
#include "../savestate.h"
namespace gambatte {
class DutyUnit : public SoundUnit {
public:
DutyUnit();
virtual void event();
virtual void resetCounters(unsigned long oldCc);
bool isHighState() const { return high_; }
void nr1Change(unsigned newNr1, unsigned long cc);
void nr3Change(unsigned newNr3, unsigned long cc);
void nr4Change(unsigned newNr4, unsigned long cc);
void reset();
void saveState(SaveState::SPU::Duty &dstate, unsigned long cc);
void loadState(SaveState::SPU::Duty const &dstate, unsigned nr1, unsigned nr4, unsigned long cc);
void killCounter();
void reviveCounter(unsigned long cc);
//intended for use by SweepUnit only.
unsigned freq() const { return 2048 - (period_ >> 1); }
void setFreq(unsigned newFreq, unsigned long cc);
private:
unsigned long nextPosUpdate_;
unsigned short period_;
unsigned char pos_;
unsigned char duty_;
unsigned char inc_;
bool high_;
bool enableEvents_;
void setCounter();
void setDuty(unsigned nr1);
void updatePos(unsigned long cc);
};
class DutyMasterDisabler : public MasterDisabler {
public:
DutyMasterDisabler(bool &m, DutyUnit &dutyUnit) : MasterDisabler(m), dutyUnit_(dutyUnit) {}
virtual void operator()() { MasterDisabler::operator()(); dutyUnit_.killCounter(); }
private:
DutyUnit &dutyUnit_;
};
}
#endif
@@ -0,0 +1,96 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "envelope_unit.h"
#include <algorithm>
namespace gambatte {
EnvelopeUnit::VolOnOffEvent EnvelopeUnit::nullEvent_;
EnvelopeUnit::EnvelopeUnit(VolOnOffEvent &volOnOffEvent)
: volOnOffEvent_(volOnOffEvent)
, nr2_(0)
, volume_(0)
{
}
void EnvelopeUnit::reset() {
counter_ = counter_disabled;
}
void EnvelopeUnit::saveState(SaveState::SPU::Env &estate) const {
estate.counter = counter_;
estate.volume = volume_;
}
void EnvelopeUnit::loadState(SaveState::SPU::Env const &estate, unsigned nr2, unsigned long cc) {
counter_ = std::max(estate.counter, cc);
volume_ = estate.volume;
nr2_ = nr2;
}
void EnvelopeUnit::event() {
unsigned long const period = nr2_ & 7;
if (period) {
unsigned newVol = volume_;
if (nr2_ & 8)
++newVol;
else
--newVol;
if (newVol < 0x10U) {
volume_ = newVol;
if (volume_ < 2)
volOnOffEvent_(counter_);
counter_ += period << 15;
} else
counter_ = counter_disabled;
} else
counter_ += 8ul << 15;
}
bool EnvelopeUnit::nr2Change(unsigned const newNr2) {
if (!(nr2_ & 7) && counter_ != counter_disabled)
++volume_;
else if (!(nr2_ & 8))
volume_ += 2;
if ((nr2_ ^ newNr2) & 8)
volume_ = 0x10 - volume_;
volume_ &= 0xF;
nr2_ = newNr2;
return !(newNr2 & 0xF8);
}
bool EnvelopeUnit::nr4Init(unsigned long const cc) {
unsigned long period = (nr2_ & 7) ? nr2_ & 7 : 8;
if (((cc + 2) & 0x7000) == 0x0000)
++period;
counter_ = cc - ((cc - 0x1000) & 0x7FFF) + period * 0x8000;
volume_ = nr2_ >> 4;
return !(nr2_ & 0xF8);
}
}
@@ -0,0 +1,53 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef ENVELOPE_UNIT_H
#define ENVELOPE_UNIT_H
#include "sound_unit.h"
#include "../savestate.h"
namespace gambatte {
class EnvelopeUnit : public SoundUnit {
public:
struct VolOnOffEvent {
virtual ~VolOnOffEvent() {}
virtual void operator()(unsigned long /*cc*/) {}
};
explicit EnvelopeUnit(VolOnOffEvent &volOnOffEvent = nullEvent_);
void event();
bool dacIsOn() const { return nr2_ & 0xF8; }
unsigned getVolume() const { return volume_; }
bool nr2Change(unsigned newNr2);
bool nr4Init(unsigned long cycleCounter);
void reset();
void saveState(SaveState::SPU::Env &estate) const;
void loadState(SaveState::SPU::Env const &estate, unsigned nr2, unsigned long cc);
private:
static VolOnOffEvent nullEvent_;
VolOnOffEvent &volOnOffEvent_;
unsigned char nr2_;
unsigned char volume_;
};
}
#endif
@@ -0,0 +1,82 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "length_counter.h"
#include "master_disabler.h"
#include <algorithm>
namespace gambatte {
LengthCounter::LengthCounter(MasterDisabler &disabler, unsigned const mask)
: disableMaster_(disabler)
, lengthCounter_(0)
, lengthMask_(mask)
{
nr1Change(0, 0, 0);
}
void LengthCounter::event() {
counter_ = counter_disabled;
lengthCounter_ = 0;
disableMaster_();
}
void LengthCounter::nr1Change(unsigned const newNr1, unsigned const nr4, unsigned long const cc) {
lengthCounter_ = (~newNr1 & lengthMask_) + 1;
counter_ = (nr4 & 0x40)
? ((cc >> 13) + lengthCounter_) << 13
: static_cast<unsigned long>(counter_disabled);
}
void LengthCounter::nr4Change(unsigned const oldNr4, unsigned const newNr4, unsigned long const cc) {
if (counter_ != counter_disabled)
lengthCounter_ = (counter_ >> 13) - (cc >> 13);
{
unsigned dec = 0;
if (newNr4 & 0x40) {
dec = ~cc >> 12 & 1;
if (!(oldNr4 & 0x40) && lengthCounter_) {
if (!(lengthCounter_ -= dec))
disableMaster_();
}
}
if ((newNr4 & 0x80) && !lengthCounter_)
lengthCounter_ = lengthMask_ + 1 - dec;
}
if ((newNr4 & 0x40) && lengthCounter_)
counter_ = ((cc >> 13) + lengthCounter_) << 13;
else
counter_ = counter_disabled;
}
void LengthCounter::saveState(SaveState::SPU::LCounter &lstate) const {
lstate.counter = counter_;
lstate.lengthCounter = lengthCounter_;
}
void LengthCounter::loadState(SaveState::SPU::LCounter const &lstate, unsigned long const cc) {
counter_ = std::max(lstate.counter, cc);
lengthCounter_ = lstate.lengthCounter;
}
}
@@ -0,0 +1,46 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef LENGTH_COUNTER_H
#define LENGTH_COUNTER_H
#include "sound_unit.h"
#include "../savestate.h"
namespace gambatte {
class MasterDisabler;
class LengthCounter : public SoundUnit {
public:
LengthCounter(MasterDisabler &disabler, unsigned lengthMask);
virtual void event();
void nr1Change(unsigned newNr1, unsigned nr4, unsigned long cc);
void nr4Change(unsigned oldNr4, unsigned newNr4, unsigned long cc);
void saveState(SaveState::SPU::LCounter &lstate) const;
void loadState(SaveState::SPU::LCounter const &lstate, unsigned long cc);
private:
MasterDisabler &disableMaster_;
unsigned short lengthCounter_;
unsigned char const lengthMask_;
};
}
#endif
@@ -0,0 +1,36 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef MASTER_DISABLER_H
#define MASTER_DISABLER_H
namespace gambatte {
class MasterDisabler {
public:
explicit MasterDisabler(bool &master) : master_(master) {}
virtual ~MasterDisabler() {}
virtual void operator()() { master_ = false; }
private:
bool &master_;
};
}
#endif
+45
View File
@@ -0,0 +1,45 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef SOUND_UNIT_H
#define SOUND_UNIT_H
namespace gambatte {
class SoundUnit {
public:
enum { counter_max = 0x80000000u, counter_disabled = 0xFFFFFFFFu };
virtual ~SoundUnit() {}
virtual void event() = 0;
virtual void resetCounters(unsigned long /*oldCc*/) {
if (counter_ != counter_disabled)
counter_ -= counter_max;
}
unsigned long counter() const { return counter_; }
protected:
SoundUnit() : counter_(counter_disabled) {}
unsigned long counter_;
};
}
#endif
@@ -0,0 +1,47 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef STATIC_OUTPUT_TESTER_H
#define STATIC_OUTPUT_TESTER_H
#include "envelope_unit.h"
namespace gambatte {
template<class Channel, class Unit>
class StaticOutputTester : public EnvelopeUnit::VolOnOffEvent {
public:
StaticOutputTester(Channel const &ch, Unit &unit) : ch_(ch), unit_(unit) {}
void operator()(unsigned long cc);
private:
Channel const &ch_;
Unit &unit_;
};
template<class Channel, class Unit>
void StaticOutputTester<Channel, Unit>::operator()(unsigned long cc) {
if (ch_.soMask_ && ch_.master_ && ch_.envelopeUnit_.getVolume())
unit_.reviveCounter(cc);
else
unit_.killCounter();
}
}
#endif
+604
View File
@@ -0,0 +1,604 @@
/***************************************************************************
* Copyright (C) 2008 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "statesaver.h"
#include "savestate.h"
#include "gambatte-array.h"
#include <stdint.h>
#include <vector>
#include <cstring>
#include <algorithm>
class omemstream
{
public:
omemstream(void *data) : wr_ptr(static_cast<uint8_t*>(data)), has_written(0) {}
void put(uint8_t data)
{
if (wr_ptr)
*wr_ptr++ = data;
has_written++;
}
void write(const void *data, size_t size)
{
if (wr_ptr)
{
std::memcpy(wr_ptr, data, size);
wr_ptr += size;
}
has_written += size;
}
size_t size() const { return has_written; }
bool fail() const { return false; }
bool good() const { return true; }
private:
uint8_t *wr_ptr;
size_t has_written;
};
class imemstream
{
public:
imemstream(const void *data, size_t size)
: rd_ptr(static_cast<const uint8_t*>(data))
, rd_end(static_cast<const uint8_t*>(data) + size)
, has_read(0)
, failed_(false)
{}
uint8_t get()
{
/* On underflow return 0 and latch failure. Callers
* cannot ignore failure in a way that would dereference
* the read pointer because rd_ptr is never advanced past
* rd_end. Previously this function unconditionally
* dereferenced and incremented rd_ptr, allowing a
* malformed savestate to read arbitrary memory past the
* buffer end. */
if (rd_ptr >= rd_end) {
failed_ = true;
return 0;
}
uint8_t ret = *rd_ptr++;
has_read++;
return ret;
}
void read(void *data, size_t size)
{
size_t avail = (size_t)(rd_end - rd_ptr);
size_t n = size < avail ? size : avail;
if (n)
std::memcpy(data, rd_ptr, n);
/* Pad the tail with zeroes if the buffer is short, to
* keep caller-visible behavior deterministic when a
* truncated state is fed in. */
if (n < size) {
std::memset(static_cast<uint8_t*>(data) + n, 0, size - n);
failed_ = true;
}
rd_ptr += n;
has_read += size;
}
void ignore(size_t len = 1)
{
size_t avail = (size_t)(rd_end - rd_ptr);
size_t n = len < avail ? len : avail;
if (n < len)
failed_ = true;
rd_ptr += n;
has_read += len;
}
void getline(char *data, size_t size, char delim = '\n')
{
size_t count = 0;
while ((count < size - 1) && (rd_ptr < rd_end) && (*rd_ptr != delim))
{
*data++ = *rd_ptr++;
has_read++;
count++;
}
if (rd_ptr < rd_end) {
rd_ptr++;
has_read++;
} else {
failed_ = true;
}
*data = '\0';
}
bool fail() const { return failed_; }
bool good() const { return !failed_ && rd_ptr < rd_end; }
private:
const uint8_t *rd_ptr;
const uint8_t *rd_end;
size_t has_read;
bool failed_;
};
namespace {
using namespace gambatte;
enum AsciiChar {
NUL, SOH, STX, ETX, EOT, ENQ, ACK, BEL, BS, TAB, LF, VT, FF, CR, SO, SI,
DLE, DC1, DC2, DC3, DC4, NAK, SYN, ETB, CAN, EM, SUB, ESC, FS, GS, RS, US,
SP, XCL, QOT, HSH, DLR, PRC, AMP, APO, LPA, RPA, AST, PLU, COM, HYP, STP, DIV,
NO0, NO1, NO2, NO3, NO4, NO5, NO6, NO7, NO8, NO9, CLN, SCL, LT, EQL, GT, QTN,
AT, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O,
P, Q, R, S, T, U, V, W, X, Y, Z, LBX, BSL, RBX, CAT, UND,
ACN, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o,
p, q, r, s, t, u, v, w, x, y, z, LBR, BAR, RBR, TLD, DEL
};
struct Saver {
const char *label;
void (*save)(omemstream &file, const SaveState &state);
void (*load)(imemstream &file, SaveState &state);
unsigned char labelsize;
};
static inline bool operator<(const Saver &l, const Saver &r) {
return std::strcmp(l.label, r.label) < 0;
}
static void put24(omemstream &file, const unsigned long data) {
file.put(data >> 16 & 0xFF);
file.put(data >> 8 & 0xFF);
file.put(data & 0xFF);
}
static void put32(omemstream &file, const unsigned long data) {
file.put(data >> 24 & 0xFF);
file.put(data >> 16 & 0xFF);
file.put(data >> 8 & 0xFF);
file.put(data & 0xFF);
}
static void write(omemstream &file, const unsigned char data) {
static const char inf[] = { 0x00, 0x00, 0x01 };
file.write(inf, sizeof(inf));
file.put(data & 0xFF);
}
static void write(omemstream &file, const unsigned short data) {
static const char inf[] = { 0x00, 0x00, 0x02 };
file.write(inf, sizeof(inf));
file.put(data >> 8 & 0xFF);
file.put(data & 0xFF);
}
static void write(omemstream &file, const unsigned long data) {
static const char inf[] = { 0x00, 0x00, 0x04 };
file.write(inf, sizeof(inf));
put32(file, data);
}
static inline void write(omemstream &file, const bool data) {
write(file, static_cast<unsigned char>(data));
}
static void write(omemstream &file, const unsigned char *data, const unsigned long sz) {
put24(file, sz);
file.write(reinterpret_cast<const char*>(data), sz);
}
static void write(omemstream &file, const bool *data, const unsigned long sz) {
put24(file, sz);
for (unsigned long i = 0; i < sz; ++i)
file.put(data[i]);
}
static unsigned long get24(imemstream &file) {
unsigned long tmp = file.get() & 0xFF;
tmp = tmp << 8 | (file.get() & 0xFF);
return tmp << 8 | (file.get() & 0xFF);
}
static unsigned long read(imemstream &file) {
unsigned long size = get24(file);
if (size > 4) {
file.ignore(size - 4);
size = 4;
}
unsigned long out = 0;
switch (size) {
case 4: out = (out | (file.get() & 0xFF)) << 8;
case 3: out = (out | (file.get() & 0xFF)) << 8;
case 2: out = (out | (file.get() & 0xFF)) << 8;
case 1: out = out | (file.get() & 0xFF);
}
return out;
}
static inline void read(imemstream &file, unsigned char &data) {
data = read(file) & 0xFF;
}
static inline void read(imemstream &file, unsigned short &data) {
data = read(file) & 0xFFFF;
}
static inline void read(imemstream &file, unsigned long &data) {
data = read(file);
}
static inline void read(imemstream &file, bool &data) {
data = read(file);
}
static void read(imemstream &file, unsigned char *data, unsigned long sz) {
const unsigned long size = get24(file);
if (size < sz)
sz = size;
file.read(reinterpret_cast<char*>(data), sz);
file.ignore(size - sz);
/* Previous code had:
* if (static_cast<unsigned char>(0x100))
* for (...) data[i] &= 0xFF;
* The condition is always false (0x100 truncated to
* unsigned char is 0), so the masking loop was dead.
* Removed. unsigned char already holds 0..0xFF by definition. */
}
static void read(imemstream &file, bool *data, unsigned long sz) {
const unsigned long size = get24(file);
if (size < sz)
sz = size;
for (unsigned long i = 0; i < sz; ++i)
data[i] = file.get();
file.ignore(size - sz);
}
} // anon namespace
namespace gambatte {
class SaverList {
public:
typedef std::vector<Saver> list_t;
typedef list_t::const_iterator const_iterator;
private:
list_t list;
unsigned char maxLabelsize_;
public:
SaverList();
const_iterator begin() const { return list.begin(); }
const_iterator end() const { return list.end(); }
unsigned char maxLabelsize() const { return maxLabelsize_; }
};
static void pushSaver(SaverList::list_t &list, const char *label,
void (*save)(omemstream &file, const SaveState &state),
void (*load)(imemstream &file, SaveState &state), unsigned char labelsize) {
const Saver saver = { label, save, load, labelsize };
list.push_back(saver);
}
SaverList::SaverList() {
#define ADD(arg) do { \
struct Func { \
static void save(omemstream &file, const SaveState &state) { write(file, state.arg); } \
static void load(imemstream &file, SaveState &state) { read(file, state.arg); } \
}; \
\
pushSaver(list, label, Func::save, Func::load, sizeof label); \
} while (0)
#define ADDPTR(arg) do { \
struct Func { \
static void save(omemstream &file, const SaveState &state) { write(file, state.arg.get(), state.arg.size()); } \
static void load(imemstream &file, SaveState &state) { read(file, state.arg.ptr, state.arg.size()); } \
}; \
\
pushSaver(list, label, Func::save, Func::load, sizeof label); \
} while (0)
#define ADDARRAY(arg) do { \
struct Func { \
static void save(omemstream &file, const SaveState &state) { write(file, state.arg, sizeof(state.arg)); } \
static void load(imemstream &file, SaveState &state) { read(file, state.arg, sizeof(state.arg)); } \
}; \
\
pushSaver(list, label, Func::save, Func::load, sizeof label); \
} while (0)
{ static const char label[] = { c,c, NUL }; ADD(cpu.cycleCounter); }
{ static const char label[] = { p,c, NUL }; ADD(cpu.pc); }
{ static const char label[] = { s,p, NUL }; ADD(cpu.sp); }
{ static const char label[] = { a, NUL }; ADD(cpu.a); }
{ static const char label[] = { b, NUL }; ADD(cpu.b); }
{ static const char label[] = { c, NUL }; ADD(cpu.c); }
{ static const char label[] = { d, NUL }; ADD(cpu.d); }
{ static const char label[] = { e, NUL }; ADD(cpu.e); }
{ static const char label[] = { f, NUL }; ADD(cpu.f); }
{ static const char label[] = { h, NUL }; ADD(cpu.h); }
{ static const char label[] = { l, NUL }; ADD(cpu.l); }
{ static const char label[] = { s,k,i,p, NUL }; ADD(cpu.skip); }
{ static const char label[] = { h,a,l,t, NUL }; ADD(mem.halted); }
{ static const char label[] = { v,r,a,m, NUL }; ADDPTR(mem.vram); }
{ static const char label[] = { s,r,a,m, NUL }; ADDPTR(mem.sram); }
{ static const char label[] = { w,r,a,m, NUL }; ADDPTR(mem.wram); }
{ static const char label[] = { h,r,a,m, NUL }; ADDPTR(mem.ioamhram); }
{ static const char label[] = { l,d,i,v,u,p, NUL }; ADD(mem.divLastUpdate); }
{ static const char label[] = { l,t,i,m,a,u,p, NUL }; ADD(mem.timaLastUpdate); }
{ static const char label[] = { t,m,a,t,i,m,e, NUL }; ADD(mem.tmatime); }
{ static const char label[] = { s,e,r,i,a,l,t, NUL }; ADD(mem.nextSerialtime); }
{ static const char label[] = { l,o,d,m,a,u,p, NUL }; ADD(mem.lastOamDmaUpdate); }
{ static const char label[] = { m,i,n,i,n,t,t, NUL }; ADD(mem.minIntTime); }
{ static const char label[] = { u,n,h,a,l,t,t, NUL }; ADD(mem.unhaltTime); }
{ static const char label[] = { r,o,m,b,a,n,k, NUL }; ADD(mem.rombank); }
{ static const char label[] = { d,m,a,s,r,c, NUL }; ADD(mem.dmaSource); }
{ static const char label[] = { d,m,a,d,s,t, NUL }; ADD(mem.dmaDestination); }
{ static const char label[] = { r,a,m,b,a,n,k, NUL }; ADD(mem.rambank); }
{ static const char label[] = { o,d,m,a,p,o,s, NUL }; ADD(mem.oamDmaPos); }
#ifdef HAVE_NETWORK
{ static const char label[] = { n,e,t,s,v, NUL }; ADD(mem.serialize_value); }
{ static const char label[] = { n,e,t,s,f,c, NUL }; ADD(mem.serialize_is_fastcgb); }
#endif
{ static const char label[] = { i,m,e, NUL }; ADD(mem.IME); }
{ static const char label[] = { s,r,a,m,o,n, NUL }; ADD(mem.enableRam); }
{ static const char label[] = { r,a,m,b,m,o,d, NUL }; ADD(mem.rambankMode); }
{ static const char label[] = { h,d,m,a, NUL }; ADD(mem.hdmaTransfer); }
{ static const char label[] = { h,u,c,NO3,r,a,m, NUL }; ADD(mem.HuC3RAMflag); }
{ static const char label[] = { s,c,h,n,o,m,k, NUL }; ADD(mem.sachenOuterMask); }
{ static const char label[] = { s,c,h,n,l,c,t, NUL }; ADD(mem.sachenLockCount); }
{ static const char label[] = { b,g,p, NUL }; ADDPTR(ppu.bgpData); }
{ static const char label[] = { o,b,j,p, NUL }; ADDPTR(ppu.objpData); }
{ static const char label[] = { s,p,o,s,b,u,f, NUL }; ADDPTR(ppu.oamReaderBuf); }
{ static const char label[] = { s,p,s,z,b,u,f, NUL }; ADDPTR(ppu.oamReaderSzbuf); }
{ static const char label[] = { d,m,g,p,a,l, NUL }; ADDARRAY(ppu.dmgPalette); }
{ static const char label[] = { s,p,a,t,t,r, NUL }; ADDARRAY(ppu.spAttribList); }
{ static const char label[] = { s,p,b,y,t,e,NO0, NUL }; ADDARRAY(ppu.spByte0List); }
{ static const char label[] = { s,p,b,y,t,e,NO1, NUL }; ADDARRAY(ppu.spByte1List); }
{ static const char label[] = { v,c,y,c,l,e,s, NUL }; ADD(ppu.videoCycles); }
{ static const char label[] = { e,d,M,NO0,t,i,m, NUL }; ADD(ppu.enableDisplayM0Time); }
{ static const char label[] = { m,NO0,t,i,m,e, NUL }; ADD(ppu.lastM0Time); }
{ static const char label[] = { n,m,NO0,i,r,q, NUL }; ADD(ppu.nextM0Irq); }
{ static const char label[] = { b,g,t,w, NUL }; ADD(ppu.tileword); }
{ static const char label[] = { b,g,n,t,w, NUL }; ADD(ppu.ntileword); }
{ static const char label[] = { w,i,n,y,p,o,s, NUL }; ADD(ppu.winYPos); }
{ static const char label[] = { x,p,o,s, NUL }; ADD(ppu.xpos); }
{ static const char label[] = { e,n,d,x, NUL }; ADD(ppu.endx); }
{ static const char label[] = { p,p,u,r,NO0, NUL }; ADD(ppu.reg0); }
{ static const char label[] = { p,p,u,r,NO1, NUL }; ADD(ppu.reg1); }
{ static const char label[] = { b,g,a,t,r,b, NUL }; ADD(ppu.attrib); }
{ static const char label[] = { b,g,n,a,t,r,b, NUL }; ADD(ppu.nattrib); }
{ static const char label[] = { p,p,u,s,t,a,t, NUL }; ADD(ppu.state); }
{ static const char label[] = { n,s,p,r,i,t,e, NUL }; ADD(ppu.nextSprite); }
{ static const char label[] = { c,s,p,r,i,t,e, NUL }; ADD(ppu.currentSprite); }
{ static const char label[] = { l,y,c, NUL }; ADD(ppu.lyc); }
{ static const char label[] = { m,NO0,l,y,c, NUL }; ADD(ppu.m0lyc); }
{ static const char label[] = { o,l,d,w,y, NUL }; ADD(ppu.oldWy); }
{ static const char label[] = { w,i,n,d,r,a,w, NUL }; ADD(ppu.winDrawState); }
{ static const char label[] = { w,s,c,x, NUL }; ADD(ppu.wscx); }
{ static const char label[] = { w,e,m,a,s,t,r, NUL }; ADD(ppu.weMaster); }
{ static const char label[] = { l,c,d,s,i,r,q, NUL }; ADD(ppu.pendingLcdstatIrq); }
{ static const char label[] = { s,p,u,c,n,t,r, NUL }; ADD(spu.cycleCounter); }
{ static const char label[] = { s,w,p,c,n,t,r, NUL }; ADD(spu.ch1.sweep.counter); }
{ static const char label[] = { s,w,p,s,h,d,w, NUL }; ADD(spu.ch1.sweep.shadow); }
{ static const char label[] = { s,w,p,n,e,g, NUL }; ADD(spu.ch1.sweep.negging); }
{ static const char label[] = { d,u,t,NO1,c,t,r, NUL }; ADD(spu.ch1.duty.nextPosUpdate); }
{ static const char label[] = { d,u,t,NO1,p,o,s, NUL }; ADD(spu.ch1.duty.pos); }
{ static char const label[] = { d,u,t,NO1,h,i, NUL }; ADD(spu.ch1.duty.high); }
{ static const char label[] = { e,n,v,NO1,c,t,r, NUL }; ADD(spu.ch1.env.counter); }
{ static const char label[] = { e,n,v,NO1,v,o,l, NUL }; ADD(spu.ch1.env.volume); }
{ static const char label[] = { l,e,n,NO1,c,t,r, NUL }; ADD(spu.ch1.lcounter.counter); }
{ static const char label[] = { l,e,n,NO1,v,a,l, NUL }; ADD(spu.ch1.lcounter.lengthCounter); }
{ static const char label[] = { n,r,NO1,NO0, NUL }; ADD(spu.ch1.sweep.nr0); }
{ static const char label[] = { n,r,NO1,NO3, NUL }; ADD(spu.ch1.duty.nr3); }
{ static const char label[] = { n,r,NO1,NO4, NUL }; ADD(spu.ch1.nr4); }
{ static const char label[] = { c,NO1,m,a,s,t,r, NUL }; ADD(spu.ch1.master); }
{ static const char label[] = { d,u,t,NO2,c,t,r, NUL }; ADD(spu.ch2.duty.nextPosUpdate); }
{ static const char label[] = { d,u,t,NO2,p,o,s, NUL }; ADD(spu.ch2.duty.pos); }
{ static char const label[] = { d,u,t,NO2,h,i, NUL }; ADD(spu.ch2.duty.high); }
{ static const char label[] = { e,n,v,NO2,c,t,r, NUL }; ADD(spu.ch2.env.counter); }
{ static const char label[] = { e,n,v,NO2,v,o,l, NUL }; ADD(spu.ch2.env.volume); }
{ static const char label[] = { l,e,n,NO2,c,t,r, NUL }; ADD(spu.ch2.lcounter.counter); }
{ static const char label[] = { l,e,n,NO2,v,a,l, NUL }; ADD(spu.ch2.lcounter.lengthCounter); }
{ static const char label[] = { n,r,NO2,NO3, NUL }; ADD(spu.ch2.duty.nr3); }
{ static const char label[] = { n,r,NO2,NO4, NUL }; ADD(spu.ch2.nr4); }
{ static const char label[] = { c,NO2,m,a,s,t,r, NUL }; ADD(spu.ch2.master); }
{ static const char label[] = { w,a,v,e,r,a,m, NUL }; ADDPTR(spu.ch3.waveRam); }
{ static const char label[] = { l,e,n,NO3,c,t,r, NUL }; ADD(spu.ch3.lcounter.counter); }
{ static const char label[] = { l,e,n,NO3,v,a,l, NUL }; ADD(spu.ch3.lcounter.lengthCounter); }
{ static const char label[] = { w,a,v,e,c,t,r, NUL }; ADD(spu.ch3.waveCounter); }
{ static const char label[] = { l,w,a,v,r,d,t, NUL }; ADD(spu.ch3.lastReadTime); }
{ static const char label[] = { w,a,v,e,p,o,s, NUL }; ADD(spu.ch3.wavePos); }
{ static const char label[] = { w,a,v,s,m,p,l, NUL }; ADD(spu.ch3.sampleBuf); }
{ static const char label[] = { n,r,NO3,NO3, NUL }; ADD(spu.ch3.nr3); }
{ static const char label[] = { n,r,NO3,NO4, NUL }; ADD(spu.ch3.nr4); }
{ static const char label[] = { c,NO3,m,a,s,t,r, NUL }; ADD(spu.ch3.master); }
{ static const char label[] = { l,f,s,r,c,t,r, NUL }; ADD(spu.ch4.lfsr.counter); }
{ static const char label[] = { l,f,s,r,r,e,g, NUL }; ADD(spu.ch4.lfsr.reg); }
{ static const char label[] = { e,n,v,NO4,c,t,r, NUL }; ADD(spu.ch4.env.counter); }
{ static const char label[] = { e,n,v,NO4,v,o,l, NUL }; ADD(spu.ch4.env.volume); }
{ static const char label[] = { l,e,n,NO4,c,t,r, NUL }; ADD(spu.ch4.lcounter.counter); }
{ static const char label[] = { l,e,n,NO4,v,a,l, NUL }; ADD(spu.ch4.lcounter.lengthCounter); }
{ static const char label[] = { n,r,NO4,NO4, NUL }; ADD(spu.ch4.nr4); }
{ static const char label[] = { c,NO4,m,a,s,t,r, NUL }; ADD(spu.ch4.master); }
{ static const char label[] = { r,t,c,b,a,s,e, NUL }; ADD(rtc.baseTime); }
{ static const char label[] = { r,t,c,h,a,l,t, NUL }; ADD(rtc.haltTime); }
{ static const char label[] = { r,t,c,d,h, NUL }; ADD(rtc.dataDh); }
{ static const char label[] = { r,t,c,d,l, NUL }; ADD(rtc.dataDl); }
{ static const char label[] = { r,t,c,h, NUL }; ADD(rtc.dataH); }
{ static const char label[] = { r,t,c,m, NUL }; ADD(rtc.dataM); }
{ static const char label[] = { r,t,c,s, NUL }; ADD(rtc.dataS); }
{ static const char label[] = { r,t,c,l,l,d, NUL }; ADD(rtc.lastLatchData); }
{ static char const label[] = { h,NO3,b,a,s,e,t, NUL }; ADD(huc3.baseTime); }
{ static const char label[] = { h,NO3,h,a,l,t,t, NUL }; ADD(huc3.haltTime); }
{ static const char label[] = { h,NO3,d,a,t,a,t, NUL }; ADD(huc3.dataTime); }
{ static const char label[] = { h,NO3,w,r,i,t,t, NUL }; ADD(huc3.writingTime); }
{ static const char label[] = { h,NO3,h,a,l,t, NUL }; ADD(huc3.halted); }
{ static const char label[] = { h,NO3,s,h,f,t, NUL }; ADD(huc3.shift); }
{ static const char label[] = { h,NO3,r,v, NUL }; ADD(huc3.ramValue); }
{ static const char label[] = { h,NO3,m,f, NUL }; ADD(huc3.modeflag); }
{ static const char label[] = { h,NO3,i,r,c,y, NUL }; ADD(huc3.irBaseCycle); }
{ static const char label[] = { h,NO3,i,r,a,c, NUL }; ADD(huc3.irReceivingPulse); }
#undef ADD
#undef ADDPTR
#undef ADDARRAY
list.resize(list.size());
std::sort(list.begin(), list.end());
maxLabelsize_ = 0;
for (std::size_t i = 0; i < list.size(); ++i) {
if (list[i].labelsize > maxLabelsize_)
maxLabelsize_ = list[i].labelsize;
}
}
}
namespace {
static void writeSnapShot(omemstream &file) {
put24(file, 0);
}
/* Function-local static to make the construction lifetime
* explicit. The previous translation-unit-scope static was
* constructed at library load (before main / before any libretro
* entry point) which made its construction order against other
* library-load work implementation-defined. The list itself is
* read-only after construction so concurrent reads are safe. */
static const SaverList &saver_list(void) {
static SaverList instance;
return instance;
}
} // anon namespace
namespace gambatte {
void StateSaver::saveState(const SaveState &state, void *data) {
omemstream file(data);
if (file.fail())
return;
{ static const char ver[] = { 0, 1 }; file.write(ver, sizeof(ver)); }
writeSnapShot(file);
const SaverList &list = saver_list();
for (SaverList::const_iterator it = list.begin(); it != list.end(); ++it) {
file.write(it->label, it->labelsize);
(*it->save)(file, state);
}
}
bool StateSaver::loadState(SaveState &state, const void *data, size_t size) {
/* The bounds-checked imemstream needs the buffer end so it
* can stop at it. Previously this constructor only took a
* pointer; a malformed savestate could claim a 24-bit chunk
* size of 0xFFFFFF (16 MB) and the parser would happily skip
* past the end and read arbitrary memory. */
imemstream file(data, size);
if (file.fail() || file.get() != 0)
return false;
file.ignore();
file.ignore(get24(file));
const SaverList &list = saver_list();
const Array<char> labelbuf(list.maxLabelsize());
const Saver labelbufSaver = { labelbuf, 0, 0, list.maxLabelsize() };
SaverList::const_iterator done = list.begin();
while (file.good() && done != list.end()) {
file.getline(labelbuf, list.maxLabelsize(), NUL);
SaverList::const_iterator it = done;
if (std::strcmp(labelbuf, it->label)) {
it = std::lower_bound(it + 1, list.end(), labelbufSaver);
if (it == list.end() || std::strcmp(labelbuf, it->label)) {
file.ignore(get24(file));
continue;
}
} else
++done;
(*it->load)(file, state);
}
state.cpu.cycleCounter &= 0x7FFFFFFF;
state.spu.cycleCounter &= 0x7FFFFFFF;
/* If the parser tripped its failure latch the data was
* malformed; reject the load so the caller doesn't end up
* with a half-applied state. */
return !file.fail();
}
size_t StateSaver::stateSize(const SaveState &state) {
omemstream file(0);
if (file.fail())
return 0;
{ static const char ver[] = { 0, 1 }; file.write(ver, sizeof(ver)); }
writeSnapShot(file);
const SaverList &list = saver_list();
for (SaverList::const_iterator it = list.begin(); it != list.end(); ++it) {
file.write(it->label, it->labelsize);
(*it->save)(file, state);
}
return file.size();
}
}
+49
View File
@@ -0,0 +1,49 @@
/***************************************************************************
* Copyright (C) 2008 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef STATESAVER_H
#define STATESAVER_H
#include "gbint.h"
#include <string>
#include <stddef.h>
namespace gambatte {
struct SaveState;
class StateSaver {
StateSaver();
public:
enum { SS_SHIFT = 2 };
enum { SS_DIV = 1 << 2 };
enum { SS_WIDTH = 160 >> SS_SHIFT };
enum { SS_HEIGHT = 144 >> SS_SHIFT };
static void saveState(const SaveState &state, void *data);
/* size is the size of the input buffer in bytes. The parser
* uses it to bound every read so a malformed savestate cannot
* cause an OOB read past the end of the buffer. */
static bool loadState(SaveState &state, const void *data, size_t size);
static size_t stateSize(const SaveState &state);
};
}
#endif
+169
View File
@@ -0,0 +1,169 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "tima.h"
#include "savestate.h"
static unsigned char const timaClock[4] = { 10, 4, 6, 8 };
namespace gambatte {
Tima::Tima()
: lastUpdate_(0)
, tmatime_(disabled_time)
, tima_(0)
, tma_(0)
, tac_(0)
{
}
void Tima::saveState(SaveState &state) const {
state.mem.timaLastUpdate = lastUpdate_;
state.mem.tmatime = tmatime_;
}
void Tima::loadState(SaveState const &state, TimaInterruptRequester timaIrq) {
lastUpdate_ = state.mem.timaLastUpdate;
tmatime_ = state.mem.tmatime;
tima_ = state.mem.ioamhram.get()[0x105];
tma_ = state.mem.ioamhram.get()[0x106];
tac_ = state.mem.ioamhram.get()[0x107];
unsigned long nextIrqEventTime = disabled_time;
if (tac_ & 4) {
nextIrqEventTime = tmatime_ != disabled_time && tmatime_ > state.cpu.cycleCounter
? tmatime_
: lastUpdate_ + ((256u - tima_) << timaClock[tac_ & 3]) + 3;
}
timaIrq.setNextIrqEventTime(nextIrqEventTime);
}
void Tima::resetCc(unsigned long const oldCc, unsigned long const newCc, TimaInterruptRequester timaIrq) {
if (tac_ & 0x04) {
updateIrq(oldCc, timaIrq);
updateTima(oldCc);
unsigned long const dec = oldCc - newCc;
lastUpdate_ -= dec;
timaIrq.setNextIrqEventTime(timaIrq.nextIrqEventTime() - dec);
if (tmatime_ != disabled_time)
tmatime_ -= dec;
}
}
void Tima::updateTima(unsigned long const cc) {
unsigned long const ticks = (cc - lastUpdate_) >> timaClock[tac_ & 3];
lastUpdate_ += ticks << timaClock[tac_ & 3];
if (cc >= tmatime_) {
if (cc >= tmatime_ + 4)
tmatime_ = disabled_time;
tima_ = tma_;
}
unsigned long tmp = tima_ + ticks;
while (tmp > 0x100)
tmp -= 0x100 - tma_;
if (tmp == 0x100) {
tmp = 0;
tmatime_ = lastUpdate_ + 3;
if (cc >= tmatime_) {
if (cc >= tmatime_ + 4)
tmatime_ = disabled_time;
tmp = tma_;
}
}
tima_ = tmp;
}
void Tima::setTima(unsigned const data, unsigned long const cc, TimaInterruptRequester timaIrq) {
if (tac_ & 0x04) {
updateIrq(cc, timaIrq);
updateTima(cc);
if (tmatime_ - cc < 4)
tmatime_ = disabled_time;
timaIrq.setNextIrqEventTime(lastUpdate_ + ((256u - data) << timaClock[tac_ & 3]) + 3);
}
tima_ = data;
}
void Tima::setTma(unsigned const data, unsigned long const cc, TimaInterruptRequester timaIrq) {
if (tac_ & 0x04) {
updateIrq(cc, timaIrq);
updateTima(cc);
}
tma_ = data;
}
void Tima::setTac(unsigned const data, unsigned long const cc, TimaInterruptRequester timaIrq) {
if (tac_ ^ data) {
unsigned long nextIrqEventTime = timaIrq.nextIrqEventTime();
if (tac_ & 0x04) {
updateIrq(cc, timaIrq);
updateTima(cc);
lastUpdate_ -= (1u << (timaClock[tac_ & 3] - 1)) + 3;
tmatime_ -= (1u << (timaClock[tac_ & 3] - 1)) + 3;
nextIrqEventTime -= (1u << (timaClock[tac_ & 3] - 1)) + 3;
if (cc >= nextIrqEventTime)
timaIrq.flagIrq();
updateTima(cc);
tmatime_ = disabled_time;
nextIrqEventTime = disabled_time;
}
if (data & 4) {
lastUpdate_ = (cc >> timaClock[data & 3]) << timaClock[data & 3];
nextIrqEventTime = lastUpdate_ + ((256u - tima_) << timaClock[data & 3]) + 3;
}
timaIrq.setNextIrqEventTime(nextIrqEventTime);
}
tac_ = data;
}
unsigned Tima::tima(unsigned long cc) {
if (tac_ & 0x04)
updateTima(cc);
return tima_;
}
void Tima::doIrqEvent(TimaInterruptRequester timaIrq) {
timaIrq.flagIrq();
timaIrq.setNextIrqEventTime(timaIrq.nextIrqEventTime()
+ ((256u - tma_) << timaClock[tac_ & 3]));
}
}
+66
View File
@@ -0,0 +1,66 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef TIMA_H
#define TIMA_H
#include "interruptrequester.h"
namespace gambatte {
class TimaInterruptRequester {
public:
explicit TimaInterruptRequester(InterruptRequester &intreq) : intreq_(intreq) {}
void flagIrq() const { intreq_.flagIrq(4); }
unsigned long nextIrqEventTime() const { return intreq_.eventTime(intevent_tima); }
void setNextIrqEventTime(unsigned long time) const { intreq_.setEventTime<intevent_tima>(time); }
private:
InterruptRequester &intreq_;
};
class Tima {
public:
Tima();
void saveState(SaveState &) const;
void loadState(const SaveState &, TimaInterruptRequester timaIrq);
void resetCc(unsigned long oldCc, unsigned long newCc, TimaInterruptRequester timaIrq);
void setTima(unsigned tima, unsigned long cc, TimaInterruptRequester timaIrq);
void setTma(unsigned tma, unsigned long cc, TimaInterruptRequester timaIrq);
void setTac(unsigned tac, unsigned long cc, TimaInterruptRequester timaIrq);
unsigned tima(unsigned long cc);
void doIrqEvent(TimaInterruptRequester timaIrq);
private:
unsigned long lastUpdate_;
unsigned long tmatime_;
unsigned char tima_;
unsigned char tma_;
unsigned char tac_;
void updateIrq(unsigned long const cc, TimaInterruptRequester timaIrq) {
while (cc >= timaIrq.nextIrqEventTime())
doIrqEvent(timaIrq);
}
void updateTima(unsigned long cc);
};
}
#endif
+777
View File
@@ -0,0 +1,777 @@
/***************************************************************************
* Copyright (C) 2007 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "video.h"
#include "savestate.h"
#include <cstring>
#include <algorithm>
#include <string>
namespace gambatte
{
void LCD::reset(const unsigned char *const oamram, unsigned char const *vram, const bool cgb)
{
ppu_.reset(oamram, vram, cgb);
lycIrq_.setCgb(cgb);
refreshPalettes();
}
static unsigned long mode2IrqSchedule(const unsigned statReg, const LyCounter &lyCounter, const unsigned long cycleCounter)
{
if (!(statReg & 0x20))
return disabled_time;
unsigned next = lyCounter.time() - cycleCounter;
if (lyCounter.ly() >= 143 || (lyCounter.ly() == 142 && next <= 4) || (statReg & 0x08))
next += (153u - lyCounter.ly()) * lyCounter.lineTime();
else
{
if (next <= 4)
next += lyCounter.lineTime();
next -= 4;
}
return cycleCounter + next;
}
static inline unsigned long m0IrqTimeFromXpos166Time(
const unsigned long xpos166Time, const bool cgb, const bool ds)
{
return xpos166Time + cgb - ds;
}
static inline unsigned long hdmaTimeFromM0Time(
const unsigned long m0Time, const bool ds)
{
return m0Time + 1 - ds;
}
static unsigned long nextHdmaTime(const unsigned long lastM0Time,
const unsigned long nextM0Time, const unsigned long cycleCounter, const bool ds)
{
return cycleCounter < hdmaTimeFromM0Time(lastM0Time, ds)
? hdmaTimeFromM0Time(lastM0Time, ds)
: hdmaTimeFromM0Time(nextM0Time, ds);
}
void LCD::setStatePtrs(SaveState &state)
{
state.ppu.bgpData.set( bgpData_, sizeof bgpData_);
state.ppu.objpData.set(objpData_, sizeof objpData_);
ppu_.setStatePtrs(state);
}
void LCD::saveState(SaveState &state) const
{
state.mem.hdmaTransfer = hdmaIsEnabled();
state.ppu.nextM0Irq = eventTimes_(MODE0_IRQ) - ppu_.now();
state.ppu.pendingLcdstatIrq = eventTimes_(ONESHOT_LCDSTATIRQ) != disabled_time;
if (isCgb())
std::memcpy(state.ppu.dmgPalette, dmgColorsGBC_, 8 * 3);
lycIrq_.saveState(state);
m0Irq_.saveState(state);
ppu_.saveState(state);
}
void LCD::loadState(const SaveState &state, const unsigned char *const oamram)
{
statReg_ = state.mem.ioamhram.get()[0x141];
m2IrqStatReg_ = statReg_;
m1IrqStatReg_ = statReg_;
ppu_.loadState(state, oamram);
lycIrq_.loadState(state);
m0Irq_.loadState(state);
if (ppu_.lcdc() & 0x80)
{
nextM0Time_.predictNextM0Time(ppu_);
lycIrq_.reschedule(ppu_.lyCounter(), ppu_.now());
eventTimes_.setm<ONESHOT_LCDSTATIRQ>(state.ppu.pendingLcdstatIrq
? ppu_.now() + 1 : static_cast<unsigned long>(disabled_time));
eventTimes_.setm<ONESHOT_UPDATEWY2>(state.ppu.oldWy != state.mem.ioamhram.get()[0x14A]
? ppu_.now() + 1 : static_cast<unsigned long>(disabled_time));
eventTimes_.set<LY_COUNT>(ppu_.lyCounter().time());
eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), ppu_.now()));
eventTimes_.setm<LYC_IRQ>(lycIrq_.time());
eventTimes_.setm<MODE1_IRQ>(ppu_.lyCounter().nextFrameCycle(144 * 456, ppu_.now()));
eventTimes_.setm<MODE2_IRQ>(mode2IrqSchedule(statReg_, ppu_.lyCounter(), ppu_.now()));
eventTimes_.setm<MODE0_IRQ>((statReg_ & 0x08) ? ppu_.now() + state.ppu.nextM0Irq : static_cast<unsigned long>(disabled_time));
eventTimes_.setm<HDMA_REQ>(state.mem.hdmaTransfer
? nextHdmaTime(ppu_.lastM0Time(), nextM0Time_.predictedNextM0Time(), ppu_.now(), isDoubleSpeed())
: static_cast<unsigned long>(disabled_time));
}
else
{
for (int i = 0; i < NUM_MEM_EVENTS; ++i)
eventTimes_.set(static_cast<MemEvent>(i), disabled_time);
}
if (isCgb())
std::memcpy(dmgColorsGBC_, state.ppu.dmgPalette, 8 * 3);
refreshPalettes();
}
void LCD::refreshPalettes()
{
if (ppu_.cgb() && !ppu_.inDmgMode())
{
for (unsigned i = 0; i < 8 * 8; i += 2)
{
ppu_.bgPalette()[i >> 1] = gbcToRgb32( bgpData_[i] | bgpData_[i + 1] << 8);
ppu_.spPalette()[i >> 1] = gbcToRgb32(objpData_[i] | objpData_[i + 1] << 8);
}
/* CGB mode: rebuild all 8 BG palette expansions, the CGB
* renderer's hot do-while indexes [nattrib & 7] per tile. */
for (unsigned p = 0; p < 8; ++p)
ppu_.refreshBgPaletteExpansion(p);
}
else
{
if (ppu_.inDmgMode())
{
for (unsigned i = 0; i < 8 * 3; i += 2)
dmgColorsRgb32_[i >> 1] = gbcToRgb32( dmgColorsGBC_[i] | dmgColorsGBC_[i + 1] << 8);
}
setDmgPalette(ppu_.bgPalette() , dmgColorsRgb32_ , bgpData_[0]);
setDmgPalette(ppu_.spPalette() , dmgColorsRgb32_ + 4, objpData_[0]);
setDmgPalette(ppu_.spPalette() + 4, dmgColorsRgb32_ + 8, objpData_[1]);
/* DMG mode: only palette 0 is used by doFullTilesUnrolledDmg.
* Rebuild slot 0; slots 1..7 are unused but stay zero-init. */
ppu_.refreshBgPaletteExpansion(0);
}
}
void LCD::resetCc(const unsigned long oldCc, const unsigned long newCc)
{
update(oldCc);
ppu_.resetCc(oldCc, newCc);
if (ppu_.lcdc() & 0x80)
{
const unsigned long dec = oldCc - newCc;
nextM0Time_.invalidatePredictedNextM0Time();
lycIrq_.reschedule(ppu_.lyCounter(), newCc);
for (int i = 0; i < NUM_MEM_EVENTS; ++i)
{
if (eventTimes_(static_cast<MemEvent>(i)) != disabled_time)
eventTimes_.set(static_cast<MemEvent>(i), eventTimes_(static_cast<MemEvent>(i)) - dec);
}
eventTimes_.set<LY_COUNT>(ppu_.lyCounter().time());
}
}
void LCD::speedChange(const unsigned long cycleCounter)
{
update(cycleCounter);
ppu_.speedChange(cycleCounter);
if (ppu_.lcdc() & 0x80)
{
nextM0Time_.predictNextM0Time(ppu_);
lycIrq_.reschedule(ppu_.lyCounter(), cycleCounter);
eventTimes_.set<LY_COUNT>(ppu_.lyCounter().time());
eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), cycleCounter));
eventTimes_.setm<LYC_IRQ>(lycIrq_.time());
eventTimes_.setm<MODE1_IRQ>(ppu_.lyCounter().nextFrameCycle(144 * 456, cycleCounter));
eventTimes_.setm<MODE2_IRQ>(mode2IrqSchedule(statReg_, ppu_.lyCounter(), cycleCounter));
if (eventTimes_(MODE0_IRQ) != disabled_time && eventTimes_(MODE0_IRQ) - cycleCounter > 1)
eventTimes_.setm<MODE0_IRQ>(m0IrqTimeFromXpos166Time(ppu_.predictedNextXposTime(166), ppu_.cgb(), isDoubleSpeed()));
if (hdmaIsEnabled() && eventTimes_(HDMA_REQ) - cycleCounter > 1)
{
eventTimes_.setm<HDMA_REQ>(nextHdmaTime(ppu_.lastM0Time(),
nextM0Time_.predictedNextM0Time(), cycleCounter, isDoubleSpeed()));
}
}
}
static inline unsigned long m0TimeOfCurrentLine(const unsigned long nextLyTime,
const unsigned long lastM0Time, const unsigned long nextM0Time)
{
return nextM0Time < nextLyTime ? nextM0Time : lastM0Time;
}
unsigned long LCD::m0TimeOfCurrentLine(const unsigned long cc)
{
if (cc >= nextM0Time_.predictedNextM0Time())
{
update(cc);
nextM0Time_.predictNextM0Time(ppu_);
}
return gambatte::m0TimeOfCurrentLine(ppu_.lyCounter().time(), ppu_.lastM0Time(), nextM0Time_.predictedNextM0Time());
}
static bool isHdmaPeriod(const LyCounter &lyCounter,
const unsigned long m0TimeOfCurrentLy, const unsigned long cycleCounter)
{
const unsigned timeToNextLy = lyCounter.time() - cycleCounter;
return /*(ppu_.lcdc & 0x80) && */lyCounter.ly() < 144 && timeToNextLy > 4
&& cycleCounter >= hdmaTimeFromM0Time(m0TimeOfCurrentLy, lyCounter.isDoubleSpeed());
}
void LCD::enableHdma(const unsigned long cycleCounter)
{
if (cycleCounter >= nextM0Time_.predictedNextM0Time())
{
update(cycleCounter);
nextM0Time_.predictNextM0Time(ppu_);
}
else if (cycleCounter >= eventTimes_.nextEventTime())
update(cycleCounter);
if (isHdmaPeriod(ppu_.lyCounter(),
gambatte::m0TimeOfCurrentLine(ppu_.lyCounter().time(),
ppu_.lastM0Time(), nextM0Time_.predictedNextM0Time()), cycleCounter))
eventTimes_.flagHdmaReq();
eventTimes_.setm<HDMA_REQ>(nextHdmaTime(ppu_.lastM0Time(), nextM0Time_.predictedNextM0Time(), cycleCounter, isDoubleSpeed()));
}
void LCD::disableHdma(const unsigned long cycleCounter)
{
if (cycleCounter >= eventTimes_.nextEventTime())
update(cycleCounter);
eventTimes_.setm<HDMA_REQ>(disabled_time);
}
bool LCD::vramAccessible(const unsigned long cc)
{
if (cc >= eventTimes_.nextEventTime())
update(cc);
return !(ppu_.lcdc() & 0x80) || ppu_.lyCounter().ly() >= 144
|| ppu_.lyCounter().lineCycles(cc) < 80U
|| cc + isDoubleSpeed() - ppu_.cgb() + 2 >= m0TimeOfCurrentLine(cc);
}
bool LCD::cgbpAccessible(const unsigned long cc)
{
if (cc >= eventTimes_.nextEventTime())
update(cc);
return !(ppu_.lcdc() & 0x80) || ppu_.lyCounter().ly() >= 144
|| ppu_.lyCounter().lineCycles(cc) < 80U + isDoubleSpeed()
|| cc >= m0TimeOfCurrentLine(cc) + 3 - isDoubleSpeed();
}
void LCD::doCgbBgColorChange(unsigned index, const unsigned data, const unsigned long cc)
{
if (cgbpAccessible(cc))
{
update(cc);
doCgbColorChange(bgpData_, ppu_.bgPalette(), index, data);
/* CGB BCPD writes one 16-bit colour entry at a time
* (palette[index >> 1] in doCgbColorChange). Each CGB palette
* has 4 entries x 2 bytes = 8 bytes, so the affected palette
* is (index >> 1) >> 2 = index >> 3. Only that slot needs
* its expansion rebuilt. */
ppu_.refreshBgPaletteExpansion(index >> 3);
}
}
void LCD::doCgbSpColorChange(unsigned index, const unsigned data, const unsigned long cc)
{
if (cgbpAccessible(cc))
{
update(cc);
doCgbColorChange(objpData_, ppu_.spPalette(), index, data);
}
}
bool LCD::oamReadable(const unsigned long cc)
{
if (!(ppu_.lcdc() & 0x80) || ppu_.inactivePeriodAfterDisplayEnable(cc))
return true;
if (cc >= eventTimes_.nextEventTime())
update(cc);
if (ppu_.lyCounter().lineCycles(cc) + 4 - ppu_.lyCounter().isDoubleSpeed() * 3u >= 456)
return ppu_.lyCounter().ly() >= 144-1 && ppu_.lyCounter().ly() != 153;
return ppu_.lyCounter().ly() >= 144 || cc + isDoubleSpeed() - ppu_.cgb() + 2 >= m0TimeOfCurrentLine(cc);
}
bool LCD::oamWritable(const unsigned long cc)
{
if (!(ppu_.lcdc() & 0x80) || ppu_.inactivePeriodAfterDisplayEnable(cc))
return true;
if (cc >= eventTimes_.nextEventTime())
update(cc);
if (ppu_.lyCounter().lineCycles(cc) + 3 + ppu_.cgb() - ppu_.lyCounter().isDoubleSpeed() * 2u >= 456)
return ppu_.lyCounter().ly() >= 144-1 && ppu_.lyCounter().ly() != 153;
return ppu_.lyCounter().ly() >= 144 || cc + isDoubleSpeed() - ppu_.cgb() + 2 >= m0TimeOfCurrentLine(cc);
}
void LCD::mode3CyclesChange()
{
nextM0Time_.invalidatePredictedNextM0Time();
if (eventTimes_(MODE0_IRQ) != disabled_time
&& eventTimes_(MODE0_IRQ) > m0IrqTimeFromXpos166Time(ppu_.now(), ppu_.cgb(), isDoubleSpeed())) {
eventTimes_.setm<MODE0_IRQ>(m0IrqTimeFromXpos166Time(ppu_.predictedNextXposTime(166), ppu_.cgb(), isDoubleSpeed()));
}
if (eventTimes_(HDMA_REQ) != disabled_time
&& eventTimes_(HDMA_REQ) > hdmaTimeFromM0Time(ppu_.lastM0Time(), isDoubleSpeed())) {
nextM0Time_.predictNextM0Time(ppu_);
eventTimes_.setm<HDMA_REQ>(hdmaTimeFromM0Time(nextM0Time_.predictedNextM0Time(), isDoubleSpeed()));
}
}
void LCD::wxChange(const unsigned newValue, const unsigned long cycleCounter)
{
update(cycleCounter + isDoubleSpeed() + 1);
ppu_.setWx(newValue);
mode3CyclesChange();
}
void LCD::wyChange(const unsigned newValue, const unsigned long cc)
{
update(cc + 1);
ppu_.setWy(newValue);
// mode3CyclesChange(); // should be safe to wait until after wy2 delay, because no mode3 events are close to when wy1 is read.
// wy2 is a delayed version of wy. really just slowness of ly == wy comparison.
if (ppu_.cgb() && (ppu_.lcdc() & 0x80)) {
eventTimes_.setm<ONESHOT_UPDATEWY2>(cc + 5);
} else {
update(cc + 2);
ppu_.updateWy2();
mode3CyclesChange();
}
}
void LCD::scxChange(const unsigned newScx, const unsigned long cycleCounter) {
update(cycleCounter + ppu_.cgb() + isDoubleSpeed());
ppu_.setScx(newScx);
mode3CyclesChange();
}
void LCD::scyChange(const unsigned newValue, const unsigned long cycleCounter) {
update(cycleCounter + ppu_.cgb() + isDoubleSpeed());
ppu_.setScy(newValue);
}
void LCD::oamChange(const unsigned long cc) {
if (ppu_.lcdc() & 0x80) {
update(cc);
ppu_.oamChange(cc);
eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), cc));
}
}
void LCD::oamChange(const unsigned char *const oamram, const unsigned long cc) {
update(cc);
ppu_.oamChange(oamram, cc);
if (ppu_.lcdc() & 0x80)
eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), cc));
}
void LCD::lcdcChange(const unsigned data, const unsigned long cc) {
const unsigned oldLcdc = ppu_.lcdc();
update(cc);
if ((oldLcdc ^ data) & 0x80)
{
ppu_.setLcdc(data, cc);
if (data & 0x80) {
lycIrq_.lcdReset();
m0Irq_.lcdReset(statReg_, lycIrq_.lycReg());
if (lycIrq_.lycReg() == 0 && (statReg_ & 0x40))
eventTimes_.flagIrq(2);
nextM0Time_.predictNextM0Time(ppu_);
lycIrq_.reschedule(ppu_.lyCounter(), cc);
eventTimes_.set<LY_COUNT>(ppu_.lyCounter().time());
eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), cc));
eventTimes_.setm<LYC_IRQ>(lycIrq_.time());
eventTimes_.setm<MODE1_IRQ>(ppu_.lyCounter().nextFrameCycle(144 * 456, cc));
eventTimes_.setm<MODE2_IRQ>(mode2IrqSchedule(statReg_, ppu_.lyCounter(), cc));
if (statReg_ & 0x08)
eventTimes_.setm<MODE0_IRQ>(m0IrqTimeFromXpos166Time(ppu_.predictedNextXposTime(166), ppu_.cgb(), isDoubleSpeed()));
if (hdmaIsEnabled())
eventTimes_.setm<HDMA_REQ>(nextHdmaTime(ppu_.lastM0Time(),
nextM0Time_.predictedNextM0Time(), cc, isDoubleSpeed()));
}
else
{
for (int i = 0; i < NUM_MEM_EVENTS; ++i)
eventTimes_.set(static_cast<MemEvent>(i), disabled_time);
}
}
else if (data & 0x80)
{
if (ppu_.cgb())
{
ppu_.setLcdc((oldLcdc & ~0x14) | (data & 0x14), cc);
if ((oldLcdc ^ data) & 0x04)
eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), cc));
update(cc + isDoubleSpeed() + 1);
ppu_.setLcdc(data, cc + isDoubleSpeed() + 1);
if ((oldLcdc ^ data) & 0x20)
mode3CyclesChange();
}
else
{
ppu_.setLcdc(data, cc);
if ((oldLcdc ^ data) & 0x04)
eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), cc));
if ((oldLcdc ^ data) & 0x22)
mode3CyclesChange();
}
}
else
ppu_.setLcdc(data, cc);
}
namespace {
struct LyCnt {
unsigned ly; int timeToNextLy;
LyCnt(unsigned ly, int timeToNextLy) : ly(ly), timeToNextLy(timeToNextLy) {}
};
static LyCnt const getLycCmpLy(LyCounter const &lyCounter, unsigned long cc) {
unsigned ly = lyCounter.ly();
int timeToNextLy = lyCounter.time() - cc;
if (ly == 153)
{
unsigned is_doublespeed = (unsigned)lyCounter.isDoubleSpeed();
if (timeToNextLy - (448 << is_doublespeed) > 0)
timeToNextLy -= (448 << is_doublespeed);
else
{
ly = 0;
timeToNextLy += lyCounter.lineTime();
}
}
return LyCnt(ly, timeToNextLy);
}
}
void LCD::lcdstatChange(const unsigned data, const unsigned long cc)
{
if (cc >= eventTimes_.nextEventTime())
update(cc);
const unsigned old = statReg_;
statReg_ = data;
lycIrq_.statRegChange(data, ppu_.lyCounter(), cc);
if (ppu_.lcdc() & 0x80)
{
int const timeToNextLy = ppu_.lyCounter().time() - cc;
LyCnt const lycCmp = getLycCmpLy(ppu_.lyCounter(), cc);
if (!ppu_.cgb())
{
if (ppu_.lyCounter().ly() < 144)
{
if (cc + 1 < m0TimeOfCurrentLine(cc))
{
if (lycCmp.ly == lycIrq_.lycReg() && !(old & 0x40))
eventTimes_.flagIrq(2);
}
else
{
if (!(old & 0x08) && !(lycCmp.ly == lycIrq_.lycReg() && (old & 0x40)))
eventTimes_.flagIrq(2);
}
}
else
{
if (!(old & 0x10) && !(lycCmp.ly == lycIrq_.lycReg() && (old & 0x40)))
eventTimes_.flagIrq(2);
}
}
else if (data & ~old & 0x78)
{
bool const lycperiod = lycCmp.ly == lycIrq_.lycReg() && lycCmp.timeToNextLy > 4 - isDoubleSpeed() * 4;
if (!(lycperiod && (old & 0x40)))
{
if (ppu_.lyCounter().ly() < 144)
{
if (cc + isDoubleSpeed() * 2 < m0TimeOfCurrentLine(cc) || timeToNextLy <= 4)
{
if (lycperiod && (data & 0x40))
eventTimes_.flagIrq(2);
}
else if (!(old & 0x08))
{
if ((data & 0x08) || (lycperiod && (data & 0x40)))
eventTimes_.flagIrq(2);
}
}
else if (!(old & 0x10))
{
if ((data & 0x10) && (ppu_.lyCounter().ly() < 153 || timeToNextLy > 4 - isDoubleSpeed() * 4))
{
eventTimes_.flagIrq(2);
}
else if (lycperiod && (data & 0x40))
eventTimes_.flagIrq(2);
}
}
if ((data & 0x28) == 0x20 && !(old & 0x20)
&& ((timeToNextLy <= 4 && ppu_.lyCounter().ly() < 143)
|| (timeToNextLy == 456*2 && ppu_.lyCounter().ly() < 144)))
{
eventTimes_.flagIrq(2);
}
}
if ((data & 0x08) && eventTimes_(MODE0_IRQ) == disabled_time)
{
update(cc);
eventTimes_.setm<MODE0_IRQ>(m0IrqTimeFromXpos166Time(ppu_.predictedNextXposTime(166), ppu_.cgb(), isDoubleSpeed()));
}
eventTimes_.setm<MODE2_IRQ>(mode2IrqSchedule(data, ppu_.lyCounter(), cc));
eventTimes_.setm<LYC_IRQ>(lycIrq_.time());
}
m2IrqStatReg_ = eventTimes_(MODE2_IRQ) - cc > (ppu_.cgb() - isDoubleSpeed()) * 4U
? data : (m2IrqStatReg_ & 0x10) | (statReg_ & ~0x10);
m1IrqStatReg_ = eventTimes_(MODE1_IRQ) - cc > (ppu_.cgb() - isDoubleSpeed()) * 4U
? data : (m1IrqStatReg_ & 0x08) | (statReg_ & ~0x08);
m0Irq_.statRegChange(data, eventTimes_(MODE0_IRQ), cc, ppu_.cgb());
}
void LCD::lycRegChange(const unsigned data, const unsigned long cc)
{
unsigned const old = lycIrq_.lycReg();
if (data == old)
return;
if (cc >= eventTimes_.nextEventTime())
update(cc);
m0Irq_.lycRegChange(data, eventTimes_(MODE0_IRQ), cc, isDoubleSpeed(), ppu_.cgb());
lycIrq_.lycRegChange(data, ppu_.lyCounter(), cc);
if (!(ppu_.lcdc() & 0x80))
return;
eventTimes_.setm<LYC_IRQ>(lycIrq_.time());
int const timeToNextLy = ppu_.lyCounter().time() - cc;
unsigned is_doublespeed = (unsigned)isDoubleSpeed();
if ((statReg_ & 0x40) && data < 154
&& (ppu_.lyCounter().ly() < 144
? !(statReg_ & 0x08) || cc < m0TimeOfCurrentLine(cc) || timeToNextLy <= 4 << ppu_.cgb()
: !(statReg_ & 0x10) || (ppu_.lyCounter().ly() == 153 && timeToNextLy <= 4 && ppu_.cgb() && !(bool)is_doublespeed)))
{
LyCnt lycCmp = getLycCmpLy(ppu_.lyCounter(), cc);
if (lycCmp.timeToNextLy <= 4 << ppu_.cgb())
{
lycCmp.ly = old != lycCmp.ly || (lycCmp.timeToNextLy <= 4 && ppu_.cgb() && !(bool)is_doublespeed)
? (lycCmp.ly == 153 ? 0 : lycCmp.ly + 1)
: 0xFF; // simultaneous ly/lyc inc. lyc flag never goes low -> no trigger.
}
if (data == lycCmp.ly)
{
if (ppu_.cgb() && !(bool)is_doublespeed)
eventTimes_.setm<ONESHOT_LCDSTATIRQ>(cc + 5);
else
eventTimes_.flagIrq(2);
}
}
}
unsigned LCD::getStat(const unsigned lycReg, const unsigned long cc)
{
unsigned stat = 0;
if (ppu_.lcdc() & 0x80)
{
if (cc >= eventTimes_.nextEventTime())
update(cc);
int const timeToNextLy = ppu_.lyCounter().time() - cc;
unsigned is_doublespeed = (unsigned)isDoubleSpeed();
if (ppu_.lyCounter().ly() > 143)
{
if (ppu_.lyCounter().ly() < 153 || timeToNextLy > 4 - is_doublespeed * 4)
stat = 1;
}
else
{
unsigned const lineCycles = 456 - (timeToNextLy >> is_doublespeed);
if (lineCycles < 80)
{
if (!ppu_.inactivePeriodAfterDisplayEnable(cc))
stat = 2;
}
else if (cc + is_doublespeed - ppu_.cgb() + 2 < m0TimeOfCurrentLine(cc))
stat = 3;
}
LyCnt const lycCmp = getLycCmpLy(ppu_.lyCounter(), cc);
if (lycReg == lycCmp.ly && lycCmp.timeToNextLy > 4 - is_doublespeed * 4)
stat |= 4;
}
return stat;
}
inline void LCD::doMode2IrqEvent()
{
const unsigned ly = eventTimes_(LY_COUNT) - eventTimes_(MODE2_IRQ) < 8
? (ppu_.lyCounter().ly() == 153 ? 0 : ppu_.lyCounter().ly() + 1)
: ppu_.lyCounter().ly();
if ((ly != 0 || !(m2IrqStatReg_ & 0x10)) &&
(!(m2IrqStatReg_ & 0x40) || (lycIrq_.lycReg() != 0 ? ly != (lycIrq_.lycReg() + 1U) : ly > 1)))
eventTimes_.flagIrq(2);
m2IrqStatReg_ = statReg_;
if (!(statReg_ & 0x08))
{
unsigned long nextTime = eventTimes_(MODE2_IRQ) + ppu_.lyCounter().lineTime();
if (ly == 0)
nextTime -= 4;
else if (ly == 143)
nextTime += ppu_.lyCounter().lineTime() * 10 + 4;
eventTimes_.setm<MODE2_IRQ>(nextTime);
}
else
eventTimes_.setm<MODE2_IRQ>(eventTimes_(MODE2_IRQ) + (70224 << (unsigned)isDoubleSpeed()));
}
inline void LCD::event()
{
switch (eventTimes_.nextEvent())
{
case MEM_EVENT:
switch (eventTimes_.nextMemEvent())
{
case MODE1_IRQ:
eventTimes_.flagIrq((m1IrqStatReg_ & 0x18) == 0x10 ? 3 : 1);
m1IrqStatReg_ = statReg_;
eventTimes_.setm<MODE1_IRQ>(eventTimes_(MODE1_IRQ) + (70224 << (unsigned)isDoubleSpeed()));
break;
case LYC_IRQ:
{
unsigned char ifreg = 0;
lycIrq_.doEvent(&ifreg, ppu_.lyCounter());
eventTimes_.flagIrq(ifreg);
eventTimes_.setm<LYC_IRQ>(lycIrq_.time());
}
break;
case SPRITE_MAP:
eventTimes_.setm<SPRITE_MAP>(ppu_.doSpriteMapEvent(eventTimes_(SPRITE_MAP)));
mode3CyclesChange();
break;
case HDMA_REQ:
eventTimes_.flagHdmaReq();
nextM0Time_.predictNextM0Time(ppu_);
eventTimes_.setm<HDMA_REQ>(hdmaTimeFromM0Time(nextM0Time_.predictedNextM0Time(), isDoubleSpeed()));
break;
case MODE2_IRQ:
doMode2IrqEvent();
break;
case MODE0_IRQ:
{
unsigned char ifreg = 0;
m0Irq_.doEvent(&ifreg, ppu_.lyCounter().ly(), statReg_, lycIrq_.lycReg());
eventTimes_.flagIrq(ifreg);
}
eventTimes_.setm<MODE0_IRQ>((statReg_ & 0x08)
? m0IrqTimeFromXpos166Time(ppu_.predictedNextXposTime(166), ppu_.cgb(), isDoubleSpeed())
: static_cast<unsigned long>(disabled_time));
break;
case ONESHOT_LCDSTATIRQ:
eventTimes_.flagIrq(2);
eventTimes_.setm<ONESHOT_LCDSTATIRQ>(disabled_time);
break;
case ONESHOT_UPDATEWY2:
ppu_.updateWy2();
mode3CyclesChange();
eventTimes_.setm<ONESHOT_UPDATEWY2>(disabled_time);
break;
}
break;
case LY_COUNT:
ppu_.doLyCountEvent();
eventTimes_.set<LY_COUNT>(ppu_.lyCounter().time());
break;
}
}
void LCD::update(const unsigned long cycleCounter)
{
if (!(ppu_.lcdc() & 0x80))
return;
while (cycleCounter >= eventTimes_.nextEventTime())
{
ppu_.update(eventTimes_.nextEventTime());
event();
}
ppu_.update(cycleCounter);
}
}
+244
View File
@@ -0,0 +1,244 @@
/***************************************************************************
* Copyright (C) 2007 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef VIDEO_H
#define VIDEO_H
#include "interruptrequester.h"
#include "video/lyc_irq.h"
#include "video/m0_irq.h"
#include "video/next_m0_time.h"
#include "video/ppu.h"
#include <memory>
namespace gambatte {
class VideoInterruptRequester
{
public:
explicit VideoInterruptRequester(InterruptRequester &intreq) : intreq_(intreq) {}
void flagHdmaReq() const { gambatte::flagHdmaReq(intreq_); }
void flagIrq(const unsigned bit) const { intreq_.flagIrq(bit); }
void setNextEventTime(const unsigned long time) const { intreq_.setEventTime<intevent_video>(time); }
private:
InterruptRequester &intreq_;
};
class LCD
{
public:
LCD(const unsigned char *oamram, const unsigned char *vram_in, VideoInterruptRequester memEventRequester);
void reset(const unsigned char *oamram, unsigned char const *vram, bool cgb);
void setStatePtrs(SaveState &state);
void saveState(SaveState &state) const;
void loadState(const SaveState &state, const unsigned char *oamram);
void setDmgPaletteColor(unsigned palNum, unsigned colorNum, video_pixel_t rgb32);
void setVideoBuffer(video_pixel_t *videoBuf, int pitch);
void setDmgMode(bool mode) { ppu_.setDmgMode(mode); }
void swapToDMG() {
ppu_.setDmgMode(true);
refreshPalettes();
}
void dmgBgPaletteChange(const unsigned data, const unsigned long cycleCounter) {
update(cycleCounter);
bgpData_[0] = data;
setDmgPalette(ppu_.bgPalette(), dmgColorsRgb32_, data);
ppu_.refreshBgPaletteExpansion(0);
}
void dmgSpPalette1Change(const unsigned data, const unsigned long cycleCounter) {
update(cycleCounter);
objpData_[0] = data;
setDmgPalette(ppu_.spPalette(), dmgColorsRgb32_ + 4, data);
}
void dmgSpPalette2Change(const unsigned data, const unsigned long cycleCounter) {
update(cycleCounter);
objpData_[1] = data;
setDmgPalette(ppu_.spPalette() + 4, dmgColorsRgb32_ + 8, data);
}
void cgbBgColorChange(unsigned index, const unsigned data, const unsigned long cycleCounter) {
if (bgpData_[index] != data) {
doCgbBgColorChange(index, data, cycleCounter);
if(index < 8)
doCgbColorChange(dmgColorsGBC_, dmgColorsRgb32_, index, data);
}
}
void cgbSpColorChange(unsigned index, const unsigned data, const unsigned long cycleCounter) {
if (objpData_[index] != data) {
doCgbSpColorChange(index, data, cycleCounter);
if(index < 8 * 2/*dmg has 2 sprite banks*/)
doCgbColorChange(dmgColorsGBC_ + 8, dmgColorsRgb32_ + 4, index, data);
}
}
unsigned cgbBgColorRead(const unsigned index, const unsigned long cycleCounter) {
return (ppu_.cgb() & cgbpAccessible(cycleCounter)) ? bgpData_[index] : 0xFF;
}
unsigned cgbSpColorRead(const unsigned index, const unsigned long cycleCounter) {
return (ppu_.cgb() & cgbpAccessible(cycleCounter)) ? objpData_[index] : 0xFF;
}
void updateScreen(bool blanklcd, unsigned long cc);
void resetCc(unsigned long oldCC, unsigned long newCc);
void speedChange(unsigned long cycleCounter);
bool vramAccessible(unsigned long cycleCounter);
bool oamReadable(unsigned long cycleCounter);
bool oamWritable(unsigned long cycleCounter);
void wxChange(unsigned newValue, unsigned long cycleCounter);
void wyChange(unsigned newValue, unsigned long cycleCounter);
void oamChange(unsigned long cycleCounter);
void oamChange(const unsigned char *oamram, unsigned long cycleCounter);
void scxChange(unsigned newScx, unsigned long cycleCounter);
void scyChange(unsigned newValue, unsigned long cycleCounter);
void vramChange(const unsigned long cycleCounter) { update(cycleCounter); }
unsigned getStat(unsigned lycReg, unsigned long cycleCounter);
unsigned getLyReg(const unsigned long cycleCounter) {
unsigned lyReg = 0;
if (ppu_.lcdc() & 0x80) {
if (cycleCounter >= ppu_.lyCounter().time())
update(cycleCounter);
lyReg = ppu_.lyCounter().ly();
if (lyReg == 153) {
if (isDoubleSpeed()) {
if (ppu_.lyCounter().time() - cycleCounter <= 456 * 2 - 8)
lyReg = 0;
} else
lyReg = 0;
} else if (ppu_.lyCounter().time() - cycleCounter <= 4)
++lyReg;
}
return lyReg;
}
unsigned long nextMode1IrqTime() const { return eventTimes_(MODE1_IRQ); }
void lcdcChange(unsigned data, unsigned long cycleCounter);
void lcdstatChange(unsigned data, unsigned long cycleCounter);
void lycRegChange(unsigned data, unsigned long cycleCounter);
void enableHdma(unsigned long cycleCounter);
void disableHdma(unsigned long cycleCounter);
bool hdmaIsEnabled() const { return eventTimes_(HDMA_REQ) != disabled_time; }
void update(unsigned long cycleCounter);
bool isCgb() const { return ppu_.cgb(); }
bool isDoubleSpeed() const { return ppu_.lyCounter().isDoubleSpeed(); }
void setColorCorrection(bool colorCorrection);
void setColorCorrectionMode(unsigned colorCorrectionMode);
void setColorCorrectionBrightness(float colorCorrectionBrightness);
void setDarkFilterLevel(unsigned darkFilterLevel);
video_pixel_t gbcToRgb32(const unsigned bgr15);
private:
enum Event { MEM_EVENT, LY_COUNT }; enum { NUM_EVENTS = LY_COUNT + 1 };
enum MemEvent { ONESHOT_LCDSTATIRQ, ONESHOT_UPDATEWY2, MODE1_IRQ, LYC_IRQ, SPRITE_MAP,
HDMA_REQ, MODE2_IRQ, MODE0_IRQ }; enum { NUM_MEM_EVENTS = MODE0_IRQ + 1 };
class EventTimes
{
public:
explicit EventTimes(const VideoInterruptRequester memEventRequester) : memEventRequester_(memEventRequester) {}
Event nextEvent() const { return static_cast<Event>(eventMin_.min()); }
unsigned long nextEventTime() const { return eventMin_.minValue(); }
unsigned long operator()(const Event e) const { return eventMin_.value(e); }
template<Event e> void set(const unsigned long time) { eventMin_.setValue<e>(time); }
void set(const Event e, const unsigned long time) { eventMin_.setValue(e, time); }
MemEvent nextMemEvent() const { return static_cast<MemEvent>(memEventMin_.min()); }
unsigned long nextMemEventTime() const { return memEventMin_.minValue(); }
unsigned long operator()(const MemEvent e) const { return memEventMin_.value(e); }
template<MemEvent e> void setm(const unsigned long time) { memEventMin_.setValue<e>(time); setMemEvent(); }
void set(const MemEvent e, const unsigned long time) { memEventMin_.setValue(e, time); setMemEvent(); }
void flagIrq(const unsigned bit) { memEventRequester_.flagIrq(bit); }
void flagHdmaReq() { memEventRequester_.flagHdmaReq(); }
private:
MinKeeper<NUM_EVENTS> eventMin_;
MinKeeper<NUM_MEM_EVENTS> memEventMin_;
VideoInterruptRequester memEventRequester_;
void setMemEvent() {
const unsigned long nmet = nextMemEventTime();
eventMin_.setValue<MEM_EVENT>(nmet);
memEventRequester_.setNextEventTime(nmet);
}
};
PPU ppu_;
video_pixel_t dmgColorsRgb32_[3 * 4];
unsigned char dmgColorsGBC_[3 * 8];
unsigned char bgpData_[8 * 8];
unsigned char objpData_[8 * 8];
EventTimes eventTimes_;
M0Irq m0Irq_;
LycIrq lycIrq_;
NextM0Time nextM0Time_;
unsigned char statReg_;
unsigned char m2IrqStatReg_;
unsigned char m1IrqStatReg_;
static void setDmgPalette(video_pixel_t *palette, const video_pixel_t *dmgColors, unsigned data);
void setDmgPaletteColor(unsigned index, video_pixel_t rgb32);
void setDBuffer();
void refreshPalettes();
void doMode2IrqEvent();
void event();
unsigned long m0TimeOfCurrentLine(unsigned long cc);
bool cgbpAccessible(unsigned long cycleCounter);
void mode3CyclesChange();
void doCgbBgColorChange(unsigned index, unsigned data, unsigned long cycleCounter);
void doCgbSpColorChange(unsigned index, unsigned data, unsigned long cycleCounter);
bool colorCorrection;
unsigned colorCorrectionMode;
float colorCorrectionBrightness;
unsigned darkFilterLevel;
void doCgbColorChange(unsigned char *const pdata,
video_pixel_t *const palette, unsigned index, const unsigned data);
void darkenRgb(float &r, float &g, float &b);
};
}
#endif
+21
View File
@@ -0,0 +1,21 @@
#ifndef LCDDEF_H
#define LCDDEF_H
namespace gambatte {
enum { lcdc_bgen = 0x01,
lcdc_objen = 0x02,
lcdc_obj2x = 0x04,
lcdc_tdsel = 0x10,
lcdc_we = 0x20,
lcdc_en = 0x80 };
enum { lcdstat_lycflag = 0x04,
lcdstat_m0irqen = 0x08,
lcdstat_m1irqen = 0x10,
lcdstat_m2irqen = 0x20,
lcdstat_lycirqen = 0x40 };
}
#endif
@@ -0,0 +1,68 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "ly_counter.h"
#include "../savestate.h"
namespace gambatte {
LyCounter::LyCounter()
: time_(0)
, lineTime_(0)
, ly_(0)
, ds_(false)
{
setDoubleSpeed(false);
reset(0, 0);
}
void LyCounter::doEvent() {
++ly_;
if (ly_ == 154)
ly_ = 0;
time_ = time_ + lineTime_;
}
unsigned long LyCounter::nextLineCycle(unsigned const lineCycle, unsigned long const cc) const {
unsigned long tmp = time_ + (lineCycle << (unsigned)ds_);
if (tmp - cc > lineTime_)
tmp -= lineTime_;
return tmp;
}
unsigned long LyCounter::nextFrameCycle(unsigned long const frameCycle, unsigned long const cc) const {
unsigned long tmp = time_ + (((153U - ly()) * 456U + frameCycle) << (unsigned)ds_);
if (tmp - cc > 70224U << (unsigned)ds_)
tmp -= 70224U << (unsigned)ds_;
return tmp;
}
void LyCounter::reset(unsigned long videoCycles, unsigned long lastUpdate) {
ly_ = videoCycles / 456;
time_ = lastUpdate + ((456 - (videoCycles - ly_ * 456ul)) << (unsigned)isDoubleSpeed());
}
void LyCounter::setDoubleSpeed(bool ds) {
ds_ = ds;
lineTime_ = 456U << (unsigned)ds;
}
}
+57
View File
@@ -0,0 +1,57 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef LY_COUNTER_H
#define LY_COUNTER_H
namespace gambatte {
struct SaveState;
class LyCounter {
public:
LyCounter();
void doEvent();
bool isDoubleSpeed() const { return ds_; }
unsigned long frameCycles(unsigned long cc) const {
return ly_ * 456ul + lineCycles(cc);
}
unsigned lineCycles(unsigned long cc) const {
return 456u - ((time_ - cc) >> (unsigned)isDoubleSpeed());
}
unsigned lineTime() const { return lineTime_; }
unsigned ly() const { return ly_; }
unsigned long nextLineCycle(unsigned lineCycle, unsigned long cycleCounter) const;
unsigned long nextFrameCycle(unsigned long frameCycle, unsigned long cycleCounter) const;
void reset(unsigned long videoCycles, unsigned long lastUpdate);
void setDoubleSpeed(bool ds);
unsigned long time() const { return time_; }
private:
unsigned long time_;
unsigned short lineTime_;
unsigned char ly_;
bool ds_;
};
}
#endif
+108
View File
@@ -0,0 +1,108 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "lyc_irq.h"
#include "counterdef.h"
#include "lcddef.h"
#include "ly_counter.h"
#include "savestate.h"
#include <algorithm>
namespace gambatte {
LycIrq::LycIrq()
: time_(disabled_time)
, lycRegSrc_(0)
, statRegSrc_(0)
, lycReg_(0)
, statReg_(0)
, cgb_(false)
{
}
static unsigned long schedule(unsigned statReg,
unsigned lycReg, LyCounter const &lyCounter, unsigned long cc) {
return (statReg & lcdstat_lycirqen) && lycReg < 154
? lyCounter.nextFrameCycle(lycReg ? lycReg * 456 : 153 * 456 + 8, cc)
: static_cast<unsigned long>(disabled_time);
}
void LycIrq::regChange(unsigned const statReg,
unsigned const lycReg, LyCounter const &lyCounter, unsigned long const cc) {
unsigned long const timeSrc = schedule(statReg, lycReg, lyCounter, cc);
statRegSrc_ = statReg;
lycRegSrc_ = lycReg;
time_ = std::min(time_, timeSrc);
if (cgb_) {
if (time_ - cc > 8 || (timeSrc != time_ && time_ - cc > 4U - lyCounter.isDoubleSpeed() * 4U))
lycReg_ = lycReg;
if (time_ - cc > 4U - lyCounter.isDoubleSpeed() * 4U)
statReg_ = statReg;
} else {
if (time_ - cc > 4 || timeSrc != time_)
lycReg_ = lycReg;
if (time_ - cc > 4 || lycReg_ != 0)
statReg_ = statReg;
statReg_ = (statReg_ & lcdstat_lycirqen) | (statReg & ~lcdstat_lycirqen);
}
}
static bool lycIrqBlockedByM2OrM1StatIrq(unsigned ly, unsigned statreg) {
return ly - 1u < 144u - 1u
? statreg & lcdstat_m2irqen
: statreg & lcdstat_m1irqen;
}
void LycIrq::doEvent(unsigned char *const ifreg, LyCounter const &lyCounter) {
if ((statReg_ | statRegSrc_) & lcdstat_lycirqen) {
unsigned cmpLy = lyCounter.time() - time_ < lyCounter.lineTime() ? 0 : lyCounter.ly();
if (lycReg_ == cmpLy && !lycIrqBlockedByM2OrM1StatIrq(lycReg_, statReg_))
*ifreg |= 2;
}
lycReg_ = lycRegSrc_;
statReg_ = statRegSrc_;
time_ = schedule(statReg_, lycReg_, lyCounter, time_);
}
void LycIrq::loadState(SaveState const &state) {
lycRegSrc_ = state.mem.ioamhram.get()[0x145];
statRegSrc_ = state.mem.ioamhram.get()[0x141];
lycReg_ = state.ppu.lyc;
statReg_ = statRegSrc_;
}
void LycIrq::saveState(SaveState &state) const {
state.ppu.lyc = lycReg_;
}
void LycIrq::reschedule(LyCounter const &lyCounter, unsigned long cc) {
time_ = std::min(schedule(statReg_ , lycReg_ , lyCounter, cc),
schedule(statRegSrc_, lycRegSrc_, lyCounter, cc));
}
void LycIrq::lcdReset() {
statReg_ = statRegSrc_;
lycReg_ = lycRegSrc_;
}
}
+61
View File
@@ -0,0 +1,61 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef VIDEO_LYC_IRQ_H
#define VIDEO_LYC_IRQ_H
namespace gambatte {
struct SaveState;
class LyCounter;
class LycIrq {
public:
LycIrq();
void doEvent(unsigned char *ifreg, LyCounter const &lyCounter);
unsigned lycReg() const { return lycRegSrc_; }
void loadState(SaveState const &state);
void saveState(SaveState &state) const;
unsigned long time() const { return time_; }
void setCgb(bool cgb) { cgb_ = cgb; }
void lcdReset();
void reschedule(LyCounter const &lyCounter, unsigned long cc);
void statRegChange(unsigned statReg, LyCounter const &lyCounter, unsigned long cc) {
regChange(statReg, lycRegSrc_, lyCounter, cc);
}
void lycRegChange(unsigned lycReg, LyCounter const &lyCounter, unsigned long cc) {
regChange(statRegSrc_, lycReg, lyCounter, cc);
}
private:
unsigned long time_;
unsigned char lycRegSrc_;
unsigned char statRegSrc_;
unsigned char lycReg_;
unsigned char statReg_;
bool cgb_;
void regChange(unsigned statReg, unsigned lycReg,
LyCounter const &lyCounter, unsigned long cc);
};
}
#endif
+63
View File
@@ -0,0 +1,63 @@
#ifndef M0_IRQ_H
#define M0_IRQ_H
#include "lcddef.h"
#include "../savestate.h"
namespace gambatte {
class M0Irq {
public:
M0Irq()
: statReg_(0)
, lycReg_(0)
{
}
void lcdReset(unsigned statReg, unsigned lycReg) {
statReg_ = statReg;
lycReg_ = lycReg;
}
void statRegChange(unsigned statReg,
unsigned long nextM0IrqTime, unsigned long cc, bool cgb) {
if (nextM0IrqTime - cc > cgb * 2U)
statReg_ = statReg;
}
void lycRegChange(unsigned lycReg,
unsigned long nextM0IrqTime, unsigned long cc,
bool ds, bool cgb) {
if (nextM0IrqTime - cc > cgb * 5 + 1U - ds)
lycReg_ = lycReg;
}
void doEvent(unsigned char *ifreg, unsigned ly, unsigned statReg, unsigned lycReg) {
if (((statReg_ | statReg) & lcdstat_m0irqen)
&& (!(statReg_ & lcdstat_lycirqen) || ly != lycReg_)) {
*ifreg |= 2;
}
statReg_ = statReg;
lycReg_ = lycReg;
}
void saveState(SaveState &state) const {
state.ppu.m0lyc = lycReg_;
}
void loadState(SaveState const &state) {
lycReg_ = state.ppu.m0lyc;
statReg_ = state.mem.ioamhram.get()[0x141];
}
unsigned statReg() const { return statReg_; }
private:
unsigned char statReg_;
unsigned char lycReg_;
};
}
#endif
@@ -0,0 +1,6 @@
#include "next_m0_time.h"
#include "ppu.h"
void gambatte::NextM0Time::predictNextM0Time(PPU const &ppu) {
predictedNextM0Time_ = ppu.predictedNextXposTime(167);
}
@@ -0,0 +1,19 @@
#ifndef NEXT_M0_TIME_H_
#define NEXT_M0_TIME_H_
namespace gambatte {
class NextM0Time {
public:
NextM0Time() : predictedNextM0Time_(0) {}
void predictNextM0Time(class PPU const &v);
void invalidatePredictedNextM0Time() { predictedNextM0Time_ = 0; }
unsigned predictedNextM0Time() const { return predictedNextM0Time_; }
private:
unsigned predictedNextM0Time_;
};
}
#endif
File diff suppressed because it is too large Load Diff
+199
View File
@@ -0,0 +1,199 @@
//
// Copyright (C) 2010 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef PPU_H
#define PPU_H
#include "lcddef.h"
#include "ly_counter.h"
#include "sprite_mapper.h"
#include "gbint.h"
#include "gambatte.h"
#include <cstddef>
namespace gambatte {
class PPUFrameBuf {
public:
PPUFrameBuf() : buf_(0), fbline_(nullfbline()), pitch_(0) {}
video_pixel_t * fb() const { return buf_; }
video_pixel_t * fbline() const { return fbline_; }
std::ptrdiff_t pitch() const { return pitch_; }
void setBuf(video_pixel_t *buf, std::ptrdiff_t pitch) { buf_ = buf; pitch_ = pitch; fbline_ = nullfbline(); }
void setFbline(unsigned ly) { fbline_ = buf_ ? buf_ + std::ptrdiff_t(ly) * pitch_ : nullfbline(); }
private:
video_pixel_t *buf_;
video_pixel_t *fbline_;
std::ptrdiff_t pitch_;
/* Shared 160-pixel scratch used as a destination when no real
* framebuf is attached. With DUAL_MODE compiled in, both PPU
* instances would write to this same buffer; the libretro
* core is single-threaded so there is no data race, but it is
* shared mutable state and nothing in the current code path
* actually reads it back. Make it a non-static member of
* PPUFrameBuf if DUAL_MODE is ever revived as a real feature. */
static video_pixel_t * nullfbline() { static video_pixel_t nullfbline_[160]; return nullfbline_; }
};
struct PPUPriv;
struct PPUState {
void (*f)(PPUPriv &v);
unsigned (*predictCyclesUntilXpos_f)(PPUPriv const &v, int targetxpos, unsigned cycles);
unsigned char id;
};
struct PPUPriv {
video_pixel_t bgPalette[8 * 4];
video_pixel_t spPalette[8 * 4];
/* Precomputed BG palette expansion.
*
* Indexed by [palette_id][byte][lane]: each byte of ntileword
* packs four 2-bit palette indices (lanes 0..3 in byte order),
* so each bgPaletteExpanded[p][byte] holds the four resolved
* colours for that nibble of the tile word. Both the DMG and
* CGB renderers issue two 16-byte loads + two 16-byte stores
* per 8-pixel tile instead of eight dependent scalar loads from
* bgPalette.
*
* DMG mode uses only palette 0. CGB mode picks palette
* (nattrib & 7) per tile. The table is rebuilt only when a BG
* palette changes: BGP / refresh / doCgbBgColorChange paths.
*
* Size: 8 * 256 * 4 * sizeof(video_pixel_t). For the default
* libretro u32 build that's 32 KiB; for the VIDEO_RGB565 /
* VIDEO_ABGR1555 u16 builds it's 16 KiB. Modern x86 L1d is
* 32-48 KiB and Cortex-A7/A53/A72 L1d is 32 KiB, so the worst
* case fits. In practice each frame uses only a few palettes
* actively, so the hot working set is much smaller than the
* full table. */
video_pixel_t bgPaletteExpanded[8][256][4];
struct Sprite { unsigned char spx, oampos, line, attrib; } spriteList[11];
unsigned short spwordList[11];
unsigned char nextSprite;
unsigned char currentSprite;
unsigned char const *vram;
PPUState const *nextCallPtr;
unsigned long now;
unsigned long lastM0Time;
long cycles;
unsigned tileword;
unsigned ntileword;
SpriteMapper spriteMapper;
LyCounter lyCounter;
PPUFrameBuf framebuf;
unsigned char lcdc;
unsigned char scy;
unsigned char scx;
unsigned char wy;
unsigned char wy2;
unsigned char wx;
unsigned char winDrawState;
unsigned char wscx;
unsigned char winYPos;
unsigned char reg0;
unsigned char reg1;
unsigned char attrib;
unsigned char nattrib;
unsigned char xpos;
unsigned char endx;
bool cgb;
bool dmgMode;
bool weMaster;
PPUPriv(NextM0Time &nextM0Time, unsigned char const *oamram, unsigned char const *vram);
};
class PPU {
public:
PPU(NextM0Time &nextM0Time, unsigned char const *oamram, unsigned char const *vram)
: p_(nextM0Time, oamram, vram)
{
}
video_pixel_t * bgPalette() { return p_.bgPalette; }
/* Recompute bgPaletteExpanded[palette_id] from the current
* bgPalette[palette_id*4 .. palette_id*4+3]. DMG mode uses
* palette 0 only. Called by the LCD on every BG palette write
* (BGP register, CGB BCPD register, refresh / savestate paths).
* Idempotent and side-effect-free. */
void refreshBgPaletteExpansion(unsigned const palette_id)
{
video_pixel_t const *const pal = p_.bgPalette + palette_id * 4;
video_pixel_t const c0 = pal[0];
video_pixel_t const c1 = pal[1];
video_pixel_t const c2 = pal[2];
video_pixel_t const c3 = pal[3];
video_pixel_t const cs[4] = { c0, c1, c2, c3 };
for (int b = 0; b < 256; ++b) {
p_.bgPaletteExpanded[palette_id][b][0] = cs[ b & 3];
p_.bgPaletteExpanded[palette_id][b][1] = cs[(b >> 2) & 3];
p_.bgPaletteExpanded[palette_id][b][2] = cs[(b >> 4) & 3];
p_.bgPaletteExpanded[palette_id][b][3] = cs[ b >> 6 ];
}
}
bool cgb() const { return p_.cgb; }
void setDmgMode(bool mode) { p_.dmgMode = mode; }
bool inDmgMode() const { return p_.dmgMode; }
void doLyCountEvent() { p_.lyCounter.doEvent(); }
unsigned long doSpriteMapEvent(unsigned long time) { return p_.spriteMapper.doEvent(time); }
PPUFrameBuf const & frameBuf() const { return p_.framebuf; }
bool inactivePeriodAfterDisplayEnable(unsigned long cc) const {
return p_.spriteMapper.inactivePeriodAfterDisplayEnable(cc);
}
unsigned long lastM0Time() const { return p_.lastM0Time; }
unsigned lcdc() const { return p_.lcdc; }
void loadState(SaveState const &state, unsigned char const *oamram);
LyCounter const & lyCounter() const { return p_.lyCounter; }
unsigned long now() const { return p_.now; }
void oamChange(unsigned long cc) { p_.spriteMapper.oamChange(cc); }
void oamChange(unsigned char const *oamram, unsigned long cc) { p_.spriteMapper.oamChange(oamram, cc); }
unsigned long predictedNextXposTime(unsigned xpos) const;
void reset(unsigned char const *oamram, unsigned char const *vram, bool cgb);
void resetCc(unsigned long oldCc, unsigned long newCc);
void saveState(SaveState &ss) const;
void setFrameBuf(video_pixel_t *buf, std::ptrdiff_t pitch) { p_.framebuf.setBuf(buf, pitch); }
void setLcdc(unsigned lcdc, unsigned long cc);
void setScx(unsigned scx) { p_.scx = scx; }
void setScy(unsigned scy) { p_.scy = scy; }
void setStatePtrs(SaveState &ss) { p_.spriteMapper.setStatePtrs(ss); }
void setWx(unsigned wx) { p_.wx = wx; }
void setWy(unsigned wy) { p_.wy = wy; }
void updateWy2() { p_.wy2 = p_.wy; }
void speedChange(unsigned long cycleCounter);
video_pixel_t * spPalette() { return p_.spPalette; }
void update(unsigned long cc);
private:
PPUPriv p_;
};
}
#endif
@@ -0,0 +1,203 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "sprite_mapper.h"
#include "counterdef.h"
#include "next_m0_time.h"
#include "../insertion_sort.h"
#include <algorithm>
#include <cstring>
namespace {
class SpxLess {
public:
explicit SpxLess(unsigned char const *spxlut) : spxlut_(spxlut) {}
bool operator()(unsigned char lhs, unsigned char rhs) const {
return spxlut_[lhs] < spxlut_[rhs];
}
private:
unsigned char const *const spxlut_;
};
}
namespace gambatte {
SpriteMapper::OamReader::OamReader(LyCounter const &lyCounter, unsigned char const *oamram)
: lyCounter_(lyCounter)
, oamram_(oamram)
, cgb_(false)
{
reset(oamram, false);
}
void SpriteMapper::OamReader::reset(unsigned char const *const oamram, bool const cgb) {
oamram_ = oamram;
cgb_ = cgb;
setLargeSpritesSrc(false);
lu_ = 0;
lastChange_ = 0xFF;
std::fill(szbuf_, szbuf_ + 40, largeSpritesSrc_);
unsigned pos = 0;
unsigned distance = 80;
while (distance--) {
buf_[pos] = oamram[((pos * 2) & ~3) | (pos & 1)];
++pos;
}
}
static unsigned toPosCycles(unsigned long const cc, LyCounter const &lyCounter) {
unsigned lc = lyCounter.lineCycles(cc) + 3 - lyCounter.isDoubleSpeed() * 3u;
if (lc >= 456)
lc -= 456;
return lc;
}
void SpriteMapper::OamReader::update(unsigned long const cc) {
if (cc > lu_) {
if (changed()) {
unsigned const lulc = toPosCycles(lu_, lyCounter_);
unsigned pos = std::min(lulc, 80u);
unsigned distance = 80;
unsigned is_doublespeed = (unsigned)lyCounter_.isDoubleSpeed();
if ((cc - lu_) >> is_doublespeed < 456)
{
unsigned cclc = toPosCycles(cc, lyCounter_);
distance = std::min(cclc, 80u) - pos + (cclc < lulc ? 80 : 0);
}
{
unsigned targetDistance =
lastChange_ - pos + (lastChange_ <= pos ? 80 : 0);
if (targetDistance <= distance) {
distance = targetDistance;
lastChange_ = 0xFF;
}
}
while (distance--) {
if (!(pos & 1)) {
if (pos == 80)
pos = 0;
if (cgb_)
szbuf_[pos >> 1] = largeSpritesSrc_;
buf_[pos ] = oamram_[pos * 2 ];
buf_[pos + 1] = oamram_[pos * 2 + 1];
} else
szbuf_[pos >> 1] = (szbuf_[pos >> 1] & cgb_) | largeSpritesSrc_;
++pos;
}
}
lu_ = cc;
}
}
void SpriteMapper::OamReader::change(unsigned long cc) {
update(cc);
lastChange_ = std::min(toPosCycles(lu_, lyCounter_), 80u);
}
void SpriteMapper::OamReader::setStatePtrs(SaveState &state) {
state.ppu.oamReaderBuf.set(buf_, sizeof buf_);
state.ppu.oamReaderSzbuf.set(szbuf_, sizeof szbuf_ / sizeof *szbuf_);
}
void SpriteMapper::OamReader::loadState(SaveState const &ss, unsigned char const *const oamram) {
oamram_ = oamram;
largeSpritesSrc_ = ss.mem.ioamhram.get()[0x140] >> 2 & 1;
lu_ = ss.ppu.enableDisplayM0Time;
change(lu_);
}
void SpriteMapper::OamReader::enableDisplay(unsigned long cc)
{
std::memset(buf_, 0x00, sizeof buf_);
std::fill(szbuf_, szbuf_ + 40, false);
unsigned is_doublespeed = (unsigned)lyCounter_.isDoubleSpeed();
lu_ = cc + (80 << is_doublespeed);
lastChange_ = 80;
}
SpriteMapper::SpriteMapper(NextM0Time &nextM0Time,
LyCounter const &lyCounter,
unsigned char const *oamram)
: nextM0Time_(nextM0Time)
, oamReader_(lyCounter, oamram)
{
clearMap();
}
void SpriteMapper::reset(unsigned char const *oamram, bool cgb) {
oamReader_.reset(oamram, cgb);
clearMap();
}
void SpriteMapper::clearMap() {
std::memset(num_, need_sorting_mask, sizeof num_);
}
void SpriteMapper::mapSprites() {
clearMap();
for (unsigned i = 0x00; i < 0x50; i += 2) {
int const spriteHeight = 8 << (unsigned)largeSprites(i >> 1);
unsigned const bottomPos = posbuf()[i] - (17u - spriteHeight);
if (bottomPos < 143u + spriteHeight) {
unsigned const startly = std::max(int(bottomPos) + 1 - spriteHeight, 0);
unsigned char *map = spritemap_ + startly * 10;
unsigned char *n = num_ + startly;
unsigned char *const nend = num_ + std::min(bottomPos, 143u) + 1;
do {
if (*n < need_sorting_mask + 10)
map[(*n)++ - need_sorting_mask] = i;
map += 10;
} while (++n != nend);
}
}
nextM0Time_.invalidatePredictedNextM0Time();
}
void SpriteMapper::sortLine(unsigned const ly) const {
num_[ly] &= ~need_sorting_mask;
insertionSort(spritemap_ + ly * 10, spritemap_ + ly * 10 + num_[ly],
SpxLess(posbuf() + 1));
}
unsigned long SpriteMapper::doEvent(unsigned long const time) {
oamReader_.update(time);
mapSprites();
return oamReader_.changed()
? time + oamReader_.lineTime()
: static_cast<unsigned long>(disabled_time);
}
}
@@ -0,0 +1,122 @@
//
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License version 2 for more details.
//
// You should have received a copy of the GNU General Public License
// version 2 along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#ifndef SPRITE_MAPPER_H
#define SPRITE_MAPPER_H
#include "ly_counter.h"
#include "../savestate.h"
namespace gambatte {
class NextM0Time;
class SpriteMapper {
public:
SpriteMapper(NextM0Time &nextM0Time,
LyCounter const &lyCounter,
unsigned char const *oamram);
void reset(unsigned char const *oamram, bool cgb);
unsigned long doEvent(unsigned long time);
bool largeSprites(unsigned spNo) const { return oamReader_.largeSprites(spNo); }
unsigned numSprites(unsigned ly) const { return num_[ly] & ~need_sorting_mask; }
void oamChange(unsigned long cc) { oamReader_.change(cc); }
void oamChange(unsigned char const *oamram, unsigned long cc) { oamReader_.change(oamram, cc); }
unsigned char const * oamram() const { return oamReader_.oam(); }
unsigned char const * posbuf() const { return oamReader_.spritePosBuf(); }
void preSpeedChange(unsigned long cc) { oamReader_.update(cc); }
void postSpeedChange(unsigned long cc) { oamReader_.change(cc); }
void resetCycleCounter(unsigned long oldCc, unsigned long newCc) {
oamReader_.update(oldCc);
oamReader_.resetCycleCounter(oldCc, newCc);
}
void setLargeSpritesSource(bool src) { oamReader_.setLargeSpritesSrc(src); }
unsigned char const * sprites(unsigned ly) const {
if (num_[ly] & need_sorting_mask)
sortLine(ly);
return spritemap_ + ly * 10;
}
void setStatePtrs(SaveState &state) { oamReader_.setStatePtrs(state); }
void enableDisplay(unsigned long cc) { oamReader_.enableDisplay(cc); }
void saveState(SaveState &state) const { oamReader_.saveState(state); }
void loadState(SaveState const &state, unsigned char const *oamram) {
oamReader_.loadState(state, oamram);
mapSprites();
}
bool inactivePeriodAfterDisplayEnable(unsigned long cc) const {
return oamReader_.inactivePeriodAfterDisplayEnable(cc);
}
static unsigned long schedule(LyCounter const &lyCounter, unsigned long cc) {
return lyCounter.nextLineCycle(80, cc);
}
private:
class OamReader {
public:
OamReader(LyCounter const &lyCounter, unsigned char const *oamram);
void reset(unsigned char const *oamram, bool cgb);
void change(unsigned long cc);
void change(unsigned char const *oamram, unsigned long cc) { change(cc); oamram_ = oamram; }
bool changed() const { return lastChange_ != 0xFF; }
bool largeSprites(unsigned spNo) const { return szbuf_[spNo]; }
unsigned char const * oam() const { return oamram_; }
void resetCycleCounter(unsigned long oldCc, unsigned long newCc) { lu_ -= oldCc - newCc; }
void setLargeSpritesSrc(bool src) { largeSpritesSrc_ = src; }
void update(unsigned long cc);
unsigned char const * spritePosBuf() const { return buf_; }
void setStatePtrs(SaveState &state);
void enableDisplay(unsigned long cc);
void saveState(SaveState &state) const { state.ppu.enableDisplayM0Time = lu_; }
void loadState(SaveState const &ss, unsigned char const *oamram);
bool inactivePeriodAfterDisplayEnable(unsigned long cc) const { return cc < lu_; }
unsigned lineTime() const { return lyCounter_.lineTime(); }
private:
unsigned char buf_[80];
bool szbuf_[40];
LyCounter const &lyCounter_;
unsigned char const *oamram_;
unsigned long lu_;
unsigned char lastChange_;
bool largeSpritesSrc_;
bool cgb_;
};
enum { need_sorting_mask = 0x80 };
mutable unsigned char spritemap_[144 * 10];
mutable unsigned char num_[144];
NextM0Time &nextM0Time_;
OamReader oamReader_;
void clearMap();
void mapSprites();
void sortLine(unsigned ly) const;
};
}
#endif
+321
View File
@@ -0,0 +1,321 @@
/***************************************************************************
* Copyright (C) 2007 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "video.h"
#include "savestate.h"
#include <cstring>
#include <algorithm>
#include <cmath>
/* GBC colour correction factors */
#define GBC_CC_LUM 0.94f
#define GBC_CC_R 0.82f
#define GBC_CC_G 0.665f
#define GBC_CC_B 0.73f
#define GBC_CC_RG 0.125f
#define GBC_CC_RB 0.195f
#define GBC_CC_GR 0.24f
#define GBC_CC_GB 0.075f
#define GBC_CC_BR -0.06f
#define GBC_CC_BG 0.21f
namespace gambatte
{
void LCD::setDmgPaletteColor(const unsigned index, const video_pixel_t rgb32)
{
dmgColorsRgb32_[index] = rgb32;
}
void LCD::setDmgPalette(video_pixel_t *const palette, const video_pixel_t *const dmgColors, const unsigned data)
{
palette[0] = dmgColors[data & 3];
palette[1] = dmgColors[data >> 2 & 3];
palette[2] = dmgColors[data >> 4 & 3];
palette[3] = dmgColors[data >> 6 & 3];
}
void LCD::setColorCorrection(bool colorCorrection_)
{
colorCorrection = colorCorrection_;
refreshPalettes();
}
void LCD::setColorCorrectionMode(unsigned colorCorrectionMode_)
{
// Note: We only have two modes, so could make 'colorCorrectionMode'
// a bool... but may want to add other modes in the future
// (e.g. a special GBA colour correction mode for when the GBA
// flag is set in Shantae/Zelda: Oracle/etc.)
colorCorrectionMode = colorCorrectionMode_;
refreshPalettes();
}
void LCD::setColorCorrectionBrightness(float colorCorrectionBrightness_)
{
colorCorrectionBrightness = colorCorrectionBrightness_;
refreshPalettes();
}
void LCD::setDarkFilterLevel(unsigned darkFilterLevel_)
{
darkFilterLevel = darkFilterLevel_;
refreshPalettes();
}
LCD::LCD(const unsigned char *const oamram, const unsigned char *const vram, const VideoInterruptRequester memEventRequester) :
ppu_(nextM0Time_, oamram, vram),
eventTimes_(memEventRequester),
statReg_(0),
m2IrqStatReg_(0),
m1IrqStatReg_(0),
/* Color-correction defaults must be initialized BEFORE the
* constructor body runs, because setColorCorrection(true)
* below calls refreshPalettes() -> gbcToRgb32(), which
* reads colorCorrectionMode/colorCorrectionBrightness/
* darkFilterLevel. Previously these members were left
* uninitialized at this point and gbcToRgb32 invoked UB
* during construction. The values get overwritten by
* check_variables() once core options are read, but the
* construction-time UB still fires under UBSan. */
colorCorrection(true),
colorCorrectionMode(0),
colorCorrectionBrightness(0.5f),
darkFilterLevel(0)
{
std::memset( bgpData_, 0, sizeof bgpData_);
std::memset(objpData_, 0, sizeof objpData_);
/* dmgColorsGBC_ is consumed by refreshPalettes() in the
* CGB-running-DMG-game path; if uninitialized it would
* produce garbage colours until the game wrote a palette. */
std::memset(dmgColorsGBC_, 0, sizeof dmgColorsGBC_);
for (std::size_t i = 0; i < sizeof(dmgColorsRgb32_) / sizeof(dmgColorsRgb32_[0]); ++i)
{
#ifdef VIDEO_RGB565
uint16_t dmgColors[4]={0xFFFF, //11111 111111 11111
0xAD55, //10101 101010 10101
0x52AA, //01010 010101 01010
0x0000};//00000 000000 00000
setDmgPaletteColor(i, dmgColors[i&3]);
#elif defined(VIDEO_ABGR1555)
uint16_t dmgColors[4]={0xEFFF, //11111 111111 11111
0x56B5, //10101 10101 10101
0x294A, //01010 01010 01010
0x0000};//00000 000000 00000
setDmgPaletteColor(i, dmgColors[i&3]);
#else
setDmgPaletteColor(i, (3 - (i & 3)) * 85 * 0x010101);
#endif
}
reset(oamram, vram, false);
/* PPUFrameBuf is default-constructed with NULL buffer and
* pitch 0; the frontend will call setVideoBuffer with the
* real buffer via cpu.setVideoBuffer once retro_run begins,
* so the previous setVideoBuffer(0, 160) here was a no-op
* with a confusing pitch. */
setColorCorrection(true);
}
void LCD::doCgbColorChange(unsigned char *const pdata,
video_pixel_t *const palette, unsigned index, const unsigned data)
{
pdata[index] = data;
index >>= 1;
palette[index] = gbcToRgb32(pdata[index << 1] | pdata[(index << 1) + 1] << 8);
}
void LCD::setVideoBuffer(video_pixel_t *const videoBuf, const int pitch)
{
ppu_.setFrameBuf(videoBuf, pitch);
}
static void clear(video_pixel_t *buf, const unsigned long color, const int dpitch)
{
unsigned lines = 144;
while (lines--)
{
std::fill_n(buf, 160, color);
buf += dpitch;
}
}
void LCD::setDmgPaletteColor(const unsigned palNum, const unsigned colorNum, const video_pixel_t rgb32)
{
if (palNum > 2 || colorNum > 3)
return;
setDmgPaletteColor(palNum * 4 | colorNum, rgb32);
refreshPalettes();
}
void LCD::updateScreen(const bool blanklcd, const unsigned long cycleCounter)
{
update(cycleCounter);
if (blanklcd && ppu_.frameBuf().fb())
{
const video_pixel_t color = ppu_.cgb() ? gbcToRgb32(0xFFFF) : dmgColorsRgb32_[0];
clear(ppu_.frameBuf().fb(), color, ppu_.frameBuf().pitch());
}
}
// RGB range: [0,1]
void LCD::darkenRgb(float &r, float &g, float &b)
{
// Note: This is *very* approximate...
// - Should be done in linear colour space. It isn't.
// - Should alter brightness by performing an RGB->HSL->RGB
// conversion. We just do simple linear scaling instead.
// Basically, this is intended for use on devices that are
// too weak to run shaders (i.e. why would you want a 'dark filter'
// if your device supports proper LCD shaders?). We therefore
// cut corners for the sake of performance...
//
// Constants
// > Luminosity factors: photometric/digital ITU BT.709
static const float lumaR = 0.2126f;
static const float lumaG = 0.7152f;
static const float lumaB = 0.0722f;
// Calculate luminosity
float luma = (lumaR * r) + (lumaG * g) + (lumaB * b);
// Get 'darkness' scaling factor
// > User set 'dark filter' level scaled by current luminosity
// (i.e. lighter colours affected more than darker colours)
float darkFactor = 1.0f - ((static_cast<float>(darkFilterLevel) * 0.01f) * luma);
darkFactor = darkFactor < 0.0f ? 0.0f : darkFactor;
// Perform scaling...
r = r * darkFactor;
g = g * darkFactor;
b = b * darkFactor;
}
video_pixel_t LCD::gbcToRgb32(const unsigned bgr15)
{
const unsigned r = bgr15 & 0x1F;
const unsigned g = bgr15 >> 5 & 0x1F;
const unsigned b = bgr15 >> 10 & 0x1F;
unsigned rFinal = 0;
unsigned gFinal = 0;
unsigned bFinal = 0;
// Constants
// (Don't know whether floating-point rounding modes can be changed
// dynamically at run time, so can't rely on constant folding of
// inline [float / float] expressions - just play it safe...)
static const float rgbMax = 31.0;
static const float rgbMaxInv = 1.0 / rgbMax;
bool isDark = false;
if (colorCorrection)
{
if (colorCorrectionMode == 1)
{
// Use fast (inaccurate) Gambatte default method
rFinal = ((r * 13) + (g * 2) + b) >> 4;
gFinal = ((g * 3) + b) >> 2;
bFinal = ((r * 3) + (g * 2) + (b * 11)) >> 4;
}
else
{
// Use Pokefan531's "gold standard" GBC colour correction
// (https://forums.libretro.com/t/real-gba-and-ds-phat-colors/1540/190)
// NB: The results produced by this implementation are ever so slightly
// different from the output of the gbc-colour shader. This is due to the
// fact that we have to tolerate rounding errors here that are simply not
// an issue when tweaking the final image with a post-processing shader.
// *However:* the difference is so tiny small that 99.9% of users will
// never notice, and the result is still 100x better than the 'fast'
// colour correction method.
//
// Constants
static const float targetGamma = 2.2;
static const float displayGammaInv = 1.0 / targetGamma;
// Perform gamma expansion
float adjustedGamma = targetGamma - colorCorrectionBrightness;
float rFloat = std::pow(static_cast<float>(r) * rgbMaxInv, adjustedGamma);
float gFloat = std::pow(static_cast<float>(g) * rgbMaxInv, adjustedGamma);
float bFloat = std::pow(static_cast<float>(b) * rgbMaxInv, adjustedGamma);
// Perform colour mangling
float rCorrect = GBC_CC_LUM * ((GBC_CC_R * rFloat) + (GBC_CC_GR * gFloat) + (GBC_CC_BR * bFloat));
float gCorrect = GBC_CC_LUM * ((GBC_CC_RG * rFloat) + (GBC_CC_G * gFloat) + (GBC_CC_BG * bFloat));
float bCorrect = GBC_CC_LUM * ((GBC_CC_RB * rFloat) + (GBC_CC_GB * gFloat) + (GBC_CC_B * bFloat));
// Range check...
rCorrect = rCorrect > 0.0f ? rCorrect : 0.0f;
gCorrect = gCorrect > 0.0f ? gCorrect : 0.0f;
bCorrect = bCorrect > 0.0f ? bCorrect : 0.0f;
// Perform gamma compression
rCorrect = std::pow(rCorrect, displayGammaInv);
gCorrect = std::pow(gCorrect, displayGammaInv);
bCorrect = std::pow(bCorrect, displayGammaInv);
// Range check...
rCorrect = rCorrect > 1.0f ? 1.0f : rCorrect;
gCorrect = gCorrect > 1.0f ? 1.0f : gCorrect;
bCorrect = bCorrect > 1.0f ? 1.0f : bCorrect;
// Perform image darkening, if required
if (darkFilterLevel > 0)
{
darkenRgb(rCorrect, gCorrect, bCorrect);
isDark = true;
}
// Convert back to 5bit unsigned
rFinal = static_cast<unsigned>((rCorrect * rgbMax) + 0.5) & 0x1F;
gFinal = static_cast<unsigned>((gCorrect * rgbMax) + 0.5) & 0x1F;
bFinal = static_cast<unsigned>((bCorrect * rgbMax) + 0.5) & 0x1F;
}
}
else
{
rFinal = r;
gFinal = g;
bFinal = b;
}
// Perform image darkening, if required and we haven't
// already done it during colour correction
if (darkFilterLevel > 0 && !isDark)
{
// Convert colour range from [0,0x1F] to [0,1]
float rDark = static_cast<float>(rFinal) * rgbMaxInv;
float gDark = static_cast<float>(gFinal) * rgbMaxInv;
float bDark = static_cast<float>(bFinal) * rgbMaxInv;
// Perform image darkening
darkenRgb(rDark, gDark, bDark);
// Convert back to 5bit unsigned
rFinal = static_cast<unsigned>((rDark * rgbMax) + 0.5) & 0x1F;
gFinal = static_cast<unsigned>((gDark * rgbMax) + 0.5) & 0x1F;
bFinal = static_cast<unsigned>((bDark * rgbMax) + 0.5) & 0x1F;
}
#ifdef VIDEO_RGB565
return rFinal << 11 | gFinal << 6 | bFinal;
#elif defined(VIDEO_ABGR1555)
return bFinal << 10 | gFinal << 5 | rFinal;
#else
const unsigned r8 = (rFinal * 255 + 15) / 31;
const unsigned g8 = (gFinal * 255 + 15) / 31;
const unsigned b8 = (bFinal * 255 + 15) / 31;
return (r8 << 16) | (g8 << 8) | b8;
#endif
}
}
File diff suppressed because it is too large Load Diff
+4
View File
@@ -2357,6 +2357,10 @@ open_builtin_menu(const char *menu_name, int keep_idx)
snprintf(label, sizeof(label), "%s Tetris", ICON_GAM);
add_menu_item(label, "tetris", 0);
}
if (access("/usr/bin/gambatte", X_OK) == 0) {
snprintf(label, sizeof(label), "%s Game Boy (Gambatte)", ICON_GAM);
add_menu_item(label, "gambatte", 0);
}
const char *rdir = getenv("XDG_RUNTIME_DIR");
char gcache[256];
snprintf(gcache, sizeof(gcache), "%s/sxmo_games.cache", (rdir && rdir[0]) ? rdir : "/run/user/10000");