Reconvergence

After slightly over a week, my laptop is back from warranty service.

In the intervening time, I’ve gotten autosave working on Composer, hugo, lure, and drascula engines, and made a few changes to how autosave works in agi, kyra, queen, and scumm, so that automated saves now use a special reserved slot with a special filename so that no manually created save will ever be overwritten by an autosave.

As my laptop is back, tomorrow I will be returning to the sword25 save portability problem.

Tangence

Working on Broken Sword 2.5, I encountered a save bug unrelated to the 32/64 bit portability issue I was intending to look into. The morning after I solved that bug, my laptop (which is also my primary development machine, and only machine with a 64-bit OS installed), suffered fan failure. It is now at warranty services getting repaired, and I have an alternate machine with which to make progress during the 7-9 day estimated turnaround.

Unfortunately, because it lacks a 64-bit OS, I’m unable to test any changes to the Broken Sword 2.5 issue. So, while I wait to get my primary machine back, I will be adding autosave support to a number of engines. I began work on this last night, and already have autosave working for Composer and Hugo engines.

Reconsideration

Upon further examination, the change I was planning to make to Audio::Mixer is not very useful without a related, but very complicated, change to AudioStream (and all of its child classes), that I am not prepared to make. And one of my mentors has informed me that the audio issues I’m encountering are acceptable. In the interest of not completely wasting time while I wait for a change on the status of the graphical solution, I will be working on a different engine in the meantime.

Specifically, it has been brought to my attention that broken sword 2.5 has a saving issue, having to do with serializing/deserializing pointers, that prevents save portability, and is the last blocking issue before the game can be called supported, so I will be looking to fix that while a decision is made on how to proceed with the HE scumm engine graphical issues.

Sound and Light

I have HE engine Scumm games saving at any time now, with only sound and graphical glitches remaining to deal with.

For the sound glitches I am currently working on an interface to Audio::Mixer which will return the AudioStream (or even just the sample buffer) associated with a sound handle, as this will allow engines to get the streams of their active sounds, in order to dump the buffers out to file.

For the graphical glitches, Jakimushka (another student) has already commited code that saves a 16/32 bit screenshot of the display surface, which I intend to make use of to save/restore the graphical state, as soon as the pull request goes through.

Revisionist future

I have heard through fuzzie that there is not global acceptance of my saving anywhere interface design, so I am delaying implementation until I can have a discussion with concerned parties. Unfortunately, I have not heard who those concerned parties are, and they have not made themselves known, so I’ll be sending out an email to scummvm-devel in the near future.

In the meantime, I’ve been looking into expanding the SCUMM engine save format to support additional cases (such as saving HE games through the GMM, instead of only supporting the in-game save menu).

So far these efforts have consisted of debug-stepping the engine loop immediately after making a (currently) buggy savegame, then loading the save and comparing the codepaths to determine what information is missing from the save. I had really wanted to had some code pushed toward this before making this blog post, but this method of research has been very slow-going, and this post is overdue as it is…

I’ll see if my mentors and I can work out a better method tomorrow.

Status and Roadmap

Composer engine saving/loading now functions, with only audio sync issues remaining, so it seems a good time to discuss the roadmap for the main body of the task.

My design is as follows:

There will be a new entry added to the EngineFeature enum: kSupportsAnytimeSaving.

Engines which support anytime saving will return true when queried about this, and implement doQuickSave(int slot, const Common::String &desc). and doQuickLoad(int slot). It will likely also be necessary for these engines to support listSaves() in order to allow the user to select a save to load.

For cases where the engine only disallows saving to prevent cheating, these functions will simply call saveGameState() and loadGameState() without regard to the status of canSaveGameCurrently(). For more complex cases, where the engine disallows saving because it doesn’t implement saving for its current state, these functions will, of course, have to implement saving in those states, which can potentially require some extensive engine refactoring.

When I have the audio issues worked out of the composer engine, it should immediately be capable of supporting anytime saving, so the next step will just be creating those wrapper functions. After that, I intend to move on to the SCUMM engine module and make it ready for anytime saving.

After that, we’ll see…

PseudoComposition

In order to familiarize myself with file interfaces and general saving/loading practices in ScummVM, I have been working on implementing standard saving/loading for the Composer engine, beginning by generating a list of what needs to be saved, and some pseudocode for how to reinitialize the objects from the stored data.

My notes on this follow:

save:
ComposerEngine:

save to savefile:

  • RandomSource* _rnd,
  • uint32 _currentTime,
  • uint32 _lastTime,
  • bool _needsUpdate,
  • List<Sprite> _sprites,
  • String _bookGroup,
  • List<Library> _Libraries,
  • Array<PendingPageChange> _pendingPageChanges,
  • Array<uint16> _stack,
  • Array<uint16> _vars,
  • List<Oldscript *> _oldScripts,
  • Array<QueuedScript> _queuedScripts,
  • bool _mouseEnabled,
  • bool _mouseVisible,
  • uint16 _mouseSpriteId

RandomSource: uint32 getSeed()
List<T>: uint32 size(), T *ConstIterator begin() .. end()
Sprite: uint16 _id
Library: uint16 _id
Array<T>: uint32 size(), T *ConstIterator begin() .. end()
PendingPageChange: uint16 _pageId, bool _remove
OldScript: uint16 _id, uint32 _currDelay
QueuedScript: uint32 _baseTime, uint32 _duration, uint32 _count, uint16 _scriptId

load:
ComposerEngine:
filename = (find book.ini), _bookIni.loadFromFile(filename)

load from savefile:

  • RandomSource* _rnd,
  • uint32 _currentTime,
  • uint32 _lastTime,
  • bool _needsUpdate,
  • List<Sprite> _sprites,
  • String _bookGroup,
  • List<Library> _Libraries,
  • Array<PendingPageChange> _pendingPageChanges,
  • Array<uint16> _stack,
  • Array<uint16> _vars,
  • List<Oldscript *> _oldScripts,
  • Array<QueuedScript> _queuedScripts,
  • bool _mouseEnabled,
  • bool _mouseVisible,
  • uint16 _mouseSpriteId

RandomSource: setSeed(uint32)
List<T>: uint32 num, for (i = 0 .. num) (T, push_back(T))
Sprite: uint16 _id, initSprite(Sprite)
Library: uint16 _id, LoadLibrary(_id)
Array<T>: uint32 num, for (i = 0 .. num) (T, push_back(T))
PendingPageChange: uint16 _pageId, bool _remove, if (!_remove) LoadLibrary(_pageId)
OldScript: uint16 id, uint32 _currDelay, OldScript(id,getResource(ID_SCRP, id))
QueuedScript: uint32 _baseTime, uint32 _duration, uint32 _count, uint16 _scriptId

(Re)Introduction

Hello dear readers of the ScummVM blog aggregator,

This is Upthorn, joining the ranks of ScummVM development bloggers once again for Google Summer of Code 2012.

My project this year is to allow for as many engines as I can to provide saving functionality even when the game it is running would not normally give the user access to a savescreen. Preferably, these saves will offer the robustness of savestates in lower level emulators, but it is likely that this will vary by engine module. The primary goal of this functionality is to allow users of mobile devices to enjoy ScummVM without concern of low battery life eating their progress.

It is good to be back.

Hope and change

So, as I suggested in my prior blog post, I’ve been working on totally redesigning the Common::Action and Common::Keymap structures.

I’ve been at work on this for a few days now, but because I’m totally redesigning the way they interact with each other, and interface with the rest of the code at large, I’m not done yet, and the code won’t compile until I finish, so I can’t commit it yet.

So, to show what I’ve been working at, I’m posting what I’ve gotten done here.

So far, all the actual rewritten code has been going into the Action struct and related enums.

enum ActionType {

//Emulate a hardware event in the engine
kSingleKeyPressActionType,
kMultiKeyPressType,
kMouseClickType,
kMouseWheelType,

//Interface directly with the VM
kSaveActionType,
kMenuActionType,
kQuitActionType,
kVirtualKeyboardActionType,
kKeyRemapActionType,
kVolumeUpActionType,
kVolumeDownActionType,

//This is the total number of types currently defined in this enum
kActionTypeMax

};

enum ClickType {

kLeftClickType,
kMiddleClickType,
kRightClickType

};

enum KeyType {

//Direction keys
kDirUpKeyType,
kDirDownKeyType,
kDirLeftKeyType,
kDirRightKeyType,

//Keyboard keys
kTextKeyType, //Letters, numbers, symbols, whitespace
kModifierKeyType, //Ctrl, Alt, Shift

//Gamepad buttons
kFaceButtonKeyType, //A, B, C, X, Y, Z, and the like
kShoulderButtonKeyType, //Separated from FaceButtons because they can be used as pseudo-modifier keys.

//System key types
kPauseKeyType, //Start, Pause, etc..
kMenuKeyType, //Select, Escape, etc…
kSystemActionKeyType, //F1-F12, volume sliders, and so forth

//This is the total number of types currently defined in this enum
kKeyTypeMax

};

struct Action {

char id[ACTION_ID_SIZE];

ActionType type;
KeyType preferredKey;

private:

List<Event> _events;
HardwareKey *_mappedKey;

public:

void mapKey (const HardwareKey *key);

void addKeyPressEvent(KeyCode code, byte modifiers) {

KeyState key = KeyState(code);
key.flags = modifiers;

Event keyPress;
keyPress.type = EVENT_KEYDOWN;
keyPress.kbd = key;

_events.push_back(keyPress);

}

void addMouseClickEvent(ClickType t) {

Event mouseClick;
if (t == kLeftClickType)

mouseClick.type = EVENT_LBUTTONDOWN;

else if (t == kRightClickType)

mouseClick.type = EVENT_RBUTTONDOWN;

else

mouseClick.type = EVENT_MBUTTONDOWN;

_events.push_back(mouseClick);

}

template <ActonType t>;
Action<kSingleKeyPressActionType> (char *i, KeyType k, KeyCode c, byte m) {

memcpy(id,i,ACTION_ID_SIZE);

type = t;
preferredKey = k;

addKeyPressEvent(c,m);

}

Action<kMultiKeyPressType> (char *i, KeyType k, List<KeyCode> cs, byte m) {

memcpy(id,i,ACTION_ID_SIZE);

type = t;
preferredKey = k;

List<KeyCode>::iterator it;
for (it = cs.begin(); it != cs.end(); it++) {

KeyCode c = *it;
addKeyPressEvent(c,m);

}

}

Action<kMouseClickType> (char *i, bool l = true, bool m = false, bool r = false) {

memcpy(id,i,ACTION_ID_SIZE);

type = t;
preferredKey = k;

if (l)

addMouseClickEvent(kLeftClickType);

if (m)

addMouseClickEvent(kMiddleClickType);

if (r)

addMouseClickEvent(kRightClickType);

}

Action<kMouseWheelType> (char *i, bool up = false) {

memcpy(id,i,ACTION_ID_SIZE);

Event evt;
if (up)

evt.type = EVENT_WHEELUP;

else

evt.type = EVENT_WHEELDOWN;

_events.push_back(evt);

}

Action (char *i) {

memcpy(id,i,ACTION_ID_SIZE);
type = t;

}

};

There’s more about how this is going to work which is still in my head, but a lot of initialization stuff that was previously done by Action methods is going to be moved into the Keymap, so there will no longer be any need for an Action to know anything about the set that it is in, except to have a HardwareKey pointer provided to it (and even that will only be so that the Keymap can do effective data-management when an action gets remapped.)

Radical changes required?

I’m at a sticking point in designing the keymapper API, or planning/progressing on the similar tasks, which stems from the complexity of the existing code.

I wonder if it might be faster, easier, and better overall to redesign some parts of the keymapper from scratch, so I can focus on building something clean and simple, instead of trying to figure out what parts of the existing structure can be cut out without compromising the function of its design.

Up until this point, I’ve been working under the assumption that the prior student’s design is fundamentally sound, and it is only the details of implementation that needed reworking, but if that is not the case, then the approach I’ve been taking will require more time and effort to be successful than a complete redesign of the component would.

I’m going to consider, over the course of the next day, how I would do this if I had to implement it from scratch, and see if I can find a simpler method than the one that was used.