/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
#include "common/debug.h"
#include "common/stream.h"
#include "common/hash-str.h"
#include "vcruise/script.h"
#include "vcruise/textparser.h"
namespace VCruise {
enum ScriptDialect {
kScriptDialectReah,
kScriptDialectSchizm,
kScriptDialectAD2044,
};
class LogicUnscrambleStream : public Common::ReadStream {
public:
LogicUnscrambleStream(Common::ReadStream *stream, uint streamSize);
bool eos() const override;
uint32 read(void *dataPtr, uint32 dataSize) override;
private:
byte _cipher[255];
uint _cipherOffset;
Common::ReadStream *_stream;
};
LogicUnscrambleStream::LogicUnscrambleStream(Common::ReadStream *stream, uint streamSize) : _stream(stream) {
int key = 255;
for (int i = 0; i < 255; i++) {
int parityBit = ((key ^ (key >> 1) ^ (key >> 6) ^ (key >> 7))) & 1;
key = (key >> 1) | (parityBit << 7);
_cipher[254 - i] = key;
}
_cipherOffset = 255u - (streamSize % 255u);
}
bool LogicUnscrambleStream::eos() const {
return _stream->eos();
}
uint32 LogicUnscrambleStream::read(void *dataPtr, uint32 dataSize) {
uint32 numRead = _stream->read(dataPtr, dataSize);
byte *decipher = static_cast(dataPtr);
uint cipherOffset = _cipherOffset;
uint32 remaining = numRead;
while (remaining) {
if (cipherOffset == 255)
cipherOffset = 0;
(*decipher++) ^= _cipher[cipherOffset++];
remaining--;
}
_cipherOffset = cipherOffset;
return numRead;
}
Instruction::Instruction() : op(ScriptOps::kInvalid), arg(0) {
}
Instruction::Instruction(ScriptOps::ScriptOp paramOp) : op(paramOp), arg(0) {
}
Instruction::Instruction(ScriptOps::ScriptOp paramOp, int32 paramArg) : op(paramOp), arg(paramArg) {
}
enum ProtoOp {
kProtoOpScript, // Use script opcode
kProtoOpNoop,
kProtoOpJumpToLabel,
kProtoOpLabel,
kProtoOpIf,
kProtoOpElse,
kProtoOpEndIf,
kProtoOpSwitch,
kProtoOpCase,
kProtoOpEndSwitch,
kProtoOpDefault,
kProtoOpBreak,
};
struct ProtoInstruction {
ProtoInstruction();
explicit ProtoInstruction(ScriptOps::ScriptOp op);
ProtoInstruction(ScriptOps::ScriptOp paramOp, int32 paramArg);
ProtoInstruction(ProtoOp paramProtoOp, ScriptOps::ScriptOp paramOp, int32 paramArg);
ProtoOp protoOp;
ScriptOps::ScriptOp op;
int32 arg;
};
ProtoInstruction::ProtoInstruction() : protoOp(kProtoOpScript), op(ScriptOps::kInvalid), arg(0) {
}
ProtoInstruction::ProtoInstruction(ScriptOps::ScriptOp paramOp) : protoOp(kProtoOpScript), op(paramOp), arg(0) {
}
ProtoInstruction::ProtoInstruction(ScriptOps::ScriptOp paramOp, int32 paramArg) : protoOp(kProtoOpScript), op(paramOp), arg(paramArg) {
}
ProtoInstruction::ProtoInstruction(ProtoOp paramProtoOp, ScriptOps::ScriptOp paramOp, int32 paramArg) : protoOp(paramProtoOp), op(paramOp), arg(paramArg) {
}
struct ProtoScript {
void reset();
Common::Array instrs;
};
void ProtoScript::reset() {
instrs.clear();
}
struct ScriptNamedInstruction {
const char *str;
ProtoOp protoOp;
ScriptOps::ScriptOp op;
};
class ScriptCompiler {
public:
ScriptCompiler(TextParser &parser, const Common::Path &blamePath, ScriptDialect dialect, uint loadAsRoom, uint fileRoom, IScriptCompilerGlobalState *gs);
void compileScriptSet(ScriptSet *ss);
private:
bool parseNumber(const Common::String &token, uint32 &outNumber, bool mayStartWithNonZeroDigit) const;
static bool parseDecNumber(const Common::String &token, uint start, uint32 &outNumber);
static bool parseHexNumber(const Common::String &token, uint start, uint32 &outNumber);
static bool parseBinNumber(const Common::String &token, uint start, uint32 &outNumber);
void expectNumber(uint32 &outNumber, bool mayStartWithNonZeroDigit);
void compileRoomScriptSet(RoomScriptSet *rss);
void compileReahOrAD2044ScreenScriptSet(ScreenScriptSet *sss);
void compileSchizmScreenScriptSet(ScreenScriptSet *sss);
void compileFunction(Script *script);
bool compileInstructionToken(ProtoScript &script, const Common::String &token);
void codeGenScript(ProtoScript &protoScript, Script &script);
uint indexString(const Common::String &str);
enum NumberParsingMode {
kNumberParsingDec,
kNumberParsingHex,
kNumberParsingBin,
};
TextParser &_parser;
NumberParsingMode _numberParsingMode;
const Common::Path _blamePath;
ScriptDialect _dialect;
uint _loadAsRoom;
const char *_scrToken;
const char *_eroomToken;
Common::HashMap _stringToIndex;
IScriptCompilerGlobalState *_gs;
};
class ScriptCompilerGlobalState : public IScriptCompilerGlobalState {
public:
void define(const Common::String &key, uint roomNumber, int32 value) override;
bool getDefine(const Common::String &str, uint &outRoomNumber, int32 &outValue) const override;
uint getFunctionIndex(const Common::String &fnName) override;
void setFunction(uint fnIndex, const Common::SharedPtr