mirror of
https://github.com/libretro/scummvm.git
synced 2025-03-05 01:38:36 +00:00
M4: Fixes for crashes in Load menu
This commit is contained in:
parent
c146fbb42f
commit
072355f870
@ -19,6 +19,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "graphics/thumbnail.h"
|
||||
#include "m4/burger/gui/game_menu.h"
|
||||
#include "m4/burger/gui/interface.h"
|
||||
#include "m4/adv_r/other.h"
|
||||
@ -53,16 +54,15 @@ void CreateSaveMenu(RGB8 *myPalette);
|
||||
void CreateLoadMenu(RGB8 *myPalette);
|
||||
|
||||
Sprite *menu_CreateThumbnail(int32 *spriteSize) {
|
||||
#ifdef TODO
|
||||
Sprite *thumbNailSprite;
|
||||
GrBuff *thumbNail;
|
||||
Buffer *scrnBuff, *intrBuff, *destBuff, RLE8Buff;
|
||||
Buffer *scrnBuff, *intrBuff, *destBuff;
|
||||
uint8 *srcPtr, *srcPtr2, *srcPtr3, *srcRowPtr, *destPtr;
|
||||
ScreenContext *gameScreen;
|
||||
int32 i, status;
|
||||
int32 currRow, beginRow, endRow;
|
||||
|
||||
// Create a Sprite for the rle8 thumbNail
|
||||
// Create a Sprite for the thumbNail
|
||||
if ((thumbNailSprite = (Sprite *)mem_alloc(sizeof(Sprite), "sprite")) == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
@ -204,27 +204,16 @@ Sprite *menu_CreateThumbnail(int32 *spriteSize) {
|
||||
memset(destPtr, 21, (destBuff->h - (currRow / 3)) * destBuff->stride);
|
||||
}
|
||||
|
||||
// Compress the thumbNail data into the RLE8Buff
|
||||
if ((*spriteSize = (int32)gr_sprite_RLE8_encode(destBuff, &RLE8Buff)) <= 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Fill in the Sprite structure
|
||||
thumbNailSprite->w = destBuff->w;
|
||||
thumbNailSprite->h = destBuff->h;
|
||||
thumbNailSprite->encoding = RLE8;
|
||||
thumbNailSprite->data = nullptr;
|
||||
thumbNailSprite->encoding = NO_COMPRESS;
|
||||
thumbNailSprite->data = destBuff->data;
|
||||
if ((thumbNailSprite->sourceHandle = NewHandle(*spriteSize, "thumbNail source")) == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
thumbNailSprite->sourceOffset = 0;
|
||||
|
||||
// Now copy the RLE8Buff into the thumbNail source handle
|
||||
HLock(thumbNailSprite->sourceHandle);
|
||||
thumbNailSprite->data = (uint8 *)(*(thumbNailSprite->sourceHandle));
|
||||
memcpy(thumbNailSprite->data, RLE8Buff.data, *spriteSize);
|
||||
HUnLock(thumbNailSprite->sourceHandle);
|
||||
|
||||
// Release all buffers
|
||||
_G(gameDrawBuff)->release();
|
||||
if (intrBuff) {
|
||||
@ -232,14 +221,10 @@ Sprite *menu_CreateThumbnail(int32 *spriteSize) {
|
||||
}
|
||||
thumbNail->release();
|
||||
|
||||
// Free up both the thumbNail and the RLE8Buff
|
||||
// Free up the thumbNail
|
||||
delete thumbNail;
|
||||
mem_free((void *)RLE8Buff.data);
|
||||
|
||||
return thumbNailSprite;
|
||||
#else
|
||||
error("TODO: createThumbnail");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@ -482,9 +467,7 @@ void menu_DrawButton(void *theItem, void *theMenu, int32 x, int32 y, int32, int3
|
||||
myButton = (menuItemButton *)myItem->itemInfo;
|
||||
|
||||
switch (myButton->buttonType) {
|
||||
|
||||
case BTN_TYPE_GM_GENERIC:
|
||||
|
||||
switch (myButton->itemFlags) {
|
||||
case BTN_STATE_NORM:
|
||||
mySprite = _GM(menuSprites)[GM_BUTTON_NORM];
|
||||
@ -503,7 +486,6 @@ void menu_DrawButton(void *theItem, void *theMenu, int32 x, int32 y, int32, int3
|
||||
break;
|
||||
|
||||
case BTN_TYPE_SL_SAVE:
|
||||
|
||||
switch (myButton->itemFlags) {
|
||||
case BTN_STATE_NORM:
|
||||
mySprite = _GM(menuSprites)[SL_SAVE_BTN_NORM];
|
||||
@ -522,7 +504,6 @@ void menu_DrawButton(void *theItem, void *theMenu, int32 x, int32 y, int32, int3
|
||||
break;
|
||||
|
||||
case BTN_TYPE_SL_LOAD:
|
||||
|
||||
switch (myButton->itemFlags) {
|
||||
case BTN_STATE_NORM:
|
||||
mySprite = _GM(menuSprites)[SL_LOAD_BTN_NORM];
|
||||
@ -541,7 +522,6 @@ void menu_DrawButton(void *theItem, void *theMenu, int32 x, int32 y, int32, int3
|
||||
break;
|
||||
|
||||
case BTN_TYPE_SL_TEXT:
|
||||
|
||||
switch (myButton->itemFlags) {
|
||||
case BTN_STATE_OVER:
|
||||
font_set_colors(TEXT_COLOR_OVER_SHADOW, TEXT_COLOR_OVER_FOREGROUND, TEXT_COLOR_OVER_HILITE);
|
||||
@ -568,7 +548,6 @@ void menu_DrawButton(void *theItem, void *theMenu, int32 x, int32 y, int32, int3
|
||||
break;
|
||||
|
||||
case BTN_TYPE_SL_CANCEL:
|
||||
|
||||
switch (myButton->itemFlags) {
|
||||
case BTN_STATE_NORM:
|
||||
mySprite = _GM(menuSprites)[SL_CANCEL_BTN_NORM];
|
||||
@ -587,7 +566,6 @@ void menu_DrawButton(void *theItem, void *theMenu, int32 x, int32 y, int32, int3
|
||||
break;
|
||||
|
||||
case BTN_TYPE_OM_DONE:
|
||||
|
||||
switch (myButton->itemFlags) {
|
||||
case BTN_STATE_NORM:
|
||||
mySprite = _GM(menuSprites)[OM_DONE_BTN_NORM];
|
||||
@ -606,7 +584,6 @@ void menu_DrawButton(void *theItem, void *theMenu, int32 x, int32 y, int32, int3
|
||||
break;
|
||||
|
||||
case BTN_TYPE_OM_CANCEL:
|
||||
|
||||
switch (myButton->itemFlags) {
|
||||
case BTN_STATE_NORM:
|
||||
mySprite = _GM(menuSprites)[OM_CANCEL_BTN_NORM];
|
||||
@ -623,9 +600,6 @@ void menu_DrawButton(void *theItem, void *theMenu, int32 x, int32 y, int32, int3
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Get the menu buffer
|
||||
@ -645,9 +619,9 @@ void menu_DrawButton(void *theItem, void *theMenu, int32 x, int32 y, int32, int3
|
||||
|
||||
// If the button is a textbutton, write in the text
|
||||
if ((myButton->buttonType == BTN_TYPE_SL_TEXT) && (myButton->prompt)) {
|
||||
//write in the special tag
|
||||
// Write in the special tag
|
||||
Common::sprintf_s(tempStr, 32, "%02d", myItem->tag - 1000 + _GM(firstSlotIndex));
|
||||
/* Common::sprintf_s(tempStr, "%02d", myButton->specialTag); */
|
||||
|
||||
gr_font_set(_GM(menuFont));
|
||||
gr_font_write(myBuff, tempStr, x + 4, y + 1, 0, -1);
|
||||
gr_font_write(myBuff, myButton->prompt, x + 26, y + 1, 0, -1);
|
||||
@ -2953,85 +2927,28 @@ bool load_Handler(void *theItem, int32 eventType, int32 event, int32 x, int32 y,
|
||||
|
||||
|
||||
bool LoadThumbNail(int32 slotNum) {
|
||||
#ifdef TODO
|
||||
char saveFN[256];
|
||||
Common::File f;
|
||||
int32 byteCount;
|
||||
bool errFlag;
|
||||
|
||||
errFlag = false;
|
||||
Common::sprintf_s(saveFN, "%s\\%s%03d.SAV", homeDir, "BURG", slotNum + 1);
|
||||
handle = fopen(saveFN, "rb");
|
||||
if (!handle) {
|
||||
errFlag = true;
|
||||
}
|
||||
|
||||
// First seek past the save game description
|
||||
if (!errFlag) {
|
||||
if (!fread(&byteCount, sizeof(int32), 1, handle)) {
|
||||
errFlag = true;
|
||||
}
|
||||
}
|
||||
if (!errFlag) {
|
||||
if (fseek(handle, byteCount, SEEK_CUR) != 0) {
|
||||
errFlag = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Read in the sprite structure
|
||||
if (!errFlag) {
|
||||
byteCount = sizeof(Sprite);
|
||||
if (!fread(_GM(thumbNails)[slotNum], sizeof(Sprite), 1, handle)) {
|
||||
errFlag = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Read in the size of the thumbnail data
|
||||
if (!errFlag) {
|
||||
if (!fread(&byteCount, sizeof(int32), 1, handle)) {
|
||||
errFlag = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Now create a handle to hold the sprite data
|
||||
if (!errFlag) {
|
||||
if ((_GM(thumbNails)[slotNum]->sourceHandle = NewHandle(byteCount, "thumbNail source")) == nullptr) {
|
||||
errFlag = true;
|
||||
}
|
||||
_GM(thumbNails)[slotNum]->sourceOffset = 0;
|
||||
}
|
||||
|
||||
// Lock the handle, and read the thumbnail data in
|
||||
if (!errFlag) {
|
||||
HLock(_GM(thumbNails)[slotNum]->sourceHandle);
|
||||
_GM(thumbNails)[slotNum]->data = (uint8 *)((int32) * (_GM(thumbNails)[slotNum]->sourceHandle) + _GM(thumbNails)[slotNum]->sourceOffset);
|
||||
if (!fread(_GM(thumbNails)[slotNum]->data, byteCount, 1, handle)) {
|
||||
errFlag = true;
|
||||
}
|
||||
HUnLock(_GM(thumbNails)[slotNum]->sourceHandle);
|
||||
}
|
||||
|
||||
// In case of an error, clean everything up
|
||||
if (errFlag) {
|
||||
if (_GM(thumbNails)[slotNum]->sourceHandle) {
|
||||
DisposeHandle(_GM(thumbNails)[slotNum]->sourceHandle);
|
||||
_GM(thumbNails)[slotNum]->sourceHandle = false;
|
||||
}
|
||||
_GM(slotInUse)[slotNum] = false;
|
||||
Common::strcpy_s(_GM(slotTitles)[slotNum], "<empty>");
|
||||
}
|
||||
if (handle) {
|
||||
fclose(handle);
|
||||
}
|
||||
|
||||
if (errFlag) {
|
||||
Sprite *&thumbNailSprite = _GM(thumbNails)[slotNum];
|
||||
if (!g_engine->loadSaveThumbnail(slotNum, thumbNailSprite))
|
||||
return false;
|
||||
/*
|
||||
const Graphics::Surface *thumbnail = g_engine->loadSaveThumbnail(slotNum);
|
||||
if (!thumbnail)
|
||||
return false;
|
||||
|
||||
|
||||
// Create a sprite based on the thumbnail data
|
||||
if ((thumbNailSprite = (Sprite *)mem_alloc(sizeof(Sprite), "sprite")) == nullptr) {
|
||||
delete thumbnail;
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
error("TODO: LoadThumbnail");
|
||||
#endif
|
||||
|
||||
thumbNailSprite->w = thumbnail->w;
|
||||
thumbNailSprite->h = thumbnail->h;
|
||||
thumbNailSprite->encoding = NO_COMPRESS;
|
||||
thumbNailSprite->data = (byte *)thumbnail->getPixels();
|
||||
thumbNailSprite->sourceOffset = 0;
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -3336,7 +3253,6 @@ void cb_SaveLoad_Cancel(void *, void *theMenu) {
|
||||
|
||||
// If a slot has been selected, cancel will re-enable all slots
|
||||
if (_GM(slotSelected) >= 0) {
|
||||
|
||||
// Enable the prev buttons
|
||||
for (i = 1001; i <= 1010; i++) {
|
||||
if (_GM(currMenuIsSave) || _GM(slotInUse)[i - 1001 + _GM(firstSlotIndex)]) {
|
||||
@ -3366,7 +3282,7 @@ void cb_SaveLoad_Cancel(void *, void *theMenu) {
|
||||
// Remove the thumbnail
|
||||
if (_GM(saveLoadThumbNail)) {
|
||||
_GM(saveLoadThumbNail) = _GM(menuSprites)[SL_EMPTY_THUMB];
|
||||
menu_ItemRefresh(nullptr, SL_TAG_THUMBNAIL, (guiMenu *)myItem->myMenu);
|
||||
menu_ItemRefresh(nullptr, SL_TAG_THUMBNAIL, myMenu);
|
||||
}
|
||||
}
|
||||
SetFirstSlot(_GM(firstSlotIndex), myMenu);
|
||||
@ -3386,10 +3302,9 @@ void cb_SaveLoad_Cancel(void *, void *theMenu) {
|
||||
|
||||
// Reset the slot selected var
|
||||
_GM(slotSelected) = -1;
|
||||
}
|
||||
|
||||
//otherwise, back to the game menu
|
||||
else {
|
||||
} else {
|
||||
// Otherwise, back to the game menu
|
||||
|
||||
// Destroy the menu
|
||||
DestroySaveLoadMenu(_GM(currMenuIsSave));
|
||||
@ -3410,6 +3325,8 @@ void cb_SaveLoad_Slot(void *theItem, void *theMenu) {
|
||||
menuItem *myItem = (menuItem *)theItem;
|
||||
menuItemButton *myButton;
|
||||
int32 i, x, y, w, h;
|
||||
char prompt[80];
|
||||
int32 specialTag;
|
||||
|
||||
// Verify params
|
||||
if ((!myMenu) || (!myItem) || (!myItem->itemInfo)) {
|
||||
@ -3418,6 +3335,8 @@ void cb_SaveLoad_Slot(void *theItem, void *theMenu) {
|
||||
|
||||
// Get the button
|
||||
myButton = (menuItemButton *)myItem->itemInfo;
|
||||
Common::strcpy_s(prompt, 80, myButton->prompt);
|
||||
specialTag = myButton->specialTag;
|
||||
|
||||
// Set the globals
|
||||
_GM(slotSelected) = myButton->specialTag;
|
||||
@ -3440,16 +3359,16 @@ void cb_SaveLoad_Slot(void *theItem, void *theMenu) {
|
||||
|
||||
if (_GM(currMenuIsSave)) {
|
||||
// Replace the current button with a textfield
|
||||
if (!strcmp(myButton->prompt, "<empty>")) {
|
||||
if (!strcmp(prompt, "<empty>")) {
|
||||
menu_TextFieldAdd(myMenu, 2000, x, y, w, h, TF_OVER,
|
||||
nullptr, myButton->specialTag, cb_SaveLoad_Save, true);
|
||||
nullptr, specialTag, cb_SaveLoad_Save, true);
|
||||
} else {
|
||||
menu_TextFieldAdd(myMenu, 2000, x, y, w, h, TF_OVER,
|
||||
myButton->prompt, myButton->specialTag, cb_SaveLoad_Save, true);
|
||||
prompt, specialTag, cb_SaveLoad_Save, true);
|
||||
}
|
||||
} else {
|
||||
menu_TextFieldAdd(myMenu, 2000, x, y, w, h, TF_NORM,
|
||||
myButton->prompt, myButton->specialTag, cb_SaveLoad_Load, true);
|
||||
prompt, specialTag, cb_SaveLoad_Load, true);
|
||||
}
|
||||
|
||||
// Disable the slider
|
||||
@ -3478,6 +3397,7 @@ void InitializeSlotTables(void) {
|
||||
for (const auto &save : saves) {
|
||||
Common::String desc = save.getDescription();
|
||||
Common::strcpy_s(_GM(slotTitles)[save.getSaveSlot()], 80, desc.c_str());
|
||||
_GM(slotInUse)[save.getSaveSlot()] = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3636,8 +3556,12 @@ void CreateSaveLoadMenu(RGB8 *myPalette, bool saveMenu) {
|
||||
}
|
||||
|
||||
if (_GM(currMenuIsSave)) {
|
||||
// Create the thumbnail
|
||||
// Create thumbnails. One in the original game format for displaying,
|
||||
// and the other in the ScummVM format for actually using in the save files
|
||||
_GM(saveLoadThumbNail) = menu_CreateThumbnail(&_GM(sizeofThumbData));
|
||||
_GM(_thumbnail).free();
|
||||
Graphics::createThumbnail(_GM(_thumbnail));
|
||||
|
||||
} else {
|
||||
UpdateThumbNails(0, _GM(slMenu));
|
||||
_GM(saveLoadThumbNail) = _GM(menuSprites)[SL_EMPTY_THUMB];
|
||||
|
@ -23,6 +23,7 @@
|
||||
#ifndef M4_BURGER_GUI_GAME_MENU_H
|
||||
#define M4_BURGER_GUI_GAME_MENU_H
|
||||
|
||||
#include "graphics/surface.h"
|
||||
#include "m4/m4_types.h"
|
||||
#include "m4/graphics/gr_buff.h"
|
||||
#include "m4/gui/gui_univ.h"
|
||||
@ -495,7 +496,8 @@ struct MenuGlobals {
|
||||
bool deleteSaveDesc = false;
|
||||
|
||||
Sprite **thumbNails = nullptr;
|
||||
Sprite *saveLoadThumbNail = nullptr;
|
||||
Sprite *saveLoadThumbNail = nullptr; // Original used for menu display
|
||||
Graphics::Surface _thumbnail; // ScummVM version used for savegame
|
||||
int32 sizeofThumbData = -1;
|
||||
int32 thumbIndex = 0;
|
||||
|
||||
@ -505,6 +507,10 @@ struct MenuGlobals {
|
||||
|
||||
int32 remember_digi_volume; // For cancelling out of the options menu
|
||||
int32 remember_digestability; // For cancelling out of the options menu
|
||||
|
||||
~MenuGlobals() {
|
||||
_thumbnail.free();
|
||||
}
|
||||
};
|
||||
|
||||
void CreateGameMenuMain(RGB8 *myPalette);
|
||||
|
@ -26,11 +26,13 @@
|
||||
#include "common/system.h"
|
||||
#include "common/savefile.h"
|
||||
#include "engines/util.h"
|
||||
#include "graphics/managed_surface.h"
|
||||
#include "graphics/palette.h"
|
||||
#include "m4/m4.h"
|
||||
#include "m4/adv_r/adv_control.h"
|
||||
#include "m4/adv_r/adv_file.h"
|
||||
#include "m4/adv_r/conv_io.h"
|
||||
#include "m4/graphics/gr_sprite.h"
|
||||
#include "m4/gui/hotkeys.h"
|
||||
#include "m4/platform/sound/digi.h"
|
||||
#include "m4/platform/sound/midi.h"
|
||||
@ -299,4 +301,35 @@ bool M4Engine::savesExist() const {
|
||||
return !listSaves().empty();
|
||||
}
|
||||
|
||||
bool M4Engine::loadSaveThumbnail(int slotNum, M4sprite *thumbnail) const {
|
||||
SaveStateDescriptor desc = getMetaEngine()->querySaveMetaInfos(_targetName.c_str(), slotNum);
|
||||
if (!desc.isValid())
|
||||
return false;
|
||||
|
||||
// Gert the thumbnail
|
||||
const Graphics::Surface *surf = desc.getThumbnail();
|
||||
|
||||
// Set up output sprite
|
||||
thumbnail->w = surf->w;
|
||||
thumbnail->h = surf->h;
|
||||
thumbnail->encoding = NO_COMPRESS;
|
||||
thumbnail->sourceOffset = 0;
|
||||
thumbnail->data = (byte *)malloc(surf->w * surf->h);
|
||||
|
||||
// Create a surface wrapper for the destination so that we can use
|
||||
// ScummVM's blitting code to down-convert the thumbnail to paletted
|
||||
Graphics::ManagedSurface dest;
|
||||
dest.w = dest.pitch = surf->w;
|
||||
dest.h = surf->h;
|
||||
dest.format = Graphics::PixelFormat::createFormatCLUT8();
|
||||
dest.setPixels(thumbnail->data);
|
||||
|
||||
byte pal[PALETTE_SIZE];
|
||||
g_system->getPaletteManager()->grabPalette(pal, 0, PALETTE_COUNT);
|
||||
dest.setPalette(pal, 0, PALETTE_COUNT);
|
||||
dest.blitFrom(*surf);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // End of namespace M4
|
||||
|
@ -154,6 +154,11 @@ public:
|
||||
*/
|
||||
SaveStateList listSaves() const;
|
||||
|
||||
/**
|
||||
* Returns the savegame thumbnail for a save
|
||||
*/
|
||||
bool loadSaveThumbnail(int slotNum, M4sprite *thumbnail) const;
|
||||
|
||||
/**
|
||||
* Show save game dialog
|
||||
*/
|
||||
|
@ -47,6 +47,10 @@ static const ADExtraGuiOptionsMap optionsList[] = {
|
||||
|
||||
} // End of namespace M4
|
||||
|
||||
M4MetaEngine::~M4MetaEngine() {
|
||||
_thumbnail.free();
|
||||
}
|
||||
|
||||
const char *M4MetaEngine::getName() const {
|
||||
return "m4";
|
||||
}
|
||||
@ -116,6 +120,13 @@ Common::InSaveFile *M4MetaEngine::getOriginalSave(const Common::String &saveName
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void M4MetaEngine::getSavegameThumbnail(Graphics::Surface &thumb) {
|
||||
if (!_thumbnail.h)
|
||||
return AdvancedMetaEngine::getSavegameThumbnail(thumb);
|
||||
|
||||
thumb.copyFrom(_thumbnail);
|
||||
}
|
||||
|
||||
#if PLUGIN_ENABLED_DYNAMIC(M4)
|
||||
REGISTER_PLUGIN_DYNAMIC(M4, PLUGIN_TYPE_ENGINE, M4MetaEngine);
|
||||
#else
|
||||
|
@ -22,6 +22,7 @@
|
||||
#ifndef M4_METAENGINE_H
|
||||
#define M4_METAENGINE_H
|
||||
|
||||
#include "graphics/surface.h"
|
||||
#include "engines/advancedDetector.h"
|
||||
|
||||
class M4MetaEngine : public AdvancedMetaEngine {
|
||||
@ -29,6 +30,11 @@ private:
|
||||
Common::InSaveFile *getOriginalSave(const Common::String &saveName) const;
|
||||
|
||||
public:
|
||||
Graphics::Surface _thumbnail;
|
||||
|
||||
public:
|
||||
~M4MetaEngine() override;
|
||||
|
||||
const char *getName() const override;
|
||||
|
||||
Common::Error createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const override;
|
||||
@ -43,6 +49,13 @@ public:
|
||||
const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override;
|
||||
|
||||
SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const override;
|
||||
|
||||
/**
|
||||
* Convert the current screen contents to a thumbnail. Can be overriden by individual
|
||||
* engine meta engines to provide their own thumb, such as hiding any on-screen save
|
||||
* dialog so that it won't appear in the thumbnail.
|
||||
*/
|
||||
void getSavegameThumbnail(Graphics::Surface &thumb) override;
|
||||
};
|
||||
|
||||
#endif // M4_METAENGINE_H
|
||||
|
Loading…
x
Reference in New Issue
Block a user