* Tasedit: major refactoring

This commit is contained in:
ansstuff 2011-10-13 14:20:45 +00:00
parent 872d55c4c3
commit 851dae6878
20 changed files with 1391 additions and 1242 deletions

View File

@ -1,4 +1,4 @@
#define MAXIMUM_NUMBER_OF_LOGS 64
#define MAXIMUM_NUMBER_OF_LOGS 256
#define DONT_ADD_NEWLINE 0
#define DO_ADD_NEWLINE 1

View File

@ -1355,7 +1355,7 @@ MENU TASEDITMENU
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
CONTROL "",IDC_LIST1,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_OWNERDATA | LVS_NOSORTHEADER | WS_BORDER,6,5,299,370
GROUPBOX "Playback",IDC_STATIC,310,5,118,62,BS_CENTER,WS_EX_RIGHT
GROUPBOX " Playback ",IDC_STATIC,310,5,118,62,BS_CENTER,WS_EX_RIGHT
PUSHBUTTON "<<",TASEDIT_REWIND_FULL,314,14,22,14,NOT WS_TABSTOP
PUSHBUTTON "<",TASEDIT_REWIND,336,14,22,14,NOT WS_TABSTOP
PUSHBUTTON "||",TASEDIT_PLAYSTOP,358,14,22,14,NOT WS_TABSTOP
@ -1366,9 +1366,9 @@ BEGIN
CONTROL " Follow cursor",CHECK_FOLLOW_CURSOR,"Button",BS_AUTOCHECKBOX,315,30,105,12
CONTROL " Auto-restore last position",CHECK_AUTORESTORE_PLAYBACK,
"Button",BS_AUTOCHECKBOX,315,53,105,12
GROUPBOX "Recording",IDC_STATIC,310,68,118,48,BS_CENTER,WS_EX_RIGHT
GROUPBOX "Editing",IDC_STATIC,310,118,118,38,BS_CENTER,WS_EX_RIGHT
GROUPBOX "Bookmarks",IDC_STATIC,310,158,118,101,BS_CENTER,WS_EX_RIGHT
GROUPBOX " Recording ",IDC_STATIC,310,68,118,48,BS_CENTER,WS_EX_RIGHT
GROUPBOX " Editing ",IDC_STATIC,310,118,118,38,BS_CENTER,WS_EX_RIGHT
GROUPBOX " Bookmarks ",IDC_BOOKMARKS_BOX,310,158,118,101,BS_CENTER,WS_EX_RIGHT
CONTROL "",IDC_BOOKMARKSLIST,"SysListView32",LVS_LIST | LVS_SINGLESEL | LVS_ALIGNLEFT | LVS_OWNERDATA | LVS_NOSCROLL | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER | WS_BORDER,315,168,108,86
CONTROL "",IDC_HISTORYLIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOLABELWRAP | LVS_ALIGNLEFT | LVS_OWNERDATA | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER | WS_BORDER,315,271,108,100
CONTROL " OFF",IDC_RADIO1,"Button",BS_AUTORADIOBUTTON,316,78,29,10
@ -1379,7 +1379,7 @@ BEGIN
CONTROL " 4P",IDC_RADIO6,"Button",BS_AUTORADIOBUTTON | WS_DISABLED,397,91,23,10
CONTROL " Superimpose",IDC_SUPERIMPOSE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,316,104,55,10
CONTROL " Omit blank",IDC_OMITBLANK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,375,104,49,10
GROUPBOX "History",IDC_STATIC,310,261,118,114,BS_CENTER,WS_EX_RIGHT
GROUPBOX " History ",IDC_STATIC,310,261,118,114,BS_CENTER,WS_EX_RIGHT
END
ASSEMBLER DIALOGEX 0, 0, 202, 135

View File

@ -500,6 +500,7 @@
#define CHECK_AUTORESTORE_PLAYBACK 1261
#define IDC_PROGRESS1 1262
#define CHECK_FOLLOW_CURSOR 1263
#define IDC_BOOKMARKS_BOX 1264
#define MENU_NETWORK 40040
#define MENU_PALETTE 40041
#define MENU_SOUND 40042
@ -869,7 +870,7 @@
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 160
#define _APS_NEXT_COMMAND_VALUE 40473
#define _APS_NEXT_CONTROL_VALUE 1263
#define _APS_NEXT_CONTROL_VALUE 1265
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

View File

@ -4,7 +4,6 @@
#include "common.h"
#include "taseditlib/taseditproj.h"
//#include "taseditlib/inputhistory.h"
#include "fceu.h"
#include "debugger.h"
#include "replay.h"
@ -22,27 +21,14 @@ using namespace std;
//to change header font
//http://forums.devx.com/archive/index.php/t-37234.html
bool old_project_changed;
int old_multitrack_recording_joypad, multitrack_recording_joypad;
bool old_movie_readonly;
int lastCursor;
bool old_emu_paused, emu_paused;
int old_pauseframe;
bool old_show_pauseframe, show_pauseframe;
int undo_hint_pos, old_undo_hint_pos, undo_hint_time;
bool old_show_undo_hint, show_undo_hint;
bool old_rewind_button_state, rewind_button_state;
bool old_forward_button_state, forward_button_state;
int button_hold_time;
int seeking_start_frame = 0;
bool TASEdit_focus = false;
int listItems; // number of items per list page
// saved FCEU config
int saved_eoptions;
int saved_EnableAutosave;
extern int EnableAutosave;
bool saved_compressSavestates;
extern bool compressSavestates;
// vars saved in cfg file
int TasEdit_wndx, TasEdit_wndy;
@ -65,13 +51,15 @@ char windowCaptions[6][30] = { "TAS Editor",
"TAS Editor (Recording 2P)",
"TAS Editor (Recording 3P)",
"TAS Editor (Recording 4P)"};
char bookmarksCaption[2][23] = { " Bookmarks ", " Bookmarks / Branches " };
HWND hwndTasEdit = 0;
HMENU hmenu, hrmenu;
HWND hwndList, hwndHeader;
WNDPROC hwndList_oldWndProc, hwndHeader_oldWndproc;
HWND hwndHistoryList;
WNDPROC hwndHistoryList_oldWndProc;
HWND hwndBookmarksList;
HWND hwndBookmarksList, hwndBookmarks;
WNDPROC hwndBookmarksList_oldWndProc;
HWND hwndProgressbar, hwndRewind, hwndForward;
HWND hwndRB_RecOff, hwndRB_RecAll, hwndRB_Rec1P, hwndRB_Rec2P, hwndRB_Rec3P, hwndRB_Rec4P;
@ -84,9 +72,11 @@ static TSelectionFrames selectionFrames;
//add a new fceud_ function?? blehhh maybe
extern EMOVIEMODE movieMode;
// all Taseditor functional modules
TASEDIT_PROJECT project;
INPUT_HISTORY history;
//GREENZONE greenzone;
PLAYBACK playback;
GREENZONE greenzone;
void GetDispInfo(NMLVDISPINFO* nmlvDispInfo)
@ -164,11 +154,11 @@ LONG CustomDraw(NMLVCUSTOMDRAW* msg)
if(cell_x == COLUMN_FRAMENUM || cell_x == COLUMN_FRAMENUM2)
{
// frame number
if (show_undo_hint && cell_y == undo_hint_pos)
if (cell_y == history.GetUndoHint())
{
// undo hint here
msg->clrTextBk = UNDOHINT_FRAMENUM_COLOR;
} else if (cell_y == currFrameCounter || (cell_y == pauseframe-1 && show_pauseframe))
} else if (cell_y == currFrameCounter || cell_y == playback.GetPauseFrame())
{
// current frame
if(TASEdit_show_markers && currMovieData.frames_flags[cell_y] & MARKER_FLAG_BIT)
@ -180,7 +170,7 @@ LONG CustomDraw(NMLVCUSTOMDRAW* msg)
{
// marked frame
msg->clrTextBk = MARKED_FRAMENUM_COLOR;
} else if(cell_y < currMovieData.greenZoneCount && !currMovieData.savestates[cell_y].empty())
} else if(cell_y < greenzone.greenZoneCount && !greenzone.savestates[cell_y].empty())
{
if (TASEdit_show_lag_frames && (currMovieData.frames_flags[cell_y] & LAG_FLAG_BIT))
{
@ -195,15 +185,15 @@ LONG CustomDraw(NMLVCUSTOMDRAW* msg)
} else if((cell_x - COLUMN_JOYPAD1_A) / NUM_JOYPAD_BUTTONS == 0 || (cell_x - COLUMN_JOYPAD1_A) / NUM_JOYPAD_BUTTONS == 2)
{
// pad 1 or 3
if (show_undo_hint && cell_y == undo_hint_pos)
if (cell_y == history.GetUndoHint())
{
// undo hint here
msg->clrTextBk = UNDOHINT_INPUT_COLOR1;
} else if (cell_y == currFrameCounter || (cell_y == pauseframe-1 && show_pauseframe))
} else if (cell_y == currFrameCounter || cell_y == playback.GetPauseFrame())
{
// current frame
msg->clrTextBk = CUR_INPUT_COLOR1;
} else if(cell_y < currMovieData.greenZoneCount && !currMovieData.savestates[cell_y].empty())
} else if(cell_y < greenzone.greenZoneCount && !greenzone.savestates[cell_y].empty())
{
if (TASEdit_show_lag_frames && (currMovieData.frames_flags[cell_y] & LAG_FLAG_BIT))
{
@ -218,15 +208,15 @@ LONG CustomDraw(NMLVCUSTOMDRAW* msg)
} else if((cell_x - COLUMN_JOYPAD1_A) / NUM_JOYPAD_BUTTONS == 1 || (cell_x - COLUMN_JOYPAD1_A) / NUM_JOYPAD_BUTTONS == 3)
{
// pad 2 or 4
if (show_undo_hint && cell_y == undo_hint_pos)
if (cell_y == history.GetUndoHint())
{
// undo hint here
msg->clrTextBk = UNDOHINT_INPUT_COLOR2;
} else if (cell_y == currFrameCounter || (cell_y == pauseframe-1 && show_pauseframe))
} else if (cell_y == currFrameCounter || cell_y == playback.GetPauseFrame())
{
// current frame
msg->clrTextBk = CUR_INPUT_COLOR2;
} else if(cell_y < currMovieData.greenZoneCount && !currMovieData.savestates[cell_y].empty())
} else if(cell_y < greenzone.greenZoneCount && !greenzone.savestates[cell_y].empty())
{
if (TASEdit_show_lag_frames && (currMovieData.frames_flags[cell_y] & LAG_FLAG_BIT))
{
@ -253,98 +243,18 @@ void UpdateTasEdit()
UpdateList();
// pause when seeking hit pauseframe
if(!FCEUI_EmulationPaused())
if(pauseframe && pauseframe <= currFrameCounter + 1)
SeekingStop();
// update seeking progressbar
old_emu_paused = emu_paused;
emu_paused = (bool)FCEUI_EmulationPaused();
if (pauseframe)
{
SendMessage(hwndProgressbar, PBM_SETPOS, PROGRESSBAR_WIDTH * (currFrameCounter-seeking_start_frame) / (pauseframe-seeking_start_frame), 0);
} else if (old_emu_paused != emu_paused)
{
// emulator got paused/unpaused externally
if (old_emu_paused && !emu_paused)
// externally unpaused - progressbar should be empty
SendMessage(hwndProgressbar, PBM_SETPOS, 0, 0);
else
// externally paused - progressbar should be full
SendMessage(hwndProgressbar, PBM_SETPOS, PROGRESSBAR_WIDTH, 0);
}
// update flashing pauseframe
if (old_pauseframe != pauseframe && old_pauseframe) RedrawRow(old_pauseframe-1);
old_pauseframe = pauseframe;
old_show_pauseframe = show_pauseframe;
if (pauseframe)
show_pauseframe = (int)(clock() / PAUSEFRAME_BLINKING_PERIOD) & 1;
else
show_pauseframe = false;
if (old_show_pauseframe != show_pauseframe) RedrawRow(pauseframe-1);
// update undo_hint
if (old_undo_hint_pos != undo_hint_pos && old_undo_hint_pos >= 0) RedrawRow(old_undo_hint_pos);
old_undo_hint_pos = undo_hint_pos;
old_show_undo_hint = show_undo_hint;
show_undo_hint = false;
if (undo_hint_pos >= 0)
{
if ((int)clock() < undo_hint_time)
show_undo_hint = true;
else
undo_hint_pos = -1; // finished hinting
}
if (old_show_undo_hint != show_undo_hint) RedrawRow(undo_hint_pos);
//update the playback cursor
if(currFrameCounter != lastCursor)
{
FollowPlayback();
//update the old and new rows
RedrawRow(lastCursor);
RedrawRow(currFrameCounter);
UpdateWindow(hwndList);
lastCursor = currFrameCounter;
}
// update < and > buttons
if(emu_paused)
{
old_rewind_button_state = rewind_button_state;
rewind_button_state = (bool)(Button_GetState(hwndRewind) & BST_PUSHED);
if (rewind_button_state)
{
if (!old_rewind_button_state)
{
button_hold_time = clock();
Tasedit_RewindFrame();
} else if (button_hold_time + HOLD_REPEAT_DELAY < clock())
{
Tasedit_RewindFrame();
}
}
old_forward_button_state = forward_button_state;
forward_button_state = (bool)(Button_GetState(hwndForward) & BST_PUSHED);
if (forward_button_state)
{
if (!old_forward_button_state)
{
button_hold_time = clock();
Tasedit_ForwardFrame();
} else if (button_hold_time + HOLD_REPEAT_DELAY < clock())
{
Tasedit_ForwardFrame();
}
}
}
greenzone.update();
playback.update();
history.update();
// update window caption
if (old_movie_readonly != movie_readonly || old_multitrack_recording_joypad != multitrack_recording_joypad || old_project_changed != project.changed)
if (project.old_changed != project.changed || old_movie_readonly != movie_readonly || old_multitrack_recording_joypad != multitrack_recording_joypad)
RedrawWindowCaption();
old_project_changed = project.changed;
project.update();
// update Bookmarks/Branches groupbox caption
if (old_movie_readonly != movie_readonly)
RedrawBookmarksCaption();
// update recording radio buttons if user used hotkey to switch R/W
if (old_movie_readonly != movie_readonly || old_multitrack_recording_joypad != multitrack_recording_joypad)
@ -379,11 +289,6 @@ void UpdateList()
}
}
void UpdateProgressbar(int a, int b)
{
SendMessage(hwndProgressbar, PBM_SETPOS, PROGRESSBAR_WIDTH * a / b, 0);
}
void RedrawWindowCaption()
{
char windowCaption[300]; // 260 + 30 + 1 + ...
@ -403,6 +308,10 @@ void RedrawWindowCaption()
strcat(windowCaption, "*");
SetWindowText(hwndTasEdit, windowCaption);
}
void RedrawBookmarksCaption()
{
SetWindowText(hwndBookmarks, bookmarksCaption[(movie_readonly)?0:1]);
}
void RedrawTasedit()
{
InvalidateRect(hwndTasEdit,0,FALSE);
@ -411,63 +320,11 @@ void RedrawList()
{
InvalidateRect(hwndList,0,FALSE);
}
void RedrawHistoryList()
{
ListView_SetItemState(hwndHistoryList, history.GetCursorPos(), LVIS_FOCUSED|LVIS_SELECTED, LVIS_FOCUSED|LVIS_SELECTED);
ListView_EnsureVisible(hwndHistoryList, history.GetCursorPos(), FALSE);
InvalidateRect(hwndHistoryList,0,FALSE);
}
void RedrawRow(int index)
{
ListView_RedrawItems(hwndList,index,index);
}
void Tasedit_RewindFrame()
{
if (currFrameCounter > 0) JumpToFrame(currFrameCounter-1);
turbo = false;
FollowPlayback();
}
void Tasedit_ForwardFrame()
{
JumpToFrame(currFrameCounter+1);
turbo = false;
FollowPlayback();
}
void Tasedit_ToggleEmulationPause()
{
if (FCEUI_EmulationPaused())
UnpauseEmulation();
else
PauseEmulation();
}
void PauseEmulation()
{
FCEUI_SetEmulationPaused(1);
// make some additional stuff
}
void UnpauseEmulation()
{
FCEUI_SetEmulationPaused(0);
// make some additional stuff
}
void SeekingStart(int finish_frame)
{
seeking_start_frame = currFrameCounter;
pauseframe = finish_frame;
turbo = true;
UnpauseEmulation();
}
void SeekingStop()
{
pauseframe = 0;
turbo = false;
PauseEmulation();
SendMessage(hwndProgressbar, PBM_SETPOS, PROGRESSBAR_WIDTH, 0);
}
enum ECONTEXTMENU
{
CONTEXTMENU_STRAY = 0,
@ -523,80 +380,7 @@ void MarkersChanged()
void InputChangedRec()
{
InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_RECORD, currFrameCounter,currFrameCounter));
}
void InvalidateGreenZone(int after)
{
if (after < 0) return;
project.changed = true;
if (currMovieData.greenZoneCount > after+1)
{
currMovieData.greenZoneCount = after+1;
currMovieData.rerecordCount++;
// either set playback cursor to the end of greenzone or run seeking to restore playback position
if (currFrameCounter >= currMovieData.greenZoneCount)
{
if (TASEdit_restore_position)
{
if (pauseframe)
JumpToFrame(pauseframe-1);
else
JumpToFrame(currFrameCounter);
} else
{
JumpToFrame(currMovieData.greenZoneCount-1);
}
}
}
// redraw list even if greenzone didn't change
RedrawList();
}
bool JumpToFrame(int index)
{
// Returns true if a jump to the frame is made, false if nothing's done.
if (index<0) return false;
if (index >= currMovieData.greenZoneCount)
{
/* Handle jumps outside greenzone. */
if (JumpToFrame(currMovieData.greenZoneCount-1))
{
// seek from the end of greenzone
SeekingStart(index+1);
return true;
}
return false;
}
/* Handle jumps inside greenzone. */
if (currMovieData.loadTasSavestate(index))
{
currFrameCounter = index;
turbo = false;
// if playback was seeking, pause emulation right here
if (pauseframe) SeekingStop();
return true;
}
//Search for an earlier frame with savestate
int i = (index>0)? index-1 : 0;
for (; i > 0; --i)
{
if (currMovieData.loadTasSavestate(i)) break;
}
if (!i)
StartFromZero();
else
currFrameCounter = i;
// continue from the frame
SeekingStart(index+1);
return true;
}
void StartFromZero()
{
poweron(true);
currFrameCounter = 0;
currMovieData.TryDumpIncremental();
greenzone.InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_RECORD, currFrameCounter, currFrameCounter));
}
void ToggleJoypadBit(int column_index, int row_index, UINT KeyFlags)
@ -610,15 +394,15 @@ void ToggleJoypadBit(int column_index, int row_index, UINT KeyFlags)
{
currMovieData.records[*it].toggleBit(joy,bit);
}
InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_CHANGE, *selectionFrames.begin(), *selectionFrames.rbegin()));
greenzone.InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_CHANGE, *selectionFrames.begin(), *selectionFrames.rbegin()));
} else
{
//update one row
currMovieData.records[row_index].toggleBit(joy,bit);
if (currMovieData.records[row_index].checkBit(joy,bit))
InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_SET, row_index, row_index));
greenzone.InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_SET, row_index, row_index));
else
InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_UNSET, row_index, row_index));
greenzone.InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_UNSET, row_index, row_index));
}
}
@ -633,7 +417,7 @@ void SingleClick(LPNMITEMACTIVATE info)
{
// click on the "icons" column - jump to the frame
ClearSelection();
JumpToFrame(row_index);
playback.JumpToFrame(row_index);
RedrawList();
} else if(column_index == COLUMN_FRAMENUM || column_index == COLUMN_FRAMENUM2)
{
@ -666,7 +450,7 @@ void DoubleClick(LPNMITEMACTIVATE info)
{
// double click sends playback to the frame
ClearSelection();
JumpToFrame(row_index);
playback.JumpToFrame(row_index);
RedrawList();
} else if(column_index >= COLUMN_JOYPAD1_A && column_index <= COLUMN_JOYPAD4_R)
{
@ -696,7 +480,7 @@ void CloneFrames()
} else frames++;
}
UpdateList();
InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_CLONE, *selectionFrames.begin()));
greenzone.InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_CLONE, *selectionFrames.begin()));
}
void InsertFrames()
@ -724,7 +508,7 @@ void InsertFrames()
} else frames++;
}
UpdateList();
InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_INSERT, *selectionFrames.begin()));
greenzone.InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_INSERT, *selectionFrames.begin()));
}
void DeleteFrames()
@ -740,11 +524,11 @@ void DeleteFrames()
}
// check if user deleted all frames
if (!currMovieData.getNumRecords())
StartFromZero();
playback.StartFromZero();
// reduce list
UpdateList();
InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_DELETE, start_index, end_index));
greenzone.InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_DELETE, start_index));
}
void ClearFrames(bool cut)
@ -755,9 +539,25 @@ void ClearFrames(bool cut)
currMovieData.records[*it].clear();
}
if (cut)
InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_CUT, *selectionFrames.begin(), *selectionFrames.rbegin()));
greenzone.InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_CUT, *selectionFrames.begin(), *selectionFrames.rbegin()));
else
InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_CLEAR, *selectionFrames.begin(), *selectionFrames.rbegin()));
greenzone.InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_CLEAR, *selectionFrames.begin(), *selectionFrames.rbegin()));
}
void Truncate()
{
int frame = currFrameCounter;
if (selectionFrames.size())
{
frame=*selectionFrames.begin();
ClearSelection();
}
if (currMovieData.getNumRecords() > frame+1)
{
currMovieData.truncateAt(frame+1);
UpdateList();
greenzone.InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_TRUNCATE, frame+1));
}
}
//the column set operation, for setting a button/Marker for a span of selected values
@ -810,9 +610,9 @@ void ColumnSet(int column)
for(TSelectionFrames::iterator it(selectionFrames.begin()); it != selectionFrames.end(); it++)
currMovieData.records[*it].setBitValue(joy,button,newValue);
if (newValue)
InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_SET, *selectionFrames.begin(), *selectionFrames.rbegin()));
greenzone.InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_SET, *selectionFrames.begin(), *selectionFrames.rbegin()));
else
InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_UNSET, *selectionFrames.begin(), *selectionFrames.rbegin()));
greenzone.InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_UNSET, *selectionFrames.begin(), *selectionFrames.rbegin()));
}
}
@ -1048,7 +848,7 @@ bool Paste()
pGlobal = strchr(pGlobal, '\n');
}
InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_PASTE, *selectionFrames.begin()));
greenzone.InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_PASTE, *selectionFrames.begin()));
result = true;
}
@ -1058,7 +858,6 @@ bool Paste()
CloseClipboard();
return result;
}
// ---------------------------------------------------------------------------------
//The subclass wndproc for the listview header
LRESULT APIENTRY HeaderWndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)
@ -1247,22 +1046,21 @@ void OpenProject()
multitrack_recording_joypad = MULTITRACK_RECORDING_ALL;
UncheckRecordingRadioButtons();
RecheckRecordingRadioButtons();
playback.reset();
// remember to update fourscore status
bool last_fourscore = currMovieData.fourscore;
// Load project
project.LoadProject(project.GetProjectFile());
UpdateList();
UpdateHistoryList();
RedrawHistoryList();
// update fourscore status
if (last_fourscore && !currMovieData.fourscore)
RemoveFourscore();
else if (!last_fourscore && currMovieData.fourscore)
AddFourscore();
SeekingStop();
FollowPlayback();
RedrawTasedit();
RedrawWindowCaption();
RedrawBookmarksCaption();
}
}
@ -1336,15 +1134,12 @@ bool AskSaveProject()
return true;
}
//Takes a selected .fm2 file and adds it to the Project inputlog
static void Import()
void Import()
{
//Pull the fm2 header, comments, subtitle information out and put it into the project info
//Pull the input out and add it to the main branch input log file
}
}
//Takes current inputlog and saves it as a .fm2 file
static void Export()
void Export()
{
//TODO: redesign this
//Dump project header info into file, then comments & subtitles, then input log
@ -1371,22 +1166,6 @@ static void Export()
}
}
void Truncate()
{
int frame = currFrameCounter;
if (selectionFrames.size())
{
frame=*selectionFrames.begin();
ClearSelection();
}
if (currMovieData.getNumRecords() > frame+1)
{
currMovieData.truncateAt(frame+1);
UpdateList();
InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_TRUNCATE, frame+1));
}
}
//used to track selection
void ItemRangeChanged(NMLVODSTATECHANGE* info)
{
@ -1449,6 +1228,7 @@ BOOL CALLBACK WndprocTasEdit(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lPar
listItems = ListView_GetCountPerPage(hwndList);
hwndHistoryList = GetDlgItem(hwndDlg, IDC_HISTORYLIST);
hwndBookmarksList = GetDlgItem(hwndDlg, IDC_BOOKMARKSLIST);
hwndBookmarks = GetDlgItem(hwndDlg, IDC_BOOKMARKS_BOX);
hwndProgressbar = GetDlgItem(hwndDlg, IDC_PROGRESS1);
SendMessage(hwndProgressbar, PBM_SETRANGE, 0, MAKELPARAM(0, PROGRESSBAR_WIDTH));
hwndRewind = GetDlgItem(hwndDlg, TASEDIT_REWIND);
@ -1518,15 +1298,15 @@ BOOL CALLBACK WndprocTasEdit(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lPar
switch(((LPNMHDR)lParam)->code)
{
case NM_CUSTOMDRAW:
SetWindowLong(hwndDlg, DWL_MSGRESULT, HistoryCustomDraw((NMLVCUSTOMDRAW*)lParam));
SetWindowLong(hwndDlg, DWL_MSGRESULT, history.CustomDraw((NMLVCUSTOMDRAW*)lParam));
return TRUE;
case LVN_GETDISPINFO:
HistoryGetDispInfo((NMLVDISPINFO*)lParam);
history.GetDispInfo((NMLVDISPINFO*)lParam);
break;
case NM_CLICK:
case NM_DBLCLK:
case NM_RCLICK:
HistoryClick((LPNMITEMACTIVATE)lParam);
history.Click((LPNMITEMACTIVATE)lParam);
break;
}
@ -1536,7 +1316,7 @@ BOOL CALLBACK WndprocTasEdit(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lPar
{
case NM_CLICK:
case NM_DBLCLK:
Tasedit_ToggleEmulationPause();
playback.ToggleEmulationPause();
break;
}
break;
@ -1623,12 +1403,12 @@ BOOL CALLBACK WndprocTasEdit(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lPar
// insert at selection
int index = *selectionFrames.begin();
currMovieData.insertEmpty(index,frames);
InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_INSERT, index));
greenzone.InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_INSERT, index));
} else
{
// insert at playback cursor
currMovieData.insertEmpty(currFrameCounter,frames);
InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_INSERT, currFrameCounter));
greenzone.InvalidateGreenZone(history.RegisterInputChanges(MODTYPE_INSERT, currFrameCounter));
}
}
}
@ -1646,15 +1426,15 @@ BOOL CALLBACK WndprocTasEdit(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lPar
break;
case TASEDIT_REWIND_FULL:
//rewinds to beginning of greenzone
JumpToFrame(FindBeginningOfGreenZone(0));
playback.JumpToFrame(greenzone.FindBeginningOfGreenZone());
FollowPlayback();
break;
case TASEDIT_PLAYSTOP:
Tasedit_ToggleEmulationPause();
playback.ToggleEmulationPause();
break;
case TASEDIT_FORWARD_FULL:
//moves to the end of greenzone
JumpToFrame(currMovieData.greenZoneCount-1);
playback.JumpToFrame(greenzone.greenZoneCount-1);
FollowPlayback();
break;
case ACCEL_CTRL_F:
@ -1708,7 +1488,7 @@ BOOL CALLBACK WndprocTasEdit(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lPar
if (new_capacity < TASEdit_greenzone_capacity)
{
TASEdit_greenzone_capacity = new_capacity;
currMovieData.ClearGreenzoneTail();
greenzone.ClearGreenzoneTail();
RedrawList();
} else TASEdit_greenzone_capacity = new_capacity;
}
@ -1745,7 +1525,8 @@ BOOL CALLBACK WndprocTasEdit(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lPar
CheckMenuItem(hmenu, ID_CONFIG_BINDMARKERSTOINPUT, TASEdit_bind_markers?MF_CHECKED : MF_UNCHECKED);
break;
case IDC_PROGRESS_BUTTON:
if (pauseframe) SeekingStop();
// click on progressbar - stop seeking
if (playback.pauseframe) playback.SeekingStop();
break;
case IDC_RADIO1:
// switch to readonly, no need to recheck radiobuttons
@ -1792,9 +1573,9 @@ BOOL CALLBACK WndprocTasEdit(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lPar
int result = history.undo();
if (result >= 0)
{
FollowUndo();
UpdateList();
InvalidateGreenZone(result);
FollowUndo();
greenzone.InvalidateGreenZone(result);
}
break;
}
@ -1804,9 +1585,9 @@ BOOL CALLBACK WndprocTasEdit(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lPar
int result = history.redo();
if (result >= 0)
{
FollowRedo();
UpdateList();
InvalidateGreenZone(result);
FollowUndo();
greenzone.InvalidateGreenZone(result);
}
break;
}
@ -1820,15 +1601,6 @@ BOOL CALLBACK WndprocTasEdit(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lPar
return FALSE;
}
int FindBeginningOfGreenZone(int starting_index)
{
for (int i=starting_index; i<currMovieData.greenZoneCount; ++i)
{
if (!currMovieData.savestates[i].empty()) return i;
}
return starting_index;
}
bool CheckItemVisible(int frame)
{
int top = ListView_GetTopIndex(hwndList);
@ -1842,10 +1614,9 @@ void FollowPlayback()
{
if (TASEdit_follow_playback) ListView_EnsureVisible(hwndList,currFrameCounter,FALSE);
}
void FollowUndo()
{
int jump_frame = history.GetNextToCurrentSnapshot().jump_frame;
int jump_frame = history.GetUndoHint();
if (TASEdit_jump_to_undo && jump_frame >= 0)
{
if (!CheckItemVisible(jump_frame))
@ -1854,24 +1625,6 @@ void FollowUndo()
ListView_EnsureVisible(hwndList, jump_frame, false);
}
}
// init undo hint
undo_hint_pos = jump_frame;
undo_hint_time = clock() + UNDO_HINT_TIME;
}
void FollowRedo()
{
int jump_frame = history.GetCurrentSnapshot().jump_frame;
if (TASEdit_jump_to_undo && jump_frame >= 0)
{
if (!CheckItemVisible(jump_frame))
{
ListView_EnsureVisible(hwndList, currMovieData.getNumRecords()-1, true);
ListView_EnsureVisible(hwndList, jump_frame, false);
}
}
// init undo hint
undo_hint_pos = jump_frame;
undo_hint_time = clock() + UNDO_HINT_TIME;
}
void EnterTasEdit()
@ -1896,9 +1649,6 @@ void EnterTasEdit()
// switch off autosaves
saved_EnableAutosave = EnableAutosave;
EnableAutosave = 0;
// switch on savestates compression
saved_compressSavestates = compressSavestates;
compressSavestates = true;
UpdateCheckedMenuItems();
@ -1916,107 +1666,96 @@ void EnterTasEdit()
CheckMenuItem(hmenu, ID_VIEW_SHOWDOTINEMPTYCELLS, TASEdit_show_dot?MF_CHECKED : MF_UNCHECKED);
SetWindowPos(hwndTasEdit,HWND_TOP,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE|SWP_NOOWNERZORDER);
}
// either start new movie or use current movie
if (movieMode == MOVIEMODE_INACTIVE)
{
FCEUI_StopMovie();
CreateCleanMovie();
StartFromZero();
}
else
{
//use current movie to create a new project
FCEUI_StopMovie();
}
// always start work from read-only mode, ready to switch to MULTITRACK_RECORDING_ALL
movie_readonly = true;
multitrack_recording_joypad = MULTITRACK_RECORDING_ALL;
RecheckRecordingRadioButtons();
movieMode = MOVIEMODE_TASEDIT;
//prepare the main listview
ListView_SetExtendedListViewStyleEx(hwndList,LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES,LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES);
//subclass the header
hwndHeader = ListView_GetHeader(hwndList);
hwndHeader_oldWndproc = (WNDPROC)SetWindowLong(hwndHeader,GWL_WNDPROC,(LONG)HeaderWndProc);
//subclass the whole listview
hwndList_oldWndProc = (WNDPROC)SetWindowLong(hwndList,GWL_WNDPROC,(LONG)ListWndProc);
//setup images for the listview
HIMAGELIST himglist = ImageList_Create(8,12,ILC_COLOR8 | ILC_MASK,1,1);
HBITMAP bmp = LoadBitmap(fceu_hInstance,MAKEINTRESOURCE(IDB_TE_ARROW));
ImageList_AddMasked(himglist, bmp, 0xFF00FF);
DeleteObject(bmp);
ListView_SetImageList(hwndList,himglist,LVSIL_SMALL);
//setup columns
LVCOLUMN lvc;
int colidx=0;
// icons column
lvc.mask = LVCF_WIDTH;
lvc.cx = 13;
ListView_InsertColumn(hwndList, colidx++, &lvc);
// frame number column
lvc.mask = LVCF_WIDTH | LVCF_TEXT | LVCF_FMT;
lvc.fmt = LVCFMT_CENTER;
lvc.cx = 75;
lvc.pszText = "Frame#";
ListView_InsertColumn(hwndList, colidx++, &lvc);
// pads columns
lvc.cx = 21;
// add pads 1 and 2
for (int joy = 0; joy < 2; ++joy)
{
for (int btn = 0; btn < NUM_JOYPAD_BUTTONS; ++btn)
playback.init();
greenzone.init();
// either start new movie or use current movie
if (movieMode == MOVIEMODE_INACTIVE)
{
lvc.pszText = buttonNames[btn];
ListView_InsertColumn(hwndList, colidx++, &lvc);
FCEUI_StopMovie();
CreateCleanMovie();
playback.StartFromZero();
}
else
{
//use current movie to create a new project
FCEUI_StopMovie();
}
// always start work from read-only mode, ready to switch to MULTITRACK_RECORDING_ALL
movie_readonly = true;
multitrack_recording_joypad = MULTITRACK_RECORDING_ALL;
RecheckRecordingRadioButtons();
movieMode = MOVIEMODE_TASEDIT;
//prepare the main listview
ListView_SetExtendedListViewStyleEx(hwndList,LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES,LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES);
//subclass the header
hwndHeader = ListView_GetHeader(hwndList);
hwndHeader_oldWndproc = (WNDPROC)SetWindowLong(hwndHeader,GWL_WNDPROC,(LONG)HeaderWndProc);
//subclass the whole listview
hwndList_oldWndProc = (WNDPROC)SetWindowLong(hwndList,GWL_WNDPROC,(LONG)ListWndProc);
//setup images for the listview
HIMAGELIST himglist = ImageList_Create(8,12,ILC_COLOR8 | ILC_MASK,1,1);
HBITMAP bmp = LoadBitmap(fceu_hInstance,MAKEINTRESOURCE(IDB_TE_ARROW));
ImageList_AddMasked(himglist, bmp, 0xFF00FF);
DeleteObject(bmp);
ListView_SetImageList(hwndList,himglist,LVSIL_SMALL);
//setup columns
LVCOLUMN lvc;
int colidx=0;
// icons column
lvc.mask = LVCF_WIDTH;
lvc.cx = 13;
ListView_InsertColumn(hwndList, colidx++, &lvc);
// frame number column
lvc.mask = LVCF_WIDTH | LVCF_TEXT | LVCF_FMT;
lvc.fmt = LVCFMT_CENTER;
lvc.cx = 75;
lvc.pszText = "Frame#";
ListView_InsertColumn(hwndList, colidx++, &lvc);
// pads columns
lvc.cx = 21;
// add pads 1 and 2
for (int joy = 0; joy < 2; ++joy)
{
for (int btn = 0; btn < NUM_JOYPAD_BUTTONS; ++btn)
{
lvc.pszText = buttonNames[btn];
ListView_InsertColumn(hwndList, colidx++, &lvc);
}
}
// add pads 3 and 4 and frame_number2
if (currMovieData.fourscore) AddFourscore();
UpdateList();
//prepare the history listview
ListView_SetExtendedListViewStyleEx(hwndHistoryList,LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES,LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES);
//subclass the whole listview
hwndHistoryList_oldWndProc = (WNDPROC)SetWindowLong(hwndHistoryList,GWL_WNDPROC,(LONG)HistoryListWndProc);
lvc.mask = LVCF_WIDTH | LVCF_TEXT | LVCF_FMT;
lvc.cx = 200;
lvc.fmt = LVCFMT_LEFT;
ListView_InsertColumn(hwndHistoryList, 0, &lvc);
// init variables
project.init();
history.init(TasEdit_undo_levels);
SetFocus(hwndHistoryList);
FCEU_DispMessage("Tasedit engaged",0);
}
// add pads 3 and 4 and frame_number2
if (currMovieData.fourscore) AddFourscore();
UpdateList();
//prepare the history listview
ListView_SetExtendedListViewStyleEx(hwndHistoryList,LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES,LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES);
//subclass the whole listview
hwndHistoryList_oldWndProc = (WNDPROC)SetWindowLong(hwndHistoryList,GWL_WNDPROC,(LONG)HistoryListWndProc);
lvc.mask = LVCF_WIDTH | LVCF_TEXT | LVCF_FMT;
lvc.cx = 200;
lvc.fmt = LVCFMT_LEFT;
ListView_InsertColumn(hwndHistoryList, 0, &lvc);
// init variables
lastCursor = -1;
old_project_changed = false;
old_pauseframe = 0;
old_show_pauseframe = show_pauseframe = false;
undo_hint_pos = old_undo_hint_pos = undo_hint_time = -1;
old_show_undo_hint = show_undo_hint = false;
old_rewind_button_state = rewind_button_state = false;
old_forward_button_state = forward_button_state = false;
old_emu_paused = emu_paused = true;
SeekingStop();
currMovieData.TryDumpIncremental();
project.init(&history);
history.init(TasEdit_undo_levels);
SetFocus(hwndHistoryList);
FCEU_DispMessage("Tasedit engaged",0);
}
bool ExitTasEdit()
{
if (!AskSaveProject()) return false;
DestroyWindow(hwndTasEdit);
hwndTasEdit = 0;
SeekingStop();
TASEdit_focus = false;
// restore "eoptions"
eoptions = saved_eoptions;
// restore autosaves
EnableAutosave = saved_EnableAutosave;
// restore compression
compressSavestates = saved_compressSavestates;
DoPriority();
UpdateCheckedMenuItems();
@ -2024,69 +1763,12 @@ bool ExitTasEdit()
KeyboardClearBackgroundAccessBit(KEYBACKACCESS_TASEDIT);
JoystickClearBackgroundAccessBit(JOYBACKACCESS_TASEDIT);
// release memory
currMovieData.clearGreenzone();
greenzone.clearGreenzone();
history.free();
movieMode = MOVIEMODE_INACTIVE;
FCEU_DispMessage("Tasedit disengaged",0);
CreateCleanMovie();
return true;
}
// -------------------------------------------------------------------------------
void HistoryGetDispInfo(NMLVDISPINFO* nmlvDispInfo)
{
LVITEM& item = nmlvDispInfo->item;
if(item.mask & LVIF_TEXT)
strcpy(item.pszText, history.GetItemDesc(item.iItem));
}
LONG HistoryCustomDraw(NMLVCUSTOMDRAW* msg)
{
switch(msg->nmcd.dwDrawStage)
{
case CDDS_PREPAINT:
return CDRF_NOTIFYITEMDRAW;
case CDDS_ITEMPREPAINT:
{
if (history.GetItemCoherence(msg->nmcd.dwItemSpec))
msg->clrTextBk = HISTORY_COHERENT_COLOR;
else
msg->clrTextBk = HISTORY_NORMAL_COLOR;
return CDRF_DODEFAULT;
}
default:
return CDRF_DODEFAULT;
}
}
void HistoryClick(LPNMITEMACTIVATE info)
{
// jump to pointed input snapshot
int item = info->iItem;
if (item >= 0)
{
int previous_item = history.GetCursorPos();
int result = history.jump(item);
if (result >= 0)
{
if (item < previous_item) FollowUndo(); else FollowRedo();
UpdateList();
InvalidateGreenZone(result);
return;
}
}
RedrawHistoryList();
}
void UpdateHistoryList()
{
//update the number of items in the history list
int currLVItemCount = ListView_GetItemCount(hwndHistoryList);
int history_size = history.GetTotalItems();
if(currLVItemCount != history_size)
{
ListView_SetItemCountEx(hwndHistoryList,history_size,LVSICF_NOSCROLL | LVSICF_NOINVALIDATEALL);
RedrawHistoryList();
}
}

View File

@ -2,9 +2,7 @@
#define NUM_JOYPADS 4
#define NUM_JOYPAD_BUTTONS 8
#define PAUSEFRAME_BLINKING_PERIOD 100
#define PROGRESSBAR_WIDTH 200
#define HOLD_REPEAT_DELAY 250 // in milliseconds
#define GREENZONE_CAPACITY_DEFAULT 100000
#define GREENZONE_CAPACITY_MIN 1
@ -14,8 +12,6 @@
#define UNDO_LEVELS_MAX 999
#define UNDO_LEVELS_DEFAULT 100
#define UNDO_HINT_TIME 200
// multitrack
#define MULTITRACK_RECORDING_ALL 0
#define MULTITRACK_RECORDING_1P 1
@ -77,44 +73,26 @@
#define CUR_INPUT_COLOR2 0xE4D8A8
#define GREENZONE_INPUT_COLOR2 0xAEE2AE
#define LAG_INPUT_COLOR2 0xB8B3E2
#define HISTORY_COHERENT_COLOR 0xF9DDE6
#define HISTORY_NORMAL_COLOR 0xFFFFFF
// -----------------------------
void EnterTasEdit();
void InitDialog();
bool ExitTasEdit();
void UpdateTasEdit();
void UpdateList();
void UpdateHistoryList();
void UpdateProgressbar(int a, int b);
void InputChangedRec();
void InvalidateGreenZone(int after);
bool JumpToFrame(int index);
int FindBeginningOfGreenZone(int starting_index);
bool CheckItemVisible(int frame);
void FollowPlayback();
void FollowUndo();
void FollowRedo();
void ClearSelection();
void ClearRowSelection(int index);
void AddFourscore();
void RemoveFourscore();
void RedrawWindowCaption();
void RedrawBookmarksCaption();
void RedrawTasedit();
void RedrawList();
void RedrawHistoryList();
void RedrawRow(int index);
void SeekingStart(int finish_frame);
void SeekingStop();
void Tasedit_ToggleEmulationPause();
void PauseEmulation();
void UnpauseEmulation();
void ToggleJoypadBit(int column_index, int row_index, UINT KeyFlags);
void Tasedit_RewindFrame();
void Tasedit_ForwardFrame();
void StartFromZero();
void SwitchToReadOnly();
void UncheckRecordingRadioButtons();
void RecheckRecordingRadioButtons();
@ -128,12 +106,8 @@ void CloneFrames();
void InsertFrames();
void DeleteFrames();
void ClearFrames(bool cut = false);
void Truncate();
void ColumnSet(int column);
bool Copy();
void Cut();
bool Paste();
void Truncate();
void HistoryGetDispInfo(NMLVDISPINFO* nmlvDispInfo);
LONG HistoryCustomDraw(NMLVCUSTOMDRAW* msg);
void HistoryClick(LPNMITEMACTIVATE info);

View File

@ -0,0 +1,248 @@
//Implementation file of Greenzone class
#include "movie.h"
#include "state.h"
#include "../common.h"
#include "zlib.h"
#include "taseditproj.h"
#include "../tasedit.h"
extern TASEDIT_PROJECT project;
extern PLAYBACK playback;
extern int TASEdit_greenzone_capacity;
extern bool TASEdit_bind_markers;
extern bool TASEdit_restore_position;
extern void FCEU_printf(char *format, ...);
GREENZONE::GREENZONE()
{
}
void GREENZONE::init()
{
clearGreenzone();
reset();
}
void GREENZONE::reset()
{
}
void GREENZONE::update()
{
}
void GREENZONE::TryDumpIncremental(bool lagFlag)
{
// if movie length is less than currFrame, pad it with empty frames
if((int)currMovieData.records.size() <= currFrameCounter)
currMovieData.insertEmpty(-1, 1 + currFrameCounter - (int)currMovieData.records.size());
// if frame chanegd - log savestate
storeTasSavestate(currFrameCounter);
// also log frame_flags
if (currFrameCounter > 0)
{
// lagFlag indicates that lag was in previous frame
if (lagFlag)
currMovieData.frames_flags[currFrameCounter-1] |= LAG_FLAG_BIT;
else
currMovieData.frames_flags[currFrameCounter-1] &= ~LAG_FLAG_BIT;
}
// update greenzone upper limit
if (greenZoneCount <= currFrameCounter)
greenZoneCount = currFrameCounter+1;
ClearGreenzoneTail();
}
bool GREENZONE::loadTasSavestate(int frame)
{
if (frame < 0 || frame >= (int)currMovieData.records.size())
return false;
if ((int)savestates.size() <= frame || savestates[frame].empty())
return false;
EMUFILE_MEMORY ms(&savestates[frame]);
return FCEUSS_LoadFP(&ms, SSLOADPARAM_NOBACKUP);
}
void GREENZONE::storeTasSavestate(int frame)
{
if ((int)savestates.size()<=frame)
savestates.resize(frame+1);
EMUFILE_MEMORY ms(&savestates[frame]);
FCEUSS_SaveMS(&ms, Z_DEFAULT_COMPRESSION);
ms.trim();
}
void GREENZONE::ClearGreenzoneTail()
{
int tail_frame = greenZoneCount-1 - TASEdit_greenzone_capacity;
if (tail_frame >= currFrameCounter) tail_frame = currFrameCounter - 1;
for (;tail_frame >= 0; tail_frame--)
{
if (savestates[tail_frame].empty()) break;
ClearSavestate(tail_frame);
RedrawRow(tail_frame);
}
}
void GREENZONE::ClearSavestate(int index)
{
std::vector<uint8> tmp;
savestates[index].swap(tmp);
}
void GREENZONE::clearGreenzone()
{
int size = savestates.size();
for (int i = 0; i < size; ++i)
{
ClearSavestate(i);
}
greenZoneCount = 1;
currMovieData.frames_flags.resize(1);
// reset lua_colorings
// reset monitorings
}
int GREENZONE::dumpGreenzone(EMUFILE *os)
{
int start = os->ftell();
int frame, size;
int last_tick = 0;
// write size
write32le(greenZoneCount, os);
write32le(currFrameCounter, os);
// write savestates
for (frame = 0; frame < greenZoneCount; ++frame)
{
// update TASEditor progressbar from time to time
if (frame / PROGRESSBAR_UPDATE_RATE > last_tick)
{
playback.SetProgressbar(frame, greenZoneCount);
last_tick = frame / PROGRESSBAR_UPDATE_RATE;
}
if (savestates[frame].empty()) continue;
write32le(frame, os);
// write frames_flags
os->fwrite(&currMovieData.frames_flags[frame], 1);
// write lua_colorings
// write monitorings
// write savestate
size = savestates[frame].size();
write32le(size, os);
os->fwrite(savestates[frame].data(), size);
}
// write -1 as eof for greenzone
write32le(-1, os);
int end = os->ftell();
return end-start;
}
bool GREENZONE::loadGreenzone(EMUFILE *is)
{
clearGreenzone();
currMovieData.frames_flags.resize(currMovieData.records.size());
int frame = 0, prev_frame = 0, size = 0;
int last_tick = 0;
// read size
if (read32le((uint32 *)&size, is) && size >= 0 && size <= (int)currMovieData.records.size())
{
greenZoneCount = size;
savestates.resize(greenZoneCount);
int greenzone_tail_frame = greenZoneCount-1 - TASEdit_greenzone_capacity;
if (read32le((uint32 *)&frame, is))
{
currFrameCounter = frame;
while(1)
{
if (!read32le((uint32 *)&frame, is)) break;
if (frame == -1) break;
// update TASEditor progressbar from time to time
if (frame / PROGRESSBAR_UPDATE_RATE > last_tick)
{
playback.SetProgressbar(frame, greenZoneCount);
last_tick = frame / PROGRESSBAR_UPDATE_RATE;
}
// read frames_flags
if ((int)is->fread(&currMovieData.frames_flags[frame],1) != 1) break;
// read lua_colorings
// read monitorings
// read savestate
if (!read32le((uint32 *)&size, is)) break;
if (size < 0) break;
if (frame > greenzone_tail_frame || frame == currFrameCounter)
{
// load savestate
savestates[frame].resize(size);
if ((int)is->fread(savestates[frame].data(),size) < size) break;
prev_frame = frame; // successfully read one greenzone frame info
} else
{
// skip loading this savestate
if (is->fseek(size,SEEK_CUR) != 0) break;
}
}
greenZoneCount = prev_frame+1; // cut greenZoneCount to last good frame
if (frame == -1)
{
// everything went fine - load savestate at cursor position
if (loadTasSavestate(currFrameCounter))
return true;
} else goto error;
}
}
error:
// there was some error while reading greenzone
FCEU_printf("Error loading greenzone\n");
return false;
}
void GREENZONE::InvalidateGreenZone(int after)
{
if (after < 0) return;
project.changed = true;
if (greenZoneCount > after+1)
{
greenZoneCount = after+1;
currMovieData.rerecordCount++;
// either set playback cursor to the end of greenzone or run seeking to restore playback position
if (currFrameCounter >= greenZoneCount)
{
if (TASEdit_restore_position)
playback.restorePosition();
else
playback.JumpToFrame(greenZoneCount-1);
}
}
// redraw list even if greenzone didn't change
RedrawList();
}
int GREENZONE::FindBeginningOfGreenZone(int starting_index)
{
for (int i = starting_index; i < greenZoneCount; ++i)
{
if (!savestates[i].empty()) return i;
}
return starting_index;
}

View File

@ -0,0 +1,34 @@
//Specification file for Greenzone class
class GREENZONE
{
public:
GREENZONE();
void init();
void reset();
void update();
void TryDumpIncremental(bool lagFlag = true);
void clearGreenzone();
int dumpGreenzone(EMUFILE *os);
bool loadGreenzone(EMUFILE *is);
bool loadTasSavestate(int frame);
void storeTasSavestate(int frame);
void ClearGreenzoneTail();
void ClearSavestate(int index);
void InvalidateGreenZone(int after);
int FindBeginningOfGreenZone(int starting_index = 0);
// data
int greenZoneCount;
std::vector<std::vector<uint8>> savestates;
private:
};

View File

@ -1,15 +1,19 @@
//Implementation file of Input History and Input Snapshot classes (for Undo feature)
//Implementation file of Input History class (Undo feature)
#include "movie.h"
#include "../common.h"
#include "../tasedit.h"
#include "inputsnapshot.h"
#include "inputhistory.h"
#include "zlib.h"
#include "playback.h"
#include "greenzone.h"
extern PLAYBACK playback;
extern void FCEU_printf(char *format, ...);
extern void RedrawHistoryList();
extern void UpdateHistoryList();
extern void UpdateProgressbar(int a, int b);
extern HWND hwndHistoryList;
extern GREENZONE greenzone;
char modCaptions[23][12] = {"Init",
char modCaptions[24][12] = {"Init",
"Change",
"Set",
"Unset",
@ -22,6 +26,7 @@ char modCaptions[23][12] = {"Init",
"PasteInsert",
"Clone",
"Record",
"Import",
"Branch0",
"Branch1",
"Branch2",
@ -34,311 +39,6 @@ char modCaptions[23][12] = {"Init",
"Branch9"};
char joypadCaptions[4][5] = {"(1P)", "(2P)", "(3P)", "(4P)"};
INPUT_SNAPSHOT::INPUT_SNAPSHOT()
{
}
void INPUT_SNAPSHOT::init(MovieData& md)
{
// retrieve input data from movie data
size = md.getNumRecords();
fourscore = md.fourscore;
int num = (fourscore)?4:2;
joysticks.resize(num*size); // it's much faster to have this format [joy + frame << JOY_POWER] than have [frame][joy]
hot_changes.resize(num*size * HOTCHANGE_BYTES_PER_JOY);
int pos = 0;
if (fourscore)
{
for (int frame = 0; frame < size; ++frame)
{
joysticks[pos++] = md.records[frame].joysticks[0];
joysticks[pos++] = md.records[frame].joysticks[1];
joysticks[pos++] = md.records[frame].joysticks[2];
joysticks[pos++] = md.records[frame].joysticks[3];
}
} else
{
for (int frame = 0; frame < size; ++frame)
{
joysticks[pos++] = md.records[frame].joysticks[0];
joysticks[pos++] = md.records[frame].joysticks[1];
}
}
coherent = true;
// save time to description
time_t raw_time;
time(&raw_time);
struct tm * timeinfo = localtime(&raw_time);
strftime(description, 10, "%H:%M:%S ", timeinfo);
}
void INPUT_SNAPSHOT::toMovie(MovieData& md, int start)
{
// write input data to movie data
md.records.resize(size);
md.frames_flags.resize(size);
if (fourscore)
{
int pos = start << 2;
for (int frame = start; frame < size; ++frame)
{
md.records[frame].joysticks[0] = joysticks[pos++];
md.records[frame].joysticks[1] = joysticks[pos++];
md.records[frame].joysticks[2] = joysticks[pos++];
md.records[frame].joysticks[3] = joysticks[pos++];
}
} else
{
int pos = start << 1;
for (int frame = start; frame < size; ++frame)
{
md.records[frame].joysticks[0] = joysticks[pos++];
md.records[frame].joysticks[1] = joysticks[pos++];
}
}
}
void INPUT_SNAPSHOT::save(EMUFILE *os)
{
// write vars
write32le(size, os);
if (fourscore) write8le(4, os); else write8le((uint8)0, os);
if (coherent) write8le(1, os); else write8le((uint8)0, os);
write32le(jump_frame, os);
// write description
int len = strlen(description);
write8le(len, os);
os->fwrite(&description[0], len);
// compress and save joysticks data
len = joysticks.size();
int comprlen = (len>>9)+12 + len;
std::vector<uint8> cbuf(comprlen);
int e = compress(cbuf.data(), (uLongf*)&comprlen,(uint8*)joysticks.data(),len);
// write size
write32le(comprlen, os);
os->fwrite(cbuf.data(), comprlen);
// compress and save hot_changes data
len = hot_changes.size();
comprlen = (len>>9)+12 + len;
std::vector<uint8> cbuf2(comprlen);
e = compress(cbuf2.data(),(uLongf*)&comprlen,(uint8*)hot_changes.data(),len);
// write size
write32le(comprlen, os);
os->fwrite(cbuf2.data(), comprlen);
}
bool INPUT_SNAPSHOT::load(EMUFILE *is)
{
int len;
uint8 tmp;
// read vars
if (!read32le(&size, is)) return false;
if (!read8le(&tmp, is)) return false;
fourscore = (tmp != 0);
if (!read8le(&tmp, is)) return false;
coherent = (tmp != 0);
if (!read32le(&jump_frame, is)) return false;
// read description
if (!read8le(&tmp, is)) return false;
if (tmp < 0 || tmp >= SNAPSHOT_DESC_MAX_LENGTH) return false;
if (is->fread(&description[0], tmp) != tmp) return false;
description[tmp] = 0; // add '0' because it wasn't saved
// read and uncompress joysticks data
len = (fourscore)?4*size:2*size;
joysticks.resize(len);
// read size
int comprlen;
if (!read32le(&comprlen, is)) return false;
if (comprlen <= 0 || comprlen > len) return false;
std::vector<uint8> cbuf(comprlen);
if (is->fread(cbuf.data(),comprlen) != comprlen) return false;
int e = uncompress((uint8*)joysticks.data(),(uLongf*)&len,cbuf.data(),comprlen);
if (e != Z_OK && e != Z_BUF_ERROR) return false;
// read and uncompress hot_changes data
len = (fourscore) ? 4*size*HOTCHANGE_BYTES_PER_JOY : 2*size*HOTCHANGE_BYTES_PER_JOY;
hot_changes.resize(len);
// read size
if (!read32le(&comprlen, is)) return false;
if (comprlen <= 0 || comprlen > len) return false;
std::vector<uint8> cbuf2(comprlen);
if (is->fread(cbuf2.data(),comprlen) != comprlen) return false;
e = uncompress(hot_changes.data(),(uLongf*)&len,cbuf.data(),comprlen);
if (e != Z_OK && e != Z_BUF_ERROR) return false;
return true;
}
bool INPUT_SNAPSHOT::skipLoad(EMUFILE *is)
{
int tmp;
uint8 tmp1;
// read vars
if (!read32le(&tmp, is)) return false;
if (!read8le(&tmp1, is)) return false;
if (!read8le(&tmp1, is)) return false;
if (!read32le(&tmp, is)) return false;
// read description
if (!read8le(&tmp1, is)) return false;
if (is->fseek(tmp1, SEEK_CUR) != 0) return false;
// read joysticks data
if (!read32le(&tmp, is)) return false;
if (is->fseek(tmp, SEEK_CUR) != 0) return false;
// read hot_changes data
if (!read32le(&tmp, is)) return false;
if (is->fseek(tmp, SEEK_CUR) != 0) return false;
return true;
}
// return true if any difference is found
bool INPUT_SNAPSHOT::checkDiff(INPUT_SNAPSHOT& inp)
{
if (size != inp.size) return true;
if (findFirstChange(inp) >= 0)
return true;
else
return false;
}
// return true if joypads differ
bool INPUT_SNAPSHOT::checkJoypadDiff(INPUT_SNAPSHOT& inp, int frame, int joy)
{
if (fourscore)
{
int pos = frame << 2;
if (pos < (inp.size << 2))
{
if (joysticks[pos+joy] != inp.joysticks[pos+joy]) return true;
} else
{
if (joysticks[pos+joy]) return true;
}
} else
{
int pos = frame << 1;
if (pos < (inp.size << 1))
{
if (joysticks[pos+joy] != inp.joysticks[pos+joy]) return true;
} else
{
if (joysticks[pos+joy]) return true;
}
}
return false;
}
// return number of first frame of difference
int INPUT_SNAPSHOT::findFirstChange(INPUT_SNAPSHOT& inp, int start, int end)
{
// search for differences to the specified end (or to size)
if (end < 0 || end >= size) end = size-1;
if (fourscore)
{
int inp_end = inp.size << 2;
end = (end << 2) + 3;
for (int pos = start << 2; pos <= end; ++pos)
{
// if found different byte, or found emptiness in inp when there's non-zero value here
if (pos < inp_end)
{
if (joysticks[pos] != inp.joysticks[pos]) return (pos >> 2);
} else
{
if (joysticks[pos]) return (pos >> 2);
}
}
} else
{
int inp_end = inp.size << 1;
end = (end << 1) + 1;
for (int pos = start << 1; pos <= end; ++pos)
{
// if found different byte, or found emptiness in inp when there's non-zero value here
if (pos < inp_end)
{
if (joysticks[pos] != inp.joysticks[pos]) return (pos >> 1);
} else
{
if (joysticks[pos]) return (pos >> 1);
}
}
}
// if current size is less then previous return size-1 as the frame of difference
if (size < inp.size) return size-1;
return -1; // no changes were found
}
int INPUT_SNAPSHOT::findFirstChange(MovieData& md)
{
// search for differences from the beginning to the end of movie (or to size)
int end = md.getNumRecords()-1;
if (end >= size) end = size-1;
if (fourscore)
{
for (int frame = 0, pos = 0; frame <= end; ++frame)
{
if (joysticks[pos++] != md.records[frame].joysticks[0]) return frame;
if (joysticks[pos++] != md.records[frame].joysticks[1]) return frame;
if (joysticks[pos++] != md.records[frame].joysticks[2]) return frame;
if (joysticks[pos++] != md.records[frame].joysticks[3]) return frame;
}
} else
{
for (int frame = 0, pos = 0; frame <= end; ++frame)
{
if (joysticks[pos++] != md.records[frame].joysticks[0]) return frame;
if (joysticks[pos++] != md.records[frame].joysticks[1]) return frame;
}
}
// if sizes differ, return last frame from the lesser of them
if (size != md.getNumRecords()) return end;
return -1; // no changes were found
}
void INPUT_SNAPSHOT::SetMaxHotChange(int frame, int absolute_button)
{
if (frame < 0 || frame >= size) return;
// set max value (15) to the button hotness
if (fourscore)
{
// 32 buttons, 16bytes
if (absolute_button & 1)
// odd buttons (B, T, D, R) - set upper 4 bits of the byte
hot_changes[(frame << 4) | (absolute_button >> 1)] &= 0xF0;
else
// even buttons (A, S, U, L) - set lower 4 bits of the byte
hot_changes[(frame << 4) | (absolute_button >> 1)] &= 0x0F;
} else
{
// 16 buttons, 8bytes
if (absolute_button & 1)
// odd buttons (B, T, D, R) - set upper 4 bits of the byte
hot_changes[(frame << 3) | (absolute_button >> 1)] &= 0xF0;
else
// even buttons (A, S, U, L) - set lower 4 bits of the byte
hot_changes[(frame << 3) | (absolute_button >> 1)] &= 0x0F;
}
}
int INPUT_SNAPSHOT::GetHotChangeInfo(int frame, int absolute_button)
{
if (frame < 0 || frame >= size) return 0;
if (absolute_button < 0 || absolute_button > 31) return 0;
uint8 val;
if (fourscore)
// 32 buttons, 16bytes
val = hot_changes[(frame << 4) + (absolute_button >> 1)];
else
// 16 buttons, 8bytes
val = hot_changes[(frame << 3) + (absolute_button >> 1)];
if (absolute_button & 1)
// odd buttons (B, T, D, R) - upper 4 bits of the byte
return val >> 4;
else
// even buttons (A, S, U, L) - lower 4 bits of the byte
return val & 15;
}
// -----------------------------------------------------------------------------
INPUT_HISTORY::INPUT_HISTORY()
{
@ -346,6 +46,9 @@ INPUT_HISTORY::INPUT_HISTORY()
void INPUT_HISTORY::init(int new_size)
{
// init vars
undo_hint_pos = old_undo_hint_pos = undo_hint_time = -1;
old_show_undo_hint = show_undo_hint = false;
history_size = new_size + 1;
// clear snapshots history
history_total_items = 0;
@ -358,12 +61,35 @@ void INPUT_HISTORY::init(int new_size)
strcat(inp.description, modCaptions[0]);
inp.jump_frame = -1;
AddInputSnapshotToHistory(inp);
UpdateHistoryList();
RedrawHistoryList();
}
void INPUT_HISTORY::free()
{
input_snapshots.resize(0);
}
void INPUT_HISTORY::update()
{
// update undo_hint
if (old_undo_hint_pos != undo_hint_pos && old_undo_hint_pos >= 0) RedrawRow(old_undo_hint_pos);
old_undo_hint_pos = undo_hint_pos;
old_show_undo_hint = show_undo_hint;
show_undo_hint = false;
if (undo_hint_pos >= 0)
{
if ((int)clock() < undo_hint_time)
show_undo_hint = true;
else
undo_hint_pos = -1; // finished hinting
}
if (old_show_undo_hint != show_undo_hint) RedrawRow(undo_hint_pos);
}
// returns frame of first input change (for greenzone invalidation)
int INPUT_HISTORY::jump(int new_pos)
{
@ -371,6 +97,14 @@ int INPUT_HISTORY::jump(int new_pos)
// if nothing is done, do not invalidate greenzone
if (new_pos == history_cursor_pos) return -1;
// create undo_hint
if (new_pos < history_cursor_pos)
undo_hint_pos = GetCurrentSnapshot().jump_frame;
else
undo_hint_pos = GetNextToCurrentSnapshot().jump_frame;
undo_hint_time = clock() + UNDO_HINT_TIME;
show_undo_hint = true;
// make jump
history_cursor_pos = new_pos;
int real_pos = (history_start_pos + history_cursor_pos) % history_size;
@ -381,6 +115,7 @@ int INPUT_HISTORY::jump(int new_pos)
// update current movie
input_snapshots[real_pos].toMovie(currMovieData, first_change);
RedrawHistoryList();
return first_change;
}
int INPUT_HISTORY::undo()
@ -392,34 +127,6 @@ int INPUT_HISTORY::redo()
return jump(history_cursor_pos + 1);
}
// ----------------------------
INPUT_SNAPSHOT& INPUT_HISTORY::GetCurrentSnapshot()
{
return input_snapshots[(history_start_pos + history_cursor_pos) % history_size];
}
INPUT_SNAPSHOT& INPUT_HISTORY::GetNextToCurrentSnapshot()
{
if (history_cursor_pos < history_total_items)
return input_snapshots[(history_start_pos + history_cursor_pos + 1) % history_size];
else
return input_snapshots[(history_start_pos + history_cursor_pos) % history_size];
}
int INPUT_HISTORY::GetCursorPos()
{
return history_cursor_pos;
}
int INPUT_HISTORY::GetTotalItems()
{
return history_total_items;
}
char* INPUT_HISTORY::GetItemDesc(int pos)
{
return input_snapshots[(history_start_pos + pos) % history_size].description;
}
bool INPUT_HISTORY::GetItemCoherence(int pos)
{
return input_snapshots[(history_start_pos + pos) % history_size].coherent;
}
// ----------------------------
void INPUT_HISTORY::AddInputSnapshotToHistory(INPUT_SNAPSHOT &inp)
{
// history uses conveyor of snapshots (vector with fixed size) to aviod resizing which is awfully expensive with such large objects as INPUT_SNAPSHOT
@ -485,6 +192,7 @@ int INPUT_HISTORY::RegisterInputChanges(int mod_type, int start, int end)
case MODTYPE_TRUNCATE:
case MODTYPE_CLEAR:
case MODTYPE_CUT:
case MODTYPE_IMPORT:
case MODTYPE_BRANCH_0: case MODTYPE_BRANCH_1:
case MODTYPE_BRANCH_2: case MODTYPE_BRANCH_3:
case MODTYPE_BRANCH_4: case MODTYPE_BRANCH_5:
@ -507,7 +215,7 @@ int INPUT_HISTORY::RegisterInputChanges(int mod_type, int start, int end)
case MODTYPE_RECORD:
{
// add info which joypads were affected
int num = (inp.fourscore)?4:2;
int num = (inp.input_type + 1) * 2; // hacky, only for distingushing between normal2p and fourscore
for (int i = 0; i < num; ++i)
{
if (inp.checkJoypadDiff(input_snapshots[real_pos], first_changes, i))
@ -531,7 +239,7 @@ int INPUT_HISTORY::RegisterInputChanges(int mod_type, int start, int end)
}
return first_changes;
}
// ----------------------------
void INPUT_HISTORY::save(EMUFILE *os)
{
int real_pos, last_tick = 0;
@ -543,7 +251,7 @@ void INPUT_HISTORY::save(EMUFILE *os)
{
real_pos = (history_start_pos + i) % history_size;
input_snapshots[real_pos].save(os);
UpdateProgressbar(i, history_total_items);
playback.SetProgressbar(i, history_total_items);
}
}
void INPUT_HISTORY::load(EMUFILE *is)
@ -555,6 +263,7 @@ void INPUT_HISTORY::load(EMUFILE *is)
// read vars
if (!read32le((uint32 *)&history_cursor_pos, is)) goto error;
if (!read32le((uint32 *)&history_total_items, is)) goto error;
if (history_cursor_pos > history_total_items) goto error;
history_start_pos = 0;
// read snapshots
int total = history_total_items;
@ -582,19 +291,109 @@ void INPUT_HISTORY::load(EMUFILE *is)
{
// skip snapshots if current history_size is less then history_total_items
if (!input_snapshots[i].load(is)) goto error;
UpdateProgressbar(i, history_total_items);
playback.SetProgressbar(i, history_total_items);
}
// skip redo snapshots if needed
for (; i < total; ++i)
if (!inp.skipLoad(is)) goto error;
// init vars
undo_hint_pos = old_undo_hint_pos = undo_hint_time = -1;
old_show_undo_hint = show_undo_hint = false;
UpdateHistoryList();
RedrawHistoryList();
return;
error:
// couldn't load full history - reset it
FCEU_printf("Error loading history\n");
init(history_size-1);
}
// ----------------------------
void INPUT_HISTORY::GetDispInfo(NMLVDISPINFO* nmlvDispInfo)
{
LVITEM& item = nmlvDispInfo->item;
if(item.mask & LVIF_TEXT)
strcpy(item.pszText, GetItemDesc(item.iItem));
}
LONG INPUT_HISTORY::CustomDraw(NMLVCUSTOMDRAW* msg)
{
switch(msg->nmcd.dwDrawStage)
{
case CDDS_PREPAINT:
return CDRF_NOTIFYITEMDRAW;
case CDDS_ITEMPREPAINT:
{
if (GetItemCoherence(msg->nmcd.dwItemSpec))
msg->clrTextBk = HISTORY_COHERENT_COLOR;
else
msg->clrTextBk = HISTORY_NORMAL_COLOR;
return CDRF_DODEFAULT;
}
default:
return CDRF_DODEFAULT;
}
}
void INPUT_HISTORY::Click(LPNMITEMACTIVATE info)
{
// jump to pointed input snapshot
int item = info->iItem;
if (item >= 0)
{
int result = jump(item);
if (result >= 0)
{
UpdateList();
FollowUndo();
greenzone.InvalidateGreenZone(result);
return;
}
}
RedrawHistoryList();
}
void INPUT_HISTORY::UpdateHistoryList()
{
//update the number of items in the history list
int currLVItemCount = ListView_GetItemCount(hwndHistoryList);
if(currLVItemCount != history_total_items)
ListView_SetItemCountEx(hwndHistoryList, history_total_items, LVSICF_NOSCROLL | LVSICF_NOINVALIDATEALL);
}
void INPUT_HISTORY::RedrawHistoryList()
{
ListView_SetItemState(hwndHistoryList, history_cursor_pos, LVIS_FOCUSED|LVIS_SELECTED, LVIS_FOCUSED|LVIS_SELECTED);
ListView_EnsureVisible(hwndHistoryList, history_cursor_pos, FALSE);
InvalidateRect(hwndHistoryList, 0, FALSE);
}
// ----------------------------
INPUT_SNAPSHOT& INPUT_HISTORY::GetCurrentSnapshot()
{
return input_snapshots[(history_start_pos + history_cursor_pos) % history_size];
}
INPUT_SNAPSHOT& INPUT_HISTORY::GetNextToCurrentSnapshot()
{
if (history_cursor_pos < history_total_items)
return input_snapshots[(history_start_pos + history_cursor_pos + 1) % history_size];
else
return input_snapshots[(history_start_pos + history_cursor_pos) % history_size];
}
char* INPUT_HISTORY::GetItemDesc(int pos)
{
return input_snapshots[(history_start_pos + pos) % history_size].description;
}
bool INPUT_HISTORY::GetItemCoherence(int pos)
{
return input_snapshots[(history_start_pos + pos) % history_size].coherent;
}
int INPUT_HISTORY::GetUndoHint()
{
if (show_undo_hint)
return undo_hint_pos;
else
return -1;
}

View File

@ -1,9 +1,6 @@
//Specification file for Input History and Input Snapshot classes
//Specification file for Input History class
#include <time.h>
#define HOTCHANGE_BYTES_PER_JOY 4
#define SNAPSHOT_DESC_MAX_LENGTH 50
#define UNDO_HINT_TIME 200
#define MODTYPE_INIT 0
#define MODTYPE_CHANGE 1
@ -18,48 +15,20 @@
#define MODTYPE_PASTEINSERT 10
#define MODTYPE_CLONE 11
#define MODTYPE_RECORD 12
#define MODTYPE_BRANCH_0 13
#define MODTYPE_BRANCH_1 14
#define MODTYPE_BRANCH_2 15
#define MODTYPE_BRANCH_3 16
#define MODTYPE_BRANCH_4 17
#define MODTYPE_BRANCH_5 18
#define MODTYPE_BRANCH_6 19
#define MODTYPE_BRANCH_7 20
#define MODTYPE_BRANCH_8 21
#define MODTYPE_BRANCH_9 22
#define MODTYPE_IMPORT 13
#define MODTYPE_BRANCH_0 14
#define MODTYPE_BRANCH_1 15
#define MODTYPE_BRANCH_2 16
#define MODTYPE_BRANCH_3 17
#define MODTYPE_BRANCH_4 18
#define MODTYPE_BRANCH_5 19
#define MODTYPE_BRANCH_6 20
#define MODTYPE_BRANCH_7 21
#define MODTYPE_BRANCH_8 22
#define MODTYPE_BRANCH_9 23
class INPUT_SNAPSHOT
{
public:
INPUT_SNAPSHOT();
void init(MovieData& md);
void toMovie(MovieData& md, int start = 0);
void save(EMUFILE *os);
bool load(EMUFILE *is);
bool skipLoad(EMUFILE *is);
bool checkDiff(INPUT_SNAPSHOT& inp);
bool checkJoypadDiff(INPUT_SNAPSHOT& inp, int frame, int joy);
int findFirstChange(INPUT_SNAPSHOT& inp, int start = 0, int end = -1);
int findFirstChange(MovieData& md);
void SetMaxHotChange(int frame, int absolute_button);
int GetHotChangeInfo(int frame, int absolute_button);
int size; // in frames
bool fourscore;
std::vector<uint8> joysticks; // Format: joy0-for-frame0, joy1-for-frame0, joy2-for-frame0, joy3-for-frame0, joy0-for-frame1, joy1-for-frame1, ...
std::vector<uint8> hot_changes; // Format: buttons01joy0-for-frame0, buttons23joy0-for-frame0, buttons45joy0-for-frame0, buttons67joy0-for-frame0, buttons01joy1-for-frame0, ...
bool coherent; // indicates whether this state was made by inputchange of previous state
int jump_frame; // for jumping when making undo
char description[SNAPSHOT_DESC_MAX_LENGTH];
private:
};
#define HISTORY_COHERENT_COLOR 0xF9DDE6
#define HISTORY_NORMAL_COLOR 0xFFFFFF
class INPUT_HISTORY
{
@ -68,6 +37,8 @@ public:
void init(int new_size);
void free();
void update(); // called every frame
void save(EMUFILE *os);
void load(EMUFILE *is);
@ -85,10 +56,16 @@ public:
INPUT_SNAPSHOT& GetCurrentSnapshot();
INPUT_SNAPSHOT& GetNextToCurrentSnapshot();
int GetCursorPos();
int GetTotalItems();
char* GetItemDesc(int pos);
bool GetItemCoherence(int pos);
int GetUndoHint();
void GetDispInfo(NMLVDISPINFO* nmlvDispInfo);
LONG CustomDraw(NMLVCUSTOMDRAW* msg);
void Click(LPNMITEMACTIVATE info);
void RedrawHistoryList();
void UpdateHistoryList();
private:
std::vector<INPUT_SNAPSHOT> input_snapshots;
@ -98,5 +75,9 @@ private:
int history_total_items;
int history_size;
int undo_hint_pos, old_undo_hint_pos;
int undo_hint_time;
bool old_show_undo_hint, show_undo_hint;
};

View File

@ -0,0 +1,334 @@
//Implementation file of Input Snapshot class (Undo feature)
#include "movie.h"
#include "inputsnapshot.h"
#include "zlib.h"
const int bytes_per_frame[NUM_SUPPORTED_INPUT_TYPES] = {2, 4}; // 16bits for normal joypads, 32bits for fourscore
extern void FCEU_printf(char *format, ...);
INPUT_SNAPSHOT::INPUT_SNAPSHOT()
{
}
void INPUT_SNAPSHOT::init(MovieData& md)
{
// retrieve input data from movie data
size = md.getNumRecords();
input_type = (md.fourscore)?1:0;
joysticks.resize(bytes_per_frame[input_type] * size); // it's much faster to have this format than have [frame][joy] or other structures
hot_changes.resize(bytes_per_frame[input_type] * size * HOTCHANGE_BYTES_PER_JOY);
// fill input vector
int pos = 0;
switch(input_type)
{
case FOURSCORE:
{
for (int frame = 0; frame < size; ++frame)
{
joysticks[pos++] = md.records[frame].joysticks[0];
joysticks[pos++] = md.records[frame].joysticks[1];
joysticks[pos++] = md.records[frame].joysticks[2];
joysticks[pos++] = md.records[frame].joysticks[3];
}
break;
}
case NORMAL_2JOYPADS:
{
for (int frame = 0; frame < size; ++frame)
{
joysticks[pos++] = md.records[frame].joysticks[0];
joysticks[pos++] = md.records[frame].joysticks[1];
}
break;
}
}
coherent = true;
// save time to description
time_t raw_time;
time(&raw_time);
struct tm * timeinfo = localtime(&raw_time);
strftime(description, 10, "%H:%M:%S ", timeinfo);
}
void INPUT_SNAPSHOT::toMovie(MovieData& md, int start)
{
// write input data to movie data
md.records.resize(size);
md.frames_flags.resize(size);
switch(input_type)
{
case FOURSCORE:
{
int pos = start * bytes_per_frame[input_type];
for (int frame = start; frame < size; ++frame)
{
md.records[frame].joysticks[0] = joysticks[pos++];
md.records[frame].joysticks[1] = joysticks[pos++];
md.records[frame].joysticks[2] = joysticks[pos++];
md.records[frame].joysticks[3] = joysticks[pos++];
}
break;
}
case NORMAL_2JOYPADS:
{
int pos = start * bytes_per_frame[input_type];
for (int frame = start; frame < size; ++frame)
{
md.records[frame].joysticks[0] = joysticks[pos++];
md.records[frame].joysticks[1] = joysticks[pos++];
}
break;
}
}
}
void INPUT_SNAPSHOT::save(EMUFILE *os)
{
// write vars
write32le(size, os);
write8le(input_type, os);
if (coherent) write8le(1, os); else write8le((uint8)0, os);
write32le(jump_frame, os);
// write description
int len = strlen(description);
write8le(len, os);
os->fwrite(&description[0], len);
// compress and save joysticks data
len = joysticks.size();
int comprlen = (len>>9)+12 + len;
std::vector<uint8> cbuf(comprlen);
int e = compress(cbuf.data(), (uLongf*)&comprlen,(uint8*)joysticks.data(),len);
// write size
write32le(comprlen, os);
os->fwrite(cbuf.data(), comprlen);
// compress and save hot_changes data
len = hot_changes.size();
comprlen = (len>>9)+12 + len;
std::vector<uint8> cbuf2(comprlen);
e = compress(cbuf2.data(),(uLongf*)&comprlen,(uint8*)hot_changes.data(),len);
// write size
write32le(comprlen, os);
os->fwrite(cbuf2.data(), comprlen);
}
bool INPUT_SNAPSHOT::load(EMUFILE *is)
{
int len;
uint8 tmp;
// read vars
if (!read32le(&size, is)) return false;
if (!read8le(&tmp, is)) return false;
input_type = tmp;
if (!read8le(&tmp, is)) return false;
coherent = (tmp != 0);
if (!read32le(&jump_frame, is)) return false;
// read description
if (!read8le(&tmp, is)) return false;
if (tmp < 0 || tmp >= SNAPSHOT_DESC_MAX_LENGTH) return false;
if (is->fread(&description[0], tmp) != tmp) return false;
description[tmp] = 0; // add '0' because it wasn't saved
// read and uncompress joysticks data
len = size * bytes_per_frame[input_type];
joysticks.resize(len);
// read size
int comprlen;
if (!read32le(&comprlen, is)) return false;
if (comprlen <= 0 || comprlen > len) return false;
std::vector<uint8> cbuf(comprlen);
if (is->fread(cbuf.data(),comprlen) != comprlen) return false;
int e = uncompress((uint8*)joysticks.data(),(uLongf*)&len,cbuf.data(),comprlen);
if (e != Z_OK && e != Z_BUF_ERROR) return false;
// read and uncompress hot_changes data
len = size * bytes_per_frame[input_type] * HOTCHANGE_BYTES_PER_JOY;
hot_changes.resize(len);
// read size
if (!read32le(&comprlen, is)) return false;
if (comprlen <= 0 || comprlen > len) return false;
std::vector<uint8> cbuf2(comprlen);
if (is->fread(cbuf2.data(),comprlen) != comprlen) return false;
e = uncompress(hot_changes.data(),(uLongf*)&len,cbuf.data(),comprlen);
if (e != Z_OK && e != Z_BUF_ERROR) return false;
return true;
}
bool INPUT_SNAPSHOT::skipLoad(EMUFILE *is)
{
int tmp;
uint8 tmp1;
// read vars
if (!read32le(&tmp, is)) return false;
if (!read8le(&tmp1, is)) return false;
if (!read8le(&tmp1, is)) return false;
if (!read32le(&tmp, is)) return false;
// read description
if (!read8le(&tmp1, is)) return false;
if (tmp1 < 0 || tmp1 >= SNAPSHOT_DESC_MAX_LENGTH) return false;
if (is->fseek(tmp1, SEEK_CUR) != 0) return false;
// read joysticks data
if (!read32le(&tmp, is)) return false;
if (is->fseek(tmp, SEEK_CUR) != 0) return false;
// read hot_changes data
if (!read32le(&tmp, is)) return false;
if (is->fseek(tmp, SEEK_CUR) != 0) return false;
return true;
}
// return true if any difference is found
bool INPUT_SNAPSHOT::checkDiff(INPUT_SNAPSHOT& inp)
{
if (size != inp.size) return true;
if (findFirstChange(inp) >= 0)
return true;
else
return false;
}
// return true if joypads differ
bool INPUT_SNAPSHOT::checkJoypadDiff(INPUT_SNAPSHOT& inp, int frame, int joy)
{
switch(input_type)
{
case FOURSCORE:
case NORMAL_2JOYPADS:
{
int pos = frame * bytes_per_frame[input_type];
if (pos < (inp.size * bytes_per_frame[input_type]))
{
if (joysticks[pos+joy] != inp.joysticks[pos+joy]) return true;
} else
{
if (joysticks[pos+joy]) return true;
}
break;
}
}
return false;
}
// return number of first frame of difference
int INPUT_SNAPSHOT::findFirstChange(INPUT_SNAPSHOT& inp, int start, int end)
{
// search for differences to the specified end (or to size)
if (end < 0 || end >= size) end = size-1;
switch(input_type)
{
case FOURSCORE:
case NORMAL_2JOYPADS:
{
int inp_end = inp.size * bytes_per_frame[input_type];
end = ((end + 1) * bytes_per_frame[input_type]) - 1;
for (int pos = start * bytes_per_frame[input_type]; pos <= end; ++pos)
{
// if found different byte, or found emptiness in inp when there's non-zero value here
if (pos < inp_end)
{
if (joysticks[pos] != inp.joysticks[pos]) return (pos / bytes_per_frame[input_type]);
} else
{
if (joysticks[pos]) return (pos / bytes_per_frame[input_type]);
}
}
break;
}
}
// if current size is less then previous return size-1 as the frame of difference
if (size < inp.size) return size-1;
// no changes were found
return -1;
}
int INPUT_SNAPSHOT::findFirstChange(MovieData& md)
{
// search for differences from the beginning to the end of movie (or to size)
int end = md.getNumRecords()-1;
if (end >= size) end = size-1;
switch(input_type)
{
case FOURSCORE:
{
for (int frame = 0, pos = 0; frame <= end; ++frame)
{
if (joysticks[pos++] != md.records[frame].joysticks[0]) return frame;
if (joysticks[pos++] != md.records[frame].joysticks[1]) return frame;
if (joysticks[pos++] != md.records[frame].joysticks[2]) return frame;
if (joysticks[pos++] != md.records[frame].joysticks[3]) return frame;
}
break;
}
case NORMAL_2JOYPADS:
{
for (int frame = 0, pos = 0; frame <= end; ++frame)
{
if (joysticks[pos++] != md.records[frame].joysticks[0]) return frame;
if (joysticks[pos++] != md.records[frame].joysticks[1]) return frame;
}
break;
}
}
// if sizes differ, return last frame from the lesser of them
if (size != md.getNumRecords()) return end;
return -1; // no changes were found
}
void INPUT_SNAPSHOT::SetMaxHotChange(int frame, int absolute_button)
{
if (frame < 0 || frame >= size) return;
// set max value (15) to the button hotness
switch(input_type)
{
case FOURSCORE:
{
// 32 buttons = 16bytes
if (absolute_button & 1)
// odd buttons (B, T, D, R) - set upper 4 bits of the byte
hot_changes[(frame << 4) | (absolute_button >> 1)] &= 0xF0;
else
// even buttons (A, S, U, L) - set lower 4 bits of the byte
hot_changes[(frame << 4) | (absolute_button >> 1)] &= 0x0F;
break;
}
case NORMAL_2JOYPADS:
{
// 16 buttons = 8bytes
if (absolute_button & 1)
// odd buttons (B, T, D, R) - set upper 4 bits of the byte
hot_changes[(frame << 3) | (absolute_button >> 1)] &= 0xF0;
else
// even buttons (A, S, U, L) - set lower 4 bits of the byte
hot_changes[(frame << 3) | (absolute_button >> 1)] &= 0x0F;
break;
}
}
}
int INPUT_SNAPSHOT::GetHotChangeInfo(int frame, int absolute_button)
{
if (frame < 0 || frame >= size) return 0;
if (absolute_button < 0 || absolute_button > 31) return 0;
uint8 val;
switch(input_type)
{
case FOURSCORE:
{
// 32 buttons, 16bytes
val = hot_changes[(frame << 4) + (absolute_button >> 1)];
break;
}
case NORMAL_2JOYPADS:
{
// 16 buttons, 8bytes
val = hot_changes[(frame << 3) + (absolute_button >> 1)];
break;
}
}
if (absolute_button & 1)
// odd buttons (B, T, D, R) - upper 4 bits of the byte
return val >> 4;
else
// even buttons (A, S, U, L) - lower 4 bits of the byte
return val & 15;
}

View File

@ -0,0 +1,43 @@
//Specification file for Input Snapshot class
#include <time.h>
#define HOTCHANGE_BYTES_PER_JOY 4
#define SNAPSHOT_DESC_MAX_LENGTH 50
#define NUM_SUPPORTED_INPUT_TYPES 2
#define NORMAL_2JOYPADS 0
#define FOURSCORE 1
class INPUT_SNAPSHOT
{
public:
INPUT_SNAPSHOT();
void init(MovieData& md);
void toMovie(MovieData& md, int start = 0);
void save(EMUFILE *os);
bool load(EMUFILE *is);
bool skipLoad(EMUFILE *is);
bool checkDiff(INPUT_SNAPSHOT& inp);
bool checkJoypadDiff(INPUT_SNAPSHOT& inp, int frame, int joy);
int findFirstChange(INPUT_SNAPSHOT& inp, int start = 0, int end = -1);
int findFirstChange(MovieData& md);
void SetMaxHotChange(int frame, int absolute_button);
int GetHotChangeInfo(int frame, int absolute_button);
int size; // in frames
int input_type; // 0=normal, 1=fourscore, in future may support other input types
std::vector<uint8> joysticks; // Format: joy0-for-frame0, joy1-for-frame0, joy2-for-frame0, joy3-for-frame0, joy0-for-frame1, joy1-for-frame1, ...
std::vector<uint8> hot_changes; // Format: buttons01joy0-for-frame0, buttons23joy0-for-frame0, buttons45joy0-for-frame0, buttons67joy0-for-frame0, buttons01joy1-for-frame0, ...
bool coherent; // indicates whether this state was made by inputchange of previous state
int jump_frame; // for jumping when making undo
char description[SNAPSHOT_DESC_MAX_LENGTH];
private:
};

View File

@ -0,0 +1,239 @@
//Implementation file of Playback class
#include "movie.h"
#include "../common.h"
#include "taseditproj.h"
#include "../tasedit.h"
extern HWND hwndProgressbar, hwndList, hwndRewind, hwndForward;
extern void FCEU_printf(char *format, ...);
extern bool turbo;
extern GREENZONE greenzone;
PLAYBACK::PLAYBACK()
{
}
void PLAYBACK::init()
{
reset();
}
void PLAYBACK::reset()
{
lastCursor = -1;
pauseframe = old_pauseframe = 0;
old_show_pauseframe = show_pauseframe = false;
old_rewind_button_state = rewind_button_state = false;
old_forward_button_state = forward_button_state = false;
old_emu_paused = emu_paused = true;
SeekingStop();
}
void PLAYBACK::update()
{
// pause when seeking hit pauseframe
if(!FCEUI_EmulationPaused())
if(pauseframe && pauseframe <= currFrameCounter + 1)
SeekingStop();
// update seeking progressbar
old_emu_paused = emu_paused;
emu_paused = (FCEUI_EmulationPaused() != 0);
if (pauseframe && !emu_paused)
{
SetProgressbar(currFrameCounter - seeking_start_frame, pauseframe-seeking_start_frame);
} else if (old_emu_paused != emu_paused)
{
// emulator got paused/unpaused externally
if (old_emu_paused && !emu_paused)
// externally unpaused - progressbar should be empty
SetProgressbar(0, 1);
else
// externally paused - progressbar should be full
SetProgressbar(1, 1);
}
// update flashing pauseframe
if (old_pauseframe != pauseframe && old_pauseframe) RedrawRow(old_pauseframe-1);
old_pauseframe = pauseframe;
old_show_pauseframe = show_pauseframe;
if (pauseframe)
show_pauseframe = (int)(clock() / PAUSEFRAME_BLINKING_PERIOD) & 1;
else
show_pauseframe = false;
if (old_show_pauseframe != show_pauseframe) RedrawRow(pauseframe-1);
//update the playback cursor
if(currFrameCounter != lastCursor)
{
FollowPlayback();
//update the old and new rows
RedrawRow(lastCursor);
RedrawRow(currFrameCounter);
UpdateWindow(hwndList);
lastCursor = currFrameCounter;
}
// update < and > buttons
if(emu_paused)
{
old_rewind_button_state = rewind_button_state;
rewind_button_state = (Button_GetState(hwndRewind) & BST_PUSHED) != 0;
if (rewind_button_state)
{
if (!old_rewind_button_state)
{
button_hold_time = clock();
RewindFrame();
} else if (button_hold_time + HOLD_REPEAT_DELAY < clock())
{
RewindFrame();
}
}
old_forward_button_state = forward_button_state;
forward_button_state = (Button_GetState(hwndForward) & BST_PUSHED) != 0;
if (forward_button_state)
{
if (!old_forward_button_state)
{
button_hold_time = clock();
ForwardFrame();
} else if (button_hold_time + HOLD_REPEAT_DELAY < clock())
{
ForwardFrame();
}
}
}
}
void PLAYBACK::updateProgressbar()
{
if (pauseframe)
{
SetProgressbar(currFrameCounter - seeking_start_frame, pauseframe-seeking_start_frame);
} else
{
if (emu_paused)
SetProgressbar(1, 1);
else
SetProgressbar(0, 1);
}
}
void PLAYBACK::ToggleEmulationPause()
{
if (FCEUI_EmulationPaused())
UnpauseEmulation();
else
PauseEmulation();
}
void PLAYBACK::PauseEmulation()
{
FCEUI_SetEmulationPaused(1);
// make some additional stuff
}
void PLAYBACK::UnpauseEmulation()
{
FCEUI_SetEmulationPaused(0);
// make some additional stuff
}
void PLAYBACK::SeekingStart(int finish_frame)
{
seeking_start_frame = currFrameCounter;
pauseframe = finish_frame;
turbo = true;
UnpauseEmulation();
}
void PLAYBACK::SeekingStop()
{
pauseframe = 0;
turbo = false;
PauseEmulation();
SetProgressbar(1, 1);
}
void PLAYBACK::RewindFrame()
{
if (currFrameCounter > 0) JumpToFrame(currFrameCounter-1);
turbo = false;
FollowPlayback();
}
void PLAYBACK::ForwardFrame()
{
JumpToFrame(currFrameCounter+1);
turbo = false;
FollowPlayback();
}
void PLAYBACK::StartFromZero()
{
poweron(true);
currFrameCounter = 0;
greenzone.TryDumpIncremental();
}
bool PLAYBACK::JumpToFrame(int index)
{
// Returns true if a jump to the frame is made, false if started seeking or if nothing's done
if (index<0) return false;
if (index >= greenzone.greenZoneCount)
{
// handle jump outside greenzone
if (JumpToFrame(greenzone.greenZoneCount-1))
// seek from the end of greenzone
SeekingStart(index+1);
return false;
}
/* Handle jumps inside greenzone. */
if (greenzone.loadTasSavestate(index))
{
currFrameCounter = index;
turbo = false;
// if playback was seeking, pause emulation right here
if (pauseframe) SeekingStop();
return true;
}
//Search for an earlier frame with savestate
int i = (index>0)? index-1 : 0;
for (; i > 0; i--)
{
if (greenzone.loadTasSavestate(i)) break;
}
if (!i)
StartFromZero();
else
currFrameCounter = i;
// continue from the frame
SeekingStart(index+1);
return false;
}
void PLAYBACK::restorePosition()
{
if (pauseframe)
JumpToFrame(pauseframe-1);
else
JumpToFrame(currFrameCounter);
}
int PLAYBACK::GetPauseFrame()
{
if (show_pauseframe)
return pauseframe-1;
else
return -1;
}
void PLAYBACK::SetProgressbar(int a, int b)
{
SendMessage(hwndProgressbar, PBM_SETPOS, PROGRESSBAR_WIDTH * a / b, 0);
}

View File

@ -0,0 +1,47 @@
//Specification file for Playback class
#define PAUSEFRAME_BLINKING_PERIOD 100
#define HOLD_REPEAT_DELAY 250 // in milliseconds
class PLAYBACK
{
public:
PLAYBACK();
void init();
void reset();
void update();
void updateProgressbar();
void SeekingStart(int finish_frame);
void SeekingStop();
void ToggleEmulationPause();
void PauseEmulation();
void UnpauseEmulation();
void RewindFrame();
void ForwardFrame();
bool JumpToFrame(int index);
void restorePosition();
void StartFromZero();
int GetPauseFrame();
void SetProgressbar(int a, int b);
int pauseframe;
private:
int lastCursor; // for currentCursor we use external variable currFrameCounter
bool old_emu_paused, emu_paused;
int old_pauseframe;
bool old_show_pauseframe, show_pauseframe;
bool old_rewind_button_state, rewind_button_state;
bool old_forward_button_state, forward_button_state;
int button_hold_time;
int seeking_start_frame;
};

View File

@ -1,34 +1,89 @@
//Implementation file of TASEdit Project file object
//Implementation file of TASEdit Project class
//Contains all the TASEDit project and all files/settings associated with it
//Also contains all methods for manipulating the project files/settings, and saving them to disk
#include <iostream>
#include <fstream>
#include "../main.h"
#include "taseditproj.h"
extern INPUT_HISTORY history;
extern PLAYBACK playback;
extern GREENZONE greenzone;
extern void FCEU_printf(char *format, ...);
TASEDIT_PROJECT::TASEDIT_PROJECT() //Non parameterized constructor, loads project with default values
{
}
void TASEDIT_PROJECT::init(INPUT_HISTORY* history_ptr)
void TASEDIT_PROJECT::init()
{
// keep references to other Taseditor objects
history = history_ptr;
//greenzone = greenzone_ptr;
//bookmarks_bookmarks_ptr;
// init new project
projectName="";
fm2FileName="";
projectFile="";
changed=false;
reset();
}
void TASEDIT_PROJECT::reset()
{
old_changed = changed = false;
}
void TASEDIT_PROJECT::update()
{
old_changed = changed;
}
//TODO: a parameterized constructor that can serve as an import fm2 function
bool TASEDIT_PROJECT::saveProject()
{
std::string PFN = GetProjectFile();
if (PFN.empty()) return false;
const char* filename = PFN.c_str();
EMUFILE_FILE* ofs = FCEUD_UTF8_fstream(filename,"wb");
//ofs << GetProjectName() << std::endl;
//ofs << GetFM2Name() << std::endl;
currMovieData.dump(ofs, true);
greenzone.dumpGreenzone(ofs);
history.save(ofs);
delete ofs;
playback.updateProgressbar();
this->reset();
return true;
}
extern bool LoadFM2(MovieData& movieData, EMUFILE* fp, int size, bool stopAfterHeader);
bool TASEDIT_PROJECT::LoadProject(std::string PFN)
{
const char* filename = PFN.c_str();
EMUFILE_FILE ifs(filename, "rb");
FCEU_printf("Loading project %s\n", filename);
LoadFM2(currMovieData, &ifs, ifs.size(), false);
LoadSubtitles(currMovieData);
// try to load greenzone
if (!greenzone.loadGreenzone(&ifs))
{
// there was some error while loading greenzone - reset playback to frame 0
poweron(true);
currFrameCounter = 0;
// try to load history
history.load(&ifs);
}
reset();
return true;
}
// -----------------------------------------------------------------
//All the get/set functions...
std::string TASEDIT_PROJECT::GetProjectName()
{
@ -55,53 +110,3 @@ void TASEDIT_PROJECT::SetProjectFile(std::string e)
projectFile = e;
}
bool TASEDIT_PROJECT::saveProject()
{
std::string PFN = GetProjectFile();
if (PFN.empty()) return false;
const char* filename = PFN.c_str();
EMUFILE_FILE* ofs = FCEUD_UTF8_fstream(filename,"wb");
//ofs << GetProjectName() << std::endl;
//ofs << GetFM2Name() << std::endl;
currMovieData.dump(ofs, true);
ofs->fputc('\0'); // TODO: Add main branch name.
currMovieData.dumpGreenzone(ofs);
history->save(ofs);
delete ofs;
changed=false;
return true;
}
extern bool LoadFM2(MovieData& movieData, EMUFILE* fp, int size, bool stopAfterHeader);
bool TASEDIT_PROJECT::LoadProject(std::string PFN)
{
const char* filename = PFN.c_str();
EMUFILE_FILE ifs(filename, "rb");
LoadFM2(currMovieData, &ifs, ifs.size(), false);
LoadSubtitles(currMovieData);
char branchname;
branchname = ifs.fgetc(); // TODO: Add main branch name.
// try to load greenzone
if (!currMovieData.loadGreenzone(&ifs))
{
// there was some error while loading greenzone - reset playback to frame 0
poweron(true);
currFrameCounter = 0;
}
// try to load history
history->load(&ifs);
changed = false;
return true;
}

View File

@ -1,82 +1,40 @@
//Specification file for the TASEdit Project file object
//Written by Chris220
//Contains all the TASEDit project and all files/settings associated with it
//Also contains all methods for manipulating the project files/settings, and saving them to disk
//adelikat: How this needs to be structured conceptually
//A project is just all the settings and associated files
//No fm2 data shoudl be loaded here, instead all input logs that are unsaved should be stored in .ilog files which are stripped fm2 (no header info)
//When saving a project all ilog files are saved to disk if necessary, and the .tas project file is saved
//The "main branch" will be an ilog file too, but probably a special case from the others
//When loading, all settings are loaded into the project, all .ilog files are loaded, the main branch .log is loaded as the current fm2 file in the emulator
//The greenzone is loaded as well
//All notes are added to teh right columns
//All ilog files are listed in the input log list
//Specification file for the TASEdit Project class
#include "movie.h"
#include "inputsnapshot.h"
#include "inputhistory.h"
//The notes feature, displays user notes in the notes column
struct TASENote
{
std::string note;
int frame;
};
//Movie header info
struct TASEHeader
{
int version; //Will always be 3 but might as well store it
int emuVersion;
bool palFlag;
std::string romFilename;
std::string romChecksum;
std::string guid;
bool fourscore; //note: TASEdit probably won't support 4 player input for quite some time
bool microphone;
bool port0; //Always true
bool port1; //Should always be true for now at least
bool port2; //Will need to always be false until fourscore is supported
bool FDS;
bool NewPPU; //Let's make this always false for now as to not support new PPU (until it is ready)
};
#include "playback.h"
#include "greenzone.h"
class TASEDIT_PROJECT
{
public:
TASEDIT_PROJECT();
void init(INPUT_HISTORY* history_ptr);
std::string GetProjectName();
void SetProjectName(std::string e);
std::string GetFM2Name();
void SetFM2Name(std::string e);
std::string GetProjectFile();
void SetProjectFile(std::string e);
void init();
void reset();
void update();
bool saveProject();
bool LoadProject(std::string PFN);
bool Export2FM2(std::string filename); //creates a fm2 out of header, comments, subtitles, and main branch input log, return false if any errors occur
std::string GetProjectName();
void SetProjectName(std::string e);
std::string GetFM2Name();
void SetFM2Name(std::string e);
std::string GetProjectFile();
void SetProjectFile(std::string e);
// public vars
bool changed;
bool changed, old_changed;
private:
std::string projectName; //The TASEdit Project's name
std::string fm2FileName; //The main branch ilog file (todo rename more appropriately)
std::string projectFile; //The TASEdit Project's filename (For saving purposes) //adelikat: TODO: why the hell is this different from project name??!
std::vector<TASENote> notes; //User notes
std::vector<std::string> inputlogs; //List of associated .ilog files
TASEHeader header;
std::vector<std::string> comments;
std::vector<std::string> subtitles;
// references to other important objects of Taseditor
INPUT_HISTORY* history;
};

View File

@ -52,6 +52,9 @@
#ifdef WIN32
#include "drivers/win/pref.h"
#include "drivers/win/taseditlib/greenzone.h"
extern GREENZONE greenzone;
#endif
#include <fstream>
@ -713,11 +716,13 @@ void FCEUI_Emulate(uint8 **pXBuf, int32 **SoundBuf, int32 *SoundBufSize, int ski
}
else justLagged = false;
// auto-savestates for TASEditor
currMovieData.TryDumpIncremental();
if (movieSubtitles)
ProcessSubtitles();
#ifdef WIN32
if(FCEUMOV_Mode(MOVIEMODE_TASEDIT))
greenzone.TryDumpIncremental(lagFlag);
#endif
}
void FCEUI_CloseGame(void)

View File

@ -37,12 +37,12 @@
#ifdef WIN32
#include <windows.h>
#include "./drivers/win/common.h"
#include "./drivers/win/tasedit.h"
#include "./drivers/win/window.h"
extern void AddRecentMovieFile(const char *filename);
extern void UpdateProgressbar(int a, int b);
extern void InputChangedRec();
extern int TASEdit_greenzone_capacity;
extern bool TASEdit_bind_markers;
#endif
using namespace std;
@ -51,10 +51,6 @@ using namespace std;
extern char FileBase[];
extern bool AutoSS; //Declared in fceu.cpp, keeps track if a auto-savestate has been made
#ifdef WIN32
extern int TASEdit_greenzone_capacity;
extern bool TASEdit_bind_markers;
#endif
std::vector<int> subtitleFrames; //Frame numbers for subtitle messages
std::vector<string> subtitleMessages; //Messages of subtitles
@ -154,56 +150,6 @@ void MovieData::cloneRegion(int at, int frames)
records[i+at].Clone(records[i+at+frames]);
}
void MovieData::TryDumpIncremental()
{
if(movieMode == MOVIEMODE_TASEDIT)
{
// if movie length is less than currFrame, pad it with empty frames
if(currFrameCounter >= (int)currMovieData.records.size() || currMovieData.records.size()==0)
currMovieData.insertEmpty(-1, 1 + currFrameCounter - (int)currMovieData.records.size());
//always log savestates in taseditor mode
currMovieData.storeTasSavestate(currFrameCounter, Z_DEFAULT_COMPRESSION);
// also log frame_flags
if (currFrameCounter > 0)
{
// lagFlag indicates that lag was in previous frame
if (lagFlag)
currMovieData.frames_flags[currFrameCounter-1] |= LAG_FLAG_BIT;
else
currMovieData.frames_flags[currFrameCounter-1] &= ~LAG_FLAG_BIT;
}
// update greenzone upper limit
if (currMovieData.greenZoneCount <= currFrameCounter)
currMovieData.greenZoneCount = currFrameCounter+1;
ClearGreenzoneTail();
}
}
void MovieData::ClearGreenzoneTail()
{
#ifdef WIN32
int tail_frame = currMovieData.greenZoneCount-1 - TASEdit_greenzone_capacity;
#else
int tail_frame = 0;
#endif
if (tail_frame >= currFrameCounter) tail_frame = currFrameCounter - 1;
for (;tail_frame >= 0; tail_frame--)
{
if (currMovieData.savestates[tail_frame].empty()) break;
ClearSavestate(tail_frame);
#ifdef WIN32
RedrawRow(tail_frame);
#endif
}
}
void MovieData::ClearSavestate(int index)
{
std::vector<uint8> tmp;
currMovieData.savestates[index].swap(tmp);
}
MovieRecord::MovieRecord()
{
joysticks.data[0] = 0;
@ -468,7 +414,6 @@ MovieData::MovieData()
, PPUflag(false)
, rerecordCount(0)
, binaryFlag(false)
, greenZoneCount(0)
, microphone(false)
{
memset(&romChecksum,0,sizeof(MD5DATA));
@ -580,125 +525,6 @@ int MovieData::dump(EMUFILE *os, bool binary)
return end-start;
}
void MovieData::clearGreenzone()
{
int size = currMovieData.savestates.size();
for (int i = 0; i < size; ++i)
{
ClearSavestate(i);
}
greenZoneCount = 1;
currMovieData.frames_flags.resize(1);
// reset lua_colorings
// reset monitorings
}
int MovieData::dumpGreenzone(EMUFILE *os)
{
int start = os->ftell();
int frame, size;
int last_tick = 0;
// write size
write32le(greenZoneCount, os);
write32le(currFrameCounter, os);
// write savestates
for (frame = 0; frame < greenZoneCount; ++frame)
{
#ifdef WIN32
// update TASEditor progressbar from time to time
if (frame / PROGRESSBAR_UPDATE_RATE > last_tick)
{
UpdateProgressbar(frame, greenZoneCount);
last_tick = frame / PROGRESSBAR_UPDATE_RATE;
}
#endif
if (savestates[frame].empty()) continue;
write32le(frame, os);
// write frames_flags
os->fwrite(&frames_flags[frame], 1);
// write lua_colorings
// write monitorings
// write savestate
size = savestates[frame].size();
write32le(size, os);
os->fwrite(savestates[frame].data(), size);
}
// write -1 as eof for greenzone
write32le(-1, os);
int end = os->ftell();
return end-start;
}
bool MovieData::loadGreenzone(EMUFILE *is)
{
clearGreenzone();
frames_flags.resize(records.size());
int frame = 0, prev_frame = 0, size = 0;
int last_tick = 0;
// read size
if (read32le((uint32 *)&size, is))
{
greenZoneCount = size;
savestates.resize(greenZoneCount);
#ifdef WIN32
int greenzone_tail_frame = greenZoneCount-1 - TASEdit_greenzone_capacity;
#else
int greenzone_tail_frame = 0;
#endif
if (read32le((uint32 *)&frame, is))
{
currFrameCounter = frame;
while(1)
{
if (!read32le((uint32 *)&frame, is)) break;
if (frame == -1) break;
#ifdef WIN32
// update TASEditor progressbar from time to time
if (frame / PROGRESSBAR_UPDATE_RATE > last_tick)
{
UpdateProgressbar(frame, greenZoneCount);
last_tick = frame / PROGRESSBAR_UPDATE_RATE;
}
#endif
// read frames_flags
if ((int)is->fread(&frames_flags[frame],1) != 1) break;
// read lua_colorings
// read monitorings
// read savestate
if (!read32le((uint32 *)&size, is)) break;
if (frame > greenzone_tail_frame || frame == currFrameCounter)
{
// load savestate
savestates[frame].resize(size);
if ((int)is->fread(savestates[frame].data(),size) < size) break;
prev_frame = frame; // successfully read one greenzone frame info
} else
{
// skip loading this savestate
if (is->fseek(size,SEEK_CUR) != 0) break;
}
}
greenZoneCount = prev_frame+1; // cut greenZoneCount to last good frame
if (frame == -1)
{
// everything went fine - load savestate at cursor position
if (currMovieData.loadTasSavestate(currFrameCounter))
return true;
} else
{
// there was some error while reading greenzone
FCEU_printf("Error loading greenzone\n");
}
}
}
return false;
}
int FCEUMOV_GetFrame(void)
{
return currFrameCounter;
@ -1013,25 +839,6 @@ void MovieData::dumpSavestateTo(std::vector<uint8>* buf, int compressionLevel)
ms.trim();
}
bool MovieData::loadTasSavestate(int frame)
{
if (frame<0 || frame>=(int)currMovieData.records.size())
return false;
if ((int)savestates.size()<=frame || savestates[frame].empty())
return false;
EMUFILE_MEMORY ms(&savestates[frame]);
return FCEUSS_LoadFP(&ms, SSLOADPARAM_NOBACKUP);
}
void MovieData::storeTasSavestate(int frame, int compression_level)
{
if ((int)savestates.size()<=frame)
savestates.resize(frame+1);
MovieData::dumpSavestateTo(&savestates[frame],compression_level);
}
//begin playing an existing movie
bool FCEUI_LoadMovie(const char *fname, bool _read_only, bool tasedit, int _pauseframe)
{
@ -1103,7 +910,6 @@ bool FCEUI_LoadMovie(const char *fname, bool _read_only, bool tasedit, int _paus
{
currFrameCounter = 0;
pauseframe = _pauseframe;
//currMovieData.TryDumpIncremental();
}
else
{
@ -1193,13 +999,13 @@ static int _currCommand = 0;
//either dumps the current joystick state or loads one state from the movie
void FCEUMOV_AddInputState()
{
//todo - for tasedit, either dump or load depending on whether input recording is enabled
//or something like that
//(input recording is just like standard read+write movie recording with input taken from gamepad)
//otherwise, it will come from the tasedit data.
#ifdef _WIN32
if(movieMode == MOVIEMODE_TASEDIT)
{
// if movie length is less than currFrame, pad it with empty frames
if((int)currMovieData.records.size() <= currFrameCounter)
currMovieData.insertEmpty(-1, 1 + currFrameCounter - (int)currMovieData.records.size());
MovieRecord* mr = &currMovieData.records[currFrameCounter];
if(movie_readonly || turbo || pauseframe > currFrameCounter)
{
@ -1218,7 +1024,7 @@ void FCEUMOV_AddInputState()
joyports[1].log(mr);
mr->commands = 0;
InputChangedRec(); // TODO: don't call function explicitly, taseditor should catch changes in UpdateTasedit function
InputChangedRec();
}
} else
#endif

View File

@ -178,7 +178,7 @@ public:
std::string romFilename;
std::vector<uint8> savestate;
std::vector<MovieRecord> records;
std::vector<std::vector<uint8> > savestates;
//std::vector<std::vector<uint8> > savestates;
std::vector<uint8> frames_flags;
std::vector<std::wstring> comments;
std::vector<std::string> subtitles;
@ -188,6 +188,7 @@ public:
//was the frame data stored in binary?
bool binaryFlag;
int loadFrameCount;
//which ports are defined for the movie
int ports[3];
@ -196,11 +197,6 @@ public:
//whether microphone is enabled
bool microphone;
//----TasEdit stuff---
int greenZoneCount;
int loadFrameCount;
//----
int getNumRecords() { return records.size(); }
class TDictionary : public std::map<std::string,std::string>
@ -234,9 +230,6 @@ public:
void truncateAt(int frame);
void installValue(std::string& key, std::string& val);
int dump(EMUFILE* os, bool binary);
void clearGreenzone();
int dumpGreenzone(EMUFILE *os);
bool loadGreenzone(EMUFILE *is);
void clearRecordRange(int start, int len);
void insertEmpty(int at, int frames);
@ -245,12 +238,6 @@ public:
static bool loadSavestateFrom(std::vector<uint8>* buf);
static void dumpSavestateTo(std::vector<uint8>* buf, int compressionLevel);
bool loadTasSavestate(int frame);
void storeTasSavestate(int frame, int compression_level);
void TryDumpIncremental();
void ClearGreenzoneTail();
void ClearSavestate(int index);
private:
void installInt(std::string& val, int& var)
{

View File

@ -418,7 +418,10 @@
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)%(Filename)1.xdc</XMLDocumentationFileName>
</ClCompile>
<ClCompile Include="..\src\drivers\win\tasedit.cpp" />
<ClCompile Include="..\src\drivers\win\taseditlib\greenzone.cpp" />
<ClCompile Include="..\src\drivers\win\taseditlib\inputhistory.cpp" />
<ClCompile Include="..\src\drivers\win\taseditlib\inputsnapshot.cpp" />
<ClCompile Include="..\src\drivers\win\taseditlib\playback.cpp" />
<ClCompile Include="..\src\drivers\win\texthook.cpp" />
<ClCompile Include="..\src\drivers\win\throttle.cpp" />
<ClCompile Include="..\src\drivers\win\timing.cpp" />

View File

@ -908,6 +908,9 @@
<Filter>boards</Filter>
</ClCompile>
<ClCompile Include="..\src\drivers\win\taseditlib\inputhistory.cpp" />
<ClCompile Include="..\src\drivers\win\taseditlib\inputsnapshot.cpp" />
<ClCompile Include="..\src\drivers\win\taseditlib\playback.cpp" />
<ClCompile Include="..\src\drivers\win\taseditlib\greenzone.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\src\cart.h">