Initial commit

This commit is contained in:
Vanessa 2025-10-13 15:12:35 +02:00
commit 1225df4426
72 changed files with 5368 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.idea
*.o
*.gbm
gbmconv
game.exe
game.img

BIN
CWSDPMI.EXE Normal file

Binary file not shown.

127
Makefile Normal file
View File

@ -0,0 +1,127 @@
HOSTCXX:=$(CXX)
ifeq ($(TARGET),SDL)
# SDL
CC=gcc
CXX=g++
LD=g++
STRIP=strip
SDL_INCLUDE=$(shell pkg-config --cflags sdl2)
SDL_LIBS=$(shell pkg-config --libs sdl2)
CFLAGS=-g -DBUILD_SDL $(SDL_INCLUDE)
CXXFLAGS=$(CFLAGS)
LDFLAGS=
LDLIBS=$(SDL_LIBS) -lstdc++
EXE=game
EXTRA_FILES=
SYSTEM=SDL
else ifeq ($(TARGET),SDLWIN)
# SDL on Windows
CC=i686-w64-mingw32-gcc
CXX=i686-w64-mingw32-g++
LD=i686-w64-mingw32-g++
STRIP=i686-w64-mingw32-strip
SDL_INCLUDE=-I../SDL2-2.32.10/i686-w64-mingw32/include/SDL2
SDL_LDFLAGS=-L../SDL2-2.32.10/i686-w64-mingw32/lib
SDL_LIBS=-lmingw32 -lSDL2main -lSDL2
CFLAGS=-g -DBUILD_SDL $(SDL_INCLUDE) -m32
CXXFLAGS=$(CFLAGS)
LDFLAGS=$(SDL_LDFLAGS) -mwindows
LDLIBS=$(SDL_LIBS) -lstdc++
EXE=game.exe
EXTRA_FILES=../SDL2-2.32.10/i686-w64-mingw32/bin/SDL2.dll
EXTRA_FILES+=/usr/lib/gcc/i686-w64-mingw32/10-win32/libgcc_s_dw2-1.dll
EXTRA_FILES+=/usr/lib/gcc/i686-w64-mingw32/10-win32/libstdc++-6.dll
SYSTEM=SDL
else ifeq ($(TARGET),SDLWIN64)
# SDL on Windows
CC=x86_64-w64-mingw32-gcc
CXX=x86_64-w64-mingw32-g++
LD=x86_64-w64-mingw32-g++
STRIP=x86_64-w64-mingw32-strip
SDL_INCLUDE=-I../SDL2-2.32.10/x86_64-w64-mingw32/include/SDL2
SDL_LDFLAGS=-L../SDL2-2.32.10/x86_64-w64-mingw32/lib
SDL_LIBS=-lmingw32 -lSDL2main -lSDL2
CFLAGS=-g -DBUILD_SDL $(SDL_INCLUDE) -m64
CXXFLAGS=$(CFLAGS)
LDFLAGS=$(SDL_LDFLAGS) -mwindows
LDLIBS=$(SDL_LIBS) -lstdc++
EXE=game.exe
EXTRA_FILES=../SDL2-2.32.10/x86_64-w64-mingw32/bin/SDL2.dll
EXTRA_FILES+=/mingw64/bin/libgcc_s_seh-1.dll
EXTRA_FILES+=/mingw64/bin/libstdc++-6.dll
SYSTEM=SDL
else
# DOS
CC=/usr/local/djgpp/bin/i586-pc-msdosdjgpp-gcc
CXX=/usr/local/djgpp/bin/i586-pc-msdosdjgpp-g++
LD=/usr/local/djgpp/bin/i586-pc-msdosdjgpp-g++
STRIP=i586-pc-msdosdjgpp-strip
CFLAGS=-O3 -march=i486
CXXFLAGS=$(CFLAGS)
LDFLAGS=
LDLIBS=
EXE=game.exe
DPMI_HOST=CWSDPMI.EXE
EXTRA_FILES=install.bat $(DPMI_HOST)
SYSTEM=DOS
endif
OBJ=main.o
OBJ+=$(patsubst %.cpp,%.o,$(wildcard graphics/*.cpp))
OBJ+=$(patsubst %.cpp,%.o,$(wildcard audio/*.cpp))
OBJ+=$(patsubst %.cpp,%.o,$(wildcard util/*.cpp))
OBJ+=$(patsubst %.cpp,%.o,$(wildcard scenes/*.cpp))
ifeq ($(SYSTEM),SDL)
OBJ+=$(patsubst %.cpp,%.o,$(wildcard system/sdl/*.cpp))
else ifeq ($(SYSTEM),DOS)
OBJ+=$(patsubst %.cpp,%.o,$(wildcard system/dos/*.cpp))
else
$(error Unknown SYSTEM variable:$(SYSTEM))
endif
CONVERTER_CXX=$(HOSTCXX)
CONVERTER_EXE=gbmconv
CONVERTER_SRC=converter.cpp
CONVERTER_SRC+=graphics/Bitmap.cpp
CONVERTER_SRC+=util/Files.cpp util/Bmp.cpp util/Gbm.cpp
COMPILED_GFX_ASSETS=assets/font1.gbm assets/cow.gbm assets/witch.gbm
GFX_ASSETS=assets/bg.bmp $(COMPILED_GFX_ASSETS)
MUSIC_ASSETS=assets/rain.rad assets/getup.rad assets/spiral.rad
RELEASE_FILES=$(EXE) $(GFX_ASSETS) $(MUSIC_ASSETS) $(EXTRA_FILES)
FLOPPY_IMG=game.img
.PHONY: all clean release debug assets floppy
all: $(CONVERTER_EXE) $(EXE)
clean: ; rm -rf $(OBJ) $(EXE) $(CONVERTER_EXE) $(FLOPPY_IMG) $(COMPILED_GFX_ASSETS) release debug
release: all assets; $(STRIP) $(EXE) && upx $(EXE); mkdir -p release; cp -Rv $(RELEASE_FILES) release/
debug: all assets; mkdir -p debug; cp -Rv $(RELEASE_FILES) debug/
floppy: release; dd if=/dev/zero of=$(FLOPPY_IMG) bs=512 count=2880 && mkfs.fat -F12 $(FLOPPY_IMG) && mcopy -i $(FLOPPY_IMG) -s release/* ::
$(CONVERTER_EXE): $(CONVERTER_SRC); $(CONVERTER_CXX) -o $@ $(CONVERTER_SRC)
$(EXE): $(OBJ); $(LD) $(LDFLAGS) -o $@ $(OBJ) $(LDLIBS)
%.o : %.c ; $(CC) $(CFLAGS) -c $< -o $@
%.o : %.cpp ; $(CXX) $(CXXFLAGS) -c $< -o $@
assets: $(CONVERTER_EXE) $(GFX_ASSETS)
%.gbm : %.bmp %_m.bmp $(CONVERTER_EXE) ; ./$(CONVERTER_EXE) $@ $^

BIN
assets/bg.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

BIN
assets/cow.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
assets/cow.xcf Normal file

Binary file not shown.

BIN
assets/cow_m.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
assets/flaunch.pcm Normal file

Binary file not shown.

BIN
assets/font1.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/font1_m.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/getup.rad Normal file

Binary file not shown.

BIN
assets/rain.rad Normal file

Binary file not shown.

BIN
assets/spiral.rad Normal file

Binary file not shown.

BIN
assets/witch.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
assets/witch.xcf Normal file

Binary file not shown.

BIN
assets/witch_m.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

35
audio/Audio.cpp Normal file
View File

@ -0,0 +1,35 @@
#include "Audio.h"
#include "../util/Files.h"
#include "AudioPlayer.h"
Audio::Audio() {
}
Audio::Audio(const char *path) {
loadFromFile(path);
}
Audio::~Audio() {
unload();
}
bool Audio::isValid() const {
return _data != nullptr;
}
void Audio::loadFromFile(const char *path) {
unload();
_length = Files::allocateBufferAndLoadFromFile(path, &_data);
}
void Audio::unload() {
if (_data) {
if (audioPlayer.isPlaying(*this)) {
audioPlayer.stopAudio(*this);
}
Files::deleteBuffer(_data);
_length = 0;
}
}

22
audio/Audio.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef GAME_AUDIO_H
#define GAME_AUDIO_H
#include <cstddef>
class Audio {
Audio();
explicit Audio(const char *path);
~Audio();
[[nodiscard]] bool isValid() const;
void loadFromFile(const char* path);
void unload();
private:
friend class AudioPlayer;
[[nodiscard]] void *getData() const { return _data; }
size_t _length{0};
void *_data{nullptr};
};
#endif //GAME_AUDIO_H

111
audio/AudioPlayer.cpp Normal file
View File

@ -0,0 +1,111 @@
#include "AudioPlayer.h"
#include "../system/Opl.h"
#include "../system/Timer.h"
AudioPlayer audioPlayer;
void radPlayerWriteReg(void *p, uint16_t reg, uint8_t data) {
auto player = static_cast<AudioPlayer *>(p);
player->writeOpl(reg, data);
}
void audioPlayerOnTimer() {
audioPlayer.onTimer();
}
AudioPlayer::AudioPlayer() {
timer.setCallback(audioPlayerOnTimer);
}
AudioPlayer::~AudioPlayer() {
timer.setCallback(nullptr);
}
void AudioPlayer::playMusic(Music &music) {
stopMusic();
_currentMusic = &music;
_musicPlayer.Init(_currentMusic->getData(), radPlayerWriteReg, this);
auto hz = _musicPlayer.GetHertz();
if (hz < 0) {
stopMusic();
return;
}
// Start music playback
timer.setFrequency(hz);
resumeMusic();
}
void AudioPlayer::stopMusic() {
if (!_currentMusic) return;
_musicPlayer.Stop();
_paused = true;
_currentMusic = nullptr;
}
void AudioPlayer::pauseMusic() {
_paused = true;
}
void AudioPlayer::resumeMusic() {
if (!_currentMusic) return;
_paused = false;
}
void AudioPlayer::playAudio(Audio &audio, int priority, int flags) {
if (priority > AUDIO_MAX_PRIORITY) priority = AUDIO_MAX_PRIORITY;
}
bool AudioPlayer::isPlaying(const Audio &audio) {
for (auto &channel : _channels) {
if (channel.audio == &audio) return true;
}
return false;
}
void AudioPlayer::stopAudio(Audio &audio) {
for (auto &channel : _channels) {
if (channel.audio == &audio) {
channel.audio = nullptr;
channel.loop = false;
}
}
}
void AudioPlayer::stopAllAudio() {
for (auto &channel : _channels) {
channel.audio = nullptr;
channel.loop = false;
}
}
void AudioPlayer::generateSamples(uint8_t *buffer, size_t size) {
}
bool AudioPlayer::isPlaying(const Music &music) const {
return _currentMusic == &music;
}
void AudioPlayer::writeOpl(uint16_t reg, uint8_t data) const {
Opl::write(reg, data);
}
void AudioPlayer::onTimer() {
if (_paused) return;
if (!_currentMusic) return;
_musicPlayer.Update();
}
void AudioPlayer::generateSamples() {
}

62
audio/AudioPlayer.h Normal file
View File

@ -0,0 +1,62 @@
#ifndef GAME_AUDIOPLAYER_H
#define GAME_AUDIOPLAYER_H
#include "Audio.h"
#include "Music.h"
#include "rad20player.h"
#define AUDIO_CHANNELS 8
#define AUDIO_MAX_PRIORITY 7
#define AUDIO_PLAY_LOOP 0x01
#define AUDIO_PLAY_FIXED_PRIORITY 0x02
class AudioPlayer {
public:
AudioPlayer();
~AudioPlayer();
void playMusic(Music &music);
void stopMusic();
void pauseMusic();
void resumeMusic();
void playAudio(Audio &audio, int priority, int flags = 0);
bool isPlaying(const Audio & audio);
void stopAudio(Audio &audio);
void stopAllAudio();
void generateSamples(uint8_t *buffer, size_t size);
private:
friend class Music;
friend void radPlayerWriteReg(void *p, uint16_t reg, uint8_t data);
friend void audioPlayerOnTimer();
RADPlayer _musicPlayer;
Music *_currentMusic{nullptr};
bool _paused{true};
struct {
Audio *audio{nullptr};
bool loop{false};
} _channels[AUDIO_CHANNELS];
[[nodiscard]] bool isPlaying(const Music &music) const;
void writeOpl(uint16_t reg, uint8_t data) const;
void onTimer();
void generateSamples();
void copySamples();
};
extern AudioPlayer audioPlayer;
#endif //GAME_AUDIOPLAYER_H

34
audio/Music.cpp Normal file
View File

@ -0,0 +1,34 @@
#include "Music.h"
#include "AudioPlayer.h"
#include "../util/Files.h"
Music::Music() {
}
Music::Music(const char *path) {
loadFromFile(path);
}
Music::~Music() {
unload();
}
bool Music::isValid() const {
return _data != nullptr;
}
void Music::loadFromFile(const char *path) {
unload();
_length = Files::allocateBufferAndLoadFromFile(path, &_data);
}
void Music::unload() {
if (_data) {
if (audioPlayer.isPlaying(*this)) {
audioPlayer.stopMusic();
}
Files::deleteBuffer(_data);
_length = 0;
}
}

23
audio/Music.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef GAME_MUSIC_H
#define GAME_MUSIC_H
#include <cstddef>
class Music {
public:
Music();
explicit Music(const char *path);
~Music();
[[nodiscard]] bool isValid() const;
void loadFromFile(const char* path);
void unload();
private:
friend class AudioPlayer;
[[nodiscard]] void *getData() const { return _data; }
size_t _length{0};
void *_data{nullptr};
};
#endif //GAME_MUSIC_H

1097
audio/rad20player.cpp Normal file

File diff suppressed because it is too large Load Diff

173
audio/rad20player.h Normal file
View File

@ -0,0 +1,173 @@
#ifndef RAD20PLAYER_H
#define RAD20PLAYER_H
#include <stdint.h>
//==================================================================================================
// RAD player class.
//==================================================================================================
class RADPlayer {
// Various constants
enum {
kTracks = 100,
kChannels = 9,
kTrackLines = 64,
kRiffTracks = 10,
kInstruments = 127,
cmPortamentoUp = 0x1,
cmPortamentoDwn = 0x2,
cmToneSlide = 0x3,
cmToneVolSlide = 0x5,
cmVolSlide = 0xA,
cmSetVol = 0xC,
cmJumpToLine = 0xD,
cmSetSpeed = 0xF,
cmIgnore = ('I' - 55),
cmMultiplier = ('M' - 55),
cmRiff = ('R' - 55),
cmTranspose = ('T' - 55),
cmFeedback = ('U' - 55),
cmVolume = ('V' - 55),
};
enum e_Source {
SNone, SRiff, SIRiff,
};
enum {
fKeyOn = 1 << 0,
fKeyOff = 1 << 1,
fKeyedOn = 1 << 2,
};
struct CInstrument {
uint8_t Feedback[2];
uint8_t Panning[2];
uint8_t Algorithm;
uint8_t Detune;
uint8_t Volume;
uint8_t RiffSpeed;
uint8_t * Riff;
uint8_t Operators[4][5];
};
struct CEffects {
int8_t PortSlide;
int8_t VolSlide;
uint16_t ToneSlideFreq;
uint8_t ToneSlideOct;
uint8_t ToneSlideSpeed;
int8_t ToneSlideDir;
};
struct CChannel {
uint8_t LastInstrument;
CInstrument * Instrument;
uint8_t Volume;
uint8_t DetuneA;
uint8_t DetuneB;
uint8_t KeyFlags;
uint16_t CurrFreq;
int8_t CurrOctave;
CEffects FX;
struct CRiff {
CEffects FX;
uint8_t * Track;
uint8_t * TrackStart;
uint8_t Line;
uint8_t Speed;
uint8_t SpeedCnt;
int8_t TransposeOctave;
int8_t TransposeNote;
uint8_t LastInstrument;
} Riff, IRiff;
};
public:
RADPlayer() : Initialised(false) {}
void Init(const void *tune, void (*opl3)(void *, uint16_t, uint8_t), void *arg);
void Stop();
bool Update();
int GetHertz() const { return Hertz; }
int GetPlayTimeInSeconds() const { return PlayTime / Hertz; }
int GetTunePos() const { return Order; }
int GetTuneLength() const { return OrderListSize; }
int GetTuneLine() const { return Line; }
void SetMasterVolume(int vol) { MasterVol = vol; }
int GetMasterVolume() const { return MasterVol; }
int GetSpeed() const { return Speed; }
#if RAD_DETECT_REPEATS
uint32_t ComputeTotalTime();
#endif
private:
bool UnpackNote(uint8_t *&s, uint8_t &last_instrument);
uint8_t * GetTrack();
uint8_t * SkipToLine(uint8_t *trk, uint8_t linenum, bool chan_riff = false);
void PlayLine();
void PlayNote(int channum, int8_t notenum, int8_t octave, uint16_t instnum, uint8_t cmd = 0, uint8_t param = 0, e_Source src = SNone, int op = 0);
void LoadInstrumentOPL3(int channum);
void PlayNoteOPL3(int channum, int8_t octave, int8_t note);
void ResetFX(CEffects *fx);
void TickRiff(int channum, CChannel::CRiff &riff, bool chan_riff);
void ContinueFX(int channum, CEffects *fx);
void SetVolume(int channum, uint8_t vol);
void GetSlideDir(int channum, CEffects *fx);
void LoadInstMultiplierOPL3(int channum, int op, uint8_t mult);
void LoadInstVolumeOPL3(int channum, int op, uint8_t vol);
void LoadInstFeedbackOPL3(int channum, int which, uint8_t fb);
void Portamento(uint16_t channum, CEffects *fx, int8_t amount, bool toneslide);
void Transpose(int8_t note, int8_t octave);
void SetOPL3(uint16_t reg, uint8_t val) {
OPL3Regs[reg] = val;
OPL3(OPL3Arg, reg, val);
}
uint8_t GetOPL3(uint16_t reg) const {
return OPL3Regs[reg];
}
void (*OPL3)(void *, uint16_t, uint8_t);
void * OPL3Arg;
CInstrument Instruments[kInstruments];
CChannel Channels[kChannels];
uint32_t PlayTime;
#if RAD_DETECT_REPEATS
uint32_t OrderMap[4];
bool Repeating;
#endif
int16_t Hertz;
uint8_t * OrderList;
uint8_t * Tracks[kTracks];
uint8_t * Riffs[kRiffTracks][kChannels];
uint8_t * Track;
bool Initialised;
uint8_t Speed;
uint8_t OrderListSize;
uint8_t SpeedCnt;
uint8_t Order;
uint8_t Line;
int8_t Entrances;
uint8_t MasterVol;
int8_t LineJump;
uint8_t OPL3Regs[512];
// Values exported by UnpackNote()
int8_t NoteNum;
int8_t OctaveNum;
uint8_t InstNum;
uint8_t EffectNum;
uint8_t Param;
bool LastNote;
static const int8_t NoteSize[];
static const uint16_t ChanOffsets3[9], Chn2Offsets3[9];
static const uint16_t NoteFreq[];
static const uint16_t OpOffsets3[9][4];
static const bool AlgCarriers[7][4];
};
#endif

53
converter.cpp Normal file
View File

@ -0,0 +1,53 @@
#include <cstdio>
#include "util/Bmp.h"
#include "util/Gbm.h"
void usage() {
printf("Usage: gbmconv <gbm output file> <source bitmap> <source mask bitmap>\n");
}
int main(int argc, char** argv) {
if (argc < 4) {
usage();
return 1;
}
char *gbmPath = argv[1];
char *bmpPath = argv[2];
char *maskBmpPath = argv[3];
auto bitmap = Bmp::loadFromFile(bmpPath);
if (bitmap.GetWidth() == 0 || bitmap.GetHeight() == 0) {
printf("Unable to load source bitmap\n");
return 1;
}
auto maskBitmap = Bmp::loadFromFile(maskBmpPath);
if (maskBitmap.GetWidth() == 0 || maskBitmap.GetHeight() == 0) {
printf("Unable to load mask bitmap\n");
return 1;
}
if (maskBitmap.GetWidth() != bitmap.GetWidth() || maskBitmap.GetHeight() != bitmap.GetHeight()) {
printf("Mask bitmap must be the same size as the source bitmap\n");
return 1;
}
// Create a combined bitmap where the most significant bit is a transparency bit
auto combined = Bitmap(bitmap.GetWidth(), bitmap.GetHeight());
for (auto y = 0u; y < bitmap.GetHeight(); y++) {
for (auto x = 0u; x < bitmap.GetWidth(); x++) {
auto isTransparent = (maskBitmap.GetPixel(x, y) == 0);
auto pv = (isTransparent ? 0x80 : 0x00) | (bitmap.GetPixel(x, y) & 0x7f);
combined.SetPixel(x, y, pv);
}
}
for (auto i = 0u; i < 256; i++) {
combined.SetPaletteEntry(i, bitmap.GetPaletteEntry(i));
}
Gbm::writeToFile(gbmPath, combined);
printf("Wrote %s\n", gbmPath);
return 0;
}

78
graphics/Bitmap.cpp Normal file
View File

@ -0,0 +1,78 @@
#include "Bitmap.h"
#include <algorithm>
#include <cstdio>
#include <cstring>
Bitmap::~Bitmap() {
clear();
}
Bitmap::Bitmap(const Bitmap &other) {
copyFrom(other);
}
Bitmap & Bitmap::operator=(const Bitmap &other) {
if (this != &other) {
clear();
copyFrom(other);
}
return *this;
}
Bitmap::Bitmap(Bitmap &&other) noexcept {
moveFrom(std::move(other));
}
Bitmap & Bitmap::operator=(Bitmap &&other) noexcept {
moveFrom(std::move(other));
return *this;
}
Bitmap::Bitmap(uint16_t width, uint16_t height)
: _width(width), _height(height), _data(new uint8_t[width * height]) {
}
Bitmap::Bitmap(uint16_t width, uint16_t height, uint8_t *data) : Bitmap(width, height) {
memcpy(_data, data, width * height);
}
void Bitmap::SetPaletteEntry(uint8_t index, Video::PaletteEntry entry) {
_pal[index] = entry;
}
Video::PaletteEntry Bitmap::GetPaletteEntry(uint8_t index) const {
return _pal[index];
}
void Bitmap::clear() {
delete[] _data;
_data = nullptr;
_width = _height = 0;
}
void Bitmap::copyFrom(const Bitmap &other) {
_width = other._width;
_height = other._height;
_data = new uint8_t[_width * _height];
for (auto i = 0; i < 256; i++) _pal[i] = other._pal[i];
memcpy(_data, other._data, _width * _height);
}
void Bitmap::moveFrom(Bitmap &&other) noexcept {
// Clear existing data
clear();
// Copy data from other
_data = other._data;
_width = other._width;
_height = other._height;
for (auto i = 0; i < 256; i++) _pal[i] = other._pal[i];
// Clear other
other._data = nullptr;
other._width = other._height = 0;
}

66
graphics/Bitmap.h Normal file
View File

@ -0,0 +1,66 @@
#ifndef GAME_BITMAP_H
#define GAME_BITMAP_H
#include <cstdint>
#include "../system/Video.h"
class Bitmap {
public:
Bitmap() = default;
~Bitmap();
Bitmap(const Bitmap& other);
Bitmap& operator=(const Bitmap& other);
Bitmap(Bitmap &&other) noexcept ;
Bitmap& operator=(Bitmap &&other) noexcept;
Bitmap(uint16_t width, uint16_t height);
Bitmap(uint16_t width, uint16_t height, uint8_t* data);
[[nodiscard]] uint8_t GetPixel(uint16_t x, uint16_t y) const {
return _data[y * _width + x];
}
void SetPixel(uint16_t x, uint16_t y, uint8_t value) {
_data[y * _width + x] = value;
}
uint8_t &operator[](uint32_t offset) {
return _data[offset];
}
const uint8_t &operator[](uint32_t offset) const {
return _data[offset];
}
void SetPaletteEntry(uint8_t index, Video::PaletteEntry entry);
[[nodiscard]] Video::PaletteEntry GetPaletteEntry(uint8_t index) const;
[[nodiscard]] uint16_t GetWidth() const {
return _width;
}
[[nodiscard]] uint16_t GetHeight() const {
return _height;
}
void clear();
private:
uint16_t _width{0};
uint16_t _height{0};
uint8_t *_data{nullptr};
Video::PaletteEntry _pal[256]{};
void copyFrom(const Bitmap& other);
void moveFrom(Bitmap&& other) noexcept;
};
#endif //GAME_BITMAP_H

67
graphics/Font.h Normal file
View File

@ -0,0 +1,67 @@
#ifndef GAME_FONT_H
#define GAME_FONT_H
#include "Bitmap.h"
class Font {
public:
Font(Bitmap *bitmap, unsigned w, unsigned h, unsigned minCode = 32, unsigned maxCode = 127)
: _font(bitmap), _w(w), _h(h), _minCode(minCode), _maxCode(maxCode) {
_charsPerRow = _font->GetWidth() / _w;
}
void renderCharAlpha(int c, int x, int y, uint8_t color, uint8_t *fb, unsigned fbWidth, unsigned fbHeight) {
if (c < _minCode || c > _maxCode) return;
c -= _minCode;
auto bitmapX = (c % _charsPerRow) * _w;
auto bitmapY = (c / _charsPerRow) * _h;
for (auto charY = 0; charY < _h; charY++) {
if (y + charY < 0) continue;
if (y + charY >= fbHeight) break;
auto yOffset = (y + charY) * fbWidth;
for (auto charX = 0; charX < _w; charX++) {
if (x + charX < 0) continue;
if (x + charX >= fbWidth) break;
auto pv = _font->GetPixel(bitmapX + charX, bitmapY + charY);
if (pv & 0x80) continue;
fb[yOffset + x + charX] = color;
}
}
}
void renderChar(int c, int x, int y, uint8_t fgColor, uint8_t bgColor, uint8_t *fb, unsigned fbWidth, unsigned fbHeight) {
if (c < _minCode || c > _maxCode) return;
c -= _minCode;
auto bitmapX = (c % _charsPerRow) * _w;
auto bitmapY = (c / _charsPerRow) * _h;
for (auto charY = 0; charY < _h; charY++) {
if (y + charY < 0) continue;
if (y + charY >= fbHeight) break;
auto yOffset = (y + charY) * fbWidth;
for (auto charX = 0; charX < _w; charX++) {
if (x + charX < 0) continue;
if (x + charX >= fbWidth) break;
auto pv = _font->GetPixel(bitmapX + charX, bitmapY + charY) & 0x80 ? bgColor : fgColor;
fb[yOffset + x + charX] = pv;
}
}
}
private:
Bitmap *_font;
unsigned _w, _h;
unsigned _minCode;
unsigned _maxCode;
unsigned _charsPerRow{0};
};
#endif //GAME_FONT_H

45
graphics/Rect.h Normal file
View File

@ -0,0 +1,45 @@
#ifndef GAME_RECT_H
#define GAME_RECT_H
struct Rect {
int x1, y1, x2, y2;
void clamp(int minX, int minY, int maxX, int maxY) {
if (x1 < minX) x1 = minX;
if (x2 > maxX) x2 = maxX;
if (y1 < minY) y1 = minY;
if (y2 > maxY) y2 = maxY;
}
void sort() {
if (x1 > x2) {
auto x = x1;
x1 = x2;
x2 = x;
}
if (y1 > y2) {
auto y = y1;
y1 = y2;
y2 = y;
}
}
Rect &operator+=(const Rect &other) {
if (other.x1 < x1) x1 = other.x1;
if (other.x2 > x2) x2 = other.x2;
if (other.y1 < y1) y1 = other.y1;
if (other.y2 > y2) y2 = other.y2;
return *this;
}
[[nodiscard]] bool contains(int x, int y) const {
return x >= x1 && y >= y1 && x < x2 && y < y2;
}
[[nodiscard]] bool overlaps(const Rect &other) const {
return !(other.x1 > x2 || other.x2 < x1 || other.y1 > y2 || other.y2 < y1);
}
};
#endif //GAME_RECT_H

68
graphics/Sprite.h Normal file
View File

@ -0,0 +1,68 @@
#ifndef GAME_SPRITE_H
#define GAME_SPRITE_H
#include "Bitmap.h"
#define SPR_SUBPX_TO_PX(v) ((v) >= 0 ? ((v) >> 8) : -((-(v)) >> 8))
#define SPR_PX_TO_SUBPX(v) ((v) >= 0 ? ((v) << 8) : -((-(v)) << 8))
class Sprite {
public:
virtual ~Sprite() = default;
virtual void Tick() = 0;
[[nodiscard]] virtual int GetWidth() const {
return _w;
}
[[nodiscard]] virtual int GetHeight() const {
return _h;
}
[[nodiscard]] int GetX() const {
return SPR_SUBPX_TO_PX(_sx);
}
[[nodiscard]] int GetY() const {
return SPR_SUBPX_TO_PX(_sy);
}
void SetX(int x) {
_sx = x >= 0 ? (x << 8) : -((-x) << 8);
}
void SetY(int y) {
_sy = y >= 0 ? (y << 8) : -((-y) << 8);
}
[[nodiscard]] Rect GetRect() const {
auto x = SPR_SUBPX_TO_PX(_sx), y = SPR_SUBPX_TO_PX(_sy);
return { x, y, x + _w, y + _h };
}
void SetPalette(uint8_t palette) { _palette = palette << 4; }
[[nodiscard]] uint8_t GetPalette() const { return _palette >> 4; }
[[nodiscard]] Bitmap *GetBitmap() const { return _bitmap; }
[[nodiscard]] unsigned GetYOffset() const { return _frame * _h; }
[[nodiscard]] bool isMirroredX() const { return _mirrorX; }
[[nodiscard]] bool isMirroredY() const { return _mirrorY; }
protected:
Sprite() = default;
Bitmap *_bitmap{nullptr};
uint8_t _palette{0};
int _sx{0}, _sy{0};
int _w{0}, _h{0};
unsigned _frame{0};
bool _mirrorX{false};
bool _mirrorY{false};
};
#endif //GAME_SPRITE_H

2
install.bat Normal file
View File

@ -0,0 +1,2 @@
mkdir C:\game\
xcopy *.* C:\game\

110
main.cpp Normal file
View File

@ -0,0 +1,110 @@
#include <cmath>
#include <cstdio>
#include <ctime>
#ifdef BUILD_SDL
#include <SDL_main.h>
#endif
#include "audio/AudioPlayer.h"
#include "audio/Music.h"
#include "system/Timer.h"
#include "system/Video.h"
#include "graphics/Bitmap.h"
#include "graphics/Font.h"
#include "scenes/Scene.h"
#include "system/init.h"
#include "system/Keyboard.h"
#include "util/Gbm.h"
#include "util/Log.h"
volatile bool showFps = true;
volatile bool running = true;
int main(int argc, char *argv[]) {
System::init();
srand(time(nullptr));
DefaultLog.log("Loading music\n");
Music music("spiral.rad");
if (!music.isValid()) {
printf("Unable to load song\n");
return 1;
}
DefaultLog.log("Playing music\n");
audioPlayer.playMusic(music);
DefaultLog.log("Changing video mode\n");
video.Enter();
// Run scenes
auto fb = static_cast<uint8_t *>(video.GetFB());
unsigned fps{0};
unsigned frameCount{0};
uint32_t fpsTick{0};
char fpsDigits[4]{'0', '0', '0', 0};
auto fontBitmap = Gbm::loadFromFile("font1.gbm");
Font font(&fontBitmap, 7, 9, 32, 127);
keyboard.setKeyDownHandler([] (unsigned char key) {
DefaultLog.log("KeyDown %u (%s)\n", key, keyboard.getKeyName(key));
});
keyboard.setKeyUpHandler([] (unsigned char key) {
DefaultLog.log("KeyUp %u (%s)\n", key, keyboard.getKeyName(key));
switch (key) {
case KeyEscape:
running = false;
break;
case KeyF:
showFps = !showFps;
video.UpdateRect({0, 0, 21, 9 });
break;
default:
// Do nothing
break;
}
});
keyboard.setKeyRepeatHandler([](unsigned char key) {
DefaultLog.log("KeyRepeat %u (%s)\n", key, keyboard.getKeyName(key));
});
while (running) {
const auto scene = Scenes::getCurrentScene();
scene->run();
// Render fps
++frameCount;
auto ticks = timer.getTicks();
if (ticks > fpsTick + 50) {
fpsTick = ticks;
fps = frameCount;
frameCount = 0;
fpsDigits[0] = '0' + (fps / 100) % 10;
fpsDigits[1] = '0' + (fps / 10) % 10;
fpsDigits[2] = '0' + fps % 10;
}
if (showFps) {
font.renderChar(fpsDigits[0], 0, 0, 242, 0, fb, SCREEN_WIDTH, SCREEN_HEIGHT);
font.renderChar(fpsDigits[1], 7, 0, 242, 0, fb, SCREEN_WIDTH, SCREEN_HEIGHT);
font.renderChar(fpsDigits[2], 14, 0, 242, 0, fb, SCREEN_WIDTH, SCREEN_HEIGHT);
video.UpdateRect({0, 0, 21, 9 });
}
// Wait for vertical retrace and then copy render buffer to screen
video.WaitForVerticalSync();
video.Flip();
}
audioPlayer.stopMusic();
System::terminate();
return 0;
}

281
scenes/GameScene.cpp Normal file
View File

@ -0,0 +1,281 @@
#include "GameScene.h"
#include <cstdio>
#include <cstdlib>
#include "../system/Keyboard.h"
#include "../system/Timer.h"
#include "../system/Video.h"
#include "../util/Bmp.h"
#include "../util/Gbm.h"
#include "../util/Log.h"
class TestSprite : public Sprite {
public:
enum { Width = 20, Height = 16, Frames = 12 };
explicit TestSprite(Bitmap *image) {
_bitmap = image;
_w = Width;
_h = Height;
SetX(10 + rand() % (SCREEN_WIDTH - 20 - Width));
SetY(10 + rand() % (SCREEN_HEIGHT - 20 - Height));
int velXSign = ((rand() & 1) << 1) - 1;
int velYSign = ((rand() & 1) << 1) - 1;
_vx = (rand() % 128 + 64) * velXSign;
_vy = (rand() % 128 + 64) * velYSign;
}
void Tick() override {
if (++_ticks >= 15) {
_ticks = 0;
if (++_frame >= Frames) {
_frame = 0;
}
}
auto newX = _sx + _vx;
if (newX < 0) {
newX = -newX;
_vx = -_vx;
} else if (newX >= MaxX) {
auto d = newX - MaxX;
newX = MaxX - d - 1;
_vx = -_vx;
}
_mirrorX = (_vx < 0);
auto newY = _sy + _vy;
if (newY < 0) {
newY = -newY;
_vy = -_vy;
} else if (newY >= MaxY) {
auto d = newY - MaxY;
newY = MaxY - d - 1;
_vy = -_vy;
}
_sx = newX;
_sy = newY;
}
protected:
int _vx{0}, _vy{0};
unsigned _ticks{0};
const int MaxX = (SCREEN_WIDTH - Width) << 8;
const int MaxY = (SCREEN_HEIGHT - Height) << 8;
};
class PlayerSprite : public Sprite {
public:
enum { Width = 24, Height = 32, Frames = 4, Speed = 480 };
explicit PlayerSprite(Bitmap *image) {
_bitmap = image;
_w = Width;
_h = Height;
SetX(20);
SetY((SCREEN_HEIGHT - Height) / 2);
}
void Tick() override {
if (++_ticks >= 15) {
_ticks = 0;
if (++_animationStep >= Frames) {
_animationStep = 0;
}
_frame = _animationMap[_animationStep];
}
if (keyboard.keyState[KeyLeft]) {
_sx -= Speed;
if (_sx < 0) _sx = 0;
}
if (keyboard.keyState[KeyRight]) {
_sx += Speed;
if (_sx > MaxX) _sx = MaxX;
}
if (keyboard.keyState[KeyUp]) {
_sy -= Speed;
if (_sy < 0) _sy = 0;
}
if (keyboard.keyState[KeyDown]) {
_sy += Speed;
if (_sy > MaxY) _sy = MaxY;
}
}
protected:
unsigned _ticks{0};
unsigned _animationStep = 0;
const int MaxX = (SCREEN_WIDTH - Width) << 8;
const int MaxY = (SCREEN_HEIGHT - Height) << 8;
const unsigned _animationMap[4] = {
0, 1, 2, 1
};
};
GameScene::GameScene() {
}
GameScene::~GameScene() {
}
void GameScene::run() {
if (_firstFrame) {
Rect bgRect{0, 0, SCREEN_WIDTH, SCREEN_HEIGHT};
for (auto y = 0; y < SCREEN_HEIGHT; y++) {
auto yOffset = y * SCREEN_WIDTH;
for (auto x = 0; x < SCREEN_WIDTH; x++) {
_fb[yOffset + x] = _bg.GetPixel(x, y);
}
}
video.UpdateRect(bgRect);
_firstFrame = false;
}
// Redraw background
for (auto &sprite : _sprites) {
clearSprite(sprite);
}
clearSprite(_playerSprite);
// Update and paint all sprites after update
for (auto &sprite : _sprites) {
sprite->Tick();
drawSprite(sprite);
}
_playerSprite->Tick();
drawSprite(_playerSprite);
}
void GameScene::enter() {
DefaultLog.log("Entering GameScene\n");
_fb = static_cast<uint8_t *>(video.GetFB());
_cow = Gbm::loadFromFile("cow.gbm");
_witch = Gbm::loadFromFile("witch.gbm");
_bg = Bmp::loadFromFile("bg.bmp");
_playerSprite = new PlayerSprite(&_witch);
_playerSprite->SetPalette(14);
for (auto &sprite : _sprites) {
sprite = new TestSprite(&_cow);
sprite->SetPalette(15);
}
// Load bg palette
for (auto i = 0u; i < 64; i++) {
video.SetPaletteEntry(i, _bg.GetPaletteEntry(i));
}
// Load sprite palette
for (auto i = 0u; i < 16; i++) {
video.SetPaletteEntry(i + 240, _cow.GetPaletteEntry(i));
}
// Load player sprite palette
for (auto i = 0u; i < 16; i++) {
video.SetPaletteEntry(i + 224, _witch.GetPaletteEntry(i));
}
_firstFrame = true;
}
void GameScene::exit() {
DefaultLog.log("Exiting GameScene\n");
for (auto &sprite : _sprites) {
delete sprite;
}
delete _playerSprite;
_cow.clear();
_bg.clear();
_witch.clear();
}
void GameScene::clearSprite(Sprite *sprite) {
auto rect = sprite->GetRect();
rect.clamp(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
for (auto y = rect.y1; y < rect.y2; y++) {
auto yOffset = y * SCREEN_WIDTH;
for (auto x = rect.x1; x < rect.x2; x++) {
_fb[yOffset + x] = _bg.GetPixel(x, y);
}
}
video.UpdateRect(rect);
}
void GameScene::drawSprite(Sprite *sprite) {
auto rect = sprite->GetRect();
rect.clamp(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
auto rx = rect.x1;
auto ry = rect.y1;
auto rx2 = rect.x2;
auto ry2 = rect.y2;
auto paletteOffset = sprite->GetPalette() << 4;
auto bitmap = sprite->GetBitmap();
auto syoff = sprite->GetYOffset();
if (sprite->isMirroredY()) {
for (auto y = ry; y < ry2; y++) {
auto yOffset = y * SCREEN_WIDTH;
auto offy = ry2 - y - 1;
if (sprite->isMirroredX()) {
for (auto x = rx; x < rx2; x++) {
auto offx = rx2 - x - 1;
auto pv = bitmap->GetPixel(offx, offy + syoff);
if (!(pv & 0x80)) _fb[yOffset + x] = (pv & 0x7f) + paletteOffset;
}
} else {
for (auto x = rx; x < rx2; x++) {
auto offx = x - rx;
auto pv = bitmap->GetPixel(offx, offy + syoff);
if (!(pv & 0x80)) _fb[yOffset + x] = (pv & 0x7f) + paletteOffset;
}
}
}
} else {
for (auto y = ry; y < ry2; y++) {
auto yOffset = y * SCREEN_WIDTH;
auto offy = y -ry;
if (sprite->isMirroredX()) {
for (auto x = rx; x < rx2; x++) {
auto offx = rx2 - x - 1;
auto pv = bitmap->GetPixel(offx, offy + syoff);
if (!(pv & 0x80)) _fb[yOffset + x] = (pv & 0x7f) + paletteOffset;
}
} else {
for (auto x = rx; x < rx2; x++) {
auto offx = x - rx;
auto pv = bitmap->GetPixel(offx, offy + syoff);
if (!(pv & 0x80)) _fb[yOffset + x] = (pv & 0x7f) + paletteOffset;
}
}
}
}
video.UpdateRect(rect);
}

37
scenes/GameScene.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef GAME_GAMESCENE_H
#define GAME_GAMESCENE_H
#include <cstdint>
#include "Scene.h"
#include "../graphics/Bitmap.h"
#include "../graphics/Sprite.h"
#define MAX_OBJECTS 16
class GameScene : public Scene {
public:
GameScene();
~GameScene() override;
void run() override;
void enter() override;
void exit() override;
private:
uint8_t *_fb{nullptr};
Bitmap _bg;
Bitmap _cow;
Bitmap _witch;
Sprite *_playerSprite;
Sprite *_sprites[MAX_OBJECTS]{nullptr};
bool _firstFrame = true;
void clearSprite(Sprite *sprite);
void drawSprite(Sprite *sprite);
};
#endif //GAME_GAMESCENE_H

11
scenes/IntroScene.cpp Normal file
View File

@ -0,0 +1,11 @@
#include "IntroScene.h"
void IntroScene::run() {
Scenes::setScene(Scenes::MainMenuScene);
}
void IntroScene::enter() {
}
void IntroScene::exit() {
}

20
scenes/IntroScene.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef GAME_INTROSCENE_H
#define GAME_INTROSCENE_H
#include "Scene.h"
class IntroScene : public Scene {
public:
IntroScene() = default;
~IntroScene() override = default;
void run() override;
void enter() override;
void exit() override;
};
#endif //GAME_INTROSCENE_H

11
scenes/MainMenuScene.cpp Normal file
View File

@ -0,0 +1,11 @@
#include "MainMenuScene.h"
void MainMenuScene::run() {
Scenes::setScene(Scenes::GameScene);
}
void MainMenuScene::enter() {
}
void MainMenuScene::exit() {
}

20
scenes/MainMenuScene.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef GAME_MAINMENUSCENE_H
#define GAME_MAINMENUSCENE_H
#include "Scene.h"
class MainMenuScene : public Scene {
public:
MainMenuScene() = default;
~MainMenuScene() override = default;
void run() override;
void enter() override;
void exit() override;
};
#endif //GAME_MAINMENUSCENE_H

19
scenes/Scene.cpp Normal file
View File

@ -0,0 +1,19 @@
#include "Scene.h"
#include "GameScene.h"
#include "IntroScene.h"
#include "MainMenuScene.h"
namespace Scenes {
static ::IntroScene introScene{};
static ::MainMenuScene mainMenuScene{};
static ::GameScene gameScene{};
SceneId currentSceneId = IntroScene;
Scene *sceneList[] = {
&introScene,
&mainMenuScene,
&gameScene
};
}

42
scenes/Scene.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef GAME_SCENE_H
#define GAME_SCENE_H
#include "../graphics/Rect.h"
class Scene {
public:
virtual ~Scene() = default;
virtual void run() = 0;
virtual void enter() = 0;
virtual void exit() = 0;
protected:
Scene() = default;
};
namespace Scenes {
enum SceneId {
IntroScene,
MainMenuScene,
GameScene,
};
extern SceneId currentSceneId;
extern Scene *sceneList[];
inline SceneId setScene(SceneId scene) {
auto previousSceneId = currentSceneId;
sceneList[currentSceneId]->exit();
currentSceneId = scene;
sceneList[currentSceneId]->enter();
return previousSceneId;
}
inline Scene *getCurrentScene() { return sceneList[currentSceneId]; }
inline SceneId getCurrentSceneId() { return currentSceneId; }
}
#endif //GAME_SCENE_H

5
system/Keyboard.h Normal file
View File

@ -0,0 +1,5 @@
#ifdef BUILD_SDL
#include "sdl/Keyboard.h"
#else
#include "dos/Keyboard.h"
#endif

9
system/Opl.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef GAME_OPL_H
#define GAME_OPL_H
#include <cstdint>
namespace Opl {
void write(uint16_t reg, uint8_t data);
}
#endif //GAME_OPL_H

12
system/Pic.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef GAME_PIC_H
#define GAME_PIC_H
#include "../util/Asm.h"
namespace Pic {
static void clearInterrupt() {
outb(0x20, 0x20);
}
}
#endif //GAME_PIC_H

36
system/SoundBlaster.h Normal file
View File

@ -0,0 +1,36 @@
#ifndef GAME_SOUNDBLASTER_H
#define GAME_SOUNDBLASTER_H
#ifdef BUILD_SDL
#else
#include <dpmi.h>
#endif
#include <cstdint>
#define SB_BUFFER_SIZE 1024
#define SB_BUFFERS 32
#define SB_DMA_BUFFER_SIZE (SB_BUFFER_SIZE * 4)
class SoundBlaster {
public:
SoundBlaster();
~SoundBlaster();
private:
friend void soundblasterIsr();
void onInterrupt();
#ifdef BUILD_SDL
#else
_go32_dpmi_seginfo _dmaBuffer{};
uint8_t (*_buffers)[SB_BUFFER_SIZE]{};
uint8_t _dmaPage{0};
uint16_t _dmaOffset{0};
unsigned _nextBufferReadIndex{0};
unsigned _nextBufferWriteIndex{0};
#endif
};
extern SoundBlaster soundblaster;
#endif //GAME_SOUNDBLASTER_H

49
system/Timer.h Normal file
View File

@ -0,0 +1,49 @@
#ifndef TICKS_H
#define TICKS_H
#include <cstdint>
#ifdef BUILD_SDL
#else
#include <dpmi.h>
#endif
class Timer {
public:
using Callback = void (*)();
Timer();
~Timer();
void setFrequency(uint16_t freq);
void setDivider(uint16_t div);
Callback setCallback(Callback);
[[nodiscard]] uint32_t getTicks() const;
[[nodiscard]] uint16_t getFrequency() const;
private:
friend void timerISR();
#ifdef BUILD_SDL
#else
_go32_dpmi_seginfo _oldIsr{}, _newIsr{};
#endif
uint32_t _ticks{0};
uint32_t _elapsed{0};
uint16_t _div{0};
uint16_t _freq{0};
void (*_callback)(){nullptr};
void update();
};
extern Timer timer;
#endif

63
system/Video.h Normal file
View File

@ -0,0 +1,63 @@
#ifndef VIDEO_H
#define VIDEO_H
#ifdef BUILD_SDL
#include <SDL.h>
#include <chrono>
#else
#include <cstdint>
#endif
#include "../graphics/Rect.h"
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 200
#define MAX_UPDATE_RECT_INDEX 255
class Video {
public:
struct PaletteEntry {
uint8_t r{0}, g{0}, b{0};
};
Video();
~Video();
void Enter();
void Exit();
void SetPaletteEntry(uint8_t index, PaletteEntry entry);
PaletteEntry GetPaletteEntry(uint8_t index);
void SetPaletteEntry(uint8_t index, PaletteEntry *entry);
void GetPaletteEntry(uint8_t index, PaletteEntry *entry);
void *GetFB();
void WaitForVerticalSync();
void UpdateRect(const Rect &rect);
void Flip();
private:
uint8_t _oldMode{0};
PaletteEntry _oldPalette[256]{};
#ifdef BUILD_SDL
SDL_Window *_window;
SDL_Surface *_windowSurface;
SDL_Surface *_fb;
PaletteEntry _palette[256]{};
std::chrono::steady_clock::time_point _lastUpdate{std::chrono::steady_clock::now()};
#else
uint8_t *_fb{nullptr};
#endif
uint8_t _renderBuffer[SCREEN_WIDTH*SCREEN_HEIGHT]{};
Rect _updatedRects[MAX_UPDATE_RECT_INDEX+1];
unsigned _updateRectIndex{0};
void SetMode(uint8_t mode);
uint8_t GetMode();
};
extern Video video;
#endif

429
system/dos/Keyboard.cpp Normal file
View File

@ -0,0 +1,429 @@
#include "../Keyboard.h"
#include <go32.h>
#include "../Pic.h"
#define PIC
#define KEYB_DATA 0x60
Keyboard keyboard;
static const char *keyNames[] = {
"<unknown:00>",
"KeyEscape", // = 0x01,
"Key1", // = 0x02,
"Key2", // = 0x03,
"Key3", // = 0x04,
"Key4", // = 0x05,
"Key5", // = 0x06,
"Key6", // = 0x07,
"Key7", // = 0x08,
"Key8", // = 0x09,
"Key9", // = 0x0a,
"Key0", // = 0x0b,
"KeyMinus", // = 0x0c,
"KeyEqual", // = 0x0d,
"KeyBackspace", // = 0x0e,
"KeyTab", // = 0x0f,
"KeyQ", // = 0x10,
"KeyW", // = 0x11,
"KeyE", // = 0x12,
"KeyR", // = 0x13,
"KeyT", // = 0x14,
"KeyY", // = 0x15,
"KeyU", // = 0x16,
"KeyI", // = 0x17,
"KeyO", // = 0x18,
"KeyP", // = 0x19,
"KeyBracketLeft", // = 0x1a,
"KeyBracketRight", // = 0x1b,
"KeyEnter", // = 0x1c,
"KeyLeftCtrl", // = 0x1d,
"KeyA", // = 0x1e,
"KeyS", // = 0x1f,
"KeyD", // = 0x20,
"KeyF", // = 0x21,
"KeyG", // = 0x22,
"KeyH", // = 0x23,
"KeyJ", // = 0x24,
"KeyK", // = 0x25,
"KeyL", // = 0x26,
"KeySemicolon", // = 0x27,
"KeyApostrophe", // = 0x28,
"KeyBacktick", // = 0x29,
"KeyLeftShift", // = 0x2a,
"KeyBackslash", // = 0x2b,
"KeyZ", // = 0x2c,
"KeyX", // = 0x2d,
"KeyC", // = 0x2e,
"KeyV", // = 0x2f,
"KeyB", // = 0x30,
"KeyN", // = 0x31,
"KeyM", // = 0x32,
"KeyComma", // = 0x33,
"KeyPeriod", // = 0x34,
"KeySlash", // = 0x35,
"KeyRightShift", // = 0x36,
"KeyKeypadMultiply", // = 0x37,
"KeyLeftAlt", // = 0x38,
"KeySpace", // = 0x39,
"KeyCapsLock", // = 0x3a,
"KeyF1", // = 0x3b,
"KeyF2", // = 0x3c,
"KeyF3", // = 0x3d,
"KeyF4", // = 0x3e,
"KeyF5", // = 0x3f,
"KeyF6", // = 0x40,
"KeyF7", // = 0x41,
"KeyF8", // = 0x42,
"KeyF9", // = 0x43,
"KeyF10", // = 0x44,
"KeyNumLock", // = 0x45,
"KeyScrollLock", // = 0x46,
"KeyKeypad7", // = 0x47,
"KeyKeypad8", // = 0x48,
"KeyKeypad9", // = 0x49,
"KeyKeypadMinus", // = 0x4a,
"KeyKeypad4", // = 0x4b,
"KeyKeypad5", // = 0x4c,
"KeyKeypad6", // = 0x4d,
"KeyKeypadPlus", // = 0x4e,
"KeyKeypad1", // = 0x4f,
"KeyKeypad2", // = 0x50,
"KeyKeypad3", // = 0x51,
"KeyKeypad0", // = 0x52,
"KeyKeypadPeriod", // = 0x53,
"<unknown:54>",
"<unknown:55>",
"<unknown:56>",
"KeyF11", // = 0x57,
"KeyF12", // = 0x58,
"<unknown:59>",
"<unknown:5a>",
"<unknown:5b>",
"<unknown:5c>",
"<unknown:5d>",
"<unknown:5e>",
"<unknown:5f>",
"<unknown:60>",
"<unknown:61>",
"<unknown:62>",
"<unknown:63>",
"<unknown:64>",
"<unknown:65>",
"<unknown:66>",
"<unknown:67>",
"<unknown:68>",
"<unknown:69>",
"<unknown:6a>",
"<unknown:6b>",
"<unknown:6c>",
"<unknown:6d>",
"<unknown:6e>",
"<unknown:6f>",
"<unknown:70>",
"<unknown:71>",
"<unknown:72>",
"<unknown:73>",
"<unknown:74>",
"<unknown:75>",
"<unknown:76>",
"<unknown:77>",
"<unknown:78>",
"<unknown:79>",
"<unknown:7a>",
"<unknown:7b>",
"<unknown:7c>",
"<unknown:7d>",
"<unknown:7e>",
"<unknown:7f>",
"<unknown:80>",
"<unknown:81>",
"<unknown:82>",
"<unknown:83>",
"<unknown:84>",
"<unknown:85>",
"<unknown:86>",
"<unknown:87>",
"<unknown:88>",
"<unknown:89>",
"<unknown:8a>",
"<unknown:8b>",
"<unknown:8c>",
"<unknown:8d>",
"<unknown:8e>",
"<unknown:8f>",
"KeyMediaPrev", // = 0x90,
"<unknown:91>",
"<unknown:92>",
"<unknown:93>",
"<unknown:94>",
"<unknown:95>",
"<unknown:96>",
"<unknown:97>",
"<unknown:98>",
"KeyMediaNext", // = 0x99,
"<unknown:9a>",
"<unknown:9b>",
"KeyKeypadEnter", // = 0x9c,
"KeyRightControl", // = 0x9d,
"<unknown:9e>",
"<unknown:9f>",
"KeyMediaMute", // = 0xa0,
"KeyMediaCalculator", // = 0xa1,
"KeyMediaPlay", // = 0xa2,
"<unknown:a3>",
"KeyMediaStop", // = 0xa4,
"<unknown:a5>",
"<unknown:a6>",
"<unknown:a7>",
"<unknown:a8>",
"<unknown:a9>",
"<unknown:aa>",
"<unknown:ab>",
"<unknown:ac>",
"<unknown:ad>",
"KeyMediaVolumeDown", // = 0xae,
"<unknown:af>",
"KeyMediaVolumeUp", // = 0xb0,
"<unknown:b1>",
"KeyMediaWww", // = 0xb2,
"<unknown:b3>",
"<unknown:b4>",
"KeyKeypadDivide", // = 0xb5,
"<unknown:b6>",
"<unknown:b7>",
"KeyRightAlt", // = 0xb8,
"<unknown:b9>",
"<unknown:ba>",
"<unknown:bb>",
"<unknown:bc>",
"<unknown:bd>",
"<unknown:be>",
"<unknown:bf>",
"<unknown:c0>",
"<unknown:c1>",
"<unknown:c2>",
"<unknown:c3>",
"<unknown:c4>",
"<unknown:c5>",
"<unknown:c6>",
"KeyHome", // = 0xc7,
"KeyUp", // = 0xc8,
"KeyPageUp", // = 0xc9,
"<unknown:ca>",
"KeyLeft", // = 0xcb,
"<unknown:cc>",
"KeyRight", // = 0xcd,
"<unknown:ce>",
"KeyEnd", // = 0xcf,
"KeyDown", // = 0xd0,
"KeyPageDown", // = 0xd1,
"KeyInsert", // = 0xd2,
"KeyDelete", // = 0xd3,
"<unknown:d4>",
"<unknown:d5>",
"<unknown:d6>",
"<unknown:d7>",
"<unknown:d8>",
"<unknown:d9>",
"<unknown:da>",
"KeyLeftGui", // = 0xdb,
"KeyRightGui", // = 0xdc,
"KeyApps", // = 0xdd,
"KeyAcpiPower", // = 0xde,
"KeyAcpiSleep", // = 0xdf,
"<unknown:e0>",
"<unknown:e1>",
"<unknown:e2>",
"KeyAcpiWake", // = 0xe3,
"<unknown:e4>",
"KeyMediaWwwSearch", // = 0xe5,
"KeyMediaWwwFavorites", // = 0xe6,
"KeyMediaWwwRefresh", // = 0xe7,
"KeyMediaWwwStop", // = 0xe8,
"KeyMediaWwwForward", // = 0xe9,
"KeyMediaWwwBack", // = 0xea,
"KeyMediaMyComputer", // = 0xeb,
"KeyMediaEmail", // = 0xec,
"KeyMediaSelect", // = 0xed,
"<unknown:ee>",
"<unknown:ef>",
"<unknown:f0>",
"<unknown:f1>",
"<unknown:f2>",
"<unknown:f3>",
"<unknown:f4>",
"<unknown:f5>",
"<unknown:f6>",
"<unknown:f7>",
"<unknown:f8>",
"<unknown:f9>",
"<unknown:fa>",
"<unknown:fb>",
"<unknown:fc>",
"<unknown:fd>",
"KeyPrint", // = 0xfe,
"KeyPause", // = 0xff,
};
void keyboardIsr() {
keyboard.Isr();
}
Keyboard::Keyboard() {
_go32_dpmi_get_protected_mode_interrupt_vector(9, &_oldIsr);
_newIsr.pm_offset = (int)keyboardIsr;
_newIsr.pm_selector = _go32_my_cs();
_go32_dpmi_allocate_iret_wrapper(&_newIsr);
_go32_dpmi_set_protected_mode_interrupt_vector(9, &_newIsr);
}
Keyboard::~Keyboard() {
_go32_dpmi_set_protected_mode_interrupt_vector(9, &_oldIsr);
_go32_dpmi_free_iret_wrapper(&_newIsr);
}
Keyboard::KeyHandleFunction Keyboard::setKeyUpHandler(KeyHandleFunction handler) {
auto oldHandler = _keyUpHandler;
_keyUpHandler = handler;
return oldHandler;
}
Keyboard::KeyHandleFunction Keyboard::setKeyDownHandler(KeyHandleFunction handler) {
auto oldHandler = _keyDownHandler;
_keyDownHandler = handler;
return oldHandler;
}
Keyboard::KeyHandleFunction Keyboard::setKeyRepeatHandler(KeyHandleFunction handler) {
auto oldHandler = _keyRepeatHandler;
_keyRepeatHandler = handler;
return oldHandler;
}
const char *Keyboard::getKeyName(unsigned char keyCode) {
return keyNames[keyCode];
}
void Keyboard::Isr() {
auto code = inb(KEYB_DATA);
switch (_state) {
case Start:
if (code == 0xe0) {
_state = Extended;
break;
}
if (code == 0xe1) {
_state = PauseBegin;
break;
}
if (code & 0x80) {
_state = Start;
keyUp(code & 0x7f);
} else {
_state = Start;
keyDown(code & 0x7f);
}
break;
case Extended:
if (code == 0x2a) {
_state = PrintPressed1;
break;
}
if (code == 0xb7) {
_state = PrintReleased1;
break;
}
if (code & 0x80) {
_state = Start;
keyUp(0x80 + (code & 0x7f));
} else {
_state = Start;
keyDown(0x80 + code);
}
break;
case PauseBegin:
if (code == 0x1d) {
_state = PausePressed1;
break;
}
if (code == 0x9d) {
_state = PauseReleased1;
break;
}
_state = Start;
break;
case PausePressed1:
if (code == 45) {
keyDown(KeyPause);
}
_state = Start;
break;
case PauseReleased1:
if (code == 0xc5) {
keyUp(KeyPause);
}
_state = Start;
break;
case PrintPressed1:
if (code == 0xe0) {
_state = PrintPressed2;
break;
}
_state = Start;
break;
case PrintPressed2:
if (code == 0x37) {
keyDown(KeyPrint);
}
_state = Start;
break;
case PrintReleased1:
if (code == 0xe0) {
_state = PrintReleased2;
break;
}
_state = Start;
break;
case PrintReleased2:
if (code == 0xaa) {
keyUp(KeyPrint);
}
_state = Start;
break;
}
Pic::clearInterrupt();
}
void Keyboard::keyDown(unsigned char c) {
if (keyState[c]) {
if (_keyRepeatHandler) _keyRepeatHandler(c);
} else {
keyState[c] = true;
if (_keyDownHandler) _keyDownHandler(c);
}
}
void Keyboard::keyUp(unsigned char c) {
keyState[c] = false;
if (_keyUpHandler) _keyUpHandler(c);
}

182
system/dos/Keyboard.h Normal file
View File

@ -0,0 +1,182 @@
#ifndef GAME_KEYBOARD_H
#define GAME_KEYBOARD_H
#ifdef BUILD_SDL
#else
#include <dpmi.h>
#endif
#define KEY_MAX 256
enum {
KeyEscape = 0x01,
Key1 = 0x02,
Key2 = 0x03,
Key3 = 0x04,
Key4 = 0x05,
Key5 = 0x06,
Key6 = 0x07,
Key7 = 0x08,
Key8 = 0x09,
Key9 = 0x0a,
Key0 = 0x0b,
KeyMinus = 0x0c,
KeyEqual = 0x0d,
KeyBackspace = 0x0e,
KeyTab = 0x0f,
KeyQ = 0x10,
KeyW = 0x11,
KeyE = 0x12,
KeyR = 0x13,
KeyT = 0x14,
KeyY = 0x15,
KeyU = 0x16,
KeyI = 0x17,
KeyO = 0x18,
KeyP = 0x19,
KeyBracketLeft = 0x1a,
KeyBracketRight = 0x1b,
KeyEnter = 0x1c,
KeyLeftCtrl = 0x1d,
KeyA = 0x1e,
KeyS = 0x1f,
KeyD = 0x20,
KeyF = 0x21,
KeyG = 0x22,
KeyH = 0x23,
KeyJ = 0x24,
KeyK = 0x25,
KeyL = 0x26,
KeySemicolon = 0x27,
KeyApostrophe = 0x28,
KeyBacktick = 0x29,
KeyLeftShift = 0x2a,
KeyBackslash = 0x2b,
KeyZ = 0x2c,
KeyX = 0x2d,
KeyC = 0x2e,
KeyV = 0x2f,
KeyB = 0x30,
KeyN = 0x31,
KeyM = 0x32,
KeyComma = 0x33,
KeyPeriod = 0x34,
KeySlash = 0x35,
KeyRightShift = 0x36,
KeyKeypadMultiply = 0x37,
KeyLeftAlt = 0x38,
KeySpace = 0x39,
KeyCapsLock = 0x3a,
KeyF1 = 0x3b,
KeyF2 = 0x3c,
KeyF3 = 0x3d,
KeyF4 = 0x3e,
KeyF5 = 0x3f,
KeyF6 = 0x40,
KeyF7 = 0x41,
KeyF8 = 0x42,
KeyF9 = 0x43,
KeyF10 = 0x44,
KeyNumLock = 0x45,
KeyScrollLock = 0x46,
KeyKeypad7 = 0x47,
KeyKeypad8 = 0x48,
KeyKeypad9 = 0x49,
KeyKeypadMinus = 0x4a,
KeyKeypad4 = 0x4b,
KeyKeypad5 = 0x4c,
KeyKeypad6 = 0x4d,
KeyKeypadPlus = 0x4e,
KeyKeypad1 = 0x4f,
KeyKeypad2 = 0x50,
KeyKeypad3 = 0x51,
KeyKeypad0 = 0x52,
KeyKeypadPeriod = 0x53,
KeyF11 = 0x57,
KeyF12 = 0x58,
KeyMediaPrev = 0x90,
KeyMediaNext = 0x99,
KeyKeypadEnter = 0x9c,
KeyRightControl = 0x9d,
KeyMediaMute = 0xa0,
KeyMediaCalculator = 0xa1,
KeyMediaPlay = 0xa2,
KeyMediaStop = 0xa4,
KeyMediaVolumeDown = 0xae,
KeyMediaVolumeUp = 0xb0,
KeyMediaWww = 0xb2,
KeyKeypadDivide = 0xb5,
KeyRightAlt = 0xb8,
KeyHome = 0xc7,
KeyUp = 0xc8,
KeyPageUp = 0xc9,
KeyLeft = 0xcb,
KeyRight = 0xcd,
KeyEnd = 0xcf,
KeyDown = 0xd0,
KeyPageDown = 0xd1,
KeyInsert = 0xd2,
KeyDelete = 0xd3,
KeyLeftGui = 0xdb,
KeyRightGui = 0xdc,
KeyApps = 0xdd,
KeyAcpiPower = 0xde,
KeyAcpiSleep = 0xdf,
KeyAcpiWake = 0xe3,
KeyMediaWwwSearch = 0xe5,
KeyMediaWwwFavorites = 0xe6,
KeyMediaWwwRefresh = 0xe7,
KeyMediaWwwStop = 0xe8,
KeyMediaWwwForward = 0xe9,
KeyMediaWwwBack = 0xea,
KeyMediaMyComputer = 0xeb,
KeyMediaEmail = 0xec,
KeyMediaSelect = 0xed,
KeyPrint = 0xfe,
KeyPause = 0xff,
};
class Keyboard {
public:
using KeyHandleFunction = void (*)(unsigned char);
Keyboard();
~Keyboard();
KeyHandleFunction setKeyUpHandler(KeyHandleFunction handler);
KeyHandleFunction setKeyDownHandler(KeyHandleFunction handler);
KeyHandleFunction setKeyRepeatHandler(KeyHandleFunction handler);
bool keyState[KEY_MAX]{};
const char *getKeyName(unsigned char keyCode);
private:
friend void keyboardIsr();
void Isr();
enum State {
Start,
Extended,
PauseBegin,
PausePressed1,
PauseReleased1,
PrintPressed1,
PrintPressed2,
PrintReleased1,
PrintReleased2,
};
State _state;
KeyHandleFunction _keyDownHandler{nullptr}, _keyUpHandler{nullptr}, _keyRepeatHandler{nullptr};
void keyDown(unsigned char c);
void keyUp(unsigned char c);
#ifdef BUILD_SDL
#else
_go32_dpmi_seginfo _oldIsr, _newIsr;
#endif
};
extern Keyboard keyboard;
#endif //GAME_KEYBOARD_H

16
system/dos/Opl.cpp Normal file
View File

@ -0,0 +1,16 @@
#include "../Opl.h"
#include "../../util/Asm.h"
#define OPL_REG 0x388
namespace Opl {
void write(uint16_t reg, uint8_t data) {
if (reg >= 0x100) {
outb(OPL_REG + 2, reg & 0xff);
outb(OPL_REG + 3, data);
} else {
outb(OPL_REG, reg);
outb(OPL_REG + 1, data);
}
}
}

View File

@ -0,0 +1,47 @@
#include "../SoundBlaster.h"
#include <go32.h>
SoundBlaster soundblaster;
void soundblasterIsr() {
soundblaster.onInterrupt();
}
SoundBlaster::SoundBlaster() {
// Allocate twice the amount needed and select one half depending on whether it crosses a 64k boundary
_dmaBuffer.size = (SB_DMA_BUFFER_SIZE + 15) >> (4-1);
_go32_dpmi_allocate_dos_memory(&_dmaBuffer);
auto physFirst = (_dmaBuffer.rm_segment << 4) + _dmaBuffer.rm_offset;
auto physSecond = physFirst + SB_DMA_BUFFER_SIZE;
auto pageFirst = physFirst >> 16;
auto pageSecond = physSecond >> 16;
if (pageFirst == pageSecond) {
// Use first half
_buffers = reinterpret_cast<decltype(_buffers)>(physFirst);
_dmaPage = pageFirst;
_dmaOffset = physFirst & 0xFFFF;
} else {
// Use second half
_buffers = reinterpret_cast<decltype(_buffers)>(physSecond);
_dmaPage = pageSecond;
_dmaOffset = physSecond & 0xFFFF;
}
}
SoundBlaster::~SoundBlaster() {
_go32_dpmi_free_dos_memory(&_dmaBuffer);
}
void SoundBlaster::onInterrupt() {
// No data available - should not happen, but let's force generate a block
if (_nextBufferReadIndex == _nextBufferWriteIndex) {
generateSamples();
}
dosmemput(_buffers, SB_BUFFER_SIZE, (_dmaBuffer.rm_segment << 4) + _nextBufferReadIndex * SB_BUFFER_SIZE);
_nextBufferReadIndex = (_nextBufferReadIndex + 1) & 3;
}

97
system/dos/Timer.cpp Normal file
View File

@ -0,0 +1,97 @@
#include <go32.h>
#include "../Timer.h"
#include "../Pic.h"
#include "../../util/Asm.h"
#define PIT_FREQ 1193181
#define PIT_MODE_CH0 0x00
#define PIT_MODE_CH1 0x40
#define PIT_MODE_CH2 0x80
#define PIT_MODE_ACCESS_LO 0x10
#define PIT_MODE_ACCESS_HI 0x20
#define PIT_MODE_ACCESS_LOHI 0x30
#define PIT_MODE_M0 0x00
#define PIT_MODE_M1 0x02
#define PIT_MODE_M2 0x04
#define PIT_MODE_M3 0x06
#define PIT_MODE_M4 0x08
#define PIT_MODE_M5 0x0a
#define PIT_MODE_BIN 0x00
#define PIT_MODE_BCD 0x01
#define PIT_REG_CH0 0x40
#define PIT_REG_CH1 0x41
#define PIT_REG_CH2 0x42
#define PIT_REG_MODE 0x43
Timer timer;
void timerISR() {
timer.update();
}
Timer::Timer() {
_go32_dpmi_get_protected_mode_interrupt_vector(8, &_oldIsr);
_newIsr.pm_offset = reinterpret_cast<int>(timerISR);
_newIsr.pm_selector = _go32_my_cs();
_go32_dpmi_allocate_iret_wrapper(&_newIsr);
_go32_dpmi_set_protected_mode_interrupt_vector(8, &_newIsr);
}
Timer::~Timer() {
setDivider(65535);
_go32_dpmi_set_protected_mode_interrupt_vector(8, &_oldIsr);
_go32_dpmi_free_iret_wrapper(&_newIsr);
}
void Timer::update() {
_ticks++;
if (_callback) _callback();
_elapsed += _div;
if (_elapsed >= 65535) {
_elapsed -= 65535;
// TODO call old isr
// Documentation is kind of nonexistant so we will not call the handler for now
Pic::clearInterrupt();
} else {
Pic::clearInterrupt();
}
}
uint32_t Timer::getTicks() const {
return _ticks;
}
uint16_t Timer::getFrequency() const {
return _freq;
}
void Timer::setFrequency(uint16_t freq) {
_freq = freq;
setDivider(PIT_FREQ / freq);
}
void Timer::setDivider(uint16_t div) {
auto wasEnabled = interruptsEnabled();
disableInterrupts();
_div = div;
// Update PIT frequency
outb(PIT_REG_MODE, PIT_MODE_CH0|PIT_MODE_ACCESS_LOHI|PIT_MODE_M2|PIT_MODE_BIN);
outb(PIT_REG_CH0, _div & 0xff);
outb(PIT_REG_CH0, (_div >> 8) & 0xff);
if (wasEnabled) enableInterrupts();
}
Timer::Callback Timer::setCallback(Timer::Callback callback) {
auto oldCallback = _callback;
_callback = callback;
return oldCallback;
}

135
system/dos/Video.cpp Normal file
View File

@ -0,0 +1,135 @@
#include <dos.h>
#include <sys/nearptr.h>
#include "../Video.h"
#include "../../util/Asm.h"
#include "../../util/Log.h"
#define VGA_DAC_ADDR_RD 0x3c7
#define VGA_DAC_ADDR_WR 0x3c8
#define VGA_DAC_DATA 0x3c9
#define VGA_EXT_INPUT_STATUS 0x3da
#define VGA_EXT_INPUT_STATUS_VRETRACE 8
Video video;
Video::Video() {
__djgpp_nearptr_enable();
// Store current video mode
_oldMode = GetMode();
// Store current palette
for (auto i = 0; i < 256; i++) {
GetPaletteEntry(i, &_oldPalette[i]);
}
}
Video::~Video() {
Exit();
}
void Video::Enter() {
SetMode(0x13);
}
void Video::Exit() {
// Restore old palette
for (auto i = 0; i < 256; i++) {
SetPaletteEntry(i, &_oldPalette[i]);
}
// Restore old video mode
SetMode(_oldMode);
}
void Video::SetMode(uint8_t mode) {
union REGS regs;
regs.h.ah = 0x00;
regs.h.al = mode;
int86(0x10, &regs, &regs);
//__djgpp_nearptr_enable();
switch (mode) {
default:
case 0x13:
_fb = (uint8_t *)(0xA0000 + __djgpp_conventional_base);
}
}
uint8_t Video::GetMode() {
union REGS regs;
regs.h.ah = 0x0f;
regs.h.al = 0;
int86(0x10, &regs, &regs);
return regs.h.al;
}
void Video::SetPaletteEntry(uint8_t index, PaletteEntry entry) {
SetPaletteEntry(index, &entry);
}
Video::PaletteEntry Video::GetPaletteEntry(uint8_t index) {
PaletteEntry entry;
GetPaletteEntry(index, &entry);
return entry;
}
void Video::SetPaletteEntry(uint8_t index, PaletteEntry *entry) {
outb(VGA_DAC_ADDR_WR, index);
outb(VGA_DAC_DATA, entry->r >> 2);
outb(VGA_DAC_DATA, entry->g >> 2);
outb(VGA_DAC_DATA, entry->b >> 2);
}
void Video::GetPaletteEntry(uint8_t index, PaletteEntry *entry) {
outb(VGA_DAC_ADDR_RD, index);
entry->r = inb(VGA_DAC_DATA);
entry->g = inb(VGA_DAC_DATA);
entry->b = inb(VGA_DAC_DATA);
}
void *Video::GetFB() {
return _renderBuffer;
}
void Video::WaitForVerticalSync() {
auto state = inb(VGA_EXT_INPUT_STATUS) & VGA_EXT_INPUT_STATUS_VRETRACE;
if (!state) {
// We started before a retrace, so wait until it begins
while (!(inb(VGA_EXT_INPUT_STATUS) & VGA_EXT_INPUT_STATUS_VRETRACE)) {}
}
// Wait until it ends
while (inb(VGA_EXT_INPUT_STATUS) & VGA_EXT_INPUT_STATUS_VRETRACE);
}
void Video::UpdateRect(const Rect &rect) {
// Merge rect if it overlaps with an existing rect
for (auto i = 0; i < _updateRectIndex; i++) {
auto &r = _updatedRects[i];
if (r.overlaps(rect)) {
r += rect;
return;
}
}
// Add a new rect to the list
_updatedRects[_updateRectIndex++] = rect;
}
void Video::Flip() {
for (auto i = 0; i <= MAX_UPDATE_RECT_INDEX; i++) {
auto &rect = _updatedRects[i];
for (auto y = rect.y1; y <= rect.y2; y++) {
auto yOffset = y * SCREEN_WIDTH;
for (auto x = rect.x1; x <= rect.x2; x++) {
_fb[yOffset + x] = _renderBuffer[yOffset + x];
}
}
}
_updateRectIndex = 0;
}

12
system/dos/init.cpp Normal file
View File

@ -0,0 +1,12 @@
#include "../init.h"
namespace System {
void init() {
}
void terminate() {
}
}

9
system/init.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef GAME_INIT_H
#define GAME_INIT_H
namespace System {
void init();
void terminate();
}
#endif //GAME_INIT_H

25
system/sdl/Events.cpp Normal file
View File

@ -0,0 +1,25 @@
#include <SDL.h>
#include "Events.h"
#include "Keyboard.h"
Events events;
void Events::poll() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
// TODO handle events
switch (event.type) {
case SDL_QUIT:
// TODO
exit(0);
break;
case SDL_KEYDOWN:
keyboard.keyDown(event.key.keysym.sym);
break;
case SDL_KEYUP:
keyboard.keyUp(event.key.keysym.sym);
break;
}
}
}

14
system/sdl/Events.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef GAME_EVENTS_H
#define GAME_EVENTS_H
class Events {
public:
Events() = default;
~Events() = default;
void poll();
};
extern Events events;
#endif //GAME_EVENTS_H

821
system/sdl/Keyboard.cpp Normal file
View File

@ -0,0 +1,821 @@
#include <SDL.h>
#include "../Keyboard.h"
Keyboard keyboard;
// TODO map unknown key codes
uint8_t mapSdlKeycode(SDL_Keycode keyCode) {
switch (keyCode) {
case SDLK_UNKNOWN:
return KeyUnknown;
case SDLK_RETURN:
return KeyUnknown;
case SDLK_ESCAPE:
return KeyEscape;
case SDLK_BACKSPACE:
return KeyBackspace;
case SDLK_TAB:
return KeyTab;
case SDLK_SPACE:
return KeySpace;
case SDLK_EXCLAIM:
return KeyUnknown;
case SDLK_QUOTEDBL:
return KeyUnknown;
case SDLK_HASH:
return KeyUnknown;
case SDLK_PERCENT:
return KeyUnknown;
case SDLK_DOLLAR:
return KeyUnknown;
case SDLK_AMPERSAND:
return KeyUnknown;
case SDLK_QUOTE:
return KeyUnknown;
case SDLK_LEFTPAREN:
return KeyUnknown;
case SDLK_RIGHTPAREN:
return KeyUnknown;
case SDLK_ASTERISK:
return KeyUnknown;
case SDLK_PLUS:
return KeyUnknown;
case SDLK_COMMA:
return KeyUnknown;
case SDLK_MINUS:
return KeyUnknown;
case SDLK_PERIOD:
return KeyUnknown;
case SDLK_SLASH:
return KeyUnknown;
case SDLK_0:
return Key0;
case SDLK_1:
return Key1;
case SDLK_2:
return Key2;
case SDLK_3:
return Key3;
case SDLK_4:
return Key4;
case SDLK_5:
return Key5;
case SDLK_6:
return Key6;
case SDLK_7:
return Key7;
case SDLK_8:
return Key8;
case SDLK_9:
return Key9;
case SDLK_COLON:
return KeyUnknown;
case SDLK_SEMICOLON:
return KeyUnknown;
case SDLK_LESS:
return KeyUnknown;
case SDLK_EQUALS:
return KeyUnknown;
case SDLK_GREATER:
return KeyUnknown;
case SDLK_QUESTION:
return KeyUnknown;
case SDLK_AT:
return KeyUnknown;
case SDLK_LEFTBRACKET:
return KeyUnknown;
case SDLK_BACKSLASH:
return KeyUnknown;
case SDLK_RIGHTBRACKET:
return KeyUnknown;
case SDLK_CARET:
return KeyUnknown;
case SDLK_UNDERSCORE:
return KeyUnknown;
case SDLK_BACKQUOTE:
return KeyUnknown;
case SDLK_a:
return KeyA;
case SDLK_b:
return KeyB;
case SDLK_c:
return KeyC;
case SDLK_d:
return KeyD;
case SDLK_e:
return KeyE;
case SDLK_f:
return KeyF;
case SDLK_g:
return KeyG;
case SDLK_h:
return KeyG;
case SDLK_i:
return KeyI;
case SDLK_j:
return KeyJ;
case SDLK_k:
return KeyK;
case SDLK_l:
return KeyL;
case SDLK_m:
return KeyM;
case SDLK_n:
return KeyN;
case SDLK_o:
return KeyO;
case SDLK_p:
return KeyP;
case SDLK_q:
return KeyQ;
case SDLK_r:
return KeyR;
case SDLK_s:
return KeyS;
case SDLK_t:
return KeyT;
case SDLK_u:
return KeyU;
case SDLK_v:
return KeyV;
case SDLK_w:
return KeyW;
case SDLK_x:
return KeyX;
case SDLK_y:
return KeyY;
case SDLK_z:
return KeyZ;
case SDLK_CAPSLOCK:
return KeyCapsLock;
case SDLK_F1:
return KeyF1;
case SDLK_F2:
return KeyF2;
case SDLK_F3:
return KeyF3;
case SDLK_F4:
return KeyF4;
case SDLK_F5:
return KeyF5;
case SDLK_F6:
return KeyF6;
case SDLK_F7:
return KeyF7;
case SDLK_F8:
return KeyF8;
case SDLK_F9:
return KeyF9;
case SDLK_F10:
return KeyF10;
case SDLK_F11:
return KeyF11;
case SDLK_F12:
return KeyF12;
case SDLK_PRINTSCREEN:
return KeyPrint;
case SDLK_SCROLLLOCK:
return KeyScrollLock;
case SDLK_PAUSE:
return KeyPause;
case SDLK_INSERT:
return KeyInsert;
case SDLK_HOME:
return KeyHome;
case SDLK_PAGEUP:
return KeyPageUp;
case SDLK_DELETE:
return KeyDelete;
case SDLK_END:
return KeyEnd;
case SDLK_PAGEDOWN:
return KeyPageDown;
case SDLK_RIGHT:
return KeyRight;
case SDLK_LEFT:
return KeyLeft;
case SDLK_DOWN:
return KeyDown;
case SDLK_UP:
return KeyUp;
case SDLK_NUMLOCKCLEAR:
return KeyUnknown;
case SDLK_KP_DIVIDE:
return KeyUnknown;
case SDLK_KP_MULTIPLY:
return KeyUnknown;
case SDLK_KP_MINUS:
return KeyUnknown;
case SDLK_KP_PLUS:
return KeyUnknown;
case SDLK_KP_ENTER:
return KeyUnknown;
case SDLK_KP_1:
return KeyUnknown;
case SDLK_KP_2:
return KeyUnknown;
case SDLK_KP_3:
return KeyUnknown;
case SDLK_KP_4:
return KeyUnknown;
case SDLK_KP_5:
return KeyUnknown;
case SDLK_KP_6:
return KeyUnknown;
case SDLK_KP_7:
return KeyUnknown;
case SDLK_KP_8:
return KeyUnknown;
case SDLK_KP_9:
return KeyUnknown;
case SDLK_KP_0:
return KeyUnknown;
case SDLK_KP_PERIOD:
return KeyUnknown;
case SDLK_APPLICATION:
return KeyUnknown;
case SDLK_POWER:
return KeyUnknown;
case SDLK_KP_EQUALS:
return KeyUnknown;
case SDLK_F13:
return KeyUnknown;
case SDLK_F14:
return KeyUnknown;
case SDLK_F15:
return KeyUnknown;
case SDLK_F16:
return KeyUnknown;
case SDLK_F17:
return KeyUnknown;
case SDLK_F18:
return KeyUnknown;
case SDLK_F19:
return KeyUnknown;
case SDLK_F20:
return KeyUnknown;
case SDLK_F21:
return KeyUnknown;
case SDLK_F22:
return KeyUnknown;
case SDLK_F23:
return KeyUnknown;
case SDLK_F24:
return KeyUnknown;
case SDLK_EXECUTE:
return KeyUnknown;
case SDLK_HELP:
return KeyUnknown;
case SDLK_MENU:
return KeyUnknown;
case SDLK_SELECT:
return KeyUnknown;
case SDLK_STOP:
return KeyUnknown;
case SDLK_AGAIN:
return KeyUnknown;
case SDLK_UNDO:
return KeyUnknown;
case SDLK_CUT:
return KeyUnknown;
case SDLK_COPY:
return KeyUnknown;
case SDLK_PASTE:
return KeyUnknown;
case SDLK_FIND:
return KeyUnknown;
case SDLK_MUTE:
return KeyUnknown;
case SDLK_VOLUMEUP:
return KeyUnknown;
case SDLK_VOLUMEDOWN:
return KeyUnknown;
case SDLK_KP_COMMA:
return KeyUnknown;
case SDLK_KP_EQUALSAS400:
return KeyUnknown;
case SDLK_ALTERASE:
return KeyUnknown;
case SDLK_SYSREQ:
return KeyUnknown;
case SDLK_CANCEL:
return KeyUnknown;
case SDLK_CLEAR:
return KeyUnknown;
case SDLK_PRIOR:
return KeyUnknown;
case SDLK_RETURN2:
return KeyUnknown;
case SDLK_SEPARATOR:
return KeyUnknown;
case SDLK_OUT:
return KeyUnknown;
case SDLK_OPER:
return KeyUnknown;
case SDLK_CLEARAGAIN:
return KeyUnknown;
case SDLK_CRSEL:
return KeyUnknown;
case SDLK_EXSEL:
return KeyUnknown;
case SDLK_KP_00:
return KeyUnknown;
case SDLK_KP_000:
return KeyUnknown;
case SDLK_THOUSANDSSEPARATOR:
return KeyUnknown;
case SDLK_DECIMALSEPARATOR:
return KeyUnknown;
case SDLK_CURRENCYUNIT:
return KeyUnknown;
case SDLK_CURRENCYSUBUNIT:
return KeyUnknown;
case SDLK_KP_LEFTPAREN:
return KeyUnknown;
case SDLK_KP_RIGHTPAREN:
return KeyUnknown;
case SDLK_KP_LEFTBRACE:
return KeyUnknown;
case SDLK_KP_RIGHTBRACE:
return KeyUnknown;
case SDLK_KP_TAB:
return KeyUnknown;
case SDLK_KP_BACKSPACE:
return KeyUnknown;
case SDLK_KP_A:
return KeyUnknown;
case SDLK_KP_B:
return KeyUnknown;
case SDLK_KP_C:
return KeyUnknown;
case SDLK_KP_D:
return KeyUnknown;
case SDLK_KP_E:
return KeyUnknown;
case SDLK_KP_F:
return KeyUnknown;
case SDLK_KP_XOR:
return KeyUnknown;
case SDLK_KP_POWER:
return KeyUnknown;
case SDLK_KP_PERCENT:
return KeyUnknown;
case SDLK_KP_LESS:
return KeyUnknown;
case SDLK_KP_GREATER:
return KeyUnknown;
case SDLK_KP_AMPERSAND:
return KeyUnknown;
case SDLK_KP_DBLAMPERSAND:
return KeyUnknown;
case SDLK_KP_VERTICALBAR:
return KeyUnknown;
case SDLK_KP_DBLVERTICALBAR:
return KeyUnknown;
case SDLK_KP_COLON:
return KeyUnknown;
case SDLK_KP_HASH:
return KeyUnknown;
case SDLK_KP_SPACE:
return KeyUnknown;
case SDLK_KP_AT:
return KeyUnknown;
case SDLK_KP_EXCLAM:
return KeyUnknown;
case SDLK_KP_MEMSTORE:
return KeyUnknown;
case SDLK_KP_MEMRECALL:
return KeyUnknown;
case SDLK_KP_MEMCLEAR:
return KeyUnknown;
case SDLK_KP_MEMADD:
return KeyUnknown;
case SDLK_KP_MEMSUBTRACT:
return KeyUnknown;
case SDLK_KP_MEMMULTIPLY:
return KeyUnknown;
case SDLK_KP_MEMDIVIDE:
return KeyUnknown;
case SDLK_KP_PLUSMINUS:
return KeyUnknown;
case SDLK_KP_CLEAR:
return KeyUnknown;
case SDLK_KP_CLEARENTRY:
return KeyUnknown;
case SDLK_KP_BINARY:
return KeyUnknown;
case SDLK_KP_OCTAL:
return KeyUnknown;
case SDLK_KP_DECIMAL:
return KeyUnknown;
case SDLK_KP_HEXADECIMAL:
return KeyUnknown;
case SDLK_LCTRL:
return KeyLeftCtrl;
case SDLK_LSHIFT:
return KeyLeftShift;
case SDLK_LALT:
return KeyLeftAlt;
case SDLK_LGUI:
return KeyUnknown;
case SDLK_RCTRL:
return KeyRightControl;
case SDLK_RSHIFT:
return KeyRightShift;
case SDLK_RALT:
return KeyRightAlt;
case SDLK_RGUI:
return KeyUnknown;
case SDLK_MODE:
return KeyUnknown;
case SDLK_AUDIONEXT:
return KeyUnknown;
case SDLK_AUDIOPREV:
return KeyUnknown;
case SDLK_AUDIOSTOP:
return KeyUnknown;
case SDLK_AUDIOPLAY:
return KeyUnknown;
case SDLK_AUDIOMUTE:
return KeyUnknown;
case SDLK_MEDIASELECT:
return KeyUnknown;
case SDLK_WWW:
return KeyUnknown;
case SDLK_MAIL:
return KeyUnknown;
case SDLK_CALCULATOR:
return KeyUnknown;
case SDLK_COMPUTER:
return KeyUnknown;
case SDLK_AC_SEARCH:
return KeyUnknown;
case SDLK_AC_HOME:
return KeyUnknown;
case SDLK_AC_BACK:
return KeyUnknown;
case SDLK_AC_FORWARD:
return KeyUnknown;
case SDLK_AC_STOP:
return KeyUnknown;
case SDLK_AC_REFRESH:
return KeyUnknown;
case SDLK_AC_BOOKMARKS:
return KeyUnknown;
case SDLK_BRIGHTNESSDOWN:
return KeyUnknown;
case SDLK_BRIGHTNESSUP:
return KeyUnknown;
case SDLK_DISPLAYSWITCH:
return KeyUnknown;
case SDLK_KBDILLUMTOGGLE:
return KeyUnknown;
case SDLK_KBDILLUMDOWN:
return KeyUnknown;
case SDLK_KBDILLUMUP:
return KeyUnknown;
case SDLK_EJECT:
return KeyUnknown;
case SDLK_SLEEP:
return KeyUnknown;
case SDLK_APP1:
return KeyUnknown;
case SDLK_APP2:
return KeyUnknown;
case SDLK_AUDIOREWIND:
return KeyUnknown;
case SDLK_AUDIOFASTFORWARD:
return KeyUnknown;
default: ;
}
return KeyUnknown;
}
static const char *keyNames[] = {
"<unknown:00>",
"KeyEscape", // = 0x01,
"Key1", // = 0x02,
"Key2", // = 0x03,
"Key3", // = 0x04,
"Key4", // = 0x05,
"Key5", // = 0x06,
"Key6", // = 0x07,
"Key7", // = 0x08,
"Key8", // = 0x09,
"Key9", // = 0x0a,
"Key0", // = 0x0b,
"KeyMinus", // = 0x0c,
"KeyEqual", // = 0x0d,
"KeyBackspace", // = 0x0e,
"KeyTab", // = 0x0f,
"KeyQ", // = 0x10,
"KeyW", // = 0x11,
"KeyE", // = 0x12,
"KeyR", // = 0x13,
"KeyT", // = 0x14,
"KeyY", // = 0x15,
"KeyU", // = 0x16,
"KeyI", // = 0x17,
"KeyO", // = 0x18,
"KeyP", // = 0x19,
"KeyBracketLeft", // = 0x1a,
"KeyBracketRight", // = 0x1b,
"KeyEnter", // = 0x1c,
"KeyLeftCtrl", // = 0x1d,
"KeyA", // = 0x1e,
"KeyS", // = 0x1f,
"KeyD", // = 0x20,
"KeyF", // = 0x21,
"KeyG", // = 0x22,
"KeyH", // = 0x23,
"KeyJ", // = 0x24,
"KeyK", // = 0x25,
"KeyL", // = 0x26,
"KeySemicolon", // = 0x27,
"KeyApostrophe", // = 0x28,
"KeyBacktick", // = 0x29,
"KeyLeftShift", // = 0x2a,
"KeyBackslash", // = 0x2b,
"KeyZ", // = 0x2c,
"KeyX", // = 0x2d,
"KeyC", // = 0x2e,
"KeyV", // = 0x2f,
"KeyB", // = 0x30,
"KeyN", // = 0x31,
"KeyM", // = 0x32,
"KeyComma", // = 0x33,
"KeyPeriod", // = 0x34,
"KeySlash", // = 0x35,
"KeyRightShift", // = 0x36,
"KeyKeypadMultiply", // = 0x37,
"KeyLeftAlt", // = 0x38,
"KeySpace", // = 0x39,
"KeyCapsLock", // = 0x3a,
"KeyF1", // = 0x3b,
"KeyF2", // = 0x3c,
"KeyF3", // = 0x3d,
"KeyF4", // = 0x3e,
"KeyF5", // = 0x3f,
"KeyF6", // = 0x40,
"KeyF7", // = 0x41,
"KeyF8", // = 0x42,
"KeyF9", // = 0x43,
"KeyF10", // = 0x44,
"KeyNumLock", // = 0x45,
"KeyScrollLock", // = 0x46,
"KeyKeypad7", // = 0x47,
"KeyKeypad8", // = 0x48,
"KeyKeypad9", // = 0x49,
"KeyKeypadMinus", // = 0x4a,
"KeyKeypad4", // = 0x4b,
"KeyKeypad5", // = 0x4c,
"KeyKeypad6", // = 0x4d,
"KeyKeypadPlus", // = 0x4e,
"KeyKeypad1", // = 0x4f,
"KeyKeypad2", // = 0x50,
"KeyKeypad3", // = 0x51,
"KeyKeypad0", // = 0x52,
"KeyKeypadPeriod", // = 0x53,
"<unknown:54>",
"<unknown:55>",
"<unknown:56>",
"KeyF11", // = 0x57,
"KeyF12", // = 0x58,
"<unknown:59>",
"<unknown:5a>",
"<unknown:5b>",
"<unknown:5c>",
"<unknown:5d>",
"<unknown:5e>",
"<unknown:5f>",
"<unknown:60>",
"<unknown:61>",
"<unknown:62>",
"<unknown:63>",
"<unknown:64>",
"<unknown:65>",
"<unknown:66>",
"<unknown:67>",
"<unknown:68>",
"<unknown:69>",
"<unknown:6a>",
"<unknown:6b>",
"<unknown:6c>",
"<unknown:6d>",
"<unknown:6e>",
"<unknown:6f>",
"<unknown:70>",
"<unknown:71>",
"<unknown:72>",
"<unknown:73>",
"<unknown:74>",
"<unknown:75>",
"<unknown:76>",
"<unknown:77>",
"<unknown:78>",
"<unknown:79>",
"<unknown:7a>",
"<unknown:7b>",
"<unknown:7c>",
"<unknown:7d>",
"<unknown:7e>",
"<unknown:7f>",
"<unknown:80>",
"<unknown:81>",
"<unknown:82>",
"<unknown:83>",
"<unknown:84>",
"<unknown:85>",
"<unknown:86>",
"<unknown:87>",
"<unknown:88>",
"<unknown:89>",
"<unknown:8a>",
"<unknown:8b>",
"<unknown:8c>",
"<unknown:8d>",
"<unknown:8e>",
"<unknown:8f>",
"KeyMediaPrev", // = 0x90,
"<unknown:91>",
"<unknown:92>",
"<unknown:93>",
"<unknown:94>",
"<unknown:95>",
"<unknown:96>",
"<unknown:97>",
"<unknown:98>",
"KeyMediaNext", // = 0x99,
"<unknown:9a>",
"<unknown:9b>",
"KeyKeypadEnter", // = 0x9c,
"KeyRightControl", // = 0x9d,
"<unknown:9e>",
"<unknown:9f>",
"KeyMediaMute", // = 0xa0,
"KeyMediaCalculator", // = 0xa1,
"KeyMediaPlay", // = 0xa2,
"<unknown:a3>",
"KeyMediaStop", // = 0xa4,
"<unknown:a5>",
"<unknown:a6>",
"<unknown:a7>",
"<unknown:a8>",
"<unknown:a9>",
"<unknown:aa>",
"<unknown:ab>",
"<unknown:ac>",
"<unknown:ad>",
"KeyMediaVolumeDown", // = 0xae,
"<unknown:af>",
"KeyMediaVolumeUp", // = 0xb0,
"<unknown:b1>",
"KeyMediaWww", // = 0xb2,
"<unknown:b3>",
"<unknown:b4>",
"KeyKeypadDivide", // = 0xb5,
"<unknown:b6>",
"<unknown:b7>",
"KeyRightAlt", // = 0xb8,
"<unknown:b9>",
"<unknown:ba>",
"<unknown:bb>",
"<unknown:bc>",
"<unknown:bd>",
"<unknown:be>",
"<unknown:bf>",
"<unknown:c0>",
"<unknown:c1>",
"<unknown:c2>",
"<unknown:c3>",
"<unknown:c4>",
"<unknown:c5>",
"<unknown:c6>",
"KeyHome", // = 0xc7,
"KeyUp", // = 0xc8,
"KeyPageUp", // = 0xc9,
"<unknown:ca>",
"KeyLeft", // = 0xcb,
"<unknown:cc>",
"KeyRight", // = 0xcd,
"<unknown:ce>",
"KeyEnd", // = 0xcf,
"KeyDown", // = 0xd0,
"KeyPageDown", // = 0xd1,
"KeyInsert", // = 0xd2,
"KeyDelete", // = 0xd3,
"<unknown:d4>",
"<unknown:d5>",
"<unknown:d6>",
"<unknown:d7>",
"<unknown:d8>",
"<unknown:d9>",
"<unknown:da>",
"KeyLeftGui", // = 0xdb,
"KeyRightGui", // = 0xdc,
"KeyApps", // = 0xdd,
"KeyAcpiPower", // = 0xde,
"KeyAcpiSleep", // = 0xdf,
"<unknown:e0>",
"<unknown:e1>",
"<unknown:e2>",
"KeyAcpiWake", // = 0xe3,
"<unknown:e4>",
"KeyMediaWwwSearch", // = 0xe5,
"KeyMediaWwwFavorites", // = 0xe6,
"KeyMediaWwwRefresh", // = 0xe7,
"KeyMediaWwwStop", // = 0xe8,
"KeyMediaWwwForward", // = 0xe9,
"KeyMediaWwwBack", // = 0xea,
"KeyMediaMyComputer", // = 0xeb,
"KeyMediaEmail", // = 0xec,
"KeyMediaSelect", // = 0xed,
"<unknown:ee>",
"<unknown:ef>",
"<unknown:f0>",
"<unknown:f1>",
"<unknown:f2>",
"<unknown:f3>",
"<unknown:f4>",
"<unknown:f5>",
"<unknown:f6>",
"<unknown:f7>",
"<unknown:f8>",
"<unknown:f9>",
"<unknown:fa>",
"<unknown:fb>",
"<unknown:fc>",
"<unknown:fd>",
"KeyPrint", // = 0xfe,
"KeyPause", // = 0xff,
};
void keyboardIsr() {
keyboard.Isr();
}
Keyboard::Keyboard() {
// TODO
}
Keyboard::~Keyboard() {
// TODO
}
Keyboard::KeyHandleFunction Keyboard::setKeyUpHandler(KeyHandleFunction handler) {
auto oldHandler = _keyUpHandler;
_keyUpHandler = handler;
return oldHandler;
}
Keyboard::KeyHandleFunction Keyboard::setKeyDownHandler(KeyHandleFunction handler) {
auto oldHandler = _keyDownHandler;
_keyDownHandler = handler;
return oldHandler;
}
Keyboard::KeyHandleFunction Keyboard::setKeyRepeatHandler(KeyHandleFunction handler) {
auto oldHandler = _keyRepeatHandler;
_keyRepeatHandler = handler;
return oldHandler;
}
const char *Keyboard::getKeyName(unsigned char keyCode) {
return keyNames[keyCode];
}
void Keyboard::Isr() {
// TODO
}
void Keyboard::keyDown(SDL_Keycode keyCode) {
auto c = mapSdlKeycode(keyCode);
if (keyState[c]) {
if (_keyRepeatHandler) _keyRepeatHandler(c);
} else {
keyState[c] = true;
if (_keyDownHandler) _keyDownHandler(c);
}
}
void Keyboard::keyUp(SDL_Keycode keyCode) {
auto c = mapSdlKeycode(keyCode);
keyState[c] = false;
if (_keyUpHandler) _keyUpHandler(c);
}

178
system/sdl/Keyboard.h Normal file
View File

@ -0,0 +1,178 @@
#ifndef GAME_KEYBOARD_H
#define GAME_KEYBOARD_H
#include <SDL.h>
#define KEY_MAX 256
enum {
KeyUnknown = 0x00,
KeyEscape = 0x01,
Key1 = 0x02,
Key2 = 0x03,
Key3 = 0x04,
Key4 = 0x05,
Key5 = 0x06,
Key6 = 0x07,
Key7 = 0x08,
Key8 = 0x09,
Key9 = 0x0a,
Key0 = 0x0b,
KeyMinus = 0x0c,
KeyEqual = 0x0d,
KeyBackspace = 0x0e,
KeyTab = 0x0f,
KeyQ = 0x10,
KeyW = 0x11,
KeyE = 0x12,
KeyR = 0x13,
KeyT = 0x14,
KeyY = 0x15,
KeyU = 0x16,
KeyI = 0x17,
KeyO = 0x18,
KeyP = 0x19,
KeyBracketLeft = 0x1a,
KeyBracketRight = 0x1b,
KeyEnter = 0x1c,
KeyLeftCtrl = 0x1d,
KeyA = 0x1e,
KeyS = 0x1f,
KeyD = 0x20,
KeyF = 0x21,
KeyG = 0x22,
KeyH = 0x23,
KeyJ = 0x24,
KeyK = 0x25,
KeyL = 0x26,
KeySemicolon = 0x27,
KeyApostrophe = 0x28,
KeyBacktick = 0x29,
KeyLeftShift = 0x2a,
KeyBackslash = 0x2b,
KeyZ = 0x2c,
KeyX = 0x2d,
KeyC = 0x2e,
KeyV = 0x2f,
KeyB = 0x30,
KeyN = 0x31,
KeyM = 0x32,
KeyComma = 0x33,
KeyPeriod = 0x34,
KeySlash = 0x35,
KeyRightShift = 0x36,
KeyKeypadMultiply = 0x37,
KeyLeftAlt = 0x38,
KeySpace = 0x39,
KeyCapsLock = 0x3a,
KeyF1 = 0x3b,
KeyF2 = 0x3c,
KeyF3 = 0x3d,
KeyF4 = 0x3e,
KeyF5 = 0x3f,
KeyF6 = 0x40,
KeyF7 = 0x41,
KeyF8 = 0x42,
KeyF9 = 0x43,
KeyF10 = 0x44,
KeyNumLock = 0x45,
KeyScrollLock = 0x46,
KeyKeypad7 = 0x47,
KeyKeypad8 = 0x48,
KeyKeypad9 = 0x49,
KeyKeypadMinus = 0x4a,
KeyKeypad4 = 0x4b,
KeyKeypad5 = 0x4c,
KeyKeypad6 = 0x4d,
KeyKeypadPlus = 0x4e,
KeyKeypad1 = 0x4f,
KeyKeypad2 = 0x50,
KeyKeypad3 = 0x51,
KeyKeypad0 = 0x52,
KeyKeypadPeriod = 0x53,
KeyF11 = 0x57,
KeyF12 = 0x58,
KeyMediaPrev = 0x90,
KeyMediaNext = 0x99,
KeyKeypadEnter = 0x9c,
KeyRightControl = 0x9d,
KeyMediaMute = 0xa0,
KeyMediaCalculator = 0xa1,
KeyMediaPlay = 0xa2,
KeyMediaStop = 0xa4,
KeyMediaVolumeDown = 0xae,
KeyMediaVolumeUp = 0xb0,
KeyMediaWww = 0xb2,
KeyKeypadDivide = 0xb5,
KeyRightAlt = 0xb8,
KeyHome = 0xc7,
KeyUp = 0xc8,
KeyPageUp = 0xc9,
KeyLeft = 0xcb,
KeyRight = 0xcd,
KeyEnd = 0xcf,
KeyDown = 0xd0,
KeyPageDown = 0xd1,
KeyInsert = 0xd2,
KeyDelete = 0xd3,
KeyLeftGui = 0xdb,
KeyRightGui = 0xdc,
KeyApps = 0xdd,
KeyAcpiPower = 0xde,
KeyAcpiSleep = 0xdf,
KeyAcpiWake = 0xe3,
KeyMediaWwwSearch = 0xe5,
KeyMediaWwwFavorites = 0xe6,
KeyMediaWwwRefresh = 0xe7,
KeyMediaWwwStop = 0xe8,
KeyMediaWwwForward = 0xe9,
KeyMediaWwwBack = 0xea,
KeyMediaMyComputer = 0xeb,
KeyMediaEmail = 0xec,
KeyMediaSelect = 0xed,
KeyPrint = 0xfe,
KeyPause = 0xff,
};
class Keyboard {
public:
using KeyHandleFunction = void (*)(unsigned char);
Keyboard();
~Keyboard();
KeyHandleFunction setKeyUpHandler(KeyHandleFunction handler);
KeyHandleFunction setKeyDownHandler(KeyHandleFunction handler);
KeyHandleFunction setKeyRepeatHandler(KeyHandleFunction handler);
bool keyState[KEY_MAX]{};
const char *getKeyName(unsigned char keyCode);
private:
friend class Events;
friend void keyboardIsr();
void Isr();
enum State {
Start,
Extended,
PauseBegin,
PausePressed1,
PauseReleased1,
PrintPressed1,
PrintPressed2,
PrintReleased1,
PrintReleased2,
};
State _state;
KeyHandleFunction _keyDownHandler{nullptr}, _keyUpHandler{nullptr}, _keyRepeatHandler{nullptr};
void keyDown(SDL_Keycode c);
void keyUp(SDL_Keycode c);
};
extern Keyboard keyboard;
#endif //GAME_KEYBOARD_H

7
system/sdl/Opl.cpp Normal file
View File

@ -0,0 +1,7 @@
#include "../Opl.h"
namespace Opl {
void write(uint16_t reg, uint8_t data) {
// TODO
}
}

View File

@ -0,0 +1,19 @@
#include "../SoundBlaster.h"
SoundBlaster soundblaster;
void soundblasterIsr() {
soundblaster.onInterrupt();
}
SoundBlaster::SoundBlaster() {
}
SoundBlaster::~SoundBlaster() {
}
void SoundBlaster::onInterrupt() {
}

51
system/sdl/Timer.cpp Normal file
View File

@ -0,0 +1,51 @@
#include <SDL.h>
#include "../Timer.h"
Timer timer;
SDL_TimerID timerId;
void timerISR() {
timer.update();
}
Timer::Timer() {
_freq = 50;
timerId = SDL_AddTimer(1000/_freq, [] (Uint32, void*) -> Uint32 {
timerISR();
return 1000 / timer.getFrequency();
}, nullptr);
}
Timer::~Timer() {
SDL_RemoveTimer(timerId);
}
void Timer::update() {
// TODO
_ticks++;
}
uint32_t Timer::getTicks() const {
return _ticks;
}
void Timer::setFrequency(uint16_t freq) {
_freq = freq;
// TODO
}
uint16_t Timer::getFrequency() const {
return _freq;
}
void Timer::setDivider(uint16_t div) {
// TODO
}
Timer::Callback Timer::setCallback(Timer::Callback callback) {
auto oldCallback = _callback;
_callback = callback;
return oldCallback;
}

124
system/sdl/Video.cpp Normal file
View File

@ -0,0 +1,124 @@
#include <iostream>
#include "../Video.h"
#include "Events.h"
#define WINDOW_WIDTH 1280
#define WINDOW_HEIGHT 800
Video video;
Video::Video() {
// TODO
}
Video::~Video() {
Exit();
}
inline void writePixel(SDL_Surface *surface, unsigned x, unsigned y, Uint8 r, Uint8 g, Uint8 b, Uint8 a) {
auto row = reinterpret_cast<Uint32*>(static_cast<Uint8*>(surface->pixels) + y * surface->pitch);
row[x] = SDL_MapRGBA(surface->format, r, g, b, a);
}
void Video::Enter() {
_window = SDL_CreateWindow("game", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1280, 800, SDL_WINDOW_SHOWN);
if (!_window) {
std::cerr << "Error creating SDL window: " << SDL_GetError() << std::endl;
abort();
}
_windowSurface = SDL_GetWindowSurface(_window);
_fb = SDL_CreateRGBSurfaceWithFormat(0, SCREEN_WIDTH, SCREEN_HEIGHT, 32, SDL_PIXELFORMAT_ARGB8888);
}
void Video::Exit() {
if (_fb) SDL_FreeSurface(_fb);
_fb = nullptr;
SDL_DestroyWindow(_window);
}
void Video::SetMode(uint8_t mode) {
// Ignored
}
uint8_t Video::GetMode() {
// Fixed
return 0x13;
}
void Video::SetPaletteEntry(uint8_t index, PaletteEntry entry) {
SetPaletteEntry(index, &entry);
}
Video::PaletteEntry Video::GetPaletteEntry(uint8_t index) {
PaletteEntry entry;
GetPaletteEntry(index, &entry);
return entry;
}
void Video::SetPaletteEntry(uint8_t index, PaletteEntry *entry) {
_palette[index] = *entry;
}
void Video::GetPaletteEntry(uint8_t index, PaletteEntry *entry) {
*entry = _palette[index];
}
void *Video::GetFB() {
return _renderBuffer;
}
void Video::WaitForVerticalSync() {
auto targetTime = _lastUpdate + std::chrono::microseconds(14286);
auto currentTime = std::chrono::steady_clock::now();
while (currentTime < targetTime) {
currentTime = std::chrono::steady_clock::now();
}
_lastUpdate = currentTime;
}
void Video::UpdateRect(const Rect &rect) {
// Merge rect if it overlaps with an existing rect
for (auto i = 0; i < _updateRectIndex; i++) {
auto &r = _updatedRects[i];
if (r.overlaps(rect)) {
r += rect;
return;
}
}
// Add a new rect to the list
_updatedRects[_updateRectIndex++] = rect;
}
void Video::Flip() {
SDL_LockSurface(_fb);
for (auto i = 0; i <= MAX_UPDATE_RECT_INDEX; i++) {
auto &rect = _updatedRects[i];
rect.clamp(0, 0, _fb->w, _fb->h);
for (auto y = rect.y1; y < rect.y2; y++) {
for (auto x = rect.x1; x < rect.x2; x++) {
auto &paletteEntry = _palette[_renderBuffer[y * SCREEN_WIDTH + x]];
writePixel(_fb, x, y, paletteEntry.r, paletteEntry.g, paletteEntry.b, 255);
}
}
}
SDL_UnlockSurface(_fb);
SDL_Rect srcRect{0, 0, SCREEN_WIDTH, SCREEN_HEIGHT};
SDL_Rect dstRect{0, 0, WINDOW_WIDTH, WINDOW_HEIGHT};
SDL_BlitScaled(_fb, &srcRect, _windowSurface, &dstRect);
SDL_UpdateWindowSurface(_window);
_updateRectIndex = 0;
events.poll();
}

21
system/sdl/init.cpp Normal file
View File

@ -0,0 +1,21 @@
#include "../init.h"
#include <iostream>
#include <SDL.h>
#include "../Video.h"
namespace System {
void init() {
if (SDL_Init( SDL_INIT_VIDEO | SDL_INIT_TIMER ) < 0) {
std::cerr << "Error initializing SDL: " << SDL_GetError() << std::endl;
abort();
}
}
void terminate() {
video.Exit();
SDL_Quit();
}
}

68
util/Asm.h Normal file
View File

@ -0,0 +1,68 @@
#ifndef ASM_H
#define ASM_H
#include <cstdint>
static inline uint8_t inb(uint16_t port) {
uint8_t value;
asm volatile ("inb %1, %0" : "=a"(value) : "Nd"(port));
return value;
}
static inline uint16_t inw(uint16_t port) {
uint16_t value;
asm volatile ("inw %1, %0" : "=a"(value) : "Nd"(port));
return value;
}
static inline uint32_t inl(uint16_t port) {
uint32_t value;
asm volatile ("inl %1, %0" : "=a"(value) : "Nd"(port));
return value;
}
static inline void outb(uint16_t port, uint8_t value) {
asm volatile ("outb %0, %1" : : "a"(value), "Nd"(port));
}
static inline void outw(uint16_t port, uint16_t value) {
asm volatile ("outw %0, %1" : : "a"(value), "Nd"(port));
}
static inline void outl(uint16_t port, uint32_t value) {
asm volatile ("outl %0, %1" : : "a"(value), "Nd"(port));
}
static inline void enableInterrupts() {
asm volatile ("sti": : :"memory");
}
static inline void disableInterrupts() {
asm volatile ("cli": : :"memory");
}
static inline uint32_t eflags() {
uint32_t value;
asm volatile ( "pushf\n\t"
"popl %0"
: "=g"(value));
return value;
}
static inline bool interruptsEnabled() {
return (eflags() & (1u << 9u)) != 0;
}
static void pmInterruptCall(unsigned short selector, unsigned long offset) {
__asm__ __volatile__ (
"pushf\n" // Push EFLAGS
"mov %0, %%ax\n" // Load the segment selector into AX
"mov %%ax, %%ds\n" // Move it to DS (or another segment register)
"call *%1\n" // Perform the indirect far call using the offset
: // No output operands
: "r" (selector), "r" (offset) // Input operands
: "%ax" // Clobbered register
);
}
#endif

97
util/Bmp.cpp Normal file
View File

@ -0,0 +1,97 @@
#include "Bmp.h"
#include <cstdio>
#include "Files.h"
namespace Bmp {
Bitmap loadFromMemory(void *buf, size_t len) {
if (len < sizeof (BmpFileHeader) + sizeof (BmpInfoHeader)) return {};
auto fileHeader = reinterpret_cast<BmpFileHeader *>(buf);
auto infoHeader = reinterpret_cast<BmpInfoHeader *>(fileHeader + 1);
if (fileHeader->signature != 0x4d42) {
printf("Invalid signature\n");
return {};
}
if (len < fileHeader->fileSize) {
printf("File too small\n");
return {};
}
if (infoHeader->headerSize != 40) {
printf("Invalid header size\n");
return {};
}
if (infoHeader->width < 0 || infoHeader->height < 0 || infoHeader->planes != 1) {
printf("Invalid width/height/bit planes\n");
return {};
}
if (infoHeader->bitsPerPixel != 1 && infoHeader->bitsPerPixel != 4 && infoHeader->bitsPerPixel != 8) {
printf("Unsupported bpp %u\n", infoHeader->bitsPerPixel);
return {};
}
if (infoHeader->compression != BmpInfoHeader::CompressionType::Rgb) {
printf("Unsupported compression\n");
return {};
}
// TODO check imageSize
Bitmap bitmap(infoHeader->width, infoHeader->height);
// Read color table
auto colorTable = reinterpret_cast<uint8_t *>(infoHeader + 1);
for (auto i = 0; i < infoHeader->colorsUsed; i++) {
Video::PaletteEntry paletteEntry;
paletteEntry.b = colorTable[4 * i + 0];
paletteEntry.g = colorTable[4 * i + 1];
paletteEntry.r = colorTable[4 * i + 2];
bitmap.SetPaletteEntry(i, paletteEntry);
}
// Read pixel data
auto data = static_cast<uint8_t *>(buf) + fileHeader->offset;
auto rowLength = infoHeader->width;
if (infoHeader->bitsPerPixel == 1) rowLength = (rowLength + 7) >> 3;
else if (infoHeader->bitsPerPixel == 4) rowLength = (rowLength + 1) >> 1;
auto stride = (rowLength + 3) >> 2 << 2;
for (auto y = 0u; y < infoHeader->height; y++) {
for (auto x = 0u; x < infoHeader->width; x++) {
uint8_t pv = 0;
switch (infoHeader->bitsPerPixel) {
case 1: {
const auto byte = data[(infoHeader->height - y - 1) * stride + (x >> 3)];
auto pixelOffset = x & 7;
pv = (byte >> (7 - pixelOffset)) & 1;
break;
}
case 4: {
const auto byte = data[(infoHeader->height - y - 1) * stride + (x >> 1)];
pv = x & 1 ? byte & 0xf : byte >> 4;
break;
}
case 8: {
pv = data[(infoHeader->height - y - 1) * stride + x];
break;
}
}
bitmap.SetPixel(x, y, pv);
}
}
return bitmap;
}
Bitmap loadFromFile(const char* path) {
void *data;
auto len = Files::allocateBufferAndLoadFromFile(path, &data);
auto bitmap = loadFromMemory(data, len);
Files::deleteBuffer(data);
return bitmap;
}
}

48
util/Bmp.h Normal file
View File

@ -0,0 +1,48 @@
#ifndef GAME_BMPLOADER_H
#define GAME_BMPLOADER_H
#include <cstddef>
#include "../graphics/Bitmap.h"
namespace Bmp {
struct BmpFileHeader {
uint16_t signature;
uint32_t fileSize;
uint16_t reserved1;
uint16_t reserved2;
uint32_t offset;
} __attribute__((__packed__));
struct BmpInfoHeader {
uint32_t headerSize;
uint32_t width;
uint32_t height;
uint16_t planes;
uint16_t bitsPerPixel;
uint32_t compression;
uint32_t imageSize;
uint32_t horizontalResolution;
uint32_t verticalResolution;
uint32_t colorsUsed;
uint32_t colorsImportant;
enum CompressionType {
Rgb = 0,
Rle8,
Rle4,
Bitfields,
Jpeg,
Png,
Alphabitfields,
Cmyk,
CmykRle8,
CmykRle4,
};
} __attribute__((__packed__));
Bitmap loadFromMemory(void *buf, size_t len);
Bitmap loadFromFile(const char* path);
};
#endif //GAME_BMPLOADER_H

27
util/Files.cpp Normal file
View File

@ -0,0 +1,27 @@
#include "Files.h"
#include <cstdio>
size_t Files::allocateBufferAndLoadFromFile(const char *path, void **bufferPointer) {
auto fp = fopen(path, "rb");
if (!fp) {
*bufferPointer = nullptr;
return 0;
}
fseek(fp, 0, SEEK_END);
size_t len = ftell(fp);
fseek(fp, 0, SEEK_SET);
auto data = new unsigned char[len];
fread(data, 1, len, fp);
fclose(fp);
*bufferPointer = data;
return len;
}
void Files::deleteBuffer(void *buffer) {
delete[] static_cast<unsigned char*>(buffer);
}

10
util/Files.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef GAME_FILES_H
#define GAME_FILES_H
#include <stddef.h>
namespace Files {
size_t allocateBufferAndLoadFromFile(const char *path, void **bufferPointer);
void deleteBuffer(void *);
}
#endif //GAME_FILES_H

68
util/Gbm.cpp Normal file
View File

@ -0,0 +1,68 @@
#include "Gbm.h"
#include <cstdio>
#include "Files.h"
#define GBM_SIGNATURE 0x204d4247
Bitmap Gbm::loadFromMemory(const void *data, size_t size) {
// Check for minimum
if (size < sizeof (uint32_t) + sizeof (uint16_t) + sizeof (uint16_t) + 16 * 3) return {};
auto gbm = static_cast<const GbmHeader *>(data);
if (gbm->signature != GBM_SIGNATURE) return {};
if (gbm->width == 0 || gbm->height == 0) return {};
Bitmap bitmap(gbm->width, gbm->height);
for (auto i = 0; i < 16; i++) {
bitmap.SetPaletteEntry(i, {gbm->palette[i].r, gbm->palette[i].g, gbm->palette[i].b});
}
for (auto y = 0; y < gbm->height; y++) {
for (auto x = 0; x < gbm->width; x++) {
auto pv = gbm->data[y * gbm->width + x];
bitmap.SetPixel(x, y, pv);
}
}
return bitmap;
}
Bitmap Gbm::loadFromFile(const char *path) {
void *data;
auto len = Files::allocateBufferAndLoadFromFile(path, &data);
auto bitmap = loadFromMemory(data, len);
Files::deleteBuffer(data);
return bitmap;
}
void Gbm::writeToFile(const char *path, const Bitmap &bitmap) {
auto fp = fopen(path, "wb");
if (!fp) return;
// Write signature
uint32_t signature = GBM_SIGNATURE;
fwrite(&signature, 4, 1, fp);
uint16_t width = bitmap.GetWidth(), height = bitmap.GetHeight();
fwrite(&width, 2, 1, fp);
fwrite(&height, 2, 1, fp);
for (auto i = 0; i < 16; i++) {
auto entry = bitmap.GetPaletteEntry(i);
auto r = entry.r, g = entry.g, b = entry.b;
fwrite(&r, 1, 1, fp);
fwrite(&g, 1, 1, fp);
fwrite(&b, 1, 1, fp);
}
for (auto y = 0; y < height; y++) {
for (auto x = 0; x < width; x++) {
auto pv = bitmap.GetPixel(x, y);
fwrite(&pv, 1, 1, fp);
}
}
fclose(fp);
}

28
util/Gbm.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef GAME_GBM_H
#define GAME_GBM_H
#include <cstddef>
#include "../graphics/Bitmap.h"
namespace Gbm {
struct GbmHeader {
uint32_t signature;
uint16_t width;
uint16_t height;
struct {
uint8_t r;
uint8_t g;
uint8_t b;
} __attribute__((__packed__)) palette[16];
uint8_t data[0];
} __attribute__((__packed__));
Bitmap loadFromMemory(const void *data, size_t size);
Bitmap loadFromFile(const char *path);
void writeToFile(const char *path, const Bitmap &bitmap);
};
#endif //GAME_GBM_H

22
util/Log.cpp Normal file
View File

@ -0,0 +1,22 @@
#include "Log.h"
#include <cstdarg>
Log DefaultLog("log.txt");
Log::Log(const char *path) {
fp = fopen(path, "w");
}
Log::~Log() {
fclose(fp);
}
void Log::log(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vfprintf(fp, fmt, args);
va_end(args);
fflush(fp);
}

19
util/Log.h Normal file
View File

@ -0,0 +1,19 @@
#ifndef GAME_LOG_H
#define GAME_LOG_H
#include <cstdio>
class Log {
public:
Log(const char *path);
~Log();
void log(const char *fmt, ...);
private:
FILE *fp;
};
extern Log DefaultLog;
#endif //GAME_LOG_H