Added rewinding support to the core and the Cocoa frontend
This commit is contained in:
parent
51eacd3174
commit
1c61b006ba
@ -28,10 +28,12 @@
|
|||||||
@"GBStart": @(kVK_Return),
|
@"GBStart": @(kVK_Return),
|
||||||
|
|
||||||
@"GBTurbo": @(kVK_Space),
|
@"GBTurbo": @(kVK_Space),
|
||||||
|
@"GBRewind": @(kVK_Tab),
|
||||||
|
|
||||||
@"GBFilter": @"NearestNeighbor",
|
@"GBFilter": @"NearestNeighbor",
|
||||||
@"GBColorCorrection": @(GB_COLOR_CORRECTION_EMULATE_HARDWARE),
|
@"GBColorCorrection": @(GB_COLOR_CORRECTION_EMULATE_HARDWARE),
|
||||||
@"GBHighpassFilter": @(GB_HIGHPASS_REMOVE_DC_OFFSET)
|
@"GBHighpassFilter": @(GB_HIGHPASS_REMOVE_DC_OFFSET),
|
||||||
|
@"GBRewindLength": @(10)
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +51,8 @@ enum model {
|
|||||||
bool logToSideView;
|
bool logToSideView;
|
||||||
bool shouldClearSideView;
|
bool shouldClearSideView;
|
||||||
enum model current_model;
|
enum model current_model;
|
||||||
|
|
||||||
|
bool rewind;
|
||||||
}
|
}
|
||||||
|
|
||||||
@property GBAudioClient *audioClient;
|
@property GBAudioClient *audioClient;
|
||||||
@ -166,6 +168,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
|
|||||||
GB_set_camera_get_pixel_callback(&gb, cameraGetPixel);
|
GB_set_camera_get_pixel_callback(&gb, cameraGetPixel);
|
||||||
GB_set_camera_update_request_callback(&gb, cameraRequestUpdate);
|
GB_set_camera_update_request_callback(&gb, cameraRequestUpdate);
|
||||||
GB_set_highpass_filter_mode(&gb, (GB_highpass_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBHighpassFilter"]);
|
GB_set_highpass_filter_mode(&gb, (GB_highpass_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBHighpassFilter"]);
|
||||||
|
GB_set_rewind_length(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]);
|
||||||
[self loadROM];
|
[self loadROM];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,6 +182,9 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
|
|||||||
[self reloadVRAMData: nil];
|
[self reloadVRAMData: nil];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (self.view.isRewinding) {
|
||||||
|
rewind = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) run
|
- (void) run
|
||||||
@ -197,8 +203,17 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
|
|||||||
NSTimer *hex_timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:YES];
|
NSTimer *hex_timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:YES];
|
||||||
[[NSRunLoop mainRunLoop] addTimer:hex_timer forMode:NSDefaultRunLoopMode];
|
[[NSRunLoop mainRunLoop] addTimer:hex_timer forMode:NSDefaultRunLoopMode];
|
||||||
while (running) {
|
while (running) {
|
||||||
|
if (rewind) {
|
||||||
|
rewind = false;
|
||||||
|
GB_rewind_pop(&gb);
|
||||||
|
if (!GB_rewind_pop(&gb)) {
|
||||||
|
rewind = self.view.isRewinding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
GB_run(&gb);
|
GB_run(&gb);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
[hex_timer invalidate];
|
[hex_timer invalidate];
|
||||||
[self.audioClient stop];
|
[self.audioClient stop];
|
||||||
self.audioClient = nil;
|
self.audioClient = nil;
|
||||||
@ -327,6 +342,11 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
|
|||||||
name:@"GBColorCorrectionChanged"
|
name:@"GBColorCorrectionChanged"
|
||||||
object:nil];
|
object:nil];
|
||||||
|
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
|
selector:@selector(updateRewindLength)
|
||||||
|
name:@"GBRewindLengthChanged"
|
||||||
|
object:nil];
|
||||||
|
|
||||||
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateDMG"]) {
|
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateDMG"]) {
|
||||||
[self initDMG];
|
[self initDMG];
|
||||||
}
|
}
|
||||||
@ -1324,4 +1344,11 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void) updateRewindLength
|
||||||
|
{
|
||||||
|
if (GB_is_inited(&gb)) {
|
||||||
|
GB_set_rewind_length(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -11,6 +11,7 @@ typedef enum : NSUInteger {
|
|||||||
GBSelect,
|
GBSelect,
|
||||||
GBStart,
|
GBStart,
|
||||||
GBTurbo,
|
GBTurbo,
|
||||||
|
GBRewind,
|
||||||
GBButtonCount
|
GBButtonCount
|
||||||
} GBButton;
|
} GBButton;
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
#import "GBButtons.h"
|
#import "GBButtons.h"
|
||||||
|
|
||||||
NSString const *GBButtonNames[] = {@"Right", @"Left", @"Up", @"Down", @"A", @"B", @"Select", @"Start", @"Turbo"};
|
NSString const *GBButtonNames[] = {@"Right", @"Left", @"Up", @"Down", @"A", @"B", @"Select", @"Start", @"Turbo", @"Rewind"};
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
@property (strong) IBOutlet NSButton *aspectRatioCheckbox;
|
@property (strong) IBOutlet NSButton *aspectRatioCheckbox;
|
||||||
@property (strong) IBOutlet NSPopUpButton *highpassFilterPopupButton;
|
@property (strong) IBOutlet NSPopUpButton *highpassFilterPopupButton;
|
||||||
@property (strong) IBOutlet NSPopUpButton *colorCorrectionPopupButton;
|
@property (strong) IBOutlet NSPopUpButton *colorCorrectionPopupButton;
|
||||||
|
@property (strong) IBOutlet NSPopUpButton *rewindPopupButton;
|
||||||
@property (strong) IBOutlet NSButton *configureJoypadButton;
|
@property (strong) IBOutlet NSButton *configureJoypadButton;
|
||||||
@property (strong) IBOutlet NSButton *skipButton;
|
@property (strong) IBOutlet NSButton *skipButton;
|
||||||
@end
|
@end
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
NSPopUpButton *_graphicsFilterPopupButton;
|
NSPopUpButton *_graphicsFilterPopupButton;
|
||||||
NSPopUpButton *_highpassFilterPopupButton;
|
NSPopUpButton *_highpassFilterPopupButton;
|
||||||
NSPopUpButton *_colorCorrectionPopupButton;
|
NSPopUpButton *_colorCorrectionPopupButton;
|
||||||
|
NSPopUpButton *_rewindPopupButton;
|
||||||
NSButton *_aspectRatioCheckbox;
|
NSButton *_aspectRatioCheckbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,6 +78,18 @@
|
|||||||
return _colorCorrectionPopupButton;
|
return _colorCorrectionPopupButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)setRewindPopupButton:(NSPopUpButton *)rewindPopupButton
|
||||||
|
{
|
||||||
|
_rewindPopupButton = rewindPopupButton;
|
||||||
|
NSInteger length = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"];
|
||||||
|
[_rewindPopupButton selectItemWithTag:length];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSPopUpButton *)rewindPopupButton
|
||||||
|
{
|
||||||
|
return _rewindPopupButton;
|
||||||
|
}
|
||||||
|
|
||||||
- (void)setHighpassFilterPopupButton:(NSPopUpButton *)highpassFilterPopupButton
|
- (void)setHighpassFilterPopupButton:(NSPopUpButton *)highpassFilterPopupButton
|
||||||
{
|
{
|
||||||
_highpassFilterPopupButton = highpassFilterPopupButton;
|
_highpassFilterPopupButton = highpassFilterPopupButton;
|
||||||
@ -161,6 +174,13 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (IBAction)rewindLengthChanged:(id)sender
|
||||||
|
{
|
||||||
|
[[NSUserDefaults standardUserDefaults] setObject:@([sender selectedTag])
|
||||||
|
forKey:@"GBRewindLength"];
|
||||||
|
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBRewindLengthChanged" object:nil];
|
||||||
|
}
|
||||||
|
|
||||||
- (IBAction) configureJoypad:(id)sender
|
- (IBAction) configureJoypad:(id)sender
|
||||||
{
|
{
|
||||||
[self.configureJoypadButton setEnabled:NO];
|
[self.configureJoypadButton setEnabled:NO];
|
||||||
|
@ -10,4 +10,5 @@
|
|||||||
@property (nonatomic) BOOL shouldBlendFrameWithPrevious;
|
@property (nonatomic) BOOL shouldBlendFrameWithPrevious;
|
||||||
@property GBShader *shader;
|
@property GBShader *shader;
|
||||||
@property (getter=isMouseHidingEnabled) BOOL mouseHidingEnabled;
|
@property (getter=isMouseHidingEnabled) BOOL mouseHidingEnabled;
|
||||||
|
@property bool isRewinding;
|
||||||
@end
|
@end
|
||||||
|
@ -173,7 +173,12 @@
|
|||||||
handled = true;
|
handled = true;
|
||||||
switch (i) {
|
switch (i) {
|
||||||
case GBTurbo:
|
case GBTurbo:
|
||||||
GB_set_turbo_mode(_gb, true, false);
|
GB_set_turbo_mode(_gb, true, self.isRewinding);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GBRewind:
|
||||||
|
self.isRewinding = true;
|
||||||
|
GB_set_turbo_mode(_gb, false, false);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -202,6 +207,10 @@
|
|||||||
GB_set_turbo_mode(_gb, false, false);
|
GB_set_turbo_mode(_gb, false, false);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case GBRewind:
|
||||||
|
self.isRewinding = false;
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
GB_set_key_state(_gb, (GB_key_t)i, false);
|
GB_set_key_state(_gb, (GB_key_t)i, false);
|
||||||
break;
|
break;
|
||||||
@ -224,7 +233,14 @@
|
|||||||
if (mapped_button && [mapped_button integerValue] == button) {
|
if (mapped_button && [mapped_button integerValue] == button) {
|
||||||
switch (i) {
|
switch (i) {
|
||||||
case GBTurbo:
|
case GBTurbo:
|
||||||
GB_set_turbo_mode(_gb, state, false);
|
GB_set_turbo_mode(_gb, state, state && self.isRewinding);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GBRewind:
|
||||||
|
self.isRewinding = state;
|
||||||
|
if (state) {
|
||||||
|
GB_set_turbo_mode(_gb, false, false);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13529" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="macosx"/>
|
<deployment identifier="macosx"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13529"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<objects>
|
<objects>
|
||||||
@ -17,14 +17,14 @@
|
|||||||
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
|
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
|
||||||
<windowCollectionBehavior key="collectionBehavior" fullScreenAuxiliary="YES"/>
|
<windowCollectionBehavior key="collectionBehavior" fullScreenAuxiliary="YES"/>
|
||||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||||
<rect key="contentRect" x="196" y="240" width="292" height="459"/>
|
<rect key="contentRect" x="196" y="240" width="292" height="516"/>
|
||||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
|
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
|
||||||
<view key="contentView" id="EiT-Mj-1SZ">
|
<view key="contentView" id="EiT-Mj-1SZ">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="292" height="459"/>
|
<rect key="frame" x="0.0" y="0.0" width="292" height="516"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T91-rh-rRp">
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T91-rh-rRp">
|
||||||
<rect key="frame" x="18" y="422" width="256" height="17"/>
|
<rect key="frame" x="18" y="487" width="256" height="17"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Graphics Filter:" id="pXg-WY-8Q5">
|
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Graphics Filter:" id="pXg-WY-8Q5">
|
||||||
<font key="font" metaFont="system"/>
|
<font key="font" metaFont="system"/>
|
||||||
@ -33,7 +33,7 @@
|
|||||||
</textFieldCell>
|
</textFieldCell>
|
||||||
</textField>
|
</textField>
|
||||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6pP-kK-EEC">
|
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6pP-kK-EEC">
|
||||||
<rect key="frame" x="30" y="390" width="245" height="26"/>
|
<rect key="frame" x="30" y="455" width="245" height="26"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
<popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="I1w-05-lGl">
|
<popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="I1w-05-lGl">
|
||||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||||
@ -67,7 +67,7 @@
|
|||||||
</connections>
|
</connections>
|
||||||
</popUpButton>
|
</popUpButton>
|
||||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Wc3-2K-6CD">
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Wc3-2K-6CD">
|
||||||
<rect key="frame" x="18" y="368" width="256" height="17"/>
|
<rect key="frame" x="18" y="433" width="256" height="17"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Color Correction:" id="5Si-hz-EK3">
|
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Color Correction:" id="5Si-hz-EK3">
|
||||||
<font key="font" metaFont="system"/>
|
<font key="font" metaFont="system"/>
|
||||||
@ -76,7 +76,7 @@
|
|||||||
</textFieldCell>
|
</textFieldCell>
|
||||||
</textField>
|
</textField>
|
||||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="VEz-N4-uP6">
|
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="VEz-N4-uP6">
|
||||||
<rect key="frame" x="30" y="336" width="245" height="26"/>
|
<rect key="frame" x="30" y="401" width="245" height="26"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
<popUpButtonCell key="cell" type="push" title="Disabled" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="D2J-wV-1vu" id="fNJ-Fi-yOm">
|
<popUpButtonCell key="cell" type="push" title="Disabled" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="D2J-wV-1vu" id="fNJ-Fi-yOm">
|
||||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||||
@ -97,7 +97,7 @@
|
|||||||
</connections>
|
</connections>
|
||||||
</popUpButton>
|
</popUpButton>
|
||||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T69-6N-dhT">
|
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T69-6N-dhT">
|
||||||
<rect key="frame" x="30" y="256" width="245" height="26"/>
|
<rect key="frame" x="30" y="321" width="245" height="26"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
<popUpButtonCell key="cell" type="push" title="Disabled (Keep DC Offset)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Fgo-0S-zUG" id="om2-Bn-43B">
|
<popUpButtonCell key="cell" type="push" title="Disabled (Keep DC Offset)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Fgo-0S-zUG" id="om2-Bn-43B">
|
||||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||||
@ -116,8 +116,32 @@
|
|||||||
<action selector="highpassFilterChanged:" target="QvC-M9-y7g" id="CYt-0v-sw0"/>
|
<action selector="highpassFilterChanged:" target="QvC-M9-y7g" id="CYt-0v-sw0"/>
|
||||||
</connections>
|
</connections>
|
||||||
</popUpButton>
|
</popUpButton>
|
||||||
|
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7fg-Ww-JjR">
|
||||||
|
<rect key="frame" x="30" y="267" width="245" height="26"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
|
<popUpButtonCell key="cell" type="push" title="Disabled" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="lxQ-4n-kEv" id="lvb-QF-0Ht">
|
||||||
|
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||||
|
<font key="font" metaFont="menu"/>
|
||||||
|
<menu key="menu" id="lbS-Lw-kQX">
|
||||||
|
<items>
|
||||||
|
<menuItem title="Disabled" id="lxQ-4n-kEv">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="10 Seconds" state="on" tag="10" id="bPU-vT-d5z"/>
|
||||||
|
<menuItem title="30 Seconds" tag="30" id="aR8-IU-fFh"/>
|
||||||
|
<menuItem title="1 Minute" tag="60" id="E0R-mf-Hdl"/>
|
||||||
|
<menuItem title="2 Minutes" tag="120" id="zb2-uh-lvj"/>
|
||||||
|
<menuItem title="5 Minutes" tag="300" id="6Jj-EI-f6k"/>
|
||||||
|
<menuItem title="10 Minutes" tag="600" id="DOL-qL-Caz"/>
|
||||||
|
</items>
|
||||||
|
</menu>
|
||||||
|
</popUpButtonCell>
|
||||||
|
<connections>
|
||||||
|
<action selector="rewindLengthChanged:" target="QvC-M9-y7g" id="5NQ-1T-RNc"/>
|
||||||
|
</connections>
|
||||||
|
</popUpButton>
|
||||||
<button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Vfj-tg-7OP">
|
<button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Vfj-tg-7OP">
|
||||||
<rect key="frame" x="18" y="311" width="256" height="18"/>
|
<rect key="frame" x="18" y="376" width="256" height="18"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
<buttonCell key="cell" type="check" title="Keep Aspect Ratio" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="lsj-rC-Eo6">
|
<buttonCell key="cell" type="check" title="Keep Aspect Ratio" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="lsj-rC-Eo6">
|
||||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||||
@ -128,7 +152,7 @@
|
|||||||
</connections>
|
</connections>
|
||||||
</button>
|
</button>
|
||||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Utu-t4-cLx">
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Utu-t4-cLx">
|
||||||
<rect key="frame" x="18" y="234" width="256" height="17"/>
|
<rect key="frame" x="18" y="245" width="256" height="17"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Button configuration:" id="YqW-Ds-VIC">
|
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Button configuration:" id="YqW-Ds-VIC">
|
||||||
<font key="font" metaFont="system"/>
|
<font key="font" metaFont="system"/>
|
||||||
@ -137,7 +161,7 @@
|
|||||||
</textFieldCell>
|
</textFieldCell>
|
||||||
</textField>
|
</textField>
|
||||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="WU3-oV-KHO">
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="WU3-oV-KHO">
|
||||||
<rect key="frame" x="18" y="288" width="256" height="17"/>
|
<rect key="frame" x="18" y="353" width="256" height="17"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="High-pass Filter:" id="YLF-RL-b2D">
|
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="High-pass Filter:" id="YLF-RL-b2D">
|
||||||
<font key="font" metaFont="system"/>
|
<font key="font" metaFont="system"/>
|
||||||
@ -145,15 +169,46 @@
|
|||||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||||
</textFieldCell>
|
</textFieldCell>
|
||||||
</textField>
|
</textField>
|
||||||
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="w9w-yX-KxB">
|
||||||
|
<rect key="frame" x="18" y="299" width="256" height="17"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
|
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Rewinding Duration:" id="JaO-5h-ugl">
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
</textFieldCell>
|
||||||
|
</textField>
|
||||||
|
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="d2I-jU-sLb">
|
||||||
|
<rect key="frame" x="211" y="8" width="67" height="32"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
|
<buttonCell key="cell" type="push" title="Skip" bezelStyle="rounded" alignment="center" enabled="NO" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="sug-xy-tbw">
|
||||||
|
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
</buttonCell>
|
||||||
|
<connections>
|
||||||
|
<action selector="skipButton:" target="QvC-M9-y7g" id="aw8-sw-yJw"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Qa7-Z7-yfO">
|
||||||
|
<rect key="frame" x="26" y="8" width="188" height="32"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
|
<buttonCell key="cell" type="push" title="Configure Joypad" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="GdK-tQ-Wim">
|
||||||
|
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
</buttonCell>
|
||||||
|
<connections>
|
||||||
|
<action selector="configureJoypad:" target="QvC-M9-y7g" id="IfY-Kc-PKU"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
<scrollView focusRingType="none" fixedFrame="YES" autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" usesPredominantAxisScrolling="NO" horizontalScrollElasticity="none" verticalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="PBp-dj-EIa">
|
<scrollView focusRingType="none" fixedFrame="YES" autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" usesPredominantAxisScrolling="NO" horizontalScrollElasticity="none" verticalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="PBp-dj-EIa">
|
||||||
<rect key="frame" x="20" y="53" width="252" height="173"/>
|
<rect key="frame" x="26" y="45" width="252" height="192"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
<clipView key="contentView" focusRingType="none" ambiguous="YES" drawsBackground="NO" id="AMs-PO-nid">
|
<clipView key="contentView" focusRingType="none" ambiguous="YES" drawsBackground="NO" id="AMs-PO-nid">
|
||||||
<rect key="frame" x="1" y="1" width="250" height="171"/>
|
<rect key="frame" x="1" y="1" width="250" height="190"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<tableView focusRingType="none" appearanceType="vibrantLight" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" id="UDd-IJ-fxX">
|
<tableView focusRingType="none" appearanceType="vibrantLight" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" id="UDd-IJ-fxX">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="250" height="171"/>
|
<rect key="frame" x="0.0" y="0.0" width="250" height="190"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<size key="intercellSpacing" width="3" height="2"/>
|
<size key="intercellSpacing" width="3" height="2"/>
|
||||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||||
@ -203,28 +258,6 @@
|
|||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
</scroller>
|
</scroller>
|
||||||
</scrollView>
|
</scrollView>
|
||||||
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="d2I-jU-sLb">
|
|
||||||
<rect key="frame" x="205" y="10" width="67" height="32"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
|
||||||
<buttonCell key="cell" type="push" title="Skip" bezelStyle="rounded" alignment="center" enabled="NO" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="sug-xy-tbw">
|
|
||||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
|
||||||
<font key="font" metaFont="system"/>
|
|
||||||
</buttonCell>
|
|
||||||
<connections>
|
|
||||||
<action selector="skipButton:" target="QvC-M9-y7g" id="aw8-sw-yJw"/>
|
|
||||||
</connections>
|
|
||||||
</button>
|
|
||||||
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Qa7-Z7-yfO">
|
|
||||||
<rect key="frame" x="20" y="10" width="188" height="32"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
|
||||||
<buttonCell key="cell" type="push" title="Configure Joypad" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="GdK-tQ-Wim">
|
|
||||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
|
||||||
<font key="font" metaFont="system"/>
|
|
||||||
</buttonCell>
|
|
||||||
<connections>
|
|
||||||
<action selector="configureJoypad:" target="QvC-M9-y7g" id="IfY-Kc-PKU"/>
|
|
||||||
</connections>
|
|
||||||
</button>
|
|
||||||
</subviews>
|
</subviews>
|
||||||
</view>
|
</view>
|
||||||
<connections>
|
<connections>
|
||||||
@ -235,9 +268,10 @@
|
|||||||
<outlet property="delegate" destination="-2" id="ASc-vN-Zbq"/>
|
<outlet property="delegate" destination="-2" id="ASc-vN-Zbq"/>
|
||||||
<outlet property="graphicsFilterPopupButton" destination="6pP-kK-EEC" id="LS7-HY-kHC"/>
|
<outlet property="graphicsFilterPopupButton" destination="6pP-kK-EEC" id="LS7-HY-kHC"/>
|
||||||
<outlet property="highpassFilterPopupButton" destination="T69-6N-dhT" id="0p6-4m-hb1"/>
|
<outlet property="highpassFilterPopupButton" destination="T69-6N-dhT" id="0p6-4m-hb1"/>
|
||||||
|
<outlet property="rewindPopupButton" destination="7fg-Ww-JjR" id="Ka2-TP-B1x"/>
|
||||||
<outlet property="skipButton" destination="d2I-jU-sLb" id="udX-8K-0sK"/>
|
<outlet property="skipButton" destination="d2I-jU-sLb" id="udX-8K-0sK"/>
|
||||||
</connections>
|
</connections>
|
||||||
<point key="canvasLocation" x="179" y="498"/>
|
<point key="canvasLocation" x="183" y="510"/>
|
||||||
</window>
|
</window>
|
||||||
</objects>
|
</objects>
|
||||||
</document>
|
</document>
|
||||||
|
@ -198,6 +198,8 @@ static uint32_t get_pixel(GB_gameboy_t *gb, uint8_t x, uint8_t y)
|
|||||||
|
|
||||||
static void display_vblank(GB_gameboy_t *gb)
|
static void display_vblank(GB_gameboy_t *gb)
|
||||||
{
|
{
|
||||||
|
gb->vblank_just_occured = true;
|
||||||
|
|
||||||
if (gb->turbo) {
|
if (gb->turbo) {
|
||||||
if (GB_timing_sync_turbo(gb)) {
|
if (GB_timing_sync_turbo(gb)) {
|
||||||
return;
|
return;
|
||||||
@ -216,8 +218,6 @@ static void display_vblank(GB_gameboy_t *gb)
|
|||||||
|
|
||||||
gb->vblank_callback(gb);
|
gb->vblank_callback(gb);
|
||||||
GB_timing_sync(gb);
|
GB_timing_sync(gb);
|
||||||
|
|
||||||
gb->vblank_just_occured = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline uint8_t scale_channel(uint8_t x)
|
static inline uint8_t scale_channel(uint8_t x)
|
||||||
|
@ -11,6 +11,10 @@
|
|||||||
#endif
|
#endif
|
||||||
#include "gb.h"
|
#include "gb.h"
|
||||||
|
|
||||||
|
/* The libretro frontend does not link against rewind.c, so we provide empty weak alternatives to its functions */
|
||||||
|
void __attribute__((weak)) GB_rewind_free(GB_gameboy_t *gb) { }
|
||||||
|
void __attribute__((weak)) GB_rewind_push(GB_gameboy_t *gb) { }
|
||||||
|
|
||||||
void GB_attributed_logv(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, va_list args)
|
void GB_attributed_logv(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, va_list args)
|
||||||
{
|
{
|
||||||
char *string = NULL;
|
char *string = NULL;
|
||||||
@ -149,6 +153,7 @@ void GB_free(GB_gameboy_t *gb)
|
|||||||
gb->reversed_symbol_map.buckets[i] = next;
|
gb->reversed_symbol_map.buckets[i] = next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
GB_rewind_free(gb);
|
||||||
memset(gb, 0, sizeof(*gb));
|
memset(gb, 0, sizeof(*gb));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,6 +285,7 @@ uint8_t GB_run(GB_gameboy_t *gb)
|
|||||||
GB_update_joyp(gb);
|
GB_update_joyp(gb);
|
||||||
GB_rtc_run(gb);
|
GB_rtc_run(gb);
|
||||||
GB_debugger_handle_async_commands(gb);
|
GB_debugger_handle_async_commands(gb);
|
||||||
|
GB_rewind_push(gb);
|
||||||
}
|
}
|
||||||
return gb->cycles_since_run;
|
return gb->cycles_since_run;
|
||||||
}
|
}
|
||||||
@ -505,6 +511,7 @@ void GB_switch_model_and_reset(GB_gameboy_t *gb, bool is_cgb)
|
|||||||
gb->vram = realloc(gb->vram, gb->vram_size = 0x2000);
|
gb->vram = realloc(gb->vram, gb->vram_size = 0x2000);
|
||||||
}
|
}
|
||||||
gb->is_cgb = is_cgb;
|
gb->is_cgb = is_cgb;
|
||||||
|
GB_rewind_free(gb);
|
||||||
GB_reset(gb);
|
GB_reset(gb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
13
Core/gb.h
13
Core/gb.h
@ -17,6 +17,7 @@
|
|||||||
#include "memory.h"
|
#include "memory.h"
|
||||||
#include "printer.h"
|
#include "printer.h"
|
||||||
#include "timing.h"
|
#include "timing.h"
|
||||||
|
#include "rewind.h"
|
||||||
#include "z80_cpu.h"
|
#include "z80_cpu.h"
|
||||||
#include "symbol_hash.h"
|
#include "symbol_hash.h"
|
||||||
|
|
||||||
@ -161,7 +162,7 @@ typedef enum {
|
|||||||
#define CPU_FREQUENCY 0x400000
|
#define CPU_FREQUENCY 0x400000
|
||||||
#define DIV_CYCLES (0x100)
|
#define DIV_CYCLES (0x100)
|
||||||
#define INTERNAL_DIV_CYCLES (0x40000)
|
#define INTERNAL_DIV_CYCLES (0x40000)
|
||||||
#define FRAME_LENGTH 16742706 // in nanoseconds
|
#define FRAME_LENGTH (1000000000LL * LCDC_PERIOD / CPU_FREQUENCY) // in nanoseconds
|
||||||
|
|
||||||
#if !defined(MIN)
|
#if !defined(MIN)
|
||||||
#define MIN(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; })
|
#define MIN(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; })
|
||||||
@ -476,6 +477,16 @@ struct GB_gameboy_internal_s {
|
|||||||
/* Ticks command */
|
/* Ticks command */
|
||||||
unsigned long debugger_ticks;
|
unsigned long debugger_ticks;
|
||||||
|
|
||||||
|
/* Rewind */
|
||||||
|
#define GB_REWIND_FRAMES_PER_KEY 255
|
||||||
|
size_t rewind_buffer_length;
|
||||||
|
struct {
|
||||||
|
uint8_t *key_state;
|
||||||
|
uint8_t *compressed_states[GB_REWIND_FRAMES_PER_KEY];
|
||||||
|
unsigned pos;
|
||||||
|
} *rewind_sequences; // lasts about 4 seconds
|
||||||
|
size_t rewind_pos;
|
||||||
|
|
||||||
/* Misc */
|
/* Misc */
|
||||||
bool turbo;
|
bool turbo;
|
||||||
bool turbo_dont_skip;
|
bool turbo_dont_skip;
|
||||||
|
205
Core/rewind.c
Normal file
205
Core/rewind.c
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
#include "rewind.h"
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
static uint8_t *state_compress(const uint8_t *prev, const uint8_t *data, size_t uncompressed_size)
|
||||||
|
{
|
||||||
|
size_t malloc_size = 0x1000;
|
||||||
|
uint8_t *compressed = malloc(malloc_size);
|
||||||
|
size_t counter_pos = 0;
|
||||||
|
size_t data_pos = sizeof(uint16_t);
|
||||||
|
bool prev_mode = true;
|
||||||
|
*(uint16_t *)compressed = 0;
|
||||||
|
#define COUNTER (*(uint16_t *)&compressed[counter_pos])
|
||||||
|
#define DATA (compressed[data_pos])
|
||||||
|
|
||||||
|
while (uncompressed_size) {
|
||||||
|
if (prev_mode) {
|
||||||
|
if (*data == *prev && COUNTER != 0xffff) {
|
||||||
|
COUNTER++;
|
||||||
|
data++;
|
||||||
|
prev++;
|
||||||
|
uncompressed_size--;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
prev_mode = false;
|
||||||
|
counter_pos += sizeof(uint16_t);
|
||||||
|
data_pos = counter_pos + sizeof(uint16_t);
|
||||||
|
if (data_pos >= malloc_size) {
|
||||||
|
malloc_size *= 2;
|
||||||
|
compressed = realloc(compressed, malloc_size);
|
||||||
|
}
|
||||||
|
COUNTER = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (*data != *prev && COUNTER != 0xffff) {
|
||||||
|
COUNTER++;
|
||||||
|
DATA = *data;
|
||||||
|
data_pos++;
|
||||||
|
data++;
|
||||||
|
prev++;
|
||||||
|
uncompressed_size--;
|
||||||
|
if (data_pos >= malloc_size) {
|
||||||
|
malloc_size *= 2;
|
||||||
|
compressed = realloc(compressed, malloc_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
prev_mode = true;
|
||||||
|
counter_pos = data_pos;
|
||||||
|
data_pos = counter_pos + sizeof(uint16_t);
|
||||||
|
if (counter_pos >= malloc_size - 1) {
|
||||||
|
malloc_size *= 2;
|
||||||
|
compressed = realloc(compressed, malloc_size);
|
||||||
|
}
|
||||||
|
COUNTER = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return realloc(compressed, data_pos);
|
||||||
|
#undef DATA
|
||||||
|
#undef COUNTER
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void state_decompress(const uint8_t *prev, uint8_t *data, uint8_t *dest, size_t uncompressed_size)
|
||||||
|
{
|
||||||
|
size_t counter_pos = 0;
|
||||||
|
size_t data_pos = sizeof(uint16_t);
|
||||||
|
bool prev_mode = true;
|
||||||
|
#define COUNTER (*(uint16_t *)&data[counter_pos])
|
||||||
|
#define DATA (data[data_pos])
|
||||||
|
|
||||||
|
while (uncompressed_size) {
|
||||||
|
if (prev_mode) {
|
||||||
|
if (COUNTER) {
|
||||||
|
COUNTER--;
|
||||||
|
*(dest++) = *(prev++);
|
||||||
|
uncompressed_size--;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
prev_mode = false;
|
||||||
|
counter_pos += sizeof(uint16_t);
|
||||||
|
data_pos = counter_pos + sizeof(uint16_t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (COUNTER) {
|
||||||
|
COUNTER--;
|
||||||
|
*(dest++) = DATA;
|
||||||
|
data_pos++;
|
||||||
|
prev++;
|
||||||
|
uncompressed_size--;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
prev_mode = true;
|
||||||
|
counter_pos = data_pos;
|
||||||
|
data_pos += sizeof(uint16_t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#undef DATA
|
||||||
|
#undef COUNTER
|
||||||
|
}
|
||||||
|
|
||||||
|
void GB_rewind_push(GB_gameboy_t *gb)
|
||||||
|
{
|
||||||
|
const size_t save_size = GB_get_save_state_size(gb);
|
||||||
|
if (!gb->rewind_sequences) {
|
||||||
|
if (gb->rewind_buffer_length) {
|
||||||
|
gb->rewind_sequences = malloc(sizeof(*gb->rewind_sequences) * gb->rewind_buffer_length);
|
||||||
|
memset(gb->rewind_sequences, 0, sizeof(*gb->rewind_sequences) * gb->rewind_buffer_length);
|
||||||
|
gb->rewind_pos = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gb->rewind_sequences[gb->rewind_pos].pos == GB_REWIND_FRAMES_PER_KEY) {
|
||||||
|
gb->rewind_pos++;
|
||||||
|
if (gb->rewind_pos == gb->rewind_buffer_length) {
|
||||||
|
gb->rewind_pos = 0;
|
||||||
|
}
|
||||||
|
if (gb->rewind_sequences[gb->rewind_pos].key_state) {
|
||||||
|
free(gb->rewind_sequences[gb->rewind_pos].key_state);
|
||||||
|
gb->rewind_sequences[gb->rewind_pos].key_state = NULL;
|
||||||
|
}
|
||||||
|
for (unsigned i = 0; i < GB_REWIND_FRAMES_PER_KEY; i++) {
|
||||||
|
if (gb->rewind_sequences[gb->rewind_pos].compressed_states[i]) {
|
||||||
|
free(gb->rewind_sequences[gb->rewind_pos].compressed_states[i]);
|
||||||
|
gb->rewind_sequences[gb->rewind_pos].compressed_states[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gb->rewind_sequences[gb->rewind_pos].pos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gb->rewind_sequences[gb->rewind_pos].key_state) {
|
||||||
|
gb->rewind_sequences[gb->rewind_pos].key_state = malloc(save_size);
|
||||||
|
GB_save_state_to_buffer(gb, gb->rewind_sequences[gb->rewind_pos].key_state);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
uint8_t *save_state = malloc(save_size);
|
||||||
|
GB_save_state_to_buffer(gb, save_state);
|
||||||
|
gb->rewind_sequences[gb->rewind_pos].compressed_states[gb->rewind_sequences[gb->rewind_pos].pos++] =
|
||||||
|
state_compress(gb->rewind_sequences[gb->rewind_pos].key_state, save_state, save_size);
|
||||||
|
free(save_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GB_rewind_pop(GB_gameboy_t *gb)
|
||||||
|
{
|
||||||
|
if (!gb->rewind_sequences || !gb->rewind_sequences[gb->rewind_pos].key_state) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t save_size = GB_get_save_state_size(gb);
|
||||||
|
if (gb->rewind_sequences[gb->rewind_pos].pos == 0) {
|
||||||
|
GB_load_state_from_buffer(gb, gb->rewind_sequences[gb->rewind_pos].key_state, save_size);
|
||||||
|
free(gb->rewind_sequences[gb->rewind_pos].key_state);
|
||||||
|
gb->rewind_sequences[gb->rewind_pos].key_state = NULL;
|
||||||
|
gb->rewind_pos = gb->rewind_pos == 0? gb->rewind_buffer_length - 1 : gb->rewind_pos - 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *save_state = malloc(save_size);
|
||||||
|
state_decompress(gb->rewind_sequences[gb->rewind_pos].key_state,
|
||||||
|
gb->rewind_sequences[gb->rewind_pos].compressed_states[--gb->rewind_sequences[gb->rewind_pos].pos],
|
||||||
|
save_state,
|
||||||
|
save_size);
|
||||||
|
free(gb->rewind_sequences[gb->rewind_pos].compressed_states[gb->rewind_sequences[gb->rewind_pos].pos]);
|
||||||
|
gb->rewind_sequences[gb->rewind_pos].compressed_states[gb->rewind_sequences[gb->rewind_pos].pos] = NULL;
|
||||||
|
GB_load_state_from_buffer(gb, save_state, save_size);
|
||||||
|
free(save_state);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GB_rewind_free(GB_gameboy_t *gb)
|
||||||
|
{
|
||||||
|
if (!gb->rewind_sequences) return;
|
||||||
|
for (unsigned i = 0; i < gb->rewind_buffer_length; i++) {
|
||||||
|
if (gb->rewind_sequences[i].key_state) {
|
||||||
|
free(gb->rewind_sequences[i].key_state);
|
||||||
|
}
|
||||||
|
for (unsigned j = 0; j < GB_REWIND_FRAMES_PER_KEY; j++) {
|
||||||
|
if (gb->rewind_sequences[i].compressed_states[j]) {
|
||||||
|
free(gb->rewind_sequences[i].compressed_states[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(gb->rewind_sequences);
|
||||||
|
gb->rewind_sequences = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GB_set_rewind_length(GB_gameboy_t *gb, double seconds)
|
||||||
|
{
|
||||||
|
GB_rewind_free(gb);
|
||||||
|
if (seconds == 0) {
|
||||||
|
gb->rewind_buffer_length = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
gb->rewind_buffer_length = (size_t) ceil(seconds * CPU_FREQUENCY / LCDC_PERIOD / GB_REWIND_FRAMES_PER_KEY);
|
||||||
|
}
|
||||||
|
}
|
13
Core/rewind.h
Normal file
13
Core/rewind.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#ifndef rewind_h
|
||||||
|
#define rewind_h
|
||||||
|
|
||||||
|
#include "gb.h"
|
||||||
|
|
||||||
|
#ifdef GB_INTERNAL
|
||||||
|
void GB_rewind_push(GB_gameboy_t *gb);
|
||||||
|
void GB_rewind_free(GB_gameboy_t *gb);
|
||||||
|
#endif
|
||||||
|
bool GB_rewind_pop(GB_gameboy_t *gb);
|
||||||
|
void GB_set_rewind_length(GB_gameboy_t *gb, double seconds);
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in New Issue
Block a user