diff options
| author | Louis Burda <quent.burda@gmail.com> | 2022-06-02 15:28:40 +0200 |
|---|---|---|
| committer | Louis Burda <quent.burda@gmail.com> | 2022-06-02 15:28:40 +0200 |
| commit | 5bc16063c29aa4d3d287ebd163ccdbcbf54c4f9f (patch) | |
| tree | c131f947a37b3af2d14d41e9eda098bdec2d061c /gearboy/src | |
| parent | 78a5f810b22f0d8cafa05f638b0cb2e889824859 (diff) | |
| download | cscg2022-gearboy-master.tar.gz cscg2022-gearboy-master.zip | |
Diffstat (limited to 'gearboy/src')
58 files changed, 27882 insertions, 0 deletions
diff --git a/gearboy/src/Audio.cpp b/gearboy/src/Audio.cpp new file mode 100644 index 00000000..c35b605d --- /dev/null +++ b/gearboy/src/Audio.cpp @@ -0,0 +1,139 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#include "Audio.h" +#include "Memory.h" + +Audio::Audio() +{ + m_bCGB = false; + m_ElapsedCycles = 0; + m_SampleRate = 44100; + InitPointer(m_pApu); + InitPointer(m_pBuffer); + InitPointer(m_pSampleBuffer); +} + +Audio::~Audio() +{ + SafeDelete(m_pApu); + SafeDelete(m_pBuffer); + SafeDeleteArray(m_pSampleBuffer); +} + +void Audio::Init() +{ + m_pSampleBuffer = new blip_sample_t[AUDIO_BUFFER_SIZE]; + + m_pApu = new Gb_Apu(); + m_pBuffer = new Stereo_Buffer(); + + m_pBuffer->clock_rate(4194304); + m_pBuffer->set_sample_rate(m_SampleRate); + + //m_pApu->treble_eq(-15.0); + //m_pBuffer->bass_freq(100); + + m_pApu->set_output(m_pBuffer->center(), m_pBuffer->left(), m_pBuffer->right()); +} + +void Audio::Reset(bool bCGB) +{ + m_bCGB = bCGB; + + Gb_Apu::mode_t mode = m_bCGB ? Gb_Apu::mode_cgb : Gb_Apu::mode_dmg; + m_pApu->reset(mode); + m_pBuffer->clear(); + + for (int reg = 0xFF10; reg <= 0xFF3F; reg++) + { + u8 value = m_bCGB ? kInitialValuesForColorFFXX[reg - 0xFF00] : kInitialValuesForFFXX[reg - 0xFF00]; + m_pApu->write_register(0, reg, value); + } + + m_ElapsedCycles = 0; +} + +void Audio::SetSampleRate(int rate) +{ + if (rate != m_SampleRate) + { + m_SampleRate = rate; + m_pBuffer->set_sample_rate(m_SampleRate); + } +} + +void Audio::SetVolume(float volume) +{ + m_pApu->volume(volume); +} + +void Audio::EndFrame(s16* pSampleBuffer, int* pSampleCount) +{ + m_pApu->end_frame(m_ElapsedCycles); + m_pBuffer->end_frame(m_ElapsedCycles); + + int count = static_cast<int>(m_pBuffer->read_samples(m_pSampleBuffer, AUDIO_BUFFER_SIZE)); + + if (IsValidPointer(pSampleBuffer) && IsValidPointer(pSampleCount)) + { + *pSampleCount = count; + + for (int i=0; i<count; i++) + { + pSampleBuffer[i] = m_pSampleBuffer[i]; + } + } + + m_ElapsedCycles = 0; +} + +void Audio::SaveState(std::ostream& stream) +{ + using namespace std; + + gb_apu_state_t apu_state; + + m_pApu->save_state(&apu_state); + + stream.write(reinterpret_cast<const char*> (&m_ElapsedCycles), sizeof(m_ElapsedCycles)); + stream.write(reinterpret_cast<const char*> (m_pSampleBuffer), sizeof(blip_sample_t) * AUDIO_BUFFER_SIZE); + stream.write(reinterpret_cast<const char*> (&apu_state), sizeof(apu_state)); +} + +void Audio::LoadState(std::istream& stream) +{ + using namespace std; + + gb_apu_state_t apu_state; + + stream.read(reinterpret_cast<char*> (&m_ElapsedCycles), sizeof(m_ElapsedCycles)); + stream.read(reinterpret_cast<char*> (m_pSampleBuffer), sizeof(blip_sample_t) * AUDIO_BUFFER_SIZE); + stream.read(reinterpret_cast<char*> (&apu_state), sizeof(apu_state)); + + Gb_Apu::mode_t mode = m_bCGB ? Gb_Apu::mode_cgb : Gb_Apu::mode_dmg; + m_pApu->reset(mode); + m_pApu->load_state(apu_state); + m_pBuffer->clear(); +} + +Gb_Apu* Audio::GetApu() +{ + return m_pApu; +} diff --git a/gearboy/src/Audio.h b/gearboy/src/Audio.h new file mode 100644 index 00000000..076541d5 --- /dev/null +++ b/gearboy/src/Audio.h @@ -0,0 +1,68 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#ifndef AUDIO_H +#define AUDIO_H + +#include "definitions.h" +#include "audio/Multi_Buffer.h" +#include "audio/Gb_Apu.h" + +class Audio +{ +public: + Audio(); + ~Audio(); + void Init(); + void Reset(bool bCGB); + void SetSampleRate(int rate); + void SetVolume(float volume); + u8 ReadAudioRegister(u16 address); + void WriteAudioRegister(u16 address, u8 value); + void Tick(unsigned int clockCycles); + void EndFrame(s16* pSampleBuffer, int* pSampleCount); + void SaveState(std::ostream& stream); + void LoadState(std::istream& stream); + Gb_Apu* GetApu(); + +private: + Gb_Apu* m_pApu; + Stereo_Buffer* m_pBuffer; + int m_ElapsedCycles; + int m_SampleRate; + blip_sample_t* m_pSampleBuffer; + bool m_bCGB; +}; + +inline void Audio::Tick(unsigned int clockCycles) +{ + m_ElapsedCycles += clockCycles; +} + +inline u8 Audio::ReadAudioRegister(u16 address) +{ + return m_pApu->read_register(m_ElapsedCycles, address); +} + +inline void Audio::WriteAudioRegister(u16 address, u8 value) +{ + m_pApu->write_register(m_ElapsedCycles, address, value); +} + +#endif /* AUDIO_H */ diff --git a/gearboy/src/Cartridge.cpp b/gearboy/src/Cartridge.cpp new file mode 100644 index 00000000..d58c7f67 --- /dev/null +++ b/gearboy/src/Cartridge.cpp @@ -0,0 +1,689 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#include <string> +#include <algorithm> +#include <ctype.h> +#include <time.h> +#include "Cartridge.h" +#include "miniz/miniz.c" + +Cartridge::Cartridge() +{ + InitPointer(m_pTheROM); + m_iTotalSize = 0; + m_szName[0] = 0; + m_iROMSize = 0; + m_iRAMSize = 0; + m_Type = CartridgeNotSupported; + m_bValidROM = false; + m_bCGB = false; + m_bSGB = false; + m_iVersion = 0; + m_bLoaded = false; + m_RTCCurrentTime = 0; + m_bBattery = false; + m_szFilePath[0] = 0; + m_szFileName[0] = 0; + m_bRTCPresent = false; + m_bRumblePresent = false; + m_iRAMBankCount = 0; + m_iROMBankCount = 0; +} + +Cartridge::~Cartridge() +{ + SafeDeleteArray(m_pTheROM); +} + +void Cartridge::Init() +{ + Reset(); +} + +void Cartridge::Reset() +{ + SafeDeleteArray(m_pTheROM); + m_iTotalSize = 0; + m_szName[0] = 0; + m_iROMSize = 0; + m_iRAMSize = 0; + m_Type = CartridgeNotSupported; + m_bValidROM = false; + m_bCGB = false; + m_bSGB = false; + m_iVersion = 0; + m_bLoaded = false; + m_RTCCurrentTime = 0; + m_bBattery = false; + m_szFilePath[0] = 0; + m_szFileName[0] = 0; + m_bRTCPresent = false; + m_bRumblePresent = false; + m_iRAMBankCount = 0; + m_iROMBankCount = 0; + m_GameGenieList.clear(); +} + +bool Cartridge::IsValidROM() const +{ + return m_bValidROM; +} + +bool Cartridge::IsLoadedROM() const +{ + return m_bLoaded; +} + +Cartridge::CartridgeTypes Cartridge::GetType() const +{ + return m_Type; +} + +int Cartridge::GetRAMSize() const +{ + return m_iRAMSize; +} + +int Cartridge::GetROMSize() const +{ + return m_iROMSize; +} + +int Cartridge::GetRAMBankCount() const +{ + return m_iRAMBankCount; +} + +int Cartridge::GetROMBankCount() const +{ + return m_iROMBankCount; +} + +const char* Cartridge::GetName() const +{ + return m_szName; +} + +const char* Cartridge::GetFilePath() const +{ + return m_szFilePath; +} + +const char* Cartridge::GetFileName() const +{ + return m_szFileName; +} + +int Cartridge::GetTotalSize() const +{ + return m_iTotalSize; +} + +bool Cartridge::HasBattery() const +{ + return m_bBattery; +} + +u8* Cartridge::GetTheROM() const +{ + return m_pTheROM; +} + +bool Cartridge::LoadFromZipFile(const u8* buffer, int size) +{ + using namespace std; + + mz_zip_archive zip_archive; + mz_bool status; + memset(&zip_archive, 0, sizeof (zip_archive)); + + status = mz_zip_reader_init_mem(&zip_archive, (void*) buffer, size, 0); + if (!status) + { + Log("mz_zip_reader_init_mem() failed!"); + return false; + } + + for (unsigned int i = 0; i < mz_zip_reader_get_num_files(&zip_archive); i++) + { + mz_zip_archive_file_stat file_stat; + if (!mz_zip_reader_file_stat(&zip_archive, i, &file_stat)) + { + Log("mz_zip_reader_file_stat() failed!"); + mz_zip_reader_end(&zip_archive); + return false; + } + + Log("ZIP Content - Filename: \"%s\", Comment: \"%s\", Uncompressed size: %u, Compressed size: %u", file_stat.m_filename, file_stat.m_comment, (unsigned int) file_stat.m_uncomp_size, (unsigned int) file_stat.m_comp_size); + + string fn((const char*) file_stat.m_filename); + transform(fn.begin(), fn.end(), fn.begin(), (int(*)(int)) tolower); + string extension = fn.substr(fn.find_last_of(".") + 1); + + if ((extension == "gb") || (extension == "dmg") || (extension == "gbc") || (extension == "cgb") || (extension == "sgb")) + { + void *p; + size_t uncomp_size; + + p = mz_zip_reader_extract_file_to_heap(&zip_archive, file_stat.m_filename, &uncomp_size, 0); + if (!p) + { + Log("mz_zip_reader_extract_file_to_heap() failed!"); + mz_zip_reader_end(&zip_archive); + return false; + } + + bool ok = LoadFromBuffer((const u8*) p, static_cast<int>(uncomp_size)); + + free(p); + mz_zip_reader_end(&zip_archive); + + return ok; + } + } + return false; +} + +bool Cartridge::LoadFromFile(const char* path) +{ + using namespace std; + + Log("Loading %s...", path); + + Reset(); + + strcpy(m_szFilePath, path); + + std::string pathstr(path); + std::string filename; + + size_t pos = pathstr.find_last_of("\\"); + if (pos != std::string::npos) + { + filename.assign(pathstr.begin() + pos + 1, pathstr.end()); + } + else + { + pos = pathstr.find_last_of("/"); + if (pos != std::string::npos) + { + filename.assign(pathstr.begin() + pos + 1, pathstr.end()); + } + else + { + filename = pathstr; + } + } + + strcpy(m_szFileName, filename.c_str()); + + ifstream file(path, ios::in | ios::binary | ios::ate); + + if (file.is_open()) + { + int size = static_cast<int> (file.tellg()); + char* memblock = new char[size]; + file.seekg(0, ios::beg); + file.read(memblock, size); + file.close(); + + string fn(path); + transform(fn.begin(), fn.end(), fn.begin(), (int(*)(int)) tolower); + string extension = fn.substr(fn.find_last_of(".") + 1); + + if (extension == "zip") + { + Log("Loading from ZIP..."); + m_bLoaded = LoadFromZipFile(reinterpret_cast<u8*> (memblock), size); + } + else + { + m_bLoaded = LoadFromBuffer(reinterpret_cast<u8*> (memblock), size); + } + + if (m_bLoaded) + { + Log("ROM loaded", path); + } + else + { + Log("There was a problem loading the memory for file %s...", path); + } + + SafeDeleteArray(memblock); + } + else + { + Log("There was a problem loading the file %s...", path); + m_bLoaded = false; + } + + if (!m_bLoaded) + { + Reset(); + } + + return m_bLoaded; +} + +bool Cartridge::LoadFromBuffer(const u8* buffer, int size) +{ + if (IsValidPointer(buffer)) + { + Log("Loading from buffer... Size: %d", size); + m_iTotalSize = size; + m_pTheROM = new u8[m_iTotalSize]; + memcpy(m_pTheROM, buffer, m_iTotalSize); + m_bLoaded = true; + return GatherMetadata(); + } + else + return false; +} + +void Cartridge::CheckCartridgeType(int type) +{ + if ((type != 0xEA) && (GetROMSize() == 0)) + type = 0; + + switch (type) + { + case 0x00: + // NO MBC + case 0x08: + // ROM + // SRAM + case 0x09: + // ROM + // SRAM + // BATT + m_Type = CartridgeNoMBC; + break; + case 0x01: + // MBC1 + case 0x02: + // MBC1 + // SRAM + case 0x03: + // MBC1 + // SRAM + // BATT + case 0xEA: + // Hack to accept 0xEA as a MBC1 (Sonic 3D Blast 5) + case 0xFF: + // Hack to accept HuC1 as a MBC1 + m_Type = CartridgeMBC1; + break; + case 0x05: + // MBC2 + // SRAM + case 0x06: + // MBC2 + // SRAM + // BATT + m_Type = CartridgeMBC2; + break; + case 0x0F: + // MBC3 + // TIMER + // BATT + case 0x10: + // MBC3 + // TIMER + // BATT + // SRAM + case 0x11: + // MBC3 + case 0x12: + // MBC3 + // SRAM + case 0x13: + // MBC3 + // BATT + // SRAM + case 0xFC: + // Game Boy Camera + m_Type = CartridgeMBC3; + break; + case 0x19: + // MBC5 + case 0x1A: + // MBC5 + // SRAM + case 0x1B: + // MBC5 + // BATT + // SRAM + case 0x1C: + // RUMBLE + case 0x1D: + // RUMBLE + // SRAM + case 0x1E: + // RUMBLE + // BATT + // SRAM + m_Type = CartridgeMBC5; + break; + case 0x0B: + // MMMO1 + case 0x0C: + // MMM01 + // SRAM + case 0x0D: + // MMM01 + // SRAM + // BATT + case 0x15: + // MBC4 + case 0x16: + // MBC4 + // SRAM + case 0x17: + // MBC4 + // SRAM + // BATT + case 0x22: + // MBC7 + // BATT + // SRAM + case 0x55: + // GG + case 0x56: + // GS3 + case 0xFD: + // TAMA 5 + case 0xFE: + // HuC3 + m_Type = CartridgeNotSupported; + Log("--> ** This cartridge is not supported. Type: %d", type); + break; + default: + m_Type = CartridgeNotSupported; + Log("--> ** Unknown cartridge type: %d", type); + } + + switch (type) + { + case 0x03: + case 0x06: + case 0x09: + case 0x0D: + case 0x0F: + case 0x10: + case 0x13: + case 0x17: + case 0x1B: + case 0x1E: + case 0x22: + case 0xFD: + case 0xFF: + m_bBattery = true; + break; + default: + m_bBattery = false; + } + + switch (type) + { + case 0x0F: + case 0x10: + m_bRTCPresent = true; + break; + default: + m_bRTCPresent = false; + } + + switch (type) + { + case 0x1C: + case 0x1D: + case 0x1E: + m_bRumblePresent = true; + break; + default: + m_bRumblePresent = false; + } +} + +int Cartridge::GetVersion() const +{ + return m_iVersion; +} + +bool Cartridge::IsSGB() const +{ + return m_bSGB; +} + +bool Cartridge::IsCGB() const +{ + return m_bCGB; +} + +void Cartridge::UpdateCurrentRTC() +{ + time(&m_RTCCurrentTime); +} + +time_t Cartridge::GetCurrentRTC() +{ + return m_RTCCurrentTime; +} + +bool Cartridge::IsRTCPresent() const +{ + return m_bRTCPresent; +} + +bool Cartridge::IsRumblePresent() const +{ + return m_bRumblePresent; +} + +void Cartridge::SetGameGenieCheat(const char* szCheat) +{ + std::string code(szCheat); + for (std::string::iterator p = code.begin(); code.end() != p; ++p) + *p = toupper(*p); + + if (m_bLoaded && (code.length() > 6) && ((code[3] < '0') || ((code[3] > '9') && (code[3] < 'A')))) + { + u8 new_value = (AsHex(code[0]) << 4 | AsHex(code[1])) & 0xFF; + u16 cheat_address = (AsHex(code[2]) << 8 | AsHex(code[4]) << 4 | AsHex(code[5]) | (AsHex(code[6]) ^ 0xF) << 12) & 0x7FFF; + bool avoid_compare = true; + u8 compare_value = 0; + + if ((code.length() == 11) && ((code[7] < '0') || ((code[7] > '9') && (code[7] < 'A')))) + { + compare_value = (AsHex(code[8]) << 4 | AsHex(code[10])) ^ 0xFF; + compare_value = ((compare_value >> 2 | compare_value << 6) ^ 0x45) & 0xFF; + avoid_compare = false; + } + + for (int bank = 0; bank < GetROMBankCount(); bank++) + { + int bank_address = (bank * 0x4000) + (cheat_address & 0x3FFF); + + if (avoid_compare || (m_pTheROM[bank_address] == compare_value)) + { + GameGenieCode undo_data; + undo_data.address = bank_address; + undo_data.old_value = m_pTheROM[bank_address]; + + m_pTheROM[bank_address] = new_value; + + m_GameGenieList.push_back(undo_data); + } + } + } +} + +void Cartridge::ClearGameGenieCheats() +{ + std::list<GameGenieCode>::iterator it; + + for (it = m_GameGenieList.begin(); it != m_GameGenieList.end(); it++) + { + m_pTheROM[it->address] = it->old_value; + } + + m_GameGenieList.clear(); +} + +unsigned int Cartridge::Pow2Ceil(unsigned int n) +{ + --n; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + ++n; + return n; +} + +bool Cartridge::GatherMetadata() +{ + char name[12] = {0}; + name[11] = 0; + + for (int i = 0; i < 11; i++) + { + name[i] = m_pTheROM[0x0134 + i]; + + if (name[i] == 0) + { + break; + } + } + + strcpy(m_szName, name); + + m_bCGB = (m_pTheROM[0x143] == 0x80) || (m_pTheROM[0x143] == 0xC0); + m_bSGB = (m_pTheROM[0x146] == 0x03); + int type = m_pTheROM[0x147]; + m_iROMSize = m_pTheROM[0x148]; + m_iRAMSize = m_pTheROM[0x149]; + m_iVersion = m_pTheROM[0x14C]; + + CheckCartridgeType(type); + + switch (m_iRAMSize) + { + case 0x00: + m_iRAMBankCount = (m_Type == Cartridge::CartridgeMBC2) ? 1 : 0; + break; + case 0x01: + case 0x02: + m_iRAMBankCount = 1; + break; + case 0x04: + m_iRAMBankCount = 16; + break; + default: + m_iRAMBankCount = 4; + break; + } + + m_iROMBankCount = std::max(Pow2Ceil(m_iTotalSize / 0x4000), 2u); + + bool presumeMultiMBC1 = ((type == 1) && (m_iRAMSize == 0) && (m_iROMBankCount == 64)); + + if ((m_Type == Cartridge::CartridgeMBC1) && presumeMultiMBC1) + { + m_Type = Cartridge::CartridgeMBC1Multi; + Log("Presumed Multi 64"); + } + + Log("Cartridge Size %d", m_iTotalSize); + Log("ROM Name %s", m_szName); + Log("ROM Version %d", m_iVersion); + Log("ROM Type %X", type); + Log("ROM Size %X", m_iROMSize); + Log("ROM Bank Count %d", m_iROMBankCount); + Log("RAM Size %X", m_iRAMSize); + Log("RAM Bank Count %d", m_iRAMBankCount); + + switch (m_Type) + { + case Cartridge::CartridgeNoMBC: + Log("No MBC found"); + break; + case Cartridge::CartridgeMBC1: + Log("MBC1 found"); + break; + case Cartridge::CartridgeMBC1Multi: + Log("MBC1 Multi 64 found"); + break; + case Cartridge::CartridgeMBC2: + Log("MBC2 found"); + break; + case Cartridge::CartridgeMBC3: + Log("MBC3 found"); + break; + case Cartridge::CartridgeMBC5: + Log("MBC5 found"); + break; + case Cartridge::CartridgeNotSupported: + Log("Cartridge not supported!!"); + break; + default: + break; + } + + if (m_bBattery) + { + Log("Battery powered RAM found"); + } + + if (m_pTheROM[0x143] == 0xC0) + { + Log("Game Boy Color only"); + } + else if (m_bCGB) + { + Log("Game Boy Color supported"); + } + + if (m_bSGB) + { + Log("Super Game Boy supported"); + } + + int checksum = 0; + + for (int j = 0x134; j < 0x14E; j++) + { + checksum += m_pTheROM[j]; + } + + m_bValidROM = ((checksum + 25) & 0xFF) == 0; + + if (m_bValidROM) + { + Log("Checksum OK!"); + } + else + { + Log("Checksum FAILED!!!"); + } + + return (m_Type != CartridgeNotSupported); +} diff --git a/gearboy/src/Cartridge.h b/gearboy/src/Cartridge.h new file mode 100644 index 00000000..6a880636 --- /dev/null +++ b/gearboy/src/Cartridge.h @@ -0,0 +1,105 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#ifndef CARTRIDGE_H +#define CARTRIDGE_H + +#include <list> +#include "definitions.h" + +class Cartridge +{ +public: + enum CartridgeTypes + { + CartridgeNoMBC, + CartridgeMBC1, + CartridgeMBC2, + CartridgeMBC3, + CartridgeMBC5, + CartridgeMBC1Multi, + CartridgeNotSupported + }; + + struct GameGenieCode + { + int address; + u8 old_value; + }; + +public: + Cartridge(); + ~Cartridge(); + void Init(); + void Reset(); + bool IsValidROM() const; + bool IsLoadedROM() const; + CartridgeTypes GetType() const; + int GetRAMSize() const; + int GetROMSize() const; + int GetROMBankCount() const; + int GetRAMBankCount() const; + const char* GetName() const; + const char* GetFilePath() const; + const char* GetFileName() const; + int GetTotalSize() const; + bool HasBattery() const; + u8* GetTheROM() const; + bool LoadFromFile(const char* path); + bool LoadFromBuffer(const u8* buffer, int size); + int GetVersion() const; + bool IsSGB() const; + bool IsCGB() const; + void UpdateCurrentRTC(); + time_t GetCurrentRTC(); + bool IsRTCPresent() const; + bool IsRumblePresent() const; + void SetGameGenieCheat(const char* szCheat); + void ClearGameGenieCheats(); + +private: + unsigned int Pow2Ceil(unsigned int n); + bool GatherMetadata(); + bool LoadFromZipFile(const u8* buffer, int size); + void CheckCartridgeType(int type); + +private: + u8* m_pTheROM; + int m_iTotalSize; + char m_szName[16]; + int m_iROMSize; + int m_iRAMSize; + CartridgeTypes m_Type; + bool m_bValidROM; + bool m_bCGB; + bool m_bSGB; + int m_iVersion; + bool m_bLoaded; + time_t m_RTCCurrentTime; + bool m_bBattery; + char m_szFilePath[512]; + char m_szFileName[512]; + bool m_bRTCPresent; + bool m_bRumblePresent; + int m_iRAMBankCount; + int m_iROMBankCount; + std::list<GameGenieCode> m_GameGenieList; +}; + +#endif /* CARTRIDGE_H */ diff --git a/gearboy/src/CommonMemoryRule.cpp b/gearboy/src/CommonMemoryRule.cpp new file mode 100644 index 00000000..8e684736 --- /dev/null +++ b/gearboy/src/CommonMemoryRule.cpp @@ -0,0 +1,37 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#include "CommonMemoryRule.h" + +CommonMemoryRule::CommonMemoryRule(Memory* pMemory) +{ + m_pMemory = pMemory; + m_bCGB = false; +} + +CommonMemoryRule::~CommonMemoryRule() +{ +} + +void CommonMemoryRule::Reset(bool bCGB) +{ + m_bCGB = bCGB; +} + + diff --git a/gearboy/src/CommonMemoryRule.h b/gearboy/src/CommonMemoryRule.h new file mode 100644 index 00000000..fec88dea --- /dev/null +++ b/gearboy/src/CommonMemoryRule.h @@ -0,0 +1,125 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#ifndef COMMONMEMORYRULE_H +#define COMMONMEMORYRULE_H + +#include "definitions.h" + +class Memory; + +class CommonMemoryRule +{ +public: + CommonMemoryRule(Memory* pMemory); + ~CommonMemoryRule(); + u8 PerformRead(u16 address); + void PerformWrite(u16 address, u8 value); + void Reset(bool bCGB); + +private: + Memory* m_pMemory; + bool m_bCGB; +}; + +#include "Memory.h" + +inline u8 CommonMemoryRule::PerformRead(u16 address) +{ + if (m_bCGB) + { + switch (address & 0xE000) + { + case 0x8000: + { + return m_pMemory->ReadCGBLCDRAM(address, false); + } + case 0xC000: + { + return m_pMemory->ReadCGBWRAM(address); + } + } + } + else if (address >= 0xFEA0 && address < 0xFF00) + { + return ((((address + ((address >> 4) - 0x0FEA)) >> 2) & 1) ? 0x00 : 0xFF); + } + + return m_pMemory->Retrieve(address); +} + +inline void CommonMemoryRule::PerformWrite(u16 address, u8 value) +{ + switch (address & 0xE000) + { + case 0x8000: + { + if (m_bCGB) + m_pMemory->WriteCGBLCDRAM(address, value); + else + m_pMemory->Load(address, value); + break; + } + case 0xC000: + { + if (address < 0xDE00) + { + if (m_bCGB) + m_pMemory->WriteCGBWRAM(address, value); + else + m_pMemory->Load(address, value); + + m_pMemory->Load(address + 0x2000, value); + } + else if (m_bCGB) + { + m_pMemory->WriteCGBWRAM(address, value); + } + else + { + m_pMemory->Load(address, value); + } + break; + } + case 0xE000: + { + if (address < 0xFE00) + { + if (m_bCGB) + m_pMemory->WriteCGBWRAM(address - 0x2000, value); + else + m_pMemory->Load(address - 0x2000, value); + + m_pMemory->Load(address, value); + } + else + { + m_pMemory->Load(address, value); + } + break; + } + default: + { + Log("--> ** Writing to invalid area %X %X", address, value); + m_pMemory->Load(address, value); + } + } +} + +#endif /* COMMONMEMORYRULE_H */ diff --git a/gearboy/src/GearboyCore.cpp b/gearboy/src/GearboyCore.cpp new file mode 100644 index 00000000..5e3ff7e6 --- /dev/null +++ b/gearboy/src/GearboyCore.cpp @@ -0,0 +1,1002 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#include "GearboyCore.h" +#include "Memory.h" +#include "Processor.h" +#include "Video.h" +#include "Audio.h" +#include "Input.h" +#include "Cartridge.h" +#include "MemoryRule.h" +#include "CommonMemoryRule.h" +#include "IORegistersMemoryRule.h" +#include "RomOnlyMemoryRule.h" +#include "MBC1MemoryRule.h" +#include "MBC2MemoryRule.h" +#include "MBC3MemoryRule.h" +#include "MBC5MemoryRule.h" +#include "MultiMBC1MemoryRule.h" + +GearboyCore::GearboyCore() +{ + InitPointer(m_pMemory); + InitPointer(m_pProcessor); + InitPointer(m_pVideo); + InitPointer(m_pAudio); + InitPointer(m_pInput); + InitPointer(m_pCartridge); + InitPointer(m_pCommonMemoryRule); + InitPointer(m_pIORegistersMemoryRule); + InitPointer(m_pRomOnlyMemoryRule); + InitPointer(m_pMBC1MemoryRule); + InitPointer(m_pMultiMBC1MemoryRule); + InitPointer(m_pMBC2MemoryRule); + InitPointer(m_pMBC3MemoryRule); + InitPointer(m_pMBC5MemoryRule); + InitPointer(m_pRamChangedCallback); + m_bCGB = false; + m_bGBA = false; + m_bPaused = false; + m_bForceDMG = false; + m_iRTCUpdateCount = 0; + m_pixelFormat = GB_PIXEL_RGB565; +} + +GearboyCore::~GearboyCore() +{ + SafeDelete(m_pMBC5MemoryRule); + SafeDelete(m_pMBC3MemoryRule); + SafeDelete(m_pMBC2MemoryRule); + SafeDelete(m_pMultiMBC1MemoryRule); + SafeDelete(m_pMBC1MemoryRule); + SafeDelete(m_pRomOnlyMemoryRule); + SafeDelete(m_pIORegistersMemoryRule); + SafeDelete(m_pCommonMemoryRule); + SafeDelete(m_pCartridge); + SafeDelete(m_pInput); + SafeDelete(m_pAudio); + SafeDelete(m_pVideo); + SafeDelete(m_pProcessor); + SafeDelete(m_pMemory); +} + +void GearboyCore::Init(GB_Color_Format pixelFormat) +{ + Log("--== %s %s by Ignacio Sanchez ==--", GEARBOY_TITLE, GEARBOY_VERSION); + + m_pixelFormat = pixelFormat; + + m_pMemory = new Memory(); + m_pProcessor = new Processor(m_pMemory); + m_pVideo = new Video(m_pMemory, m_pProcessor); + m_pAudio = new Audio(); + m_pInput = new Input(m_pMemory, m_pProcessor); + m_pCartridge = new Cartridge(); + + m_pMemory->Init(); + m_pProcessor->Init(); + m_pVideo->Init(); + m_pAudio->Init(); + m_pInput->Init(); + m_pCartridge->Init(); + + InitMemoryRules(); + InitDMGPalette(); +} + +bool GearboyCore::RunToVBlank(u16* pFrameBuffer, s16* pSampleBuffer, int* pSampleCount, bool bDMGbuffer, bool step, bool stopOnBreakpoints) +{ + bool breakpoint = false; + + if (!m_bPaused && m_pCartridge->IsLoadedROM()) + { + bool vblank = false; + int totalClocks = 0; + while (!vblank) + { + #ifdef PERFORMANCE + unsigned int clockCycles = m_pProcessor->RunFor(75); + #else + unsigned int clockCycles = m_pProcessor->RunFor(1); + #endif + + m_pProcessor->UpdateTimers(clockCycles); + m_pProcessor->UpdateSerial(clockCycles); + + vblank = m_pVideo->Tick(clockCycles, pFrameBuffer, m_pixelFormat); + m_pAudio->Tick(clockCycles); + m_pInput->Tick(clockCycles); + + totalClocks += clockCycles; + +#ifndef GEARBOY_DISABLE_DISASSEMBLER + if ((step || (stopOnBreakpoints && m_pProcessor->BreakpointHit())) && !m_pProcessor->Halted() && !m_pProcessor->DuringOpCode()) + { + vblank = true; + if (m_pProcessor->BreakpointHit()) + breakpoint = true; + } +#endif + + if (totalClocks > 702240) + vblank = true; + } + + m_pAudio->EndFrame(pSampleBuffer, pSampleCount); + + m_iRTCUpdateCount++; + if (m_iRTCUpdateCount == 20) + { + m_iRTCUpdateCount = 0; + m_pCartridge->UpdateCurrentRTC(); + } + + if (!m_bCGB && !bDMGbuffer) + { + RenderDMGFrame(pFrameBuffer); + } + } + + return breakpoint; +} + +bool GearboyCore::LoadROM(const char* szFilePath, bool forceDMG, Cartridge::CartridgeTypes forceType, bool forceGBA) +{ + if (m_pCartridge->LoadFromFile(szFilePath)) + { + m_bForceDMG = forceDMG; + Reset(m_bForceDMG ? false : m_pCartridge->IsCGB(), forceGBA); + m_pMemory->ResetDisassembledMemory(); + m_pMemory->LoadBank0and1FromROM(m_pCartridge->GetTheROM()); + bool romTypeOK = AddMemoryRules(forceType); +#ifndef GEARBOY_DISABLE_DISASSEMBLER + m_pProcessor->Disassemble(m_pProcessor->GetState()->PC->GetValue()); +#endif + + if (!romTypeOK) + { + Log("There was a problem with the cartridge header. File: %s...", szFilePath); + } + + return romTypeOK; + } + else + return false; +} + +bool GearboyCore::LoadROMFromBuffer(const u8* buffer, int size, bool forceDMG, Cartridge::CartridgeTypes forceType, bool forceGBA) +{ + if (m_pCartridge->LoadFromBuffer(buffer, size)) + { + m_bForceDMG = forceDMG; + Reset(m_bForceDMG ? false : m_pCartridge->IsCGB(), forceGBA); + m_pMemory->ResetDisassembledMemory(); + m_pMemory->LoadBank0and1FromROM(m_pCartridge->GetTheROM()); + bool romTypeOK = AddMemoryRules(forceType); + + if (!romTypeOK) + { + Log("There was a problem with the cartridge header."); + } + + return romTypeOK; + } + else + return false; +} + +void GearboyCore::SaveMemoryDump() +{ + if (m_pCartridge->IsLoadedROM() && (strlen(m_pCartridge->GetFilePath()) > 0)) + { + using namespace std; + + char path[512]; + + strcpy(path, m_pCartridge->GetFilePath()); + strcat(path, ".dump"); + + Log("Saving Memory Dump %s...", path); + + m_pMemory->MemoryDump(path); + + Log("Memory Dump Saved"); + } +} + +void GearboyCore::SaveDisassembledROM() +{ + Memory::stDisassembleRecord** romMap = m_pMemory->GetDisassembledROMMemoryMap(); + + if (m_pCartridge->IsLoadedROM() && (strlen(m_pCartridge->GetFilePath()) > 0) && IsValidPointer(romMap)) + { + using namespace std; + + char path[512]; + + strcpy(path, m_pCartridge->GetFilePath()); + strcat(path, ".dis"); + + Log("Saving Disassembled ROM %s...", path); + + ofstream myfile(path, ios::out | ios::trunc); + + if (myfile.is_open()) + { + for (int i = 0; i < 65536; i++) + { + if (IsValidPointer(romMap[i]) && (romMap[i]->name[0] != 0)) + { + myfile << "0x" << hex << i << "\t " << romMap[i]->name << "\n"; + i += (romMap[i]->size - 1); + } + } + + myfile.close(); + } + + Log("Disassembled ROM Saved"); + } +} + +Memory* GearboyCore::GetMemory() +{ + return m_pMemory; +} + +Cartridge* GearboyCore::GetCartridge() +{ + return m_pCartridge; +} + +Processor* GearboyCore::GetProcessor() +{ + return m_pProcessor; +} + +Audio* GearboyCore::GetAudio() +{ + return m_pAudio; +} + +Video* GearboyCore::GetVideo() +{ + return m_pVideo; +} + +void GearboyCore::KeyPressed(Gameboy_Keys key) +{ + m_pInput->KeyPressed(key); +} + +void GearboyCore::KeyReleased(Gameboy_Keys key) +{ + m_pInput->KeyReleased(key); +} + +void GearboyCore::Pause(bool paused) +{ + m_bPaused = paused; +} + +bool GearboyCore::IsPaused() +{ + return m_bPaused; +} + +void GearboyCore::ResetROM(bool forceDMG, Cartridge::CartridgeTypes forceType, bool forceGBA) +{ + if (m_pCartridge->IsLoadedROM()) + { + m_bForceDMG = forceDMG; + Reset(m_bForceDMG ? false : m_pCartridge->IsCGB(), forceGBA); + m_pMemory->LoadBank0and1FromROM(m_pCartridge->GetTheROM()); + AddMemoryRules(forceType); +#ifndef GEARBOY_DISABLE_DISASSEMBLER + m_pProcessor->Disassemble(m_pProcessor->GetState()->PC->GetValue()); +#endif + } +} + +void GearboyCore::ResetROMPreservingRAM(bool forceDMG, Cartridge::CartridgeTypes forceType, bool forceGBA) +{ + if (m_pCartridge->IsLoadedROM()) + { + Log("Resetting preserving RAM..."); + + using namespace std; + stringstream stream; + + m_pMemory->GetCurrentRule()->SaveRam(stream); + + ResetROM(forceDMG, forceType, forceGBA); + + stream.seekg(0, stream.end); + s32 size = (s32)stream.tellg(); + stream.seekg(0, stream.beg); + + m_pMemory->GetCurrentRule()->LoadRam(stream, size); + } +} + +void GearboyCore::ResetSound() +{ + m_pAudio->Reset(m_bCGB); +} + +void GearboyCore::SetSoundSampleRate(int rate) +{ + m_pAudio->SetSampleRate(rate); +} + +void GearboyCore::SetSoundVolume(float volume) +{ + m_pAudio->SetVolume(volume); +} + +u16* GearboyCore::GetDMGInternalPalette() +{ + return m_DMGPalette; +} + +void GearboyCore::SetDMGPalette(GB_Color& color1, GB_Color& color2, GB_Color& color3, + GB_Color& color4) +{ + bool format_565 = (m_pixelFormat == GB_PIXEL_RGB565) || (m_pixelFormat == GB_PIXEL_BGR565); + bool order_RGB = (m_pixelFormat == GB_PIXEL_RGB565) || (m_pixelFormat == GB_PIXEL_RGB555); + + int multiplier = format_565 ? 63 : 31; + int shift = format_565 ? 11 : 10; + + if (order_RGB) + { + m_DMGPalette[0] = (((color1.red * 31) / 255) << shift ) | (((color1.green * multiplier) / 255) << 5 ) | ((color1.blue * 31) / 255); + m_DMGPalette[1] = (((color2.red * 31) / 255) << shift ) | (((color2.green * multiplier) / 255) << 5 ) | ((color2.blue * 31) / 255); + m_DMGPalette[2] = (((color3.red * 31) / 255) << shift ) | (((color3.green * multiplier) / 255) << 5 ) | ((color3.blue * 31) / 255); + m_DMGPalette[3] = (((color4.red * 31) / 255) << shift ) | (((color4.green * multiplier) / 255) << 5 ) | ((color4.blue * 31) / 255); + } + else + { + m_DMGPalette[0] = (((color1.blue * 31) / 255) << shift ) | (((color1.green * multiplier) / 255) << 5 ) | ((color1.red * 31) / 255); + m_DMGPalette[1] = (((color2.blue * 31) / 255) << shift ) | (((color2.green * multiplier) / 255) << 5 ) | ((color2.red * 31) / 255); + m_DMGPalette[2] = (((color3.blue * 31) / 255) << shift ) | (((color3.green * multiplier) / 255) << 5 ) | ((color3.red * 31) / 255); + m_DMGPalette[3] = (((color4.blue * 31) / 255) << shift ) | (((color4.green * multiplier) / 255) << 5 ) | ((color4.red * 31) / 255); + } + + if (!format_565) + { + m_DMGPalette[0] |= 0x8000; + m_DMGPalette[1] |= 0x8000; + m_DMGPalette[2] |= 0x8000; + m_DMGPalette[3] |= 0x8000; + } +} + +void GearboyCore::SaveRam() +{ + SaveRam(NULL); +} + +void GearboyCore::SaveRam(const char* szPath, bool fullPath) +{ + if (m_pCartridge->IsLoadedROM() && m_pCartridge->HasBattery() && IsValidPointer(m_pMemory->GetCurrentRule())) + { + Log("Saving RAM..."); + + using namespace std; + + string path = ""; + + if (IsValidPointer(szPath)) + { + path += szPath; + + if (!fullPath) + { + path += "/"; + path += m_pCartridge->GetFileName(); + } + } + else + { + path = m_pCartridge->GetFilePath(); + } + + string::size_type i = path.rfind('.', path.length()); + + if (i != string::npos) { + path.replace(i + 1, 3, "sav"); + } + + Log("Save file: %s", path.c_str()); + + ofstream file(path.c_str(), ios::out | ios::binary); + + m_pMemory->GetCurrentRule()->SaveRam(file); + + Log("RAM saved"); + } +} + +void GearboyCore::LoadRam() +{ + LoadRam(NULL); +} + +void GearboyCore::LoadRam(const char* szPath, bool fullPath) +{ + if (m_pCartridge->IsLoadedROM() && m_pCartridge->HasBattery() && IsValidPointer(m_pMemory->GetCurrentRule())) + { + Log("Loading RAM..."); + + using namespace std; + + string sav_path = ""; + + if (IsValidPointer(szPath)) + { + sav_path += szPath; + + if (!fullPath) + { + sav_path += "/"; + sav_path += m_pCartridge->GetFileName(); + } + } + else + { + sav_path = m_pCartridge->GetFilePath(); + } + + string rom_path = sav_path; + + string::size_type i = sav_path.rfind('.', sav_path.length()); + + if (i != string::npos) { + sav_path.replace(i + 1, 3, "sav"); + } + + Log("Opening save file: %s", sav_path.c_str()); + + ifstream file; + + file.open(sav_path.c_str(), ios::in | ios::binary); + + // check for old .gearboy saves + if (file.fail()) + { + Log("Save file doesn't exist"); + string old_sav_file = rom_path + ".gearboy"; + + Log("Opening old save file: %s", old_sav_file.c_str()); + file.open(old_sav_file.c_str(), ios::in | ios::binary); + } + + if (!file.fail()) + { + file.seekg(0, file.end); + s32 fileSize = (s32)file.tellg(); + file.seekg(0, file.beg); + + if (m_pMemory->GetCurrentRule()->LoadRam(file, fileSize)) + { + Log("RAM loaded"); + } + else + { + Log("Save file size incorrect: %d", fileSize); + } + } + else + { + Log("Save file doesn't exist"); + } + } +} + +void GearboyCore::SaveState(int index) +{ + if (m_pMemory->IsBootromRegistryEnabled()) + { + Log("Save states disabled when running bootrom"); + return; + } + + Log("Creating save state %d...", index); + + SaveState(NULL, index); + + Log("Save state %d created", index); +} + +void GearboyCore::SaveState(const char* szPath, int index) +{ + if (m_pMemory->IsBootromRegistryEnabled()) + { + Log("Save states disabled when running bootrom"); + return; + } + + Log("Saving state..."); + + using namespace std; + + size_t size; + SaveState(NULL, size); + + u8* buffer = new u8[size]; + string path = ""; + + if (IsValidPointer(szPath)) + { + path += szPath; + path += "/"; + path += m_pCartridge->GetFileName(); + } + else + { + path = m_pCartridge->GetFilePath(); + } + + string::size_type i = path.rfind('.', path.length()); + + if (i != string::npos) { + path.replace(i + 1, 3, "state"); + } + + std::stringstream sstm; + + if (index < 0) + sstm << szPath; + else + sstm << path << index; + + Log("Save state file: %s", sstm.str().c_str()); + + ofstream file(sstm.str().c_str(), ios::out | ios::binary); + + SaveState(file, size); + + SafeDeleteArray(buffer); + + file.close(); + + Log("Save state created"); +} + +bool GearboyCore::SaveState(u8* buffer, size_t& size) +{ + if (m_pMemory->IsBootromRegistryEnabled()) + { + Log("Save states disabled when running bootrom"); + return false; + } + + bool ret = false; + + if (m_pCartridge->IsLoadedROM() && IsValidPointer(m_pMemory->GetCurrentRule())) + { + using namespace std; + + stringstream stream; + + if (SaveState(stream, size)) + ret = true; + + if (IsValidPointer(buffer)) + { + Log("Saving state to buffer [%d bytes]...", size); + memcpy(buffer, stream.str().c_str(), size); + ret = true; + } + } + else + { + Log("Invalid rom or memory rule."); + } + + return ret; +} + +bool GearboyCore::SaveState(std::ostream& stream, size_t& size) +{ + if (m_pMemory->IsBootromRegistryEnabled()) + { + Log("Save states disabled when running bootrom"); + return false; + } + + if (m_pCartridge->IsLoadedROM() && IsValidPointer(m_pMemory->GetCurrentRule())) + { + Log("Gathering save state data..."); + + using namespace std; + + m_pMemory->SaveState(stream); + m_pProcessor->SaveState(stream); + m_pVideo->SaveState(stream); + m_pInput->SaveState(stream); + m_pAudio->SaveState(stream); + m_pMemory->GetCurrentRule()->SaveState(stream); + + size = static_cast<size_t>(stream.tellp()); + + size += (sizeof(u32) * 2); + + u32 header_magic = SAVESTATE_MAGIC; + u32 header_size = static_cast<u32>(size); + + stream.write(reinterpret_cast<const char*> (&header_magic), sizeof(header_magic)); + stream.write(reinterpret_cast<const char*> (&header_size), sizeof(header_size)); + + Log("Save state size: %d", static_cast<size_t>(stream.tellp())); + + return true; + } + + Log("Invalid rom or memory rule."); + + return false; +} + +void GearboyCore::LoadState(int index) +{ + if (m_pMemory->IsBootromRegistryEnabled()) + { + Log("Save states disabled when running bootrom"); + return; + } + + Log("Loading save state %d...", index); + + LoadState(NULL, index); + + Log("State %d file loaded", index); +} + +void GearboyCore::LoadState(const char* szPath, int index) +{ + if (m_pMemory->IsBootromRegistryEnabled()) + { + Log("Save states disabled when running bootrom"); + return; + } + + Log("Loading save state..."); + + using namespace std; + + string sav_path = ""; + + if (IsValidPointer(szPath)) + { + sav_path += szPath; + sav_path += "/"; + sav_path += m_pCartridge->GetFileName(); + } + else + { + sav_path = m_pCartridge->GetFilePath(); + } + + string rom_path = sav_path; + + string::size_type i = sav_path.rfind('.', sav_path.length()); + + if (i != string::npos) { + sav_path.replace(i + 1, 3, "state"); + } + + std::stringstream sstm; + + if (index < 0) + sstm << szPath; + else + sstm << sav_path << index; + + Log("Opening save file: %s", sstm.str().c_str()); + + ifstream file; + + file.open(sstm.str().c_str(), ios::in | ios::binary); + + if (!file.fail()) + { + if (LoadState(file)) + { + Log("Save state loaded"); + } + } + else + { + Log("Save state file doesn't exist"); + } + + file.close(); +} + +bool GearboyCore::LoadState(const u8* buffer, size_t size) +{ + if (m_pMemory->IsBootromRegistryEnabled()) + { + Log("Save states disabled when running bootrom"); + return false; + } + + if (m_pCartridge->IsLoadedROM() && IsValidPointer(m_pMemory->GetCurrentRule()) && (size > 0) && IsValidPointer(buffer)) + { + Log("Gathering load state data [%d bytes]...", size); + + using namespace std; + + stringstream stream; + + stream.write(reinterpret_cast<const char*> (buffer), size); + + return LoadState(stream); + } + + Log("Invalid rom or memory rule."); + + return false; +} + +bool GearboyCore::LoadState(std::istream& stream) +{ + if (m_pMemory->IsBootromRegistryEnabled()) + { + Log("Save states disabled when running bootrom"); + return false; + } + + if (m_pCartridge->IsLoadedROM() && IsValidPointer(m_pMemory->GetCurrentRule())) + { + using namespace std; + + u32 header_magic = 0; + u32 header_size = 0; + + stream.seekg(0, ios::end); + size_t size = static_cast<size_t>(stream.tellg()); + + Log("Load state stream size: %d", size); + + stream.seekg(size - (2 * sizeof(u32)), ios::beg); + stream.read(reinterpret_cast<char*> (&header_magic), sizeof(header_magic)); + stream.read(reinterpret_cast<char*> (&header_size), sizeof(header_size)); + stream.seekg(0, ios::beg); + + Log("Load state magic: 0x%08x", header_magic); + Log("Load state size: %d", header_size); + + if ((header_size == size) && (header_magic == SAVESTATE_MAGIC)) + { + Log("Loading state..."); + + m_pMemory->LoadState(stream); + m_pProcessor->LoadState(stream); + m_pVideo->LoadState(stream); + m_pInput->LoadState(stream); + m_pAudio->LoadState(stream); + m_pMemory->GetCurrentRule()->LoadState(stream); + + return true; + } + else + { + Log("Invalid save state size"); + } + } + else + { + Log("Invalid rom or memory rule"); + } + + return false; +} + +void GearboyCore::SetCheat(const char* szCheat) +{ + std::string s = szCheat; + if ((s.length() == 7) || (s.length() == 11)) + { + m_pCartridge->SetGameGenieCheat(szCheat); + if (m_pCartridge->IsLoadedROM()) + m_pMemory->LoadBank0and1FromROM(m_pCartridge->GetTheROM()); + } + else + { + m_pProcessor->SetGameSharkCheat(szCheat); + } +} + +void GearboyCore::ClearCheats() +{ + m_pCartridge->ClearGameGenieCheats(); + m_pProcessor->ClearGameSharkCheats(); + if (m_pCartridge->IsLoadedROM()) + m_pMemory->LoadBank0and1FromROM(m_pCartridge->GetTheROM()); +} + +void GearboyCore::SetRamModificationCallback(RamChangedCallback callback) +{ + m_pRamChangedCallback = callback; +} + +bool GearboyCore::IsCGB() +{ + return m_bCGB; +} + +bool GearboyCore::IsGBA() +{ + return m_bGBA; +} + +void GearboyCore::InitDMGPalette() +{ + GB_Color color[4]; + + color[0].red = 0x87; + color[0].green = 0x96; + color[0].blue = 0x03; + + color[1].red = 0x4d; + color[1].green = 0x6b; + color[1].blue = 0x03; + + color[2].red = 0x2b; + color[2].green = 0x55; + color[2].blue = 0x03; + + color[3].red = 0x14; + color[3].green = 0x44; + color[3].blue = 0x03; + + SetDMGPalette(color[0], color[1], color[2], color[3]); +} + +void GearboyCore::InitMemoryRules() +{ + m_pIORegistersMemoryRule = new IORegistersMemoryRule(m_pProcessor, m_pMemory, m_pVideo, m_pInput, m_pAudio); + + m_pCommonMemoryRule = new CommonMemoryRule(m_pMemory); + + m_pRomOnlyMemoryRule = new RomOnlyMemoryRule(m_pProcessor, m_pMemory, + m_pVideo, m_pInput, m_pCartridge, m_pAudio); + + m_pMBC1MemoryRule = new MBC1MemoryRule(m_pProcessor, m_pMemory, + m_pVideo, m_pInput, m_pCartridge, m_pAudio); + + m_pMultiMBC1MemoryRule = new MultiMBC1MemoryRule(m_pProcessor, m_pMemory, + m_pVideo, m_pInput, m_pCartridge, m_pAudio); + + m_pMBC2MemoryRule = new MBC2MemoryRule(m_pProcessor, m_pMemory, + m_pVideo, m_pInput, m_pCartridge, m_pAudio); + + m_pMBC3MemoryRule = new MBC3MemoryRule(m_pProcessor, m_pMemory, + m_pVideo, m_pInput, m_pCartridge, m_pAudio); + + m_pMBC5MemoryRule = new MBC5MemoryRule(m_pProcessor, m_pMemory, + m_pVideo, m_pInput, m_pCartridge, m_pAudio); + + m_pMemory->SetCurrentRule(m_pRomOnlyMemoryRule); + m_pMemory->SetIORule(m_pIORegistersMemoryRule); + m_pMemory->SetCommonRule(m_pCommonMemoryRule); +} + +bool GearboyCore::AddMemoryRules(Cartridge::CartridgeTypes forceType) +{ + m_pMemory->SetIORule(m_pIORegistersMemoryRule); + m_pMemory->SetCommonRule(m_pCommonMemoryRule); + + Cartridge::CartridgeTypes type = m_pCartridge->GetType(); + + bool notSupported = false; + + if (forceType != Cartridge::CartridgeNotSupported) + type = forceType; + + switch (type) + { + case Cartridge::CartridgeNoMBC: + m_pMemory->SetCurrentRule(m_pRomOnlyMemoryRule); + break; + case Cartridge::CartridgeMBC1: + m_pMemory->SetCurrentRule(m_pMBC1MemoryRule); + break; + case Cartridge::CartridgeMBC1Multi: + m_pMemory->SetCurrentRule(m_pMultiMBC1MemoryRule); + break; + case Cartridge::CartridgeMBC2: + m_pMemory->SetCurrentRule(m_pMBC2MemoryRule); + break; + case Cartridge::CartridgeMBC3: + m_pMemory->SetCurrentRule(m_pMBC3MemoryRule); + break; + case Cartridge::CartridgeMBC5: + m_pMemory->SetCurrentRule(m_pMBC5MemoryRule); + break; + case Cartridge::CartridgeNotSupported: + notSupported = true; + break; + default: + notSupported = true; + } + + if (!notSupported) + { + m_pMemory->GetCurrentRule()->SetRamChangedCallback(m_pRamChangedCallback); + } + + return !notSupported; +} + +void GearboyCore::Reset(bool bCGB, bool bGBA) +{ + m_bCGB = bCGB; + m_bGBA = bGBA; + + if (m_bGBA && m_bCGB) + { + Log("Reset: Switching to Game Boy Advance"); + } + else if (m_bCGB) + { + Log("Reset: Switching to Game Boy Color"); + } + else + { + Log("Reset: Defaulting to Game Boy DMG"); + } + + m_pMemory->Reset(m_bCGB); + m_pProcessor->Reset(m_bCGB, m_bGBA); + m_pVideo->Reset(m_bCGB); + m_pAudio->Reset(m_bCGB); + m_pInput->Reset(); + m_pCartridge->UpdateCurrentRTC(); + m_iRTCUpdateCount = 0; + + m_pCommonMemoryRule->Reset(m_bCGB); + m_pRomOnlyMemoryRule->Reset(m_bCGB); + m_pMBC1MemoryRule->Reset(m_bCGB); + m_pMultiMBC1MemoryRule->Reset(m_bCGB); + m_pMBC2MemoryRule->Reset(m_bCGB); + m_pMBC3MemoryRule->Reset(m_bCGB); + m_pMBC5MemoryRule->Reset(m_bCGB); + m_pIORegistersMemoryRule->Reset(m_bCGB); + + m_bPaused = false; +} + +void GearboyCore::RenderDMGFrame(u16* pFrameBuffer) const +{ + if (IsValidPointer(pFrameBuffer)) + { + int pixels = GAMEBOY_WIDTH * GAMEBOY_HEIGHT; + const u8* pGameboyFrameBuffer = m_pVideo->GetFrameBuffer(); + + for (int i = 0; i < pixels; i++) + { + pFrameBuffer[i] = m_DMGPalette[pGameboyFrameBuffer[i]]; + } + } +} diff --git a/gearboy/src/GearboyCore.h b/gearboy/src/GearboyCore.h new file mode 100644 index 00000000..85ce805e --- /dev/null +++ b/gearboy/src/GearboyCore.h @@ -0,0 +1,118 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#ifndef CORE_H +#define CORE_H + +#include "definitions.h" +#include "Cartridge.h" + +class Memory; +class Processor; +class Video; +class Audio; +class Input; +class CommonMemoryRule; +class IORegistersMemoryRule; +class RomOnlyMemoryRule; +class MBC1MemoryRule; +class MBC2MemoryRule; +class MBC3MemoryRule; +class MBC5MemoryRule; +class MultiMBC1MemoryRule; +class MemoryRule; + +class GearboyCore +{ +public: + GearboyCore(); + ~GearboyCore(); + void Init(GB_Color_Format pixelFormat = GB_PIXEL_RGB565); + bool RunToVBlank(u16* pFrameBuffer, s16* pSampleBuffer, int* pSampleCount, bool bDMGbuffer = false, bool step = false, bool stopOnBreakpoints = false); + bool LoadROM(const char* szFilePath, bool forceDMG, Cartridge::CartridgeTypes forceType = Cartridge::CartridgeNotSupported, bool forceGBA = false); + bool LoadROMFromBuffer(const u8* buffer, int size, bool forceDMG, Cartridge::CartridgeTypes forceType = Cartridge::CartridgeNotSupported, bool forceGBA = false); + void SaveDisassembledROM(); + void SaveMemoryDump(); + void KeyPressed(Gameboy_Keys key); + void KeyReleased(Gameboy_Keys key); + void Pause(bool paused); + bool IsPaused(); + void ResetROM(bool forceDMG, Cartridge::CartridgeTypes forceType = Cartridge::CartridgeNotSupported, bool forceGBA = false); + void ResetROMPreservingRAM(bool forceDMG, Cartridge::CartridgeTypes forceType = Cartridge::CartridgeNotSupported, bool forceGBA = false); + void ResetSound(); + void SetSoundSampleRate(int rate); + void SetSoundVolume(float volume); + void SetDMGPalette(GB_Color& color1, GB_Color& color2, GB_Color& color3, GB_Color& color4); + u16* GetDMGInternalPalette(); + void SaveRam(); + void SaveRam(const char* szPath, bool fullPath = false); + void LoadRam(); + void LoadRam(const char* szPath, bool fullPath = false); + void SaveState(int index); + void SaveState(const char* szPath, int index); + bool SaveState(u8* buffer, size_t& size); + bool SaveState(std::ostream& stream, size_t& size); + void LoadState(int index); + void LoadState(const char* szPath, int index); + bool LoadState(const u8* buffer, size_t size); + bool LoadState(std::istream& stream); + void SetCheat(const char* szCheat); + void ClearCheats(); + void SetRamModificationCallback(RamChangedCallback callback); + bool IsCGB(); + bool IsGBA(); + Memory* GetMemory(); + Cartridge* GetCartridge(); + Processor* GetProcessor(); + Audio* GetAudio(); + Video* GetVideo(); + +private: + void RenderDMGFrame(u16* pFrameBuffer) const; + void InitDMGPalette(); + void InitMemoryRules(); + bool AddMemoryRules(Cartridge::CartridgeTypes forceType = Cartridge::CartridgeNotSupported); + void Reset(bool bCGB, bool bGBA); + +private: + Memory* m_pMemory; + Processor* m_pProcessor; + Video* m_pVideo; + Audio* m_pAudio; + Input* m_pInput; + Cartridge* m_pCartridge; + CommonMemoryRule* m_pCommonMemoryRule; + IORegistersMemoryRule* m_pIORegistersMemoryRule; + RomOnlyMemoryRule* m_pRomOnlyMemoryRule; + MBC1MemoryRule* m_pMBC1MemoryRule; + MBC2MemoryRule* m_pMBC2MemoryRule; + MBC3MemoryRule* m_pMBC3MemoryRule; + MBC5MemoryRule* m_pMBC5MemoryRule; + MultiMBC1MemoryRule* m_pMultiMBC1MemoryRule; + bool m_bCGB; + bool m_bGBA; + bool m_bPaused; + u16 m_DMGPalette[4]; + bool m_bForceDMG; + int m_iRTCUpdateCount; + RamChangedCallback m_pRamChangedCallback; + GB_Color_Format m_pixelFormat; +}; + +#endif /* CORE_H */ diff --git a/gearboy/src/IORegistersMemoryRule.cpp b/gearboy/src/IORegistersMemoryRule.cpp new file mode 100644 index 00000000..6c6e6e72 --- /dev/null +++ b/gearboy/src/IORegistersMemoryRule.cpp @@ -0,0 +1,41 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#include "IORegistersMemoryRule.h" + +IORegistersMemoryRule::IORegistersMemoryRule(Processor* pProcessor, + Memory* pMemory, Video* pVideo, Input* pInput, Audio* pAudio) +{ + m_pProcessor = pProcessor; + m_pMemory = pMemory; + m_pVideo = pVideo; + m_pInput = pInput; + m_pAudio = pAudio; + m_bCGB = false; +} + +IORegistersMemoryRule::~IORegistersMemoryRule() +{ +} + +void IORegistersMemoryRule::Reset(bool bCGB) +{ + m_bCGB = bCGB; +} + diff --git a/gearboy/src/IORegistersMemoryRule.h b/gearboy/src/IORegistersMemoryRule.h new file mode 100644 index 00000000..8c7639f3 --- /dev/null +++ b/gearboy/src/IORegistersMemoryRule.h @@ -0,0 +1,539 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#ifndef IOREGISTERSMEMORYRULE_H +#define IOREGISTERSMEMORYRULE_H + +#include "definitions.h" + +class Video; +class Processor; +class Input; +class Audio; +class Memory; + +class IORegistersMemoryRule +{ +public: + IORegistersMemoryRule(Processor* pProcessor, Memory* pMemory, Video* pVideo, Input* pInput, Audio* pAudio); + ~IORegistersMemoryRule(); + u8 PerformRead(u16 address); + void PerformWrite(u16 address, u8 value); + void Reset(bool bCGB); + +private: + Processor* m_pProcessor; + Memory* m_pMemory; + Video* m_pVideo; + Input* m_pInput; + Audio* m_pAudio; + bool m_bCGB; +}; + +#include "Video.h" +#include "Processor.h" +#include "Input.h" +#include "Audio.h" +#include "Memory.h" + +inline u8 IORegistersMemoryRule::PerformRead(u16 address) +{ + switch (address) + { + case 0xFF00: + { + // P1 + return m_pInput->Read(); + } + case 0xFF03: + { + // UNDOCUMENTED + return 0xFF; + } + case 0xFF07: + { + // TAC + return m_pMemory->Retrieve(0xFF07) | 0xF8; + } + case 0xFF08: + case 0xFF09: + case 0xFF0A: + case 0xFF0B: + case 0xFF0C: + case 0xFF0D: + case 0xFF0E: + { + // UNDOCUMENTED + return 0xFF; + } + case 0xFF0F: + { + // IF + return m_pMemory->Retrieve(0xFF0F) | 0xE0; + } + case 0xFF10: + case 0xFF11: + case 0xFF12: + case 0xFF13: + case 0xFF14: + case 0xFF15: + case 0xFF16: + case 0xFF17: + case 0xFF18: + case 0xFF19: + case 0xFF1A: + case 0xFF1B: + case 0xFF1C: + case 0xFF1D: + case 0xFF1E: + case 0xFF1F: + case 0xFF20: + case 0xFF21: + case 0xFF22: + case 0xFF23: + case 0xFF24: + case 0xFF25: + case 0xFF26: + case 0xFF27: + case 0xFF28: + case 0xFF29: + case 0xFF2A: + case 0xFF2B: + case 0xFF2C: + case 0xFF2D: + case 0xFF2E: + case 0xFF2F: + case 0xFF30: + case 0xFF31: + case 0xFF32: + case 0xFF33: + case 0xFF34: + case 0xFF35: + case 0xFF36: + case 0xFF37: + case 0xFF38: + case 0xFF39: + case 0xFF3A: + case 0xFF3B: + case 0xFF3C: + case 0xFF3D: + case 0xFF3E: + case 0xFF3F: + { + // SOUND REGISTERS + return m_pAudio->ReadAudioRegister(address); + } + case 0xFF41: + { + // STAT + return m_pMemory->Retrieve(0xFF41) | 0x80; + } + case 0xFF44: + { + // LY + return (m_pVideo->IsScreenEnabled() ? m_pMemory->Retrieve(0xFF44) : 0x00); + } + case 0xFF4C: + { + // UNDOCUMENTED + return 0xFF; + } + case 0xFF4F: + { + // VBK + return m_pMemory->Retrieve(0xFF4F) | 0xFE; + } + case 0xFF51: + { + // HDMA1 + return (m_bCGB ? m_pMemory->GetHDMARegister(1) : m_pMemory->Retrieve(address)); + } + case 0xFF52: + { + // HDMA2 + return (m_bCGB ? m_pMemory->GetHDMARegister(2) : m_pMemory->Retrieve(address)); + } + case 0xFF53: + { + // HDMA3 + return (m_bCGB ? m_pMemory->GetHDMARegister(3) : m_pMemory->Retrieve(address)); + } + case 0xFF54: + { + // HDMA4 + return (m_bCGB ? m_pMemory->GetHDMARegister(4) : m_pMemory->Retrieve(address)); + } + case 0xFF55: + { + // DMA CGB + return (m_bCGB ? m_pMemory->GetHDMARegister(5) : m_pMemory->Retrieve(address)); + } + case 0xFF68: + case 0xFF6A: + { + // BCPS, OCPS + return (m_bCGB ? (m_pMemory->Retrieve(address) | 0x40) : 0xC0); + } + case 0xFF69: + case 0xFF6B: + { + // BCPD, OCPD + return (m_bCGB ? m_pMemory->Retrieve(address) : 0xFF); + } + case 0xFF70: + { + // SVBK + return (m_bCGB ? (m_pMemory->Retrieve(0xFF70) | 0xF8) : 0xFF); + } + case 0xFF76: + { + // UNDOCUMENTED + return (m_bCGB ? 0x00 : 0xFF); + } + case 0xFF77: + { + // UNDOCUMENTED + return (m_bCGB ? 0x0 : 0xFF); + } + } + + return m_pMemory->Retrieve(address); +} + +inline void IORegistersMemoryRule::PerformWrite(u16 address, u8 value) +{ + switch (address) + { + case 0xFF00: + { + // P1 + m_pInput->Write(value); + break; + } + case 0xFF04: + { + // DIV + m_pProcessor->ResetDIVCycles(); + break; + } + case 0xFF07: + { + // TAC + value &= 0x07; + u8 current_tac = m_pMemory->Retrieve(0xFF07); + if ((current_tac & 0x03) != (value & 0x03)) + { + m_pProcessor->ResetTIMACycles(); + } + m_pMemory->Load(address, value); + break; + } + case 0xFF0F: + { + // IF + m_pMemory->Load(address, value & 0x1F); + break; + } + case 0xFF10: + case 0xFF11: + case 0xFF12: + case 0xFF13: + case 0xFF14: + case 0xFF15: + case 0xFF16: + case 0xFF17: + case 0xFF18: + case 0xFF19: + case 0xFF1A: + case 0xFF1B: + case 0xFF1C: + case 0xFF1D: + case 0xFF1E: + case 0xFF1F: + case 0xFF20: + case 0xFF21: + case 0xFF22: + case 0xFF23: + case 0xFF24: + case 0xFF25: + case 0xFF26: + case 0xFF27: + case 0xFF28: + case 0xFF29: + case 0xFF2A: + case 0xFF2B: + case 0xFF2C: + case 0xFF2D: + case 0xFF2E: + case 0xFF2F: + case 0xFF30: + case 0xFF31: + case 0xFF32: + case 0xFF33: + case 0xFF34: + case 0xFF35: + case 0xFF36: + case 0xFF37: + case 0xFF38: + case 0xFF39: + case 0xFF3A: + case 0xFF3B: + case 0xFF3C: + case 0xFF3D: + case 0xFF3E: + case 0xFF3F: + { + // SOUND REGISTERS + m_pAudio->WriteAudioRegister(address, value); + break; + } + case 0xFF40: + { + // LCDC + u8 current_lcdc = m_pMemory->Retrieve(0xFF40); + u8 new_lcdc = value; + m_pMemory->Load(address, new_lcdc); + if (!IsSetBit(current_lcdc, 5) && IsSetBit(new_lcdc, 5)) + m_pVideo->ResetWindowLine(); + if (IsSetBit(new_lcdc, 7)) + m_pVideo->EnableScreen(); + else + m_pVideo->DisableScreen(); + break; + } + case 0xFF41: + { + // STAT + u8 current_stat = m_pMemory->Retrieve(0xFF41) & 0x07; + u8 new_stat = (value & 0x78) | (current_stat & 0x07); + m_pMemory->Load(address, new_stat); + u8 lcdc = m_pMemory->Retrieve(0xFF40); + u8 signal = m_pVideo->GetIRQ48Signal(); + int mode = m_pVideo->GetCurrentStatusMode(); + signal &= ((new_stat >> 3) & 0x0F); + m_pVideo->SetIRQ48Signal(signal); + + if (IsSetBit(lcdc, 7)) + { + if (IsSetBit(new_stat, 3) && (mode == 0)) + { + if (signal == 0) + { + m_pProcessor->RequestInterrupt(Processor::LCDSTAT_Interrupt); + } + signal = SetBit(signal, 0); + } + if (IsSetBit(new_stat, 4) && (mode == 1)) + { + if (signal == 0) + { + m_pProcessor->RequestInterrupt(Processor::LCDSTAT_Interrupt); + } + signal = SetBit(signal, 1); + } + if (IsSetBit(new_stat, 5) && (mode == 2)) + { + if (signal == 0) + { + m_pProcessor->RequestInterrupt(Processor::LCDSTAT_Interrupt); + } + //signal = SetBit(signal, 2); + } + m_pVideo->CompareLYToLYC(); + } + break; + } + case 0xFF44: + { + // LY + u8 current_ly = m_pMemory->Retrieve(0xFF44); + if (IsSetBit(current_ly, 7) && !IsSetBit(value, 7)) + { + m_pVideo->DisableScreen(); + } + break; + } + case 0xFF45: + { + // LYC + u8 current_lyc = m_pMemory->Retrieve(0xFF45); + if (current_lyc != value) + { + m_pMemory->Load(0xFF45, value); + u8 lcdc = m_pMemory->Retrieve(0xFF40); + if (IsSetBit(lcdc, 7)) + { + m_pVideo->CompareLYToLYC(); + } + } + break; + } + case 0xFF46: + { + // DMA + m_pMemory->Load(address, value); + m_pMemory->PerformDMA(value); + break; + } + case 0xFF4D: + { + // KEY1 + if (m_bCGB) + { + u8 current_key1 = m_pMemory->Retrieve(0xFF4D); + m_pMemory->Load(address, (current_key1 & 0x80) | (value & 0x01) | 0x7E); + } + else + m_pMemory->Load(address, value); + break; + } + case 0xFF4F: + { + // VBK + if (m_bCGB) + { + value &= 0x01; + m_pMemory->SwitchCGBLCDRAM(value); + } + m_pMemory->Load(address, value); + break; + } + case 0xFF50: + { + // BOOT + if ((value & 0x01) > 0) + { + m_pMemory->DisableBootromRegistry(); + } + break; + } + case 0xFF51: + { + // HDMA1 + if (m_bCGB) + m_pMemory->SetHDMARegister(1, value); + else + m_pMemory->Load(address, value); + break; + } + case 0xFF52: + { + // HDMA2 + if (m_bCGB) + m_pMemory->SetHDMARegister(2, value); + else + m_pMemory->Load(address, value); + break; + } + case 0xFF53: + { + // HDMA3 + if (m_bCGB) + m_pMemory->SetHDMARegister(3, value); + else + m_pMemory->Load(address, value); + break; + } + case 0xFF54: + { + // HDMA4 + if (m_bCGB) + m_pMemory->SetHDMARegister(4, value); + else + m_pMemory->Load(address, value); + break; + } + case 0xFF55: + { + // DMA CGB + if (m_bCGB) + m_pMemory->SwitchCGBDMA(value); + else + m_pMemory->Load(address, value); + break; + } + case 0xFF68: + { + // BCPS + m_pMemory->Load(address, value); + if (m_bCGB) + m_pVideo->UpdatePaletteToSpecification(true, value); + break; + } + case 0xFF69: + { + // BCPD + m_pMemory->Load(address, value); + if (m_bCGB) + m_pVideo->SetColorPalette(true, value); + break; + } + case 0xFF6A: + { + // OCPS + m_pMemory->Load(address, value); + if (m_bCGB) + m_pVideo->UpdatePaletteToSpecification(false, value); + break; + } + case 0xFF6B: + { + // OCPD + m_pMemory->Load(address, value); + if (m_bCGB) + m_pVideo->SetColorPalette(false, value); + break; + } + case 0xFF6C: + { + // UNDOCUMENTED + m_pMemory->Load(0xFF6C, value | 0xFE); + break; + } + case 0xFF70: + { + // SVBK + if (m_bCGB) + { + value &= 0x07; + m_pMemory->SwitchCGBWRAM(value); + } + m_pMemory->Load(address, value); + break; + } + case 0xFF75: + { + // UNDOCUMENTED + m_pMemory->Load(0xFF75, value | 0x8F); + break; + } + case 0xFFFF: + { + // IE + m_pMemory->Load(address, value & 0x1F); + break; + } + default: + { + m_pMemory->Load(address, value); + } + } +} + +#endif /* IOREGISTERSMEMORYRULE_H */ diff --git a/gearboy/src/Input.cpp b/gearboy/src/Input.cpp new file mode 100644 index 00000000..acc1e3fc --- /dev/null +++ b/gearboy/src/Input.cpp @@ -0,0 +1,100 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#include "Input.h" +#include "Memory.h" +#include "Processor.h" + +Input::Input(Memory* pMemory, Processor* pProcessor) +{ + m_pMemory = pMemory; + m_pProcessor = pProcessor; + m_JoypadState = 0xFF; + m_P1 = 0xFF; + m_iInputCycles = 0; +} + +void Input::Init() +{ + Reset(); +} + +void Input::Reset() +{ + m_JoypadState = 0xFF; + m_P1 = 0xFF; + m_iInputCycles = 0; +} + +void Input::KeyPressed(Gameboy_Keys key) +{ + m_JoypadState = UnsetBit(m_JoypadState, key); +} + +void Input::KeyReleased(Gameboy_Keys key) +{ + m_JoypadState = SetBit(m_JoypadState, key); +} + +void Input::Update() +{ + u8 current = m_P1 & 0xF0; + + switch (current & 0x30) + { + case 0x10: + { + u8 topJoypad = (m_JoypadState >> 4) & 0x0F; + current |= topJoypad; + break; + } + case 0x20: + { + u8 bottomJoypad = m_JoypadState & 0x0F; + current |= bottomJoypad; + break; + } + case 0x30: + current |= 0x0F; + break; + } + + if ((m_P1 & ~current & 0x0F) != 0) + m_pProcessor->RequestInterrupt(Processor::Joypad_Interrupt); + + m_P1 = current; +} + +void Input::SaveState(std::ostream& stream) +{ + using namespace std; + + stream.write(reinterpret_cast<const char*> (&m_JoypadState), sizeof(m_JoypadState)); + stream.write(reinterpret_cast<const char*> (&m_P1), sizeof(m_P1)); + stream.write(reinterpret_cast<const char*> (&m_iInputCycles), sizeof(m_iInputCycles)); +} + +void Input::LoadState(std::istream& stream) +{ + using namespace std; + + stream.read(reinterpret_cast<char*> (&m_JoypadState), sizeof(m_JoypadState)); + stream.read(reinterpret_cast<char*> (&m_P1), sizeof(m_P1)); + stream.read(reinterpret_cast<char*> (&m_iInputCycles), sizeof(m_iInputCycles)); +} diff --git a/gearboy/src/Input.h b/gearboy/src/Input.h new file mode 100644 index 00000000..c15b2711 --- /dev/null +++ b/gearboy/src/Input.h @@ -0,0 +1,76 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#ifndef INPUT_H +#define INPUT_H + +#include "definitions.h" + +class Memory; +class Processor; + +class Input +{ +public: + Input(Memory* pMemory, Processor* pProcessor); + void Init(); + void Reset(); + void Tick(unsigned int clockCycles); + void KeyPressed(Gameboy_Keys key); + void KeyReleased(Gameboy_Keys key); + void Write(u8 value); + u8 Read(); + void SaveState(std::ostream& stream); + void LoadState(std::istream& stream); + +private: + void Update(); + +private: + Memory* m_pMemory; + Processor* m_pProcessor; + u8 m_JoypadState; + u8 m_P1; + int m_iInputCycles; +}; + +inline void Input::Tick(unsigned int clockCycles) +{ + m_iInputCycles += clockCycles; + + // Joypad Poll Speed (64 Hz) + if (m_iInputCycles >= 65536) + { + m_iInputCycles -= 65536; + Update(); + } +} + +inline void Input::Write(u8 value) +{ + m_P1 = (m_P1 & 0xCF) | (value & 0x30); + Update(); +} + +inline u8 Input::Read() +{ + return m_P1; +} + +#endif /* INPUT_H */ diff --git a/gearboy/src/MBC1MemoryRule.cpp b/gearboy/src/MBC1MemoryRule.cpp new file mode 100644 index 00000000..fac1e55a --- /dev/null +++ b/gearboy/src/MBC1MemoryRule.cpp @@ -0,0 +1,313 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#include "MBC1MemoryRule.h" +#include "Video.h" +#include "Memory.h" +#include "Processor.h" +#include "Input.h" +#include "Cartridge.h" + +const int kMBC1RamBanksSize = 0x8000; + +MBC1MemoryRule::MBC1MemoryRule(Processor* pProcessor, + Memory* pMemory, Video* pVideo, Input* pInput, + Cartridge* pCartridge, Audio* pAudio) : MemoryRule(pProcessor, +pMemory, pVideo, pInput, pCartridge, pAudio) +{ + m_pRAMBanks = new u8[kMBC1RamBanksSize]; + Reset(false); +} + +MBC1MemoryRule::~MBC1MemoryRule() +{ + SafeDeleteArray(m_pRAMBanks); +} + +void MBC1MemoryRule::Reset(bool bCGB) +{ + m_bCGB = bCGB; + m_iMode = 0; + m_iCurrentRAMBank = 0; + m_iCurrentROMBank = 1; + m_HigherRomBankBits = 0; + m_bRamEnabled = false; + for (int i = 0; i < kMBC1RamBanksSize; i++) + m_pRAMBanks[i] = 0xFF; + m_CurrentROMAddress = 0x4000; + m_CurrentRAMAddress = 0; +} + +u8 MBC1MemoryRule::PerformRead(u16 address) +{ + switch (address & 0xE000) + { + case 0x4000: + case 0x6000: + { + u8* pROM = m_pCartridge->GetTheROM(); + return pROM[(address - 0x4000) + m_CurrentROMAddress]; + } + case 0xA000: + { + if (m_bRamEnabled) + { + if (m_iMode == 0) + { + if ((m_pCartridge->GetRAMSize() == 1) && (address >= 0xA800)) + { + // only 2KB of ram + Log("--> ** Attempting to read from invalid RAM %X", address); + } + return m_pRAMBanks[address - 0xA000]; + } + else + return m_pRAMBanks[(address - 0xA000) + m_CurrentRAMAddress]; + } + else + { + Log("--> ** Attempting to read from disabled ram %X", address); + return 0xFF; + } + } + default: + { + return m_pMemory->Retrieve(address); + } + } +} + +void MBC1MemoryRule::PerformWrite(u16 address, u8 value) +{ + switch (address & 0xE000) + { + case 0x0000: + { + if (m_pCartridge->GetRAMSize() > 0) + { + bool previous = m_bRamEnabled; + m_bRamEnabled = ((value & 0x0F) == 0x0A); + + if (IsValidPointer(m_pRamChangedCallback) && previous && !m_bRamEnabled) + { + (*m_pRamChangedCallback)(); + } + } + break; + } + case 0x2000: + { + if (m_iMode == 0) + { + m_iCurrentROMBank = (value & 0x1F) | (m_HigherRomBankBits << 5); + } + else + { + m_iCurrentROMBank = value & 0x1F; + } + + if (m_iCurrentROMBank == 0x00 || m_iCurrentROMBank == 0x20 + || m_iCurrentROMBank == 0x40 || m_iCurrentROMBank == 0x60) + m_iCurrentROMBank++; + + m_iCurrentROMBank &= (m_pCartridge->GetROMBankCount() - 1); + m_CurrentROMAddress = m_iCurrentROMBank * 0x4000; + break; + } + case 0x4000: + { + if (m_iMode == 1) + { + m_iCurrentRAMBank = value & 0x03; + m_iCurrentRAMBank &= (m_pCartridge->GetRAMBankCount() - 1); + m_CurrentRAMAddress = m_iCurrentRAMBank * 0x2000; + } + else + { + m_HigherRomBankBits = value & 0x03; + m_iCurrentROMBank = (m_iCurrentROMBank & 0x1F) | (m_HigherRomBankBits << 5); + + if (m_iCurrentROMBank == 0x00 || m_iCurrentROMBank == 0x20 + || m_iCurrentROMBank == 0x40 || m_iCurrentROMBank == 0x60) + m_iCurrentROMBank++; + + m_iCurrentROMBank &= (m_pCartridge->GetROMBankCount() - 1); + m_CurrentROMAddress = m_iCurrentROMBank * 0x4000; + } + break; + } + case 0x6000: + { + if ((m_pCartridge->GetRAMSize() != 3) && (value & 0x01)) + { + Log("--> ** Attempting to change MBC1 to mode 1 with incorrect RAM banks %X %X", address, value); + } + else + { + m_iMode = value & 0x01; + } + break; + } + case 0xA000: + { + if (m_bRamEnabled) + { + if (m_iMode == 0) + { + if ((m_pCartridge->GetRAMSize() == 1) && (address >= 0xA800)) + { + // only 2KB of ram + Log("--> ** Attempting to write on invalid RAM %X %X", address, value); + } + + m_pRAMBanks[address - 0xA000] = value; + } + else + m_pRAMBanks[(address - 0xA000) + m_CurrentRAMAddress] = value; + } + else + { + Log("--> ** Attempting to write on RAM when ram is disabled %X %X", address, value); + } + break; + } + default: + { + m_pMemory->Load(address, value); + break; + } + } +} + +void MBC1MemoryRule::SaveRam(std::ostream &file) +{ + Log("MBC1MemoryRule save RAM..."); + Log("MBC1MemoryRule saving %d banks...", m_pCartridge->GetRAMBankCount()); + + u32 ramSize = m_pCartridge->GetRAMBankCount() * 0x2000; + + for (u32 i = 0; i < ramSize; i++) + { + u8 ram_byte = m_pRAMBanks[i]; + file.write(reinterpret_cast<const char*> (&ram_byte), 1); + } + + Log("MBC1MemoryRule save RAM done"); +} + +bool MBC1MemoryRule::LoadRam(std::istream &file, s32 fileSize) +{ + Log("MBC1MemoryRule load RAM..."); + Log("MBC1MemoryRule loading %d banks...", m_pCartridge->GetRAMBankCount()); + + s32 ramSize = m_pCartridge->GetRAMBankCount() * 0x2000; + + if ((fileSize > 0) && (fileSize != ramSize)) + { + Log("MBC1MemoryRule incorrect size. Expected: %d Found: %d", ramSize, fileSize); + return false; + } + else if (fileSize == 0) + { + // compatibility with old saves + u8 mode; + file.read(reinterpret_cast<char*> (&mode), 1); + Log("MBC1MemoryRule load RAM mode %d", mode); + } + + for (s32 i = 0; i < ramSize; i++) + { + u8 ram_byte = 0; + file.read(reinterpret_cast<char*> (&ram_byte), 1); + m_pRAMBanks[i] = ram_byte; + } + + Log("MBC1MemoryRule load RAM done"); + + return true; +} + +size_t MBC1MemoryRule::GetRamSize() +{ + return m_pCartridge->GetRAMBankCount() * 0x2000; +} + +u8* MBC1MemoryRule::GetRamBanks() +{ + return m_pRAMBanks; +} + +u8* MBC1MemoryRule::GetCurrentRamBank() +{ + return m_pRAMBanks + m_CurrentRAMAddress; +} + +int MBC1MemoryRule::GetCurrentRamBankIndex() +{ + return m_iCurrentRAMBank; +} + +u8* MBC1MemoryRule::GetCurrentRomBank1() +{ + u8* pROM = m_pCartridge->GetTheROM(); + return &pROM[m_CurrentROMAddress]; +} + +int MBC1MemoryRule::GetCurrentRomBank1Index() +{ + return m_iCurrentROMBank; +} + +u8* MBC1MemoryRule::GetRomBank0() +{ + return m_pMemory->GetMemoryMap() + 0x0000; +} + +int MBC1MemoryRule::GetCurrentRomBank0Index() +{ + return 0; +} + +void MBC1MemoryRule::SaveState(std::ostream& stream) +{ + using namespace std; + + stream.write(reinterpret_cast<const char*> (&m_iMode), sizeof(m_iMode)); + stream.write(reinterpret_cast<const char*> (&m_iCurrentRAMBank), sizeof(m_iCurrentRAMBank)); + stream.write(reinterpret_cast<const char*> (&m_iCurrentROMBank), sizeof(m_iCurrentROMBank)); + stream.write(reinterpret_cast<const char*> (&m_bRamEnabled), sizeof(m_bRamEnabled)); + stream.write(reinterpret_cast<const char*> (&m_HigherRomBankBits), sizeof(m_HigherRomBankBits)); + stream.write(reinterpret_cast<const char*> (m_pRAMBanks), kMBC1RamBanksSize); + stream.write(reinterpret_cast<const char*> (&m_CurrentROMAddress), sizeof(m_CurrentROMAddress)); + stream.write(reinterpret_cast<const char*> (&m_CurrentRAMAddress), sizeof(m_CurrentRAMAddress)); +} + +void MBC1MemoryRule::LoadState(std::istream& stream) +{ + using namespace std; + + stream.read(reinterpret_cast<char*> (&m_iMode), sizeof(m_iMode)); + stream.read(reinterpret_cast<char*> (&m_iCurrentRAMBank), sizeof(m_iCurrentRAMBank)); + stream.read(reinterpret_cast<char*> (&m_iCurrentROMBank), sizeof(m_iCurrentROMBank)); + stream.read(reinterpret_cast<char*> (&m_bRamEnabled), sizeof(m_bRamEnabled)); + stream.read(reinterpret_cast<char*> (&m_HigherRomBankBits), sizeof(m_HigherRomBankBits)); + stream.read(reinterpret_cast<char*> (m_pRAMBanks), kMBC1RamBanksSize); + stream.read(reinterpret_cast<char*> (&m_CurrentROMAddress), sizeof(m_CurrentROMAddress)); + stream.read(reinterpret_cast<char*> (&m_CurrentRAMAddress), sizeof(m_CurrentRAMAddress)); +} diff --git a/gearboy/src/MBC1MemoryRule.h b/gearboy/src/MBC1MemoryRule.h new file mode 100644 index 00000000..1e342459 --- /dev/null +++ b/gearboy/src/MBC1MemoryRule.h @@ -0,0 +1,58 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#ifndef MBC1MEMORYRULE_H +#define MBC1MEMORYRULE_H + +#include "MemoryRule.h" + +class MBC1MemoryRule : public MemoryRule +{ +public: + MBC1MemoryRule(Processor* pProcessor, Memory* pMemory, + Video* pVideo, Input* pInput, Cartridge* pCartridge, Audio* pAudio); + virtual ~MBC1MemoryRule(); + virtual u8 PerformRead(u16 address); + virtual void PerformWrite(u16 address, u8 value); + virtual void Reset(bool bCGB); + virtual void SaveRam(std::ostream &file); + virtual bool LoadRam(std::istream &file, s32 fileSize); + virtual size_t GetRamSize(); + virtual u8* GetRamBanks(); + virtual u8* GetCurrentRamBank(); + virtual int GetCurrentRamBankIndex(); + virtual u8* GetRomBank0(); + virtual int GetCurrentRomBank0Index(); + virtual u8* GetCurrentRomBank1(); + virtual int GetCurrentRomBank1Index(); + virtual void SaveState(std::ostream& stream); + virtual void LoadState(std::istream& stream); + +private: + int m_iMode; + int m_iCurrentRAMBank; + int m_iCurrentROMBank; + bool m_bRamEnabled; + u8 m_HigherRomBankBits; + u8* m_pRAMBanks; + int m_CurrentROMAddress; + int m_CurrentRAMAddress; +}; + +#endif /* MBC1MEMORYRULE_H */ diff --git a/gearboy/src/MBC2MemoryRule.cpp b/gearboy/src/MBC2MemoryRule.cpp new file mode 100644 index 00000000..e3d2c04d --- /dev/null +++ b/gearboy/src/MBC2MemoryRule.cpp @@ -0,0 +1,245 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#include "MBC2MemoryRule.h" +#include "Video.h" +#include "Memory.h" +#include "Processor.h" +#include "Input.h" +#include "Cartridge.h" + +MBC2MemoryRule::MBC2MemoryRule(Processor* pProcessor, + Memory* pMemory, Video* pVideo, Input* pInput, + Cartridge* pCartridge, Audio* pAudio) : MemoryRule(pProcessor, +pMemory, pVideo, pInput, pCartridge, pAudio) +{ + Reset(false); +} + +MBC2MemoryRule::~MBC2MemoryRule() +{ +} + +void MBC2MemoryRule::Reset(bool bCGB) +{ + m_bCGB = bCGB; + m_iCurrentROMBank = 1; + m_CurrentROMAddress = 0x4000; + m_bRamEnabled = false; +} + +u8 MBC2MemoryRule::PerformRead(u16 address) +{ + switch (address & 0xE000) + { + case 0x4000: + case 0x6000: + { + u8* pROM = m_pCartridge->GetTheROM(); + return pROM[(address - 0x4000) + m_CurrentROMAddress]; + } + case 0xA000: + { + if (address < 0xA200) + { + if (m_bRamEnabled) + return m_pMemory->Retrieve(address); + else + { + Log("--> ** Attempting to read from disabled ram %X", address); + return 0xFF; + } + } + else + { + Log("--> ** Attempting to read from ivalid RAM %X", address); + return 0x00; + } + } + default: + { + return m_pMemory->Retrieve(address); + } + } +} + +void MBC2MemoryRule::PerformWrite(u16 address, u8 value) +{ + switch (address & 0xE000) + { + case 0x0000: + { + if (!(address & 0x0100)) + { + bool previous = m_bRamEnabled; + m_bRamEnabled = ((value & 0x0F) == 0x0A); + + if (IsValidPointer(m_pRamChangedCallback) && previous && !m_bRamEnabled) + { + (*m_pRamChangedCallback)(); + } + } + else + { + Log("--> ** Attempting to write on invalid register %X %X", address, value); + } + break; + } + case 0x2000: + { + if (address & 0x0100) + { + m_iCurrentROMBank = value & 0x0F; + if (m_iCurrentROMBank == 0) + m_iCurrentROMBank = 1; + m_iCurrentROMBank &= (m_pCartridge->GetROMBankCount() - 1); + m_CurrentROMAddress = m_iCurrentROMBank * 0x4000; + } + else + { + Log("--> ** Attempting to write on invalid register %X %X", address, value); + } + break; + } + case 0x4000: + case 0x6000: + { + Log("--> ** Attempting to write on invalid address %X %X", address, value); + break; + } + case 0xA000: + { + if (address < 0xA200) + { + if (m_bRamEnabled) + { + m_pMemory->Load(address, value & 0x0F); + } + else + { + Log("--> ** Attempting to write on RAM when ram is disabled %X %X", address, value); + } + } + else + { + Log("--> ** Attempting to write on invalid RAM %X %X", address, value); + } + break; + } + default: + { + m_pMemory->Load(address, value); + break; + } + } +} + +void MBC2MemoryRule::SaveRam(std::ostream & file) +{ + Log("MBC2MemoryRule save RAM..."); + + for (int i = 0xA000; i < 0xA200; i++) + { + u8 ram_byte = m_pMemory->Retrieve(i); + file.write(reinterpret_cast<const char*> (&ram_byte), 1); + } + + Log("MBC2MemoryRule save RAM done"); +} + +bool MBC2MemoryRule::LoadRam(std::istream & file, s32 fileSize) +{ + Log("MBC2MemoryRule load RAM..."); + + if ((fileSize > 0) && (fileSize != 512)) + { + Log("MBC2MemoryRule incorrect size. Expected: 512 Found: %d", fileSize); + return false; + } + + for (int i = 0xA000; i < 0xA200; i++) + { + u8 ram_byte = 0; + file.read(reinterpret_cast<char*> (&ram_byte), 1); + m_pMemory->Load(i, ram_byte); + } + + Log("MBC2MemoryRule load RAM done"); + + return true; +} + +size_t MBC2MemoryRule::GetRamSize() +{ + return 0x200; +} + +u8* MBC2MemoryRule::GetRamBanks() +{ + return m_pMemory->GetMemoryMap() + 0xA000; +} + +u8* MBC2MemoryRule::GetCurrentRamBank() +{ + return m_pMemory->GetMemoryMap() + 0xA000; +} + +int MBC2MemoryRule::GetCurrentRamBankIndex() +{ + return 0; +} + +u8* MBC2MemoryRule::GetCurrentRomBank1() +{ + u8* pROM = m_pCartridge->GetTheROM(); + return &pROM[m_CurrentROMAddress]; +} + +u8* MBC2MemoryRule::GetRomBank0() +{ + return m_pMemory->GetMemoryMap() + 0x0000; +} + +int MBC2MemoryRule::GetCurrentRomBank0Index() +{ + return 0; +} + +int MBC2MemoryRule::GetCurrentRomBank1Index() +{ + return m_iCurrentROMBank; +} + +void MBC2MemoryRule::SaveState(std::ostream& stream) +{ + using namespace std; + + stream.write(reinterpret_cast<const char*> (&m_iCurrentROMBank), sizeof(m_iCurrentROMBank)); + stream.write(reinterpret_cast<const char*> (&m_bRamEnabled), sizeof(m_bRamEnabled)); + stream.write(reinterpret_cast<const char*> (&m_CurrentROMAddress), sizeof(m_CurrentROMAddress)); +} + +void MBC2MemoryRule::LoadState(std::istream& stream) +{ + using namespace std; + + stream.read(reinterpret_cast<char*> (&m_iCurrentROMBank), sizeof(m_iCurrentROMBank)); + stream.read(reinterpret_cast<char*> (&m_bRamEnabled), sizeof(m_bRamEnabled)); + stream.read(reinterpret_cast<char*> (&m_CurrentROMAddress), sizeof(m_CurrentROMAddress)); +} diff --git a/gearboy/src/MBC2MemoryRule.h b/gearboy/src/MBC2MemoryRule.h new file mode 100644 index 00000000..ba8b390d --- /dev/null +++ b/gearboy/src/MBC2MemoryRule.h @@ -0,0 +1,53 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#ifndef MBC2MEMORYRULE_H +#define MBC2MEMORYRULE_H + +#include "MemoryRule.h" + +class MBC2MemoryRule : public MemoryRule +{ +public: + MBC2MemoryRule(Processor* pProcessor, Memory* pMemory, + Video* pVideo, Input* pInput, Cartridge* pCartridge, Audio* pAudio); + virtual ~MBC2MemoryRule(); + virtual u8 PerformRead(u16 address); + virtual void PerformWrite(u16 address, u8 value); + virtual void Reset(bool bCGB); + virtual void SaveRam(std::ostream &file); + virtual bool LoadRam(std::istream &file, s32 fileSize); + virtual size_t GetRamSize(); + virtual u8* GetRamBanks(); + virtual u8* GetCurrentRamBank(); + virtual int GetCurrentRomBank0Index(); + virtual int GetCurrentRamBankIndex(); + virtual u8* GetRomBank0(); + virtual u8* GetCurrentRomBank1(); + virtual int GetCurrentRomBank1Index(); + virtual void SaveState(std::ostream& stream); + virtual void LoadState(std::istream& stream); + +private: + int m_iCurrentROMBank; + bool m_bRamEnabled; + int m_CurrentROMAddress; +}; + +#endif /* MBC2MEMORYRULE_H */ diff --git a/gearboy/src/MBC3MemoryRule.cpp b/gearboy/src/MBC3MemoryRule.cpp new file mode 100644 index 00000000..49d1f789 --- /dev/null +++ b/gearboy/src/MBC3MemoryRule.cpp @@ -0,0 +1,457 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#include "MBC3MemoryRule.h" +#include "Video.h" +#include "Memory.h" +#include "Processor.h" +#include "Input.h" +#include "Cartridge.h" + +MBC3MemoryRule::MBC3MemoryRule(Processor* pProcessor, + Memory* pMemory, Video* pVideo, Input* pInput, + Cartridge* pCartridge, Audio* pAudio) : MemoryRule(pProcessor, +pMemory, pVideo, pInput, pCartridge, pAudio) +{ + m_pRAMBanks = new u8[0x8000]; + Reset(false); +} + +MBC3MemoryRule::~MBC3MemoryRule() +{ + SafeDeleteArray(m_pRAMBanks); +} + +void MBC3MemoryRule::Reset(bool bCGB) +{ + m_bCGB = bCGB; + m_iCurrentRAMBank = 0; + m_iCurrentROMBank = 1; + m_bRamEnabled = false; + m_bRTCEnabled = false; + for (int i = 0; i < 0x8000; i++) + m_pRAMBanks[i] = 0xFF; + m_RTC.Seconds = 0; + m_RTC.Minutes = 0; + m_RTC.Hours = 0; + m_RTC.Days = 0; + m_RTC.Control = 0; + m_RTC.LatchedSeconds = 0; + m_RTC.LatchedMinutes = 0; + m_RTC.LatchedHours = 0; + m_RTC.LatchedDays = 0; + m_RTC.LatchedControl = 0; + m_RTC.LastTime = static_cast<s32>(m_pCartridge->GetCurrentRTC()); + m_RTC.padding = 0; + m_iRTCLatch = 0; + m_RTCRegister = 0; + m_RTCLastTimeCache = m_RTC.LastTime; + m_CurrentROMAddress = 0x4000; + m_CurrentRAMAddress = 0; +} + +u8 MBC3MemoryRule::PerformRead(u16 address) +{ + switch (address & 0xE000) + { + case 0x4000: + case 0x6000: + { + u8* pROM = m_pCartridge->GetTheROM(); + return pROM[(address - 0x4000) + m_CurrentROMAddress]; + } + case 0xA000: + { + if (m_iCurrentRAMBank >= 0) + { + if (m_bRamEnabled) + { + return m_pRAMBanks[(address - 0xA000) + m_CurrentRAMAddress]; + } + else + { + Log("--> ** Attempting to read from disabled ram %X", address); + return 0xFF; + } + } + else if (m_pCartridge->IsRTCPresent() && m_bRTCEnabled) + { + switch (m_RTCRegister) + { + case 0x08: + return m_RTC.LatchedSeconds; + break; + case 0x09: + return m_RTC.LatchedMinutes; + break; + case 0x0A: + return m_RTC.LatchedHours; + break; + case 0x0B: + return m_RTC.LatchedDays; + break; + case 0x0C: + return m_RTC.LatchedControl; + break; + default: + return 0xFF; + } + } + else + { + Log("--> ** Attempting to read from disabled RTC %X", address); + return 0xFF; + } + } + default: + { + return m_pMemory->Retrieve(address); + } + } +} + +void MBC3MemoryRule::PerformWrite(u16 address, u8 value) +{ + switch (address & 0xE000) + { + case 0x0000: + { + if (m_pCartridge->GetRAMSize() > 0) + { + bool previous = m_bRamEnabled; + m_bRamEnabled = ((value & 0x0F) == 0x0A); + + if (IsValidPointer(m_pRamChangedCallback) && previous && !m_bRamEnabled) + { + (*m_pRamChangedCallback)(); + } + } + m_bRTCEnabled = ((value & 0x0F) == 0x0A); + break; + } + case 0x2000: + { + m_iCurrentROMBank = value & 0x7F; + if (m_iCurrentROMBank == 0) + m_iCurrentROMBank = 1; + m_iCurrentROMBank &= (m_pCartridge->GetROMBankCount() - 1); + m_CurrentROMAddress = m_iCurrentROMBank * 0x4000; + break; + } + case 0x4000: + { + if ((value >= 0x08) && (value <= 0x0C)) + { + // RTC + if (m_pCartridge->IsRTCPresent() && m_bRTCEnabled) + { + m_RTCRegister = value; + m_iCurrentRAMBank = -1; + } + else + { + Log("--> ** Attempting to select RTC register when RTC is disabled or not present %X %X", address, value); + } + } + else if (value <= 0x03) + { + m_iCurrentRAMBank = value; + m_iCurrentRAMBank &= (m_pCartridge->GetRAMBankCount() - 1); + m_CurrentRAMAddress = m_iCurrentRAMBank * 0x2000; + } + else + { + Log("--> ** Attempting to select unkwon register %X %X", address, value); + } + break; + } + case 0x6000: + { + if (m_pCartridge->IsRTCPresent()) + { + // RTC Latch + if ((m_iRTCLatch == 0x00) && (value == 0x01)) + { + UpdateRTC(); + m_RTC.LatchedSeconds = m_RTC.Seconds; + m_RTC.LatchedMinutes = m_RTC.Minutes; + m_RTC.LatchedHours = m_RTC.Hours; + m_RTC.LatchedDays = m_RTC.Days; + m_RTC.LatchedControl = m_RTC.Control; + } + + m_iRTCLatch = value; + } + break; + } + case 0xA000: + { + if (m_iCurrentRAMBank >= 0) + { + if (m_bRamEnabled) + { + m_pRAMBanks[(address - 0xA000) + m_CurrentRAMAddress] = value; + } + else + { + Log("--> ** Attempting to write on RAM when ram is disabled %X %X", address, value); + } + } + else if (m_pCartridge->IsRTCPresent() && m_bRTCEnabled) + { + switch (m_RTCRegister) + { + case 0x08: + m_RTC.Seconds = value; + break; + case 0x09: + m_RTC.Minutes = value; + break; + case 0x0A: + m_RTC.Hours = value; + break; + case 0x0B: + m_RTC.Days = value; + break; + case 0x0C: + m_RTC.Control = (m_RTC.Control & 0x80) | (value & 0xC1); + break; + } + } + else + { + Log("--> ** Attempting to write on RTC when RTC is disabled or not present %X %X", address, value); + } + break; + } + default: + { + m_pMemory->Load(address, value); + break; + } + } +} + +void MBC3MemoryRule::UpdateRTC() +{ + s32 now = static_cast<s32>(m_pCartridge->GetCurrentRTC()); + + if (!IsSetBit(m_RTC.Control, 6) && (m_RTCLastTimeCache != now)) + { + m_RTCLastTimeCache = now; + s32 difference = now - m_RTC.LastTime; + m_RTC.LastTime = now; + + if (difference > 0) + { + m_RTC.Seconds += (s32) (difference % 60); + + if (m_RTC.Seconds > 59) + { + m_RTC.Seconds -= 60; + m_RTC.Minutes++; + } + + difference /= 60; + m_RTC.Minutes += (s32) (difference % 60); + + if (m_RTC.Minutes > 59) + { + m_RTC.Minutes -= 60; + m_RTC.Hours++; + } + + difference /= 60; + m_RTC.Hours += (s32) (difference % 24); + + if (m_RTC.Hours > 23) + { + m_RTC.Hours -= 24; + m_RTC.Days++; + } + + difference /= 24; + m_RTC.Days += (s32) (difference & 0xffffffff); + + if (m_RTC.Days > 0xFF) + { + m_RTC.Control = (m_RTC.Control & 0xC1) | 0x01; + + if (m_RTC.Days > 511) + { + m_RTC.Days %= 512; + m_RTC.Control |= 0x80; + m_RTC.Control &= 0xC0; + } + } + } + } +} + +void MBC3MemoryRule::SaveRam(std::ostream & file) +{ + Log("MBC3MemoryRule save RAM..."); + + for (int i = 0; i < 0x8000; i++) + { + u8 ram_byte = m_pRAMBanks[i]; + file.write(reinterpret_cast<const char*> (&ram_byte), 1); + } + + if (m_pCartridge->IsRTCPresent()) + { + file.write(reinterpret_cast<const char*> (&m_RTC), sizeof(m_RTC)); + } + + Log("MBC3MemoryRule save RAM done"); +} + +bool MBC3MemoryRule::LoadRam(std::istream & file, s32 fileSize) +{ + Log("MBC3MemoryRule load RAM..."); + + bool loadRTC = m_pCartridge->IsRTCPresent(); + + if (fileSize > 0) + { + if (fileSize < 0x8000) + { + Log("MBC3MemoryRule incorrect RAM size. Expected: %d Found: %d", 0x8000, fileSize); + return false; + } + + if (loadRTC) + { + s32 minExpectedSize = 0x8000 + 44; + s32 maxExpectedSize = 0x8000 + 48; + + if ((fileSize != minExpectedSize) && (fileSize != maxExpectedSize)) + { + Log("MBC3MemoryRule incorrect RTC size. MinExpected: %d MaxExpected: %d Found: %d", minExpectedSize, maxExpectedSize, fileSize); + } + + if (fileSize < minExpectedSize) + { + Log("MBC3MemoryRule ignoring RTC data"); + loadRTC = false; + } + } + } + + for (int i = 0; i < 0x8000; i++) + { + u8 ram_byte = 0; + file.read(reinterpret_cast<char*> (&ram_byte), 1); + m_pRAMBanks[i] = ram_byte; + } + + if (loadRTC) + { + file.read(reinterpret_cast<char*> (&m_RTC), 44); + } + + Log("MBC3MemoryRule load RAM done"); + + return true; +} + +size_t MBC3MemoryRule::GetRamSize() +{ + return 0x8000; +} + +size_t MBC3MemoryRule::GetRTCSize() +{ + return m_pCartridge->IsRTCPresent() ? sizeof(m_RTC) : 0; +} + +u8* MBC3MemoryRule::GetRamBanks() +{ + return m_pRAMBanks; +} + +u8* MBC3MemoryRule::GetCurrentRamBank() +{ + return m_pRAMBanks + m_CurrentRAMAddress; +} + +int MBC3MemoryRule::GetCurrentRamBankIndex() +{ + return m_iCurrentRAMBank > 0 ? m_iCurrentRAMBank : 0; +} + +u8* MBC3MemoryRule::GetRomBank0() +{ + return m_pMemory->GetMemoryMap() + 0x0000; +} + +int MBC3MemoryRule::GetCurrentRomBank0Index() +{ + return 0; +} + +u8* MBC3MemoryRule::GetCurrentRomBank1() +{ + u8* pROM = m_pCartridge->GetTheROM(); + return &pROM[m_CurrentROMAddress]; +} + +int MBC3MemoryRule::GetCurrentRomBank1Index() +{ + return m_iCurrentROMBank; +} + +u8* MBC3MemoryRule::GetRTCMemory() +{ + return m_pCartridge->IsRTCPresent() ? reinterpret_cast<u8*>(&m_RTC) : NULL; +} + +void MBC3MemoryRule::SaveState(std::ostream& stream) +{ + using namespace std; + + stream.write(reinterpret_cast<const char*> (&m_iCurrentRAMBank), sizeof(m_iCurrentRAMBank)); + stream.write(reinterpret_cast<const char*> (&m_iCurrentROMBank), sizeof(m_iCurrentROMBank)); + stream.write(reinterpret_cast<const char*> (&m_bRamEnabled), sizeof(m_bRamEnabled)); + stream.write(reinterpret_cast<const char*> (&m_bRTCEnabled), sizeof(m_bRTCEnabled)); + stream.write(reinterpret_cast<const char*> (m_pRAMBanks), 0x8000); + stream.write(reinterpret_cast<const char*> (&m_iRTCLatch), sizeof(m_iRTCLatch)); + stream.write(reinterpret_cast<const char*> (&m_RTCRegister), sizeof(m_RTCRegister)); + stream.write(reinterpret_cast<const char*> (&m_RTCLastTimeCache), sizeof(m_RTCLastTimeCache)); + stream.write(reinterpret_cast<const char*> (&m_CurrentROMAddress), sizeof(m_CurrentROMAddress)); + stream.write(reinterpret_cast<const char*> (&m_CurrentRAMAddress), sizeof(m_CurrentRAMAddress)); + stream.write(reinterpret_cast<const char*> (&m_RTC), sizeof(m_RTC)); +} + +void MBC3MemoryRule::LoadState(std::istream& stream) +{ + using namespace std; + + stream.read(reinterpret_cast<char*> (&m_iCurrentRAMBank), sizeof(m_iCurrentRAMBank)); + stream.read(reinterpret_cast<char*> (&m_iCurrentROMBank), sizeof(m_iCurrentROMBank)); + stream.read(reinterpret_cast<char*> (&m_bRamEnabled), sizeof(m_bRamEnabled)); + stream.read(reinterpret_cast<char*> (&m_bRTCEnabled), sizeof(m_bRTCEnabled)); + stream.read(reinterpret_cast<char*> (m_pRAMBanks), 0x8000); + stream.read(reinterpret_cast<char*> (&m_iRTCLatch), sizeof(m_iRTCLatch)); + stream.read(reinterpret_cast<char*> (&m_RTCRegister), sizeof(m_RTCRegister)); + stream.read(reinterpret_cast<char*> (&m_RTCLastTimeCache), sizeof(m_RTCLastTimeCache)); + stream.read(reinterpret_cast<char*> (&m_CurrentROMAddress), sizeof(m_CurrentROMAddress)); + stream.read(reinterpret_cast<char*> (&m_CurrentRAMAddress), sizeof(m_CurrentRAMAddress)); + stream.read(reinterpret_cast<char*> (&m_RTC), sizeof(m_RTC)); +} diff --git a/gearboy/src/MBC3MemoryRule.h b/gearboy/src/MBC3MemoryRule.h new file mode 100644 index 00000000..1bec9ccd --- /dev/null +++ b/gearboy/src/MBC3MemoryRule.h @@ -0,0 +1,82 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#ifndef MBC3MEMORYRULE_H +#define MBC3MEMORYRULE_H + +#include "MemoryRule.h" + +struct RTC_Registers +{ + s32 Seconds; + s32 Minutes; + s32 Hours; + s32 Days; + s32 Control; + s32 LatchedSeconds; + s32 LatchedMinutes; + s32 LatchedHours; + s32 LatchedDays; + s32 LatchedControl; + s32 LastTime; + s32 padding; +}; + +class MBC3MemoryRule : public MemoryRule +{ +public: + MBC3MemoryRule(Processor* pProcessor, Memory* pMemory, + Video* pVideo, Input* pInput, Cartridge* pCartridge, Audio* pAudio); + virtual ~MBC3MemoryRule(); + virtual u8 PerformRead(u16 address); + virtual void PerformWrite(u16 address, u8 value); + virtual void Reset(bool bCGB); + virtual void SaveRam(std::ostream &file); + virtual bool LoadRam(std::istream &file, s32 fileSize); + virtual size_t GetRamSize(); + virtual size_t GetRTCSize(); + virtual u8* GetRamBanks(); + virtual u8* GetCurrentRamBank(); + virtual int GetCurrentRamBankIndex(); + virtual u8* GetRomBank0(); + virtual int GetCurrentRomBank0Index(); + virtual u8* GetCurrentRomBank1(); + virtual int GetCurrentRomBank1Index(); + virtual u8* GetRTCMemory(); + virtual void SaveState(std::ostream& stream); + virtual void LoadState(std::istream& stream); + +private: + void UpdateRTC(); + +private: + int m_iCurrentRAMBank; + int m_iCurrentROMBank; + bool m_bRamEnabled; + bool m_bRTCEnabled; + u8* m_pRAMBanks; + s32 m_iRTCLatch; + u8 m_RTCRegister; + s32 m_RTCLastTimeCache; + int m_CurrentROMAddress; + int m_CurrentRAMAddress; + RTC_Registers m_RTC; +}; + +#endif /* MBC3MEMORYRULE_H */ diff --git a/gearboy/src/MBC5MemoryRule.cpp b/gearboy/src/MBC5MemoryRule.cpp new file mode 100644 index 00000000..7a830241 --- /dev/null +++ b/gearboy/src/MBC5MemoryRule.cpp @@ -0,0 +1,254 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#include "MBC5MemoryRule.h" +#include "Video.h" +#include "Memory.h" +#include "Processor.h" +#include "Input.h" +#include "Cartridge.h" + +MBC5MemoryRule::MBC5MemoryRule(Processor* pProcessor, + Memory* pMemory, Video* pVideo, Input* pInput, + Cartridge* pCartridge, Audio* pAudio) : MemoryRule(pProcessor, +pMemory, pVideo, pInput, pCartridge, pAudio) +{ + m_pRAMBanks = new u8[0x20000]; + Reset(false); +} + +MBC5MemoryRule::~MBC5MemoryRule() +{ + SafeDeleteArray(m_pRAMBanks); +} + +void MBC5MemoryRule::Reset(bool bCGB) +{ + m_bCGB = bCGB; + m_iCurrentRAMBank = 0; + m_iCurrentROMBank = 1; + m_iCurrentROMBankHi = 0; + m_bRamEnabled = false; + for (int i = 0; i < 0x20000; i++) + m_pRAMBanks[i] = 0xFF; + m_CurrentROMAddress = 0x4000; + m_CurrentRAMAddress = 0; +} + +u8 MBC5MemoryRule::PerformRead(u16 address) +{ + switch (address & 0xE000) + { + case 0x4000: + case 0x6000: + { + u8* pROM = m_pCartridge->GetTheROM(); + return pROM[(address - 0x4000) + m_CurrentROMAddress]; + } + case 0xA000: + { + if (m_bRamEnabled) + { + return m_pRAMBanks[(address - 0xA000) + m_CurrentRAMAddress]; + } + else + { + Log("--> ** Attempting to read from disabled ram %X", address); + return 0xFF; + } + } + default: + { + return m_pMemory->Retrieve(address); + } + } +} + +void MBC5MemoryRule::PerformWrite(u16 address, u8 value) +{ + switch (address & 0xE000) + { + case 0x0000: + { + if (m_pCartridge->GetRAMSize() > 0) + { + bool previous = m_bRamEnabled; + m_bRamEnabled = ((value & 0x0F) == 0x0A); + + if (IsValidPointer(m_pRamChangedCallback) && previous && !m_bRamEnabled) + { + (*m_pRamChangedCallback)(); + } + } + break; + } + case 0x2000: + { + if (address < 0x3000) + { + m_iCurrentROMBank = value | (m_iCurrentROMBankHi << 8); + } + else + { + m_iCurrentROMBankHi = value & 0x01; + m_iCurrentROMBank = (m_iCurrentROMBank & 0xFF) | (m_iCurrentROMBankHi << 8); + } + m_iCurrentROMBank &= (m_pCartridge->GetROMBankCount() - 1); + m_CurrentROMAddress = m_iCurrentROMBank * 0x4000; + break; + } + case 0x4000: + { + m_iCurrentRAMBank = value & 0x0F; + m_iCurrentRAMBank &= (m_pCartridge->GetRAMBankCount() - 1); + m_CurrentRAMAddress = m_iCurrentRAMBank * 0x2000; + break; + } + case 0x6000: + { + Log("--> ** Attempting to write on invalid address %X %X", address, value); + break; + } + case 0xA000: + { + if (m_bRamEnabled) + { + m_pRAMBanks[(address - 0xA000) + m_CurrentRAMAddress] = value; + } + else + { + Log("--> ** Attempting to write on RAM when ram is disabled %X %X", address, value); + } + break; + } + default: + { + m_pMemory->Load(address, value); + break; + } + } +} + +void MBC5MemoryRule::SaveRam(std::ostream & file) +{ + Log("MBC5MemoryRule save RAM..."); + Log("MBC5MemoryRule saving %d banks...", m_pCartridge->GetRAMBankCount()); + + s32 ramSize = m_pCartridge->GetRAMBankCount() * 0x2000; + + for (s32 i = 0; i < ramSize; i++) + { + u8 ram_byte = m_pRAMBanks[i]; + file.write(reinterpret_cast<const char*> (&ram_byte), 1); + } + + Log("MBC5MemoryRule save RAM done"); +} + +bool MBC5MemoryRule::LoadRam(std::istream & file, s32 fileSize) +{ + Log("MBC5MemoryRule load RAM..."); + Log("MBC5MemoryRule loading %d banks...", m_pCartridge->GetRAMBankCount()); + + s32 ramSize = m_pCartridge->GetRAMBankCount() * 0x2000; + + if ((fileSize > 0) && (fileSize != ramSize)) + { + Log("MBC5MemoryRule incorrect size. Expected: %d Found: %d", ramSize, fileSize); + return false; + } + + for (s32 i = 0; i < ramSize; i++) + { + u8 ram_byte = 0; + file.read(reinterpret_cast<char*> (&ram_byte), 1); + m_pRAMBanks[i] = ram_byte; + } + + Log("MBC5MemoryRule load RAM done"); + + return true; +} + +size_t MBC5MemoryRule::GetRamSize() +{ + return m_pCartridge->GetRAMBankCount() * 0x2000; +} + +u8* MBC5MemoryRule::GetRamBanks() +{ + return m_pRAMBanks; +} + +u8* MBC5MemoryRule::GetCurrentRamBank() +{ + return m_pRAMBanks + m_CurrentRAMAddress; +} + +int MBC5MemoryRule::GetCurrentRamBankIndex() +{ + return m_iCurrentRAMBank; +} + +u8* MBC5MemoryRule::GetRomBank0() +{ + return m_pMemory->GetMemoryMap() + 0x0000; +} + +int MBC5MemoryRule::GetCurrentRomBank0Index() +{ + return 0; +} + +u8* MBC5MemoryRule::GetCurrentRomBank1() +{ + u8* pROM = m_pCartridge->GetTheROM(); + return &pROM[m_CurrentROMAddress]; +} + +int MBC5MemoryRule::GetCurrentRomBank1Index() +{ + return m_iCurrentROMBank; +} + +void MBC5MemoryRule::SaveState(std::ostream& stream) +{ + using namespace std; + + stream.write(reinterpret_cast<const char*> (&m_iCurrentRAMBank), sizeof(m_iCurrentRAMBank)); + stream.write(reinterpret_cast<const char*> (&m_iCurrentROMBank), sizeof(m_iCurrentROMBank)); + stream.write(reinterpret_cast<const char*> (&m_iCurrentROMBankHi), sizeof(m_iCurrentROMBankHi)); + stream.write(reinterpret_cast<const char*> (&m_bRamEnabled), sizeof(m_bRamEnabled)); + stream.write(reinterpret_cast<const char*> (m_pRAMBanks), 0x20000); + stream.write(reinterpret_cast<const char*> (&m_CurrentROMAddress), sizeof(m_CurrentROMAddress)); + stream.write(reinterpret_cast<const char*> (&m_CurrentRAMAddress), sizeof(m_CurrentRAMAddress)); +} + +void MBC5MemoryRule::LoadState(std::istream& stream) +{ + using namespace std; + + stream.read(reinterpret_cast<char*> (&m_iCurrentRAMBank), sizeof(m_iCurrentRAMBank)); + stream.read(reinterpret_cast<char*> (&m_iCurrentROMBank), sizeof(m_iCurrentROMBank)); + stream.read(reinterpret_cast<char*> (&m_iCurrentROMBankHi), sizeof(m_iCurrentROMBankHi)); + stream.read(reinterpret_cast<char*> (&m_bRamEnabled), sizeof(m_bRamEnabled)); + stream.read(reinterpret_cast<char*> (m_pRAMBanks), 0x20000); + stream.read(reinterpret_cast<char*> (&m_CurrentROMAddress), sizeof(m_CurrentROMAddress)); + stream.read(reinterpret_cast<char*> (&m_CurrentRAMAddress), sizeof(m_CurrentRAMAddress)); +} diff --git a/gearboy/src/MBC5MemoryRule.h b/gearboy/src/MBC5MemoryRule.h new file mode 100644 index 00000000..c80a6fb1 --- /dev/null +++ b/gearboy/src/MBC5MemoryRule.h @@ -0,0 +1,57 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#ifndef MBC5MEMORYRULE_H +#define MBC5MEMORYRULE_H + +#include "MemoryRule.h" + +class MBC5MemoryRule : public MemoryRule +{ +public: + MBC5MemoryRule(Processor* pProcessor, Memory* pMemory, + Video* pVideo, Input* pInput, Cartridge* pCartridge, Audio* pAudio); + virtual ~MBC5MemoryRule(); + virtual u8 PerformRead(u16 address); + virtual void PerformWrite(u16 address, u8 value); + virtual void Reset(bool bCGB); + virtual void SaveRam(std::ostream &file); + virtual bool LoadRam(std::istream &file, s32 fileSize); + virtual size_t GetRamSize(); + virtual u8* GetRamBanks(); + virtual u8* GetCurrentRamBank(); + virtual int GetCurrentRamBankIndex(); + virtual u8* GetRomBank0(); + virtual int GetCurrentRomBank0Index(); + virtual u8* GetCurrentRomBank1(); + virtual int GetCurrentRomBank1Index(); + virtual void SaveState(std::ostream& stream); + virtual void LoadState(std::istream& stream); + +private: + int m_iCurrentRAMBank; + int m_iCurrentROMBank; + int m_iCurrentROMBankHi; + bool m_bRamEnabled; + u8* m_pRAMBanks; + int m_CurrentROMAddress; + int m_CurrentRAMAddress; +}; + +#endif /* MBC5MEMORYRULE_H */ diff --git a/gearboy/src/Memory.cpp b/gearboy/src/Memory.cpp new file mode 100644 index 00000000..88eb9975 --- /dev/null +++ b/gearboy/src/Memory.cpp @@ -0,0 +1,736 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#include <iostream> +#include <fstream> +#include "Memory.h" +#include "Processor.h" +#include "Video.h" + +Memory::Memory() +{ + InitPointer(m_pProcessor); + InitPointer(m_pVideo); + InitPointer(m_pMap); + InitPointer(m_pDisassembledMap); + InitPointer(m_pDisassembledROMMap); + InitPointer(m_pWRAMBanks); + InitPointer(m_pLCDRAMBank1); + InitPointer(m_pCommonMemoryRule); + InitPointer(m_pIORegistersMemoryRule); + InitPointer(m_pCurrentMemoryRule); + InitPointer(m_pBootromDMG); + InitPointer(m_pBootromGBC); + m_bCGB = false; + m_iCurrentWRAMBank = 1; + m_iCurrentLCDRAMBank = 0; + m_bHDMAEnabled = false; + m_iHDMABytes = 0; + for (int i = 0; i < 5; i++) + m_HDMA[i] = 0; + m_HDMASource = 0; + m_HDMADestination = 0; + m_bBootromDMGEnabled = false; + m_bBootromGBCEnabled = false; + m_bBootromRegistryDisabled = false; + m_bBootromDMGLoaded = false; + m_bBootromGBCLoaded = false; +} + +Memory::~Memory() +{ + InitPointer(m_pProcessor); + InitPointer(m_pVideo); + SafeDeleteArray(m_pMap); + SafeDeleteArray(m_pWRAMBanks); + SafeDeleteArray(m_pLCDRAMBank1); + InitPointer(m_pCommonMemoryRule); + InitPointer(m_pIORegistersMemoryRule); + InitPointer(m_pCurrentMemoryRule); + SafeDeleteArray(m_pBootromDMG); + SafeDeleteArray(m_pBootromGBC); + + if (IsValidPointer(m_pDisassembledROMMap)) + { + for (int i = 0; i < MAX_ROM_SIZE; i++) + { + SafeDelete(m_pDisassembledROMMap[i]); + } + SafeDeleteArray(m_pDisassembledROMMap); + } + + if (IsValidPointer(m_pDisassembledMap)) + { + for (int i = 0; i < 65536; i++) + { + SafeDelete(m_pDisassembledMap[i]); + } + SafeDeleteArray(m_pDisassembledMap); + } +} + +void Memory::SetProcessor(Processor* pProcessor) +{ + m_pProcessor = pProcessor; +} + +void Memory::SetVideo(Video* pVideo) +{ + m_pVideo = pVideo; +} + +void Memory::Init() +{ + m_pMap = new u8[65536]; + m_pWRAMBanks = new u8[0x8000]; + m_pLCDRAMBank1 = new u8[0x2000]; + m_pBootromDMG = new u8[0x100]; + m_pBootromGBC = new u8[0x900]; +#ifndef GEARBOY_DISABLE_DISASSEMBLER + m_pDisassembledMap = new stDisassembleRecord*[65536]; + for (int i = 0; i < 65536; i++) + { + InitPointer(m_pDisassembledMap[i]); + } + + m_pDisassembledROMMap = new stDisassembleRecord*[MAX_ROM_SIZE]; + for (int i = 0; i < MAX_ROM_SIZE; i++) + { + InitPointer(m_pDisassembledROMMap[i]); + } +#endif + m_BreakpointsCPU.clear(); + m_BreakpointsMem.clear(); + InitPointer(m_pRunToBreakpoint); + Reset(false); +} + +void Memory::Reset(bool bCGB) +{ + m_bCGB = bCGB; + InitPointer(m_pCommonMemoryRule); + InitPointer(m_pIORegistersMemoryRule); + InitPointer(m_pCurrentMemoryRule); + m_iCurrentWRAMBank = 1; + m_iCurrentLCDRAMBank = 0; + m_bHDMAEnabled = false; + m_iHDMABytes = 0; + m_bBootromRegistryDisabled = false; + + if (IsBootromEnabled()) + ResetBootromDisassembledMemory(); + + for (int i = 0; i < 65536; i++) + { + m_pMap[i] = 0x00; + + if ((i >= 0x8000) && (i < 0xA000)) + { + m_pMap[i] = 0x00; + m_pLCDRAMBank1[i - 0x8000] = 0x00; + } + else if ((i >= 0xC000) && (i < 0xE000)) + { + if ((i & 0x8) ^((i & 0x800) >> 8)) + { + if (m_bCGB) + { + m_pMap[i] = 0x00; + if (i >= 0xD000) + { + for (int a = 0; a < 8; a++) + { + if (a != 2) + m_pWRAMBanks[(i - 0xD000) + (0x1000 * a)] = m_pMap[i - 0x1000]; + else + m_pWRAMBanks[(i - 0xD000) + (0x1000 * a)] = 0x00; + } + } + } + else + m_pMap[i] = 0x0f; + } + else + { + m_pMap[i] = 0xff; + if (i >= 0xD000) + { + for (int a = 0; a < 8; a++) + { + if (a != 2) + m_pWRAMBanks[(i - 0xD000) + (0x1000 * a)] = m_pMap[i - 0x1000]; + else + m_pWRAMBanks[(i - 0xD000) + (0x1000 * a)] = 0x00; + } + } + } + } + else if (i >= 0xFF00) + { + if (m_bCGB) + m_pMap[i] = kInitialValuesForColorFFXX[i - 0xFF00]; + else + m_pMap[i] = kInitialValuesForFFXX[i - 0xFF00]; + } + else + { + m_pMap[i] = 0xFF; + } + } + + if (m_bCGB) + { + for (int i = 0; i < 5; i++) + { + m_HDMA[i] = m_pMap[0xFF51 + i]; + } + + u8 hdma1 = m_HDMA[0]; + u8 hdma2 = m_HDMA[1]; + u8 hdma3 = m_HDMA[2]; + u8 hdma4 = m_HDMA[3]; + + if (hdma1 > 0x7f && hdma1 < 0xa0) + hdma1 = 0; + + m_HDMASource = (hdma1 << 8) | (hdma2 & 0xF0); + m_HDMADestination = ((hdma3 & 0x1F) << 8) | (hdma4 & 0xF0); + m_HDMADestination |= 0x8000; + } +} + +void Memory::SetCurrentRule(MemoryRule* pRule) +{ + m_pCurrentMemoryRule = pRule; +} + +void Memory::SetCommonRule(CommonMemoryRule* pRule) +{ + m_pCommonMemoryRule = pRule; +} + +void Memory::SetIORule(IORegistersMemoryRule* pRule) +{ + m_pIORegistersMemoryRule = pRule; +} + +MemoryRule* Memory::GetCurrentRule() +{ + return m_pCurrentMemoryRule; +} + +u8* Memory::GetMemoryMap() +{ + return m_pMap; +} + +void Memory::LoadBank0and1FromROM(u8* pTheROM) +{ + // loads the first 32KB only (bank 0 and 1) + for (int i = 0; i < 0x8000; i++) + { + m_pMap[i] = pTheROM[i]; + } +} + +void Memory::MemoryDump(const char* szFilePath) +{ + if (!IsValidPointer(m_pDisassembledMap)) + return; + + using namespace std; + + ofstream myfile(szFilePath, ios::out | ios::trunc); + + if (myfile.is_open()) + { + for (int i = 0; i < 65536; i++) + { + if (IsValidPointer(m_pDisassembledMap[i]) && (m_pDisassembledMap[i]->name[0] != 0)) + { + myfile << "0x" << hex << i << "\t " << m_pDisassembledMap[i]->name << "\n"; + i += (m_pDisassembledMap[i]->size - 1); + } + else + { + myfile << "0x" << hex << i << "\t [0x" << hex << (int) m_pMap[i] << "]\n"; + } + } + + myfile.close(); + } +} + +void Memory::PerformDMA(u8 value) +{ + if (m_bCGB) + { + u16 address = value << 8; + if (address < 0xE000) + { + if (address >= 0x8000 && address < 0xA000) + { + for (int i = 0; i < 0xA0; i++) + Load(0xFE00 + i, ReadCGBLCDRAM(address + i, false)); + } + else if (address >= 0xD000 && address < 0xE000) + { + for (int i = 0; i < 0xA0; i++) + Load(0xFE00 + i, ReadCGBWRAM(address + i)); + } + else + { + for (int i = 0; i < 0xA0; i++) + Load(0xFE00 + i, Read(address + i)); + } + } + } + else + { + u16 address = value << 8; + if (address >= 0x8000 && address < 0xE000) + { + for (int i = 0; i < 0xA0; i++) + Load(0xFE00 + i, Read(address + i)); + } + } +} + +void Memory::SwitchCGBDMA(u8 value) +{ + m_iHDMABytes = 16 + ((value & 0x7f) * 16); + + if (m_bHDMAEnabled) + { + if (IsSetBit(value, 7)) + { + m_HDMA[4] = value & 0x7F; + } + else + { + m_HDMA[4] = 0xFF; + m_bHDMAEnabled = false; + } + } + else + { + if (IsSetBit(value, 7)) + { + m_bHDMAEnabled = true; + m_HDMA[4] = value & 0x7F; + if (m_pVideo->GetCurrentStatusMode() == 0) + { + m_pProcessor->AddCycles(PerformHDMA()); + } + } + else + { + PerformGDMA(value); + } + } +} + +unsigned int Memory::PerformHDMA() +{ + u16 source = m_HDMASource & 0xFFF0; + u16 destination = (m_HDMADestination & 0x1FF0) | 0x8000; + + if (source >= 0xD000 && source < 0xE000) + { + for (int i = 0; i < 0x10; i++) + WriteCGBLCDRAM(destination + i, ReadCGBWRAM(source + i)); + } + else + { + for (int i = 0; i < 0x10; i++) + WriteCGBLCDRAM(destination + i, Read(source + i)); + } + + m_HDMADestination += 0x10; + if (m_HDMADestination == 0xA000) + m_HDMADestination = 0x8000; + + m_HDMASource += 0x10; + if (m_HDMASource == 0x8000) + m_HDMASource = 0xA000; + + m_HDMA[1] = m_HDMASource & 0xFF; + m_HDMA[0] = m_HDMASource >> 8; + + m_HDMA[3] = m_HDMADestination & 0xFF; + m_HDMA[2] = m_HDMADestination >> 8; + + m_iHDMABytes -= 0x10; + m_HDMA[4]--; + + if (m_HDMA[4] == 0xFF) + m_bHDMAEnabled = false; + + // return clock cycles used + return (m_pProcessor->CGBSpeed() ? 17 : 9) * 4; +} + +void Memory::PerformGDMA(u8 value) +{ + u16 source = m_HDMASource & 0xFFF0; + u16 destination = (m_HDMADestination & 0x1FF0) | 0x8000; + + if (source >= 0xD000 && source < 0xE000) + { + for (int i = 0; i < m_iHDMABytes; i++) + WriteCGBLCDRAM(destination + i, ReadCGBWRAM(source + i)); + } + else + { + for (int i = 0; i < m_iHDMABytes; i++) + WriteCGBLCDRAM(destination + i, Read(source + i)); + } + + m_HDMADestination += m_iHDMABytes; + m_HDMASource += m_iHDMABytes; + + for (int i = 0; i < 5; i++) + m_HDMA[i] = 0xFF; + + int clock_cycles = 0; + + if (m_pProcessor->CGBSpeed()) + clock_cycles = 2 + 16 * ((value & 0x7f) + 1); + else + clock_cycles = 1 + 8 * ((value & 0x7f) + 1); + + m_pProcessor->AddCycles(clock_cycles * 4); +} + +bool Memory::IsHDMAEnabled() const +{ + return m_bHDMAEnabled; +} + +void Memory::SetHDMARegister(int reg, u8 value) +{ + switch (reg) + { + case 1: + { + // HDMA1 + if (value > 0x7f && value < 0xa0) + value = 0; + m_HDMASource = (value << 8) | (m_HDMASource & 0xF0); + break; + } + case 2: + { + // HDMA2 + value &= 0xF0; + m_HDMASource = (m_HDMASource & 0xFF00) | value; + break; + } + case 3: + { + // HDMA3 + value &= 0x1F; + m_HDMADestination = (value << 8) | (m_HDMADestination & 0xF0); + m_HDMADestination |= 0x8000; + break; + } + case 4: + { + // HDMA4 + value &= 0xF0; + m_HDMADestination = (m_HDMADestination & 0x1F00) | value; + m_HDMADestination |= 0x8000; + break; + } + } + + m_HDMA[reg - 1] = value; +} + +u8 Memory::GetHDMARegister(int reg) const +{ + return m_HDMA[reg - 1]; +} + +u8* Memory::GetCGBRAM() +{ + return m_pWRAMBanks; +} + +int Memory::GetCurrentCGBRAMBank() +{ + return m_iCurrentWRAMBank; +} + +int Memory::GetCurrentLCDRAMBank() +{ + return m_iCurrentLCDRAMBank; +} + +void Memory::SaveState(std::ostream& stream) +{ + using namespace std; + + stream.write(reinterpret_cast<const char*> (m_pMap), 65536); + stream.write(reinterpret_cast<const char*> (&m_iCurrentWRAMBank), sizeof(m_iCurrentWRAMBank)); + stream.write(reinterpret_cast<const char*> (&m_iCurrentLCDRAMBank), sizeof(m_iCurrentLCDRAMBank)); + stream.write(reinterpret_cast<const char*> (m_pWRAMBanks), 0x8000); + stream.write(reinterpret_cast<const char*> (m_pLCDRAMBank1), 0x2000); + stream.write(reinterpret_cast<const char*> (&m_bHDMAEnabled), sizeof(m_bHDMAEnabled)); + stream.write(reinterpret_cast<const char*> (&m_iHDMABytes), sizeof(m_iHDMABytes)); + stream.write(reinterpret_cast<const char*> (m_HDMA), sizeof(m_HDMA)); + stream.write(reinterpret_cast<const char*> (&m_HDMASource), sizeof(m_HDMASource)); + stream.write(reinterpret_cast<const char*> (&m_HDMADestination), sizeof(m_HDMADestination)); +} + +void Memory::LoadState(std::istream& stream) +{ + using namespace std; + + stream.read(reinterpret_cast<char*> (m_pMap), 65536); + stream.read(reinterpret_cast<char*> (&m_iCurrentWRAMBank), sizeof(m_iCurrentWRAMBank)); + stream.read(reinterpret_cast<char*> (&m_iCurrentLCDRAMBank), sizeof(m_iCurrentLCDRAMBank)); + stream.read(reinterpret_cast<char*> (m_pWRAMBanks), 0x8000); + stream.read(reinterpret_cast<char*> (m_pLCDRAMBank1), 0x2000); + stream.read(reinterpret_cast<char*> (&m_bHDMAEnabled), sizeof(m_bHDMAEnabled)); + stream.read(reinterpret_cast<char*> (&m_iHDMABytes), sizeof(m_iHDMABytes)); + stream.read(reinterpret_cast<char*> (m_HDMA), sizeof(m_HDMA)); + stream.read(reinterpret_cast<char*> (&m_HDMASource), sizeof(m_HDMASource)); + stream.read(reinterpret_cast<char*> (&m_HDMADestination), sizeof(m_HDMADestination)); +} + +u8* Memory::GetROM0() +{ + return m_pCurrentMemoryRule->GetRomBank0(); +} + +u8* Memory::GetROM1() +{ + return m_pCurrentMemoryRule->GetCurrentRomBank1(); +} + +u8* Memory::GetVRAM() +{ + if (m_bCGB) + return (m_iCurrentLCDRAMBank == 1) ? m_pLCDRAMBank1 : m_pMap + 0x8000; + else + return m_pMap + 0x8000; +} + +u8* Memory::GetRAM() +{ + return m_pCurrentMemoryRule->GetCurrentRamBank(); +} + +u8* Memory::GetWRAM0() +{ + return m_bCGB ? m_pWRAMBanks : m_pMap + 0xC000; +} + +u8* Memory::GetWRAM1() +{ + return m_bCGB ? m_pWRAMBanks + (0x1000 * m_iCurrentWRAMBank) : m_pMap + 0xD000; +} + +std::vector<Memory::stDisassembleRecord*>* Memory::GetBreakpointsCPU() +{ + return &m_BreakpointsCPU; +} + +std::vector<Memory::stMemoryBreakpoint>* Memory::GetBreakpointsMem() +{ + return &m_BreakpointsMem; +} + +Memory::stDisassembleRecord* Memory::GetRunToBreakpoint() +{ + return m_pRunToBreakpoint; +} + +void Memory::SetRunToBreakpoint(Memory::stDisassembleRecord* pBreakpoint) +{ + m_pRunToBreakpoint = pBreakpoint; +} + +void Memory::EnableBootromDMG(bool enable) +{ + m_bBootromDMGEnabled = enable; + + if (m_bBootromDMGEnabled) + { + Log("DMG Bootrom enabled"); + } + else + { + Log("DMG Bootrom disabled"); + } +} + +void Memory::EnableBootromGBC(bool enable) +{ + m_bBootromGBCEnabled = enable; + + if (m_bBootromGBCEnabled) + { + Log("GBC Bootrom enabled"); + } + else + { + Log("GBC Bootrom disabled"); + } +} + +void Memory::LoadBootromDMG(const char* szFilePath) +{ + Log("Loading DMG Bootrom %s...", szFilePath); + + LoadBootroom(szFilePath, false); +} + +void Memory::LoadBootromGBC(const char* szFilePath) +{ + Log("Loading GBC Bootrom %s...", szFilePath); + + LoadBootroom(szFilePath, true); +} + +bool Memory::IsBootromEnabled() +{ + return (m_bBootromDMGEnabled && m_bBootromDMGLoaded && !m_bCGB) || (m_bBootromGBCEnabled && m_bBootromGBCLoaded && m_bCGB); +} + +void Memory::DisableBootromRegistry() +{ + if (!m_bBootromRegistryDisabled && IsBootromEnabled()) + { + ResetBootromDisassembledMemory(); + } + + m_bBootromRegistryDisabled = true; +} + +bool Memory::IsBootromRegistryEnabled() +{ + return !m_bBootromRegistryDisabled; +} + +void Memory::ResetDisassembledMemory() +{ + #ifndef GEARBOY_DISABLE_DISASSEMBLER + + if (IsValidPointer(m_pDisassembledROMMap)) + { + for (int i = 0; i < MAX_ROM_SIZE; i++) + { + SafeDelete(m_pDisassembledROMMap[i]); + } + } + if (IsValidPointer(m_pDisassembledMap)) + { + for (int i = 0; i < 65536; i++) + { + SafeDelete(m_pDisassembledMap[i]); + } + } + + #endif +} + +void Memory::ResetBootromDisassembledMemory() +{ + #ifndef GEARBOY_DISABLE_DISASSEMBLER + + m_BreakpointsCPU.clear(); + + if (IsValidPointer(m_pDisassembledROMMap)) + { + for (int i = 0; i < 0x0100; i++) + { + SafeDelete(m_pDisassembledROMMap[i]); + } + } + if (IsValidPointer(m_pDisassembledMap)) + { + for (int i = 0; i < 0x0100; i++) + { + SafeDelete(m_pDisassembledMap[i]); + } + } + + if (m_bCGB) + { + if (IsValidPointer(m_pDisassembledROMMap)) + { + for (int i = 0x0200; i < 0x0900; i++) + { + SafeDelete(m_pDisassembledROMMap[i]); + } + } + if (IsValidPointer(m_pDisassembledMap)) + { + for (int i = 0x0200; i < 0x0900; i++) + { + SafeDelete(m_pDisassembledMap[i]); + } + } + } + + #endif +} + +void Memory::LoadBootroom(const char* szFilePath, bool gbc) +{ + using namespace std; + + int expectedSize = gbc ? 0x900 : 0x100; + u8* bootrom = gbc ? m_pBootromGBC : m_pBootromDMG; + + ifstream file(szFilePath, ios::in | ios::binary | ios::ate); + + bool ret = false; + + if (file.is_open()) + { + int size = static_cast<int> (file.tellg()); + + if (size == expectedSize) + { + file.seekg(0, ios::beg); + file.read(reinterpret_cast<char*>(bootrom), size); + file.close(); + + ret = true; + + Log("Bootrom %s loaded", szFilePath); + } + else + { + Log("Incompatible bootrom size (expected 0x%X): 0x%X", expectedSize, size); + } + } + else + { + Log("There was a problem opening the file %s", szFilePath); + } + + if (gbc) + m_bBootromGBCLoaded = ret; + else + m_bBootromDMGLoaded = ret; +} diff --git a/gearboy/src/Memory.h b/gearboy/src/Memory.h new file mode 100644 index 00000000..2bfbc4fb --- /dev/null +++ b/gearboy/src/Memory.h @@ -0,0 +1,190 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#ifndef MEMORY_H +#define MEMORY_H + +#include "definitions.h" +#include "MemoryRule.h" +#include <vector> + +class Processor; +class Video; +class CommonMemoryRule; +class IORegistersMemoryRule; + +class Memory +{ +public: + struct stDisassembleRecord + { + u16 address; + char name[32]; + char bytes[16]; + int size; + int bank; + u8 opcodes[4]; + bool jump; + u16 jump_address; + }; + + struct stMemoryBreakpoint + { + u16 address1; + u16 address2; + bool read; + bool write; + bool range; + }; + +public: + Memory(); + ~Memory(); + void SetProcessor(Processor* pProcessor); + void SetVideo(Video* pVideo); + void Init(); + void Reset(bool bCGB); + void SetCurrentRule(MemoryRule* pRule); + void SetCommonRule(CommonMemoryRule* pRule); + void SetIORule(IORegistersMemoryRule* pRule); + MemoryRule* GetCurrentRule(); + u8* GetMemoryMap(); + u8 Read(u16 address); + void Write(u16 address, u8 value); + u8 ReadCGBWRAM(u16 address); + void WriteCGBWRAM(u16 address, u8 value); + void SwitchCGBWRAM(u8 value); + u8 ReadCGBLCDRAM(u16 address, bool forceBank1); + void WriteCGBLCDRAM(u16 address, u8 value); + void SwitchCGBLCDRAM(u8 value); + u8 Retrieve(u16 address); + void Load(u16 address, u8 value); + stDisassembleRecord** GetDisassembledMemoryMap(); + stDisassembleRecord** GetDisassembledROMMemoryMap(); + void LoadBank0and1FromROM(u8* pTheROM); + void MemoryDump(const char* szFilePath); + void PerformDMA(u8 value); + void SwitchCGBDMA(u8 value); + unsigned int PerformHDMA(); + void PerformGDMA(u8 value); + bool IsHDMAEnabled() const; + void SetHDMARegister(int reg, u8 value); + u8 GetHDMARegister(int reg) const; + u8* GetCGBRAM(); + int GetCurrentCGBRAMBank(); + int GetCurrentLCDRAMBank(); + void SaveState(std::ostream& stream); + void LoadState(std::istream& stream); + u8* GetROM0(); + u8* GetROM1(); + u8* GetVRAM(); + u8* GetRAM(); + u8* GetWRAM0(); + u8* GetWRAM1(); + std::vector<stDisassembleRecord*>* GetBreakpointsCPU(); + std::vector<stMemoryBreakpoint>* GetBreakpointsMem(); + stDisassembleRecord* GetRunToBreakpoint(); + void SetRunToBreakpoint(stDisassembleRecord* pBreakpoint); + void EnableBootromDMG(bool enable); + void EnableBootromGBC(bool enable); + void LoadBootromDMG(const char* szFilePath); + void LoadBootromGBC(const char* szFilePath); + bool IsBootromEnabled(); + void DisableBootromRegistry(); + bool IsBootromRegistryEnabled(); + void ResetDisassembledMemory(); + void ResetBootromDisassembledMemory(); + +private: + void LoadBootroom(const char* szFilePath, bool gbc); + void CheckBreakpoints(u16 address, bool write); + +private: + Processor* m_pProcessor; + Video* m_pVideo; + CommonMemoryRule* m_pCommonMemoryRule; + IORegistersMemoryRule* m_pIORegistersMemoryRule; + MemoryRule* m_pCurrentMemoryRule; + u8* m_pMap; + stDisassembleRecord** m_pDisassembledMap; + stDisassembleRecord** m_pDisassembledROMMap; + std::vector<stDisassembleRecord*> m_BreakpointsCPU; + std::vector<stMemoryBreakpoint> m_BreakpointsMem; + stDisassembleRecord* m_pRunToBreakpoint; + bool m_bCGB; + int m_iCurrentWRAMBank; + int m_iCurrentLCDRAMBank; + u8* m_pWRAMBanks; + u8* m_pLCDRAMBank1; + bool m_bHDMAEnabled; + int m_iHDMABytes; + u8 m_HDMA[5]; + u16 m_HDMASource; + u16 m_HDMADestination; + bool m_bBootromDMGEnabled; + bool m_bBootromGBCEnabled; + bool m_bBootromDMGLoaded; + bool m_bBootromGBCLoaded; + u8* m_pBootromDMG; + u8* m_pBootromGBC; + bool m_bBootromRegistryDisabled; +}; + +#include "Memory_inline.h" + +// From Gambatte emulator +const u8 kInitialValuesForFFXX[256] = { + 0xCF, 0x00, 0x7E, 0xFF, 0xD3, 0x00, 0x00, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, + 0x80, 0xBF, 0xF3, 0xFF, 0xBF, 0xFF, 0x3F, 0x00, 0xFF, 0xBF, 0x7F, 0xFF, 0x9F, 0xFF, 0xBF, 0xFF, + 0xFF, 0x00, 0x00, 0xBF, 0x77, 0xF3, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x71, 0x72, 0xD5, 0x91, 0x58, 0xBB, 0x2A, 0xFA, 0xCF, 0x3C, 0x54, 0x75, 0x48, 0xCF, 0x8F, 0xD9, + 0x91, 0x80, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x2B, 0x0B, 0x64, 0x2F, 0xAF, 0x15, 0x60, 0x6D, 0x61, 0x4E, 0xAC, 0x45, 0x0F, 0xDA, 0x92, 0xF3, + 0x83, 0x38, 0xE4, 0x4E, 0xA7, 0x6C, 0x38, 0x58, 0xBE, 0xEA, 0xE5, 0x81, 0xB4, 0xCB, 0xBF, 0x7B, + 0x59, 0xAD, 0x50, 0x13, 0x5E, 0xF6, 0xB3, 0xC1, 0xDC, 0xDF, 0x9E, 0x68, 0xD7, 0x59, 0x26, 0xF3, + 0x62, 0x54, 0xF8, 0x36, 0xB7, 0x78, 0x6A, 0x22, 0xA7, 0xDD, 0x88, 0x15, 0xCA, 0x96, 0x39, 0xD3, + 0xE6, 0x55, 0x6E, 0xEA, 0x90, 0x76, 0xB8, 0xFF, 0x50, 0xCD, 0xB5, 0x1B, 0x1F, 0xA5, 0x4D, 0x2E, + 0xB4, 0x09, 0x47, 0x8A, 0xC4, 0x5A, 0x8C, 0x4E, 0xE7, 0x29, 0x50, 0x88, 0xA8, 0x66, 0x85, 0x4B, + 0xAA, 0x38, 0xE7, 0x6B, 0x45, 0x3E, 0x30, 0x37, 0xBA, 0xC5, 0x31, 0xF2, 0x71, 0xB4, 0xCF, 0x29, + 0xBC, 0x7F, 0x7E, 0xD0, 0xC7, 0xC3, 0xBD, 0xCF, 0x59, 0xEA, 0x39, 0x01, 0x2E, 0x00, 0x69, 0x00 +}; + +const u8 kInitialValuesForColorFFXX[256] = { + 0xCF, 0x00, 0x7C, 0xFF, 0x44, 0x00, 0x00, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, + 0x80, 0xBF, 0xF3, 0xFF, 0xBF, 0xFF, 0x3F, 0x00, 0xFF, 0xBF, 0x7F, 0xFF, 0x9F, 0xFF, 0xBF, 0xFF, + 0xFF, 0x00, 0x00, 0xBF, 0x77, 0xF3, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x91, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x7E, 0xFF, 0xFE, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xC1, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, + 0xF8, 0xFF, 0x00, 0x00, 0x00, 0x8F, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, 0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D, + 0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E, 0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99, + 0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC, 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E, + 0x45, 0xEC, 0x42, 0xFA, 0x08, 0xB7, 0x07, 0x5D, 0x01, 0xF5, 0xC0, 0xFF, 0x08, 0xFC, 0x00, 0xE5, + 0x0B, 0xF8, 0xC2, 0xCA, 0xF4, 0xF9, 0x0D, 0x7F, 0x44, 0x6D, 0x19, 0xFE, 0x46, 0x97, 0x33, 0x5E, + 0x08, 0xFF, 0xD1, 0xFF, 0xC6, 0x8B, 0x24, 0x74, 0x12, 0xFC, 0x00, 0x9F, 0x94, 0xB7, 0x06, 0xD5, + 0x40, 0x7A, 0x20, 0x9E, 0x04, 0x5F, 0x41, 0x2F, 0x3D, 0x77, 0x36, 0x75, 0x81, 0x8A, 0x70, 0x3A, + 0x98, 0xD1, 0x71, 0x02, 0x4D, 0x01, 0xC1, 0xFF, 0x0D, 0x00, 0xD3, 0x05, 0xF9, 0x00, 0x0B, 0x00 +}; + +#endif /* MEMORY_H */ diff --git a/gearboy/src/MemoryRule.cpp b/gearboy/src/MemoryRule.cpp new file mode 100644 index 00000000..bb771413 --- /dev/null +++ b/gearboy/src/MemoryRule.cpp @@ -0,0 +1,124 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#include "MemoryRule.h" + +MemoryRule::MemoryRule(Processor* pProcessor, Memory* pMemory, + Video* pVideo, Input* pInput, Cartridge* pCartridge, Audio* pAudio) +{ + m_pProcessor = pProcessor; + m_pMemory = pMemory; + m_pVideo = pVideo; + m_pInput = pInput; + m_pCartridge = pCartridge; + m_pAudio = pAudio; + m_bCGB = false; + InitPointer(m_pRamChangedCallback); +} + +MemoryRule::~MemoryRule() +{ + +} + +void MemoryRule::SaveRam(std::ostream&) +{ + Log("MemoryRule::SaveRam not implemented"); +} + +bool MemoryRule::LoadRam(std::istream&, s32) +{ + Log("MemoryRule::LoadRam not implemented"); + return false; +} + +void MemoryRule::SetRamChangedCallback(RamChangedCallback callback) +{ + m_pRamChangedCallback = callback; +} + +size_t MemoryRule::GetRamSize() +{ + Log("MemoryRule::GetRamSize not implemented"); + return 0; +} + +size_t MemoryRule::GetRTCSize() +{ + Log("MemoryRule::GetRTCSize not implemented"); + return 0; +} + +u8* MemoryRule::GetRamBanks() +{ + Log("MemoryRule::GetRamBanks not implemented"); + return NULL; +} + +u8* MemoryRule::GetCurrentRamBank() +{ + Log("MemoryRule::GetCurrentRamBank not implemented"); + return NULL; +} + +int MemoryRule::GetCurrentRamBankIndex() +{ + Log("MemoryRule::GetCurrentRamBankIndex not implemented"); + return 0; +} + +u8* MemoryRule::GetRomBank0() +{ + Log("MemoryRule::GetRomBank0 not implemented"); + return NULL; +} + +int MemoryRule::GetCurrentRomBank0Index() +{ + Log("MemoryRule::GetCurrentRomBank0Index not implemented"); + return 0; +} + +u8* MemoryRule::GetCurrentRomBank1() +{ + Log("MemoryRule::GetCurrentRomBank1 not implemented"); + return NULL; +} + +int MemoryRule::GetCurrentRomBank1Index() +{ + Log("MemoryRule::GetCurrentRomBank1Index not implemented"); + return 1; +} + +u8* MemoryRule::GetRTCMemory() +{ + Log("MemoryRule::GetRTCMemory not implemented"); + return NULL; +} + +void MemoryRule::SaveState(std::ostream&) +{ + Log("MemoryRule::SaveState not implemented"); +} + +void MemoryRule::LoadState(std::istream&) +{ + Log("MemoryRule::LoadState not implemented"); +} diff --git a/gearboy/src/MemoryRule.h b/gearboy/src/MemoryRule.h new file mode 100644 index 00000000..3cf896cd --- /dev/null +++ b/gearboy/src/MemoryRule.h @@ -0,0 +1,69 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#ifndef MEMORYRULE_H +#define MEMORYRULE_H + +#include "definitions.h" +#include <vector> + +class Memory; +class Video; +class Processor; +class Input; +class Cartridge; +class Audio; + +class MemoryRule +{ +public: + MemoryRule(Processor* pProcessor, Memory* pMemory, Video* pVideo, + Input* pInput, Cartridge* pCartridge, Audio* pAudio); + virtual ~MemoryRule(); + virtual u8 PerformRead(u16 address) = 0; + virtual void PerformWrite(u16 address, u8 value) = 0; + virtual void Reset(bool bCGB) = 0; + virtual void SaveRam(std::ostream &file); + virtual bool LoadRam(std::istream &file, s32 fileSize); + virtual void SetRamChangedCallback(RamChangedCallback callback); + virtual size_t GetRamSize(); + virtual size_t GetRTCSize(); + virtual u8* GetRamBanks(); + virtual u8* GetCurrentRamBank(); + virtual int GetCurrentRamBankIndex(); + virtual u8* GetRomBank0(); + virtual int GetCurrentRomBank0Index(); + virtual u8* GetCurrentRomBank1(); + virtual int GetCurrentRomBank1Index(); + virtual u8* GetRTCMemory(); + virtual void SaveState(std::ostream& stream); + virtual void LoadState(std::istream& stream); + +protected: + Processor* m_pProcessor; + Memory* m_pMemory; + Video* m_pVideo; + Input* m_pInput; + Cartridge* m_pCartridge; + Audio* m_pAudio; + bool m_bCGB; + RamChangedCallback m_pRamChangedCallback; +}; + +#endif /* MEMORYRULE_H */ diff --git a/gearboy/src/Memory_inline.h b/gearboy/src/Memory_inline.h new file mode 100644 index 00000000..9d605223 --- /dev/null +++ b/gearboy/src/Memory_inline.h @@ -0,0 +1,207 @@ +#ifndef MEMORY_INLINE_H +#define MEMORY_INLINE_H + +#include "CommonMemoryRule.h" +#include "IORegistersMemoryRule.h" + +inline u8 Memory::Read(u16 address) +{ + #ifndef GEARBOY_DISABLE_DISASSEMBLER + CheckBreakpoints(address, false); + #endif + + switch (address & 0xE000) + { + case 0x0000: + { + if (!m_bBootromRegistryDisabled) + { + if (m_bCGB) + { + if (m_bBootromGBCEnabled && m_bBootromGBCLoaded && ((address < 0x0100) || (address < 0x0900 && address > 0x01FF))) + return m_pBootromGBC[address]; + } + else + { + if (m_bBootromDMGEnabled && m_bBootromDMGLoaded && (address < 0x0100)) + return m_pBootromDMG[address]; + } + } + + return m_pCurrentMemoryRule->PerformRead(address); + } + case 0x2000: + case 0x4000: + case 0x6000: + { + return m_pCurrentMemoryRule->PerformRead(address); + } + case 0x8000: + { + return m_pCommonMemoryRule->PerformRead(address); + } + case 0xA000: + { + return m_pCurrentMemoryRule->PerformRead(address); + } + case 0xC000: + case 0xE000: + { + if (address < 0xFF00) + return m_pCommonMemoryRule->PerformRead(address); + else + return m_pIORegistersMemoryRule->PerformRead(address); + } + default: + { + return Retrieve(address); + } + } +} + +inline void Memory::Write(u16 address, u8 value) +{ + #ifndef GEARBOY_DISABLE_DISASSEMBLER + CheckBreakpoints(address, true); + #endif + + switch (address & 0xE000) + { + case 0x0000: + case 0x2000: + case 0x4000: + case 0x6000: + { + m_pCurrentMemoryRule->PerformWrite(address, value); + break; + } + case 0x8000: + { + m_pCommonMemoryRule->PerformWrite(address, value); + break; + } + case 0xA000: + { + m_pCurrentMemoryRule->PerformWrite(address, value); + break; + } + case 0xC000: + case 0xE000: + { + if (address < 0xFF00) + m_pCommonMemoryRule->PerformWrite(address, value); + else + m_pIORegistersMemoryRule->PerformWrite(address, value); + break; + } + default: + { + Load(address, value); + break; + } + } +} + +inline u8 Memory::ReadCGBWRAM(u16 address) +{ + if (address < 0xD000) + return m_pWRAMBanks[(address - 0xC000)]; + else + return m_pWRAMBanks[(address - 0xD000) + (0x1000 * m_iCurrentWRAMBank)]; +} + +inline void Memory::WriteCGBWRAM(u16 address, u8 value) +{ + if (address < 0xD000) + m_pWRAMBanks[(address - 0xC000)] = value; + else + m_pWRAMBanks[(address - 0xD000) + (0x1000 * m_iCurrentWRAMBank)] = value; +} + +inline void Memory::SwitchCGBWRAM(u8 value) +{ + m_iCurrentWRAMBank = value; + + if (m_iCurrentWRAMBank == 0) + m_iCurrentWRAMBank = 1; +} + +inline u8 Memory::ReadCGBLCDRAM(u16 address, bool forceBank1) +{ + if (forceBank1 || (m_iCurrentLCDRAMBank == 1)) + return m_pLCDRAMBank1[address - 0x8000]; + else + return Retrieve(address); +} + +inline void Memory::WriteCGBLCDRAM(u16 address, u8 value) +{ + if (m_iCurrentLCDRAMBank == 1) + m_pLCDRAMBank1[address - 0x8000] = value; + else + Load(address, value); +} + +inline void Memory::SwitchCGBLCDRAM(u8 value) +{ + m_iCurrentLCDRAMBank = value; +} + +inline u8 Memory::Retrieve(u16 address) +{ + return m_pMap[address]; +} + +inline void Memory::Load(u16 address, u8 value) +{ + m_pMap[address] = value; +} + +inline Memory::stDisassembleRecord** Memory::GetDisassembledMemoryMap() +{ + return m_pDisassembledMap; +} + +inline Memory::stDisassembleRecord** Memory::GetDisassembledROMMemoryMap() +{ + return m_pDisassembledROMMap; +} + +inline void Memory::CheckBreakpoints(u16 address, bool write) +{ + std::size_t size = m_BreakpointsMem.size(); + + for (std::size_t b = 0; b < size; b++) + { + if (write && !m_BreakpointsMem[b].write) + continue; + + if (!write && !m_BreakpointsMem[b].read) + continue; + + bool proceed = false; + + if (m_BreakpointsMem[b].range) + { + if ((address >= m_BreakpointsMem[b].address1) && (address <= m_BreakpointsMem[b].address2)) + { + proceed = true; + } + } + else + { + if (m_BreakpointsMem[b].address1 == address) + { + proceed = true; + } + } + + if (proceed) + { + m_pProcessor->RequestMemoryBreakpoint(); + break; + } + } +} + +#endif /* MEMORY_INLINE_H */ diff --git a/gearboy/src/MultiMBC1MemoryRule.cpp b/gearboy/src/MultiMBC1MemoryRule.cpp new file mode 100644 index 00000000..252b7bf7 --- /dev/null +++ b/gearboy/src/MultiMBC1MemoryRule.cpp @@ -0,0 +1,206 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#include "MultiMBC1MemoryRule.h" +#include "MBC1MemoryRule.h" +#include "Video.h" +#include "Memory.h" +#include "Processor.h" +#include "Input.h" +#include "Cartridge.h" + +MultiMBC1MemoryRule::MultiMBC1MemoryRule(Processor* pProcessor, + Memory* pMemory, Video* pVideo, Input* pInput, + Cartridge* pCartridge, Audio* pAudio) : MemoryRule(pProcessor, +pMemory, pVideo, pInput, pCartridge, pAudio) +{ + Reset(false); +} + +MultiMBC1MemoryRule::~MultiMBC1MemoryRule() +{ + +} + +void MultiMBC1MemoryRule::Reset(bool bCGB) +{ + m_bCGB = bCGB; + m_iMulticartMode = 0; + m_iROMBankHi = 0; + m_iROMBankLo = 1; + SetROMBanks(); +} + +u8 MultiMBC1MemoryRule::PerformRead(u16 address) +{ + switch (address & 0xE000) + { + case 0x0000: + case 0x2000: + { + u8* pROM = m_pCartridge->GetTheROM(); + + if (m_iMulticartMode == 0) + { + return pROM[address]; + } + else + { + int bank_addr = (m_iMBC1MBank_0 * 0x4000) + address; + return pROM[bank_addr]; + } + } + case 0x4000: + case 0x6000: + { + u8* pROM = m_pCartridge->GetTheROM(); + + if (m_iMulticartMode == 0) + { + int bank_addr = (m_iMBC1Bank_1 * 0x4000) + (address & 0x3FFF); + return pROM[bank_addr]; + } + else + { + int bank_addr = (m_iMBC1MBank_1 * 0x4000) + (address & 0x3FFF); + return pROM[bank_addr]; + } + } + default: + { + return 0xFF; + } + } +} + +void MultiMBC1MemoryRule::PerformWrite(u16 address, u8 value) +{ + switch (address & 0xE000) + { + case 0x2000: + { + m_iROMBankLo = value & 0x1F; + SetROMBanks(); + break; + } + case 0x4000: + { + m_iROMBankHi = value & 0x03; + SetROMBanks(); + break; + } + case 0x6000: + { + m_iMulticartMode = value & 0x01; + break; + } + default: + { + Log("--> ** Attempting to write on invalid address %X %X", address, value); + break; + } + } +} + +void MultiMBC1MemoryRule::SetROMBanks() +{ + int full_bank = (m_iROMBankHi << 5) | m_iROMBankLo; + + m_iMBC1Bank_1 = full_bank; + + if (full_bank == 0x00 || full_bank == 0x20 || full_bank == 0x40 || full_bank == 0x60) + m_iMBC1Bank_1 = full_bank + 1; + + m_iMBC1MBank_0 = ((full_bank >> 1) & 0x30); + m_iMBC1MBank_1 = ((full_bank >> 1) & 0x30) | (full_bank & 0x0F); +} + +size_t MultiMBC1MemoryRule::GetRamSize() +{ + return 0; +} + +u8* MultiMBC1MemoryRule::GetRamBanks() +{ + return 0; +} + +u8* MultiMBC1MemoryRule::GetCurrentRamBank() +{ + return m_pMemory->GetMemoryMap() + 0xA000; +} + +int MultiMBC1MemoryRule::GetCurrentRamBankIndex() +{ + return 0; +} + +u8* MultiMBC1MemoryRule::GetRomBank0() +{ + u8* pROM = m_pCartridge->GetTheROM(); + + if (m_iMulticartMode == 0) + return pROM; + else + return pROM + (m_iMBC1MBank_0 * 0x4000); +} + +int MultiMBC1MemoryRule::GetCurrentRomBank0Index() +{ + return m_iMBC1MBank_0; +} + +u8* MultiMBC1MemoryRule::GetCurrentRomBank1() +{ + u8* pROM = m_pCartridge->GetTheROM(); + + if (m_iMulticartMode == 0) + return &pROM[m_iMBC1Bank_1 * 0x4000]; + else + return &pROM[m_iMBC1MBank_1 * 0x4000]; +} + +int MultiMBC1MemoryRule::GetCurrentRomBank1Index() +{ + return m_iMBC1Bank_1; +} + +void MultiMBC1MemoryRule::SaveState(std::ostream& stream) +{ + using namespace std; + + stream.write(reinterpret_cast<const char*> (&m_iMulticartMode), sizeof(m_iMulticartMode)); + stream.write(reinterpret_cast<const char*> (&m_iROMBankHi), sizeof(m_iROMBankHi)); + stream.write(reinterpret_cast<const char*> (&m_iROMBankLo), sizeof(m_iROMBankLo)); + stream.write(reinterpret_cast<const char*> (&m_iMBC1Bank_1), sizeof(m_iMBC1Bank_1)); + stream.write(reinterpret_cast<const char*> (&m_iMBC1MBank_0), sizeof(m_iMBC1MBank_0)); + stream.write(reinterpret_cast<const char*> (&m_iMBC1MBank_1), sizeof(m_iMBC1MBank_1)); +} + +void MultiMBC1MemoryRule::LoadState(std::istream& stream) +{ + using namespace std; + + stream.read(reinterpret_cast<char*> (&m_iMulticartMode), sizeof(m_iMulticartMode)); + stream.read(reinterpret_cast<char*> (&m_iROMBankHi), sizeof(m_iROMBankHi)); + stream.read(reinterpret_cast<char*> (&m_iROMBankLo), sizeof(m_iROMBankLo)); + stream.read(reinterpret_cast<char*> (&m_iMBC1Bank_1), sizeof(m_iMBC1Bank_1)); + stream.read(reinterpret_cast<char*> (&m_iMBC1MBank_0), sizeof(m_iMBC1MBank_0)); + stream.read(reinterpret_cast<char*> (&m_iMBC1MBank_1), sizeof(m_iMBC1MBank_1)); +} diff --git a/gearboy/src/MultiMBC1MemoryRule.h b/gearboy/src/MultiMBC1MemoryRule.h new file mode 100644 index 00000000..5f818f98 --- /dev/null +++ b/gearboy/src/MultiMBC1MemoryRule.h @@ -0,0 +1,57 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#ifndef MULTIMBC1MEMORYRULE_H +#define MULTIMBC1MEMORYRULE_H + +#include "MemoryRule.h" + +class MultiMBC1MemoryRule : public MemoryRule +{ +public: + MultiMBC1MemoryRule(Processor* pProcessor, Memory* pMemory, + Video* pVideo, Input* pInput, Cartridge* pCartridge, Audio* pAudio); + virtual ~MultiMBC1MemoryRule(); + virtual u8 PerformRead(u16 address); + virtual void PerformWrite(u16 address, u8 value); + virtual void Reset(bool bCGB); + virtual size_t GetRamSize(); + virtual u8* GetRamBanks(); + virtual u8* GetCurrentRamBank(); + virtual int GetCurrentRamBankIndex(); + virtual u8* GetRomBank0(); + virtual int GetCurrentRomBank0Index(); + virtual u8* GetCurrentRomBank1(); + virtual int GetCurrentRomBank1Index(); + virtual void SaveState(std::ostream& stream); + virtual void LoadState(std::istream& stream); + +private: + void SetROMBanks(); + +private: + int m_iMulticartMode; + int m_iROMBankHi; + int m_iROMBankLo; + int m_iMBC1Bank_1; + int m_iMBC1MBank_0; + int m_iMBC1MBank_1; +}; + +#endif /* MULTIMBC1MEMORYRULE_H */ diff --git a/gearboy/src/Processor.cpp b/gearboy/src/Processor.cpp new file mode 100644 index 00000000..079445d5 --- /dev/null +++ b/gearboy/src/Processor.cpp @@ -0,0 +1,1263 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#include <algorithm> +#include <ctype.h> +#include "Processor.h" +#include "opcode_timing.h" +#include "opcode_names.h" + +Processor::Processor(Memory* pMemory) +{ + m_pMemory = pMemory; + m_pMemory->SetProcessor(this); + InitOPCodeFunctors(); + m_bIME = false; + m_bHalt = false; + m_bCGBSpeed = false; + m_iSpeedMultiplier = 0; + m_bBranchTaken = false; + m_bSkipPCBug = false; + m_iCurrentClockCycles = 0; + m_iDIVCycles = 0; + m_iTIMACycles = 0; + m_iIMECycles = 0; + m_iSerialBit = 0; + m_iSerialCycles = 0; + m_bCGB = false; + m_iUnhaltCycles = 0; + m_iInterruptDelayCycles = 0; + m_iAccurateOPCodeState = 0; + m_iReadCache = 0; + m_bBreakpointHit = false; + m_bRequestMemBreakpoint = false; + + m_ProcessorState.AF = &AF; + m_ProcessorState.BC = &BC; + m_ProcessorState.DE = &DE; + m_ProcessorState.HL = &HL; + m_ProcessorState.SP = &SP; + m_ProcessorState.PC = &PC; + m_ProcessorState.IME = &m_bIME; + m_ProcessorState.Halt = &m_bHalt; +} + +Processor::~Processor() +{ +} + +void Processor::Init() +{ + Reset(false, false); +} + +void Processor::Reset(bool bCGB, bool bGBA) +{ + m_bCGB = bCGB; + m_bIME = false; + m_bHalt = false; + m_bCGBSpeed = false; + m_iSpeedMultiplier = 0; + m_bBranchTaken = false; + m_bSkipPCBug = false; + m_iCurrentClockCycles = 0; + m_iDIVCycles = 0; + m_iTIMACycles = 0; + m_iIMECycles = 0; + m_iSerialBit = 0; + m_iSerialCycles = 0; + m_iUnhaltCycles = 0; + + if(m_pMemory->IsBootromEnabled()) + { + PC.SetValue(0); + SP.SetValue(0); + AF.SetValue(0); + BC.SetValue(0); + DE.SetValue(0); + HL.SetValue(0); + } + else + { + m_pMemory->DisableBootromRegistry(); + PC.SetValue(0x100); + SP.SetValue(0xFFFE); + + if (m_bCGB) + { + if (bGBA) + { + AF.SetValue(0x1100); + BC.SetValue(0x0100); + } + else + { + AF.SetValue(0x1180); + BC.SetValue(0x0000); + } + DE.SetValue(0xFF56); + HL.SetValue(0x000D); + } + else + { + AF.SetValue(0x01B0); + BC.SetValue(0x0013); + DE.SetValue(0x00D8); + HL.SetValue(0x014D); + } + } + + m_iInterruptDelayCycles = 0; + m_iAccurateOPCodeState = 0; + m_iReadCache = 0; + m_GameSharkList.clear(); + m_bBreakpointHit = false; + m_bRequestMemBreakpoint = false; +} + +u8 Processor::RunFor(u8 ticks) +{ + u8 executed = 0; + + while (executed < ticks) + { + m_iCurrentClockCycles = 0; + m_bBreakpointHit = false; + m_bRequestMemBreakpoint = false; + + if (m_iAccurateOPCodeState == 0 && m_bHalt) + { + m_iCurrentClockCycles += AdjustedCycles(4); + + if (m_iUnhaltCycles > 0) + { + m_iUnhaltCycles -= m_iCurrentClockCycles; + + if (m_iUnhaltCycles <= 0) + { + m_iUnhaltCycles = 0; + m_bHalt = false; + } + } + + if (m_bHalt && (InterruptPending() != None_Interrupt) && (m_iUnhaltCycles == 0)) + { + m_iUnhaltCycles = AdjustedCycles(12); + } + } + + bool interrupt_served = false; + + if (!m_bHalt) + { + Interrupts interrupt = InterruptPending(); + + if (m_bIME && (interrupt != None_Interrupt) && (m_iAccurateOPCodeState == 0)) + { + ServeInterrupt(interrupt); + interrupt_served = true; + } + else + { + u8 opcode = m_pMemory->Read(PC.GetValue()); + PC.Increment(); + + if (m_bSkipPCBug) + { + m_bSkipPCBug = false; + PC.Decrement(); + } + + const u8* accurateOPcodes; + const u8* machineCycles; + OPCptr* opcodeTable; + bool isCB = (opcode == 0xCB); + + if (isCB) + { + accurateOPcodes = kOPCodeCBAccurate; + machineCycles = kOPCodeCBMachineCycles; + opcodeTable = m_OPCodesCB; + + opcode = m_pMemory->Read(PC.GetValue()); + PC.Increment(); + + if (m_bSkipPCBug) + { + m_bSkipPCBug = false; + PC.Decrement(); + } + } + else + { + accurateOPcodes = kOPCodeAccurate; + machineCycles = kOPCodeMachineCycles; + opcodeTable = m_OPCodes; + } + + if ((accurateOPcodes[opcode] != 0) && (m_iAccurateOPCodeState == 0)) + { + int left_cycles = (accurateOPcodes[opcode] < 3 ? 2 : 3); + m_iCurrentClockCycles += (machineCycles[opcode] - left_cycles) * AdjustedCycles(4); + m_iAccurateOPCodeState = 1; + PC.Decrement(); + if (isCB) + PC.Decrement(); + } + else + { + (this->*opcodeTable[opcode])(); + + if (m_bBranchTaken) + { + m_bBranchTaken = false; + m_iCurrentClockCycles += kOPCodeBranchMachineCycles[opcode] * AdjustedCycles(4); + } + else + { + switch (m_iAccurateOPCodeState) + { + case 0: + m_iCurrentClockCycles += machineCycles[opcode] * AdjustedCycles(4); + break; + case 1: + if (accurateOPcodes[opcode] == 3) + { + m_iCurrentClockCycles += 1 * AdjustedCycles(4); + m_iAccurateOPCodeState = 2; + PC.Decrement(); + if (isCB) + PC.Decrement(); + } + else + { + m_iCurrentClockCycles += 2 * AdjustedCycles(4); + m_iAccurateOPCodeState = 0; + } + break; + case 2: + m_iCurrentClockCycles += 2 * AdjustedCycles(4); + m_iAccurateOPCodeState = 0; + break; + } + } + } + } + + #ifndef GEARBOY_DISABLE_DISASSEMBLER + if (Disassemble(PC.GetValue()) || m_bRequestMemBreakpoint) + m_bBreakpointHit = true; + #endif + } + + if (!interrupt_served && (m_iInterruptDelayCycles > 0)) + { + m_iInterruptDelayCycles -= m_iCurrentClockCycles; + } + + if (!interrupt_served && (m_iAccurateOPCodeState == 0) && (m_iIMECycles > 0)) + { + m_iIMECycles -= m_iCurrentClockCycles; + + if (m_iIMECycles <= 0) + { + m_iIMECycles = 0; + m_bIME = true; + } + } + + executed += m_iCurrentClockCycles; + } + + return executed; +} + +void Processor::ServeInterrupt(Interrupts interrupt) +{ + u8 if_reg = m_pMemory->Retrieve(0xFF0F); + m_bIME = false; + StackPush(&PC); + m_iCurrentClockCycles += AdjustedCycles(20); + + switch (interrupt) + { + case VBlank_Interrupt: + m_iInterruptDelayCycles= 0; + m_pMemory->Load(0xFF0F, if_reg & 0xFE); + PC.SetValue(0x0040); + UpdateGameShark(); + break; + case LCDSTAT_Interrupt: + m_pMemory->Load(0xFF0F, if_reg & 0xFD); + PC.SetValue(0x0048); + break; + case Timer_Interrupt: + m_pMemory->Load(0xFF0F, if_reg & 0xFB); + PC.SetValue(0x0050); + break; + case Serial_Interrupt: + m_pMemory->Load(0xFF0F, if_reg & 0xF7); + PC.SetValue(0x0058); + break; + case Joypad_Interrupt: + m_pMemory->Load(0xFF0F, if_reg & 0xEF); + PC.SetValue(0x0060); + break; + case None_Interrupt: + break; + } +} + +void Processor::UpdateTimers(u8 ticks) +{ + m_iDIVCycles += ticks; + + unsigned int div_cycles = AdjustedCycles(256); + + while (m_iDIVCycles >= div_cycles) + { + m_iDIVCycles -= div_cycles; + u8 div = m_pMemory->Retrieve(0xFF04); + div++; + m_pMemory->Load(0xFF04, div); + } + + u8 tac = m_pMemory->Retrieve(0xFF07); + + // if tima is running + if (tac & 0x04) + { + m_iTIMACycles += ticks; + + unsigned int freq = 0; + + switch (tac & 0x03) + { + case 0: + freq = AdjustedCycles(1024); + break; + case 1: + freq = AdjustedCycles(16); + break; + case 2: + freq = AdjustedCycles(64); + break; + case 3: + freq = AdjustedCycles(256); + break; + } + + while (m_iTIMACycles >= freq) + { + m_iTIMACycles -= freq; + u8 tima = m_pMemory->Retrieve(0xFF05); + + if (tima == 0xFF) + { + tima = m_pMemory->Retrieve(0xFF06); + RequestInterrupt(Timer_Interrupt); + } + else + tima++; + + m_pMemory->Load(0xFF05, tima); + } + } +} + +void Processor::UpdateSerial(u8 ticks) +{ + u8 sc = m_pMemory->Retrieve(0xFF02); + + if (IsSetBit(sc, 7) && IsSetBit(sc, 0)) + { + m_iSerialCycles += ticks; + + if (m_iSerialBit < 0) + { + m_iSerialBit = 0; + m_iSerialCycles = 0; + return; + } + + int serial_cycles = AdjustedCycles(512); + + if (m_iSerialCycles >= serial_cycles) + { + if (m_iSerialBit > 7) + { + m_pMemory->Load(0xFF02, sc & 0x7F); + RequestInterrupt(Serial_Interrupt); + m_iSerialBit = -1; + + return; + } + + u8 sb = m_pMemory->Retrieve(0xFF01); + sb <<= 1; + sb |= 0x01; + m_pMemory->Load(0xFF01, sb); + + m_iSerialCycles -= serial_cycles; + m_iSerialBit++; + } + } +} + +void Processor::UpdateGameShark() +{ + std::list<GameSharkCode>::iterator it; + + for (it = m_GameSharkList.begin(); it != m_GameSharkList.end(); it++) + { + if (it->type == 0x01) + { + m_pMemory->Write(it->address, it->value); + } + } +} + +bool Processor::Disassemble(u16 address) +{ + Memory::stDisassembleRecord** memoryMap = m_pMemory->GetDisassembledMemoryMap(); + Memory::stDisassembleRecord** romMap = m_pMemory->GetDisassembledROMMemoryMap(); + + Memory::stDisassembleRecord** map = NULL; + + int offset = address; + int bank = 0; + bool rom = false; + + if ((address & 0xC000) == 0x0000) + { + bank = m_pMemory->GetCurrentRule()->GetCurrentRomBank0Index(); + offset = (0x4000 * bank) + address; + map = romMap; + rom = true; + } + else if ((address & 0xC000) == 0x4000) + { + bank = m_pMemory->GetCurrentRule()->GetCurrentRomBank1Index(); + offset = (0x4000 * bank) + (address & 0x3FFF); + map = romMap; + rom = true; + } + else + { + map = memoryMap; + rom = false; + } + + if (!IsValidPointer(map[offset])) + { + map[offset] = new Memory::stDisassembleRecord; + + if (rom) + { + map[offset]->address = offset & 0x3FFF; + map[offset]->bank = offset >> 14; + } + else + { + map[offset]->address = 0; + map[offset]->bank = 0; + } + + map[offset]->name[0] = 0; + map[offset]->bytes[0] = 0; + map[offset]->size = 0; + map[offset]->jump = false; + map[offset]->jump_address = 0; + for (int i = 0; i < 4; i++) + map[offset]->opcodes[i] = 0; + } + + u8 opcodes[4]; + bool changed = false; + + for (int i = 0; i < map[offset]->size; i++) + { + opcodes[i] = m_pMemory->Read(address + i); + + if (opcodes[i] != map[offset]->opcodes[i]) + changed = true; + } + + if ((map[offset]->size == 0) || changed) + { + map[offset]->bank = bank; + map[offset]->address = address; + + for (int i = 0; i < 4; i++) + map[offset]->opcodes[i] = m_pMemory->Read(address + i); + + u8 opcode = map[offset]->opcodes[0]; + bool cb = false; + + if (opcode == 0xCB) + { + cb = true; + opcode = map[offset]->opcodes[1]; + } + + stOPCodeInfo info = cb ? kOPCodeCBNames[opcode] : kOPCodeNames[opcode]; + + map[offset]->size = info.size; + + map[offset]->bytes[0] = 0; + + for (int i = 0; i < 4; i++) + { + if (i < info.size) + { + char value[8]; + sprintf(value, "%02X", map[offset]->opcodes[i]); + strcat(map[offset]->bytes, value); + } + else + { + strcat(map[offset]->bytes, " "); + } + + if (i < 3) + strcat(map[offset]->bytes, " "); + } + + switch (info.type) + { + case 0: + strcpy(map[offset]->name, info.name); + break; + case 1: + sprintf(map[offset]->name, info.name, map[offset]->opcodes[1]); + break; + case 2: + map[offset]->jump = true; + map[offset]->jump_address = (map[offset]->opcodes[2] << 8) | map[offset]->opcodes[1]; + sprintf(map[offset]->name, info.name, map[offset]->jump_address); + break; + case 3: + sprintf(map[offset]->name, info.name, (s8)map[offset]->opcodes[1]); + break; + case 4: + map[offset]->jump = true; + map[offset]->jump_address = address + info.size + (s8)map[offset]->opcodes[1]; + sprintf(map[offset]->name, info.name, map[offset]->jump_address, (s8)map[offset]->opcodes[1]); + break; + case 5: + sprintf(map[offset]->name, info.name, map[offset]->opcodes[1], kRegisterNames[map[offset]->opcodes[1]]); + break; + default: + strcpy(map[offset]->name, "PARSE ERROR"); + } + } + + Memory::stDisassembleRecord* runtobreakpoint = m_pMemory->GetRunToBreakpoint(); + std::vector<Memory::stDisassembleRecord*>* breakpoints = m_pMemory->GetBreakpointsCPU(); + + if (IsValidPointer(runtobreakpoint)) + { + if (runtobreakpoint == map[offset]) + { + m_pMemory->SetRunToBreakpoint(NULL); + return true; + } + else + return false; + } + else + { + std::size_t size = breakpoints->size(); + + for (std::size_t b = 0; b < size; b++) + { + if ((*breakpoints)[b] == map[offset]) + { + return true; + } + } + } + + return false; +} + +bool Processor::BreakpointHit() +{ + return m_bBreakpointHit; +} + +void Processor::RequestMemoryBreakpoint() +{ + m_bRequestMemBreakpoint = true; +} + +void Processor::SaveState(std::ostream& stream) +{ + using namespace std; + + u16 af = AF.GetValue(); + u16 bc = BC.GetValue(); + u16 de = DE.GetValue(); + u16 hl = HL.GetValue(); + u16 sp = SP.GetValue(); + u16 pc = PC.GetValue(); + + stream.write(reinterpret_cast<const char*> (&af), sizeof(af)); + stream.write(reinterpret_cast<const char*> (&bc), sizeof(bc)); + stream.write(reinterpret_cast<const char*> (&de), sizeof(de)); + stream.write(reinterpret_cast<const char*> (&hl), sizeof(hl)); + stream.write(reinterpret_cast<const char*> (&sp), sizeof(sp)); + stream.write(reinterpret_cast<const char*> (&pc), sizeof(pc)); + + stream.write(reinterpret_cast<const char*> (&m_bIME), sizeof(m_bIME)); + stream.write(reinterpret_cast<const char*> (&m_bHalt), sizeof(m_bHalt)); + stream.write(reinterpret_cast<const char*> (&m_bBranchTaken), sizeof(m_bBranchTaken)); + stream.write(reinterpret_cast<const char*> (&m_bSkipPCBug), sizeof(m_bSkipPCBug)); + stream.write(reinterpret_cast<const char*> (&m_iCurrentClockCycles), sizeof(m_iCurrentClockCycles)); + stream.write(reinterpret_cast<const char*> (&m_iDIVCycles), sizeof(m_iDIVCycles)); + stream.write(reinterpret_cast<const char*> (&m_iTIMACycles), sizeof(m_iTIMACycles)); + stream.write(reinterpret_cast<const char*> (&m_iSerialBit), sizeof(m_iSerialBit)); + stream.write(reinterpret_cast<const char*> (&m_iSerialCycles), sizeof(m_iSerialCycles)); + stream.write(reinterpret_cast<const char*> (&m_iIMECycles), sizeof(m_iIMECycles)); + stream.write(reinterpret_cast<const char*> (&m_iUnhaltCycles), sizeof(m_iUnhaltCycles)); + stream.write(reinterpret_cast<const char*> (&m_iInterruptDelayCycles), sizeof(m_iInterruptDelayCycles)); + stream.write(reinterpret_cast<const char*> (&m_bCGBSpeed), sizeof(m_bCGBSpeed)); + stream.write(reinterpret_cast<const char*> (&m_iSpeedMultiplier), sizeof(m_iSpeedMultiplier)); + stream.write(reinterpret_cast<const char*> (&m_iAccurateOPCodeState), sizeof(m_iAccurateOPCodeState)); + stream.write(reinterpret_cast<const char*> (&m_iReadCache), sizeof(m_iReadCache)); +} + +void Processor::LoadState(std::istream& stream) +{ + using namespace std; + + u16 af; + u16 bc; + u16 de; + u16 hl; + u16 sp; + u16 pc; + + stream.read(reinterpret_cast<char*> (&af), sizeof(af)); + stream.read(reinterpret_cast<char*> (&bc), sizeof(bc)); + stream.read(reinterpret_cast<char*> (&de), sizeof(de)); + stream.read(reinterpret_cast<char*> (&hl), sizeof(hl)); + stream.read(reinterpret_cast<char*> (&sp), sizeof(sp)); + stream.read(reinterpret_cast<char*> (&pc), sizeof(pc)); + + AF.SetValue(af); + BC.SetValue(bc); + DE.SetValue(de); + HL.SetValue(hl); + SP.SetValue(sp); + PC.SetValue(pc); + + stream.read(reinterpret_cast<char*> (&m_bIME), sizeof(m_bIME)); + stream.read(reinterpret_cast<char*> (&m_bHalt), sizeof(m_bHalt)); + stream.read(reinterpret_cast<char*> (&m_bBranchTaken), sizeof(m_bBranchTaken)); + stream.read(reinterpret_cast<char*> (&m_bSkipPCBug), sizeof(m_bSkipPCBug)); + stream.read(reinterpret_cast<char*> (&m_iCurrentClockCycles), sizeof(m_iCurrentClockCycles)); + stream.read(reinterpret_cast<char*> (&m_iDIVCycles), sizeof(m_iDIVCycles)); + stream.read(reinterpret_cast<char*> (&m_iTIMACycles), sizeof(m_iTIMACycles)); + stream.read(reinterpret_cast<char*> (&m_iSerialBit), sizeof(m_iSerialBit)); + stream.read(reinterpret_cast<char*> (&m_iSerialCycles), sizeof(m_iSerialCycles)); + stream.read(reinterpret_cast<char*> (&m_iIMECycles), sizeof(m_iIMECycles)); + stream.read(reinterpret_cast<char*> (&m_iUnhaltCycles), sizeof(m_iUnhaltCycles)); + stream.read(reinterpret_cast<char*> (&m_iInterruptDelayCycles), sizeof(m_iInterruptDelayCycles)); + stream.read(reinterpret_cast<char*> (&m_bCGBSpeed), sizeof(m_bCGBSpeed)); + stream.read(reinterpret_cast<char*> (&m_iSpeedMultiplier), sizeof(m_iSpeedMultiplier)); + stream.read(reinterpret_cast<char*> (&m_iAccurateOPCodeState), sizeof(m_iAccurateOPCodeState)); + stream.read(reinterpret_cast<char*> (&m_iReadCache), sizeof(m_iReadCache)); +} + +void Processor::SetGameSharkCheat(const char* szCheat) +{ + std::string code(szCheat); + for (std::string::iterator p = code.begin(); code.end() != p; ++p) + *p = toupper(*p); + + if (code.length() == 8) + { + GameSharkCode gsc; + + gsc.type = AsHex(code[0]) << 4 | AsHex(code[1]); + gsc.value = (AsHex(code[2]) << 4 | AsHex(code[3])) & 0xFF; + gsc.address = (AsHex(code[4]) << 4 | AsHex(code[5]) | AsHex(code[6]) << 12 | AsHex(code[7]) << 8) & 0xFFFF; + + m_GameSharkList.push_back(gsc); + } +} + +void Processor::ClearGameSharkCheats() +{ + m_GameSharkList.clear(); +} + +Processor::ProcessorState* Processor::GetState() +{ + return &m_ProcessorState; +} + +void Processor::InitOPCodeFunctors() +{ + m_OPCodes[0x00] = &Processor::OPCode0x00; + m_OPCodes[0x01] = &Processor::OPCode0x01; + m_OPCodes[0x02] = &Processor::OPCode0x02; + m_OPCodes[0x03] = &Processor::OPCode0x03; + m_OPCodes[0x04] = &Processor::OPCode0x04; + m_OPCodes[0x05] = &Processor::OPCode0x05; + m_OPCodes[0x06] = &Processor::OPCode0x06; + m_OPCodes[0x07] = &Processor::OPCode0x07; + m_OPCodes[0x08] = &Processor::OPCode0x08; + m_OPCodes[0x09] = &Processor::OPCode0x09; + m_OPCodes[0x0A] = &Processor::OPCode0x0A; + m_OPCodes[0x0B] = &Processor::OPCode0x0B; + m_OPCodes[0x0C] = &Processor::OPCode0x0C; + m_OPCodes[0x0D] = &Processor::OPCode0x0D; + m_OPCodes[0x0E] = &Processor::OPCode0x0E; + m_OPCodes[0x0F] = &Processor::OPCode0x0F; + + m_OPCodes[0x10] = &Processor::OPCode0x10; + m_OPCodes[0x11] = &Processor::OPCode0x11; + m_OPCodes[0x12] = &Processor::OPCode0x12; + m_OPCodes[0x13] = &Processor::OPCode0x13; + m_OPCodes[0x14] = &Processor::OPCode0x14; + m_OPCodes[0x15] = &Processor::OPCode0x15; + m_OPCodes[0x16] = &Processor::OPCode0x16; + m_OPCodes[0x17] = &Processor::OPCode0x17; + m_OPCodes[0x18] = &Processor::OPCode0x18; + m_OPCodes[0x19] = &Processor::OPCode0x19; + m_OPCodes[0x1A] = &Processor::OPCode0x1A; + m_OPCodes[0x1B] = &Processor::OPCode0x1B; + m_OPCodes[0x1C] = &Processor::OPCode0x1C; + m_OPCodes[0x1D] = &Processor::OPCode0x1D; + m_OPCodes[0x1E] = &Processor::OPCode0x1E; + m_OPCodes[0x1F] = &Processor::OPCode0x1F; + + m_OPCodes[0x20] = &Processor::OPCode0x20; + m_OPCodes[0x21] = &Processor::OPCode0x21; + m_OPCodes[0x22] = &Processor::OPCode0x22; + m_OPCodes[0x23] = &Processor::OPCode0x23; + m_OPCodes[0x24] = &Processor::OPCode0x24; + m_OPCodes[0x25] = &Processor::OPCode0x25; + m_OPCodes[0x26] = &Processor::OPCode0x26; + m_OPCodes[0x27] = &Processor::OPCode0x27; + m_OPCodes[0x28] = &Processor::OPCode0x28; + m_OPCodes[0x29] = &Processor::OPCode0x29; + m_OPCodes[0x2A] = &Processor::OPCode0x2A; + m_OPCodes[0x2B] = &Processor::OPCode0x2B; + m_OPCodes[0x2C] = &Processor::OPCode0x2C; + m_OPCodes[0x2D] = &Processor::OPCode0x2D; + m_OPCodes[0x2E] = &Processor::OPCode0x2E; + m_OPCodes[0x2F] = &Processor::OPCode0x2F; + + m_OPCodes[0x30] = &Processor::OPCode0x30; + m_OPCodes[0x31] = &Processor::OPCode0x31; + m_OPCodes[0x32] = &Processor::OPCode0x32; + m_OPCodes[0x33] = &Processor::OPCode0x33; + m_OPCodes[0x34] = &Processor::OPCode0x34; + m_OPCodes[0x35] = &Processor::OPCode0x35; + m_OPCodes[0x36] = &Processor::OPCode0x36; + m_OPCodes[0x37] = &Processor::OPCode0x37; + m_OPCodes[0x38] = &Processor::OPCode0x38; + m_OPCodes[0x39] = &Processor::OPCode0x39; + m_OPCodes[0x3A] = &Processor::OPCode0x3A; + m_OPCodes[0x3B] = &Processor::OPCode0x3B; + m_OPCodes[0x3C] = &Processor::OPCode0x3C; + m_OPCodes[0x3D] = &Processor::OPCode0x3D; + m_OPCodes[0x3E] = &Processor::OPCode0x3E; + m_OPCodes[0x3F] = &Processor::OPCode0x3F; + + m_OPCodes[0x40] = &Processor::OPCode0x40; + m_OPCodes[0x41] = &Processor::OPCode0x41; + m_OPCodes[0x42] = &Processor::OPCode0x42; + m_OPCodes[0x43] = &Processor::OPCode0x43; + m_OPCodes[0x44] = &Processor::OPCode0x44; + m_OPCodes[0x45] = &Processor::OPCode0x45; + m_OPCodes[0x46] = &Processor::OPCode0x46; + m_OPCodes[0x47] = &Processor::OPCode0x47; + m_OPCodes[0x48] = &Processor::OPCode0x48; + m_OPCodes[0x49] = &Processor::OPCode0x49; + m_OPCodes[0x4A] = &Processor::OPCode0x4A; + m_OPCodes[0x4B] = &Processor::OPCode0x4B; + m_OPCodes[0x4C] = &Processor::OPCode0x4C; + m_OPCodes[0x4D] = &Processor::OPCode0x4D; + m_OPCodes[0x4E] = &Processor::OPCode0x4E; + m_OPCodes[0x4F] = &Processor::OPCode0x4F; + + m_OPCodes[0x50] = &Processor::OPCode0x50; + m_OPCodes[0x51] = &Processor::OPCode0x51; + m_OPCodes[0x52] = &Processor::OPCode0x52; + m_OPCodes[0x53] = &Processor::OPCode0x53; + m_OPCodes[0x54] = &Processor::OPCode0x54; + m_OPCodes[0x55] = &Processor::OPCode0x55; + m_OPCodes[0x56] = &Processor::OPCode0x56; + m_OPCodes[0x57] = &Processor::OPCode0x57; + m_OPCodes[0x58] = &Processor::OPCode0x58; + m_OPCodes[0x59] = &Processor::OPCode0x59; + m_OPCodes[0x5A] = &Processor::OPCode0x5A; + m_OPCodes[0x5B] = &Processor::OPCode0x5B; + m_OPCodes[0x5C] = &Processor::OPCode0x5C; + m_OPCodes[0x5D] = &Processor::OPCode0x5D; + m_OPCodes[0x5E] = &Processor::OPCode0x5E; + m_OPCodes[0x5F] = &Processor::OPCode0x5F; + + m_OPCodes[0x60] = &Processor::OPCode0x60; + m_OPCodes[0x61] = &Processor::OPCode0x61; + m_OPCodes[0x62] = &Processor::OPCode0x62; + m_OPCodes[0x63] = &Processor::OPCode0x63; + m_OPCodes[0x64] = &Processor::OPCode0x64; + m_OPCodes[0x65] = &Processor::OPCode0x65; + m_OPCodes[0x66] = &Processor::OPCode0x66; + m_OPCodes[0x67] = &Processor::OPCode0x67; + m_OPCodes[0x68] = &Processor::OPCode0x68; + m_OPCodes[0x69] = &Processor::OPCode0x69; + m_OPCodes[0x6A] = &Processor::OPCode0x6A; + m_OPCodes[0x6B] = &Processor::OPCode0x6B; + m_OPCodes[0x6C] = &Processor::OPCode0x6C; + m_OPCodes[0x6D] = &Processor::OPCode0x6D; + m_OPCodes[0x6E] = &Processor::OPCode0x6E; + m_OPCodes[0x6F] = &Processor::OPCode0x6F; + + m_OPCodes[0x70] = &Processor::OPCode0x70; + m_OPCodes[0x71] = &Processor::OPCode0x71; + m_OPCodes[0x72] = &Processor::OPCode0x72; + m_OPCodes[0x73] = &Processor::OPCode0x73; + m_OPCodes[0x74] = &Processor::OPCode0x74; + m_OPCodes[0x75] = &Processor::OPCode0x75; + m_OPCodes[0x76] = &Processor::OPCode0x76; + m_OPCodes[0x77] = &Processor::OPCode0x77; + m_OPCodes[0x78] = &Processor::OPCode0x78; + m_OPCodes[0x79] = &Processor::OPCode0x79; + m_OPCodes[0x7A] = &Processor::OPCode0x7A; + m_OPCodes[0x7B] = &Processor::OPCode0x7B; + m_OPCodes[0x7C] = &Processor::OPCode0x7C; + m_OPCodes[0x7D] = &Processor::OPCode0x7D; + m_OPCodes[0x7E] = &Processor::OPCode0x7E; + m_OPCodes[0x7F] = &Processor::OPCode0x7F; + + m_OPCodes[0x80] = &Processor::OPCode0x80; + m_OPCodes[0x81] = &Processor::OPCode0x81; + m_OPCodes[0x82] = &Processor::OPCode0x82; + m_OPCodes[0x83] = &Processor::OPCode0x83; + m_OPCodes[0x84] = &Processor::OPCode0x84; + m_OPCodes[0x85] = &Processor::OPCode0x85; + m_OPCodes[0x86] = &Processor::OPCode0x86; + m_OPCodes[0x87] = &Processor::OPCode0x87; + m_OPCodes[0x88] = &Processor::OPCode0x88; + m_OPCodes[0x89] = &Processor::OPCode0x89; + m_OPCodes[0x8A] = &Processor::OPCode0x8A; + m_OPCodes[0x8B] = &Processor::OPCode0x8B; + m_OPCodes[0x8C] = &Processor::OPCode0x8C; + m_OPCodes[0x8D] = &Processor::OPCode0x8D; + m_OPCodes[0x8E] = &Processor::OPCode0x8E; + m_OPCodes[0x8F] = &Processor::OPCode0x8F; + + m_OPCodes[0x90] = &Processor::OPCode0x90; + m_OPCodes[0x91] = &Processor::OPCode0x91; + m_OPCodes[0x92] = &Processor::OPCode0x92; + m_OPCodes[0x93] = &Processor::OPCode0x93; + m_OPCodes[0x94] = &Processor::OPCode0x94; + m_OPCodes[0x95] = &Processor::OPCode0x95; + m_OPCodes[0x96] = &Processor::OPCode0x96; + m_OPCodes[0x97] = &Processor::OPCode0x97; + m_OPCodes[0x98] = &Processor::OPCode0x98; + m_OPCodes[0x99] = &Processor::OPCode0x99; + m_OPCodes[0x9A] = &Processor::OPCode0x9A; + m_OPCodes[0x9B] = &Processor::OPCode0x9B; + m_OPCodes[0x9C] = &Processor::OPCode0x9C; + m_OPCodes[0x9D] = &Processor::OPCode0x9D; + m_OPCodes[0x9E] = &Processor::OPCode0x9E; + m_OPCodes[0x9F] = &Processor::OPCode0x9F; + + m_OPCodes[0xA0] = &Processor::OPCode0xA0; + m_OPCodes[0xA1] = &Processor::OPCode0xA1; + m_OPCodes[0xA2] = &Processor::OPCode0xA2; + m_OPCodes[0xA3] = &Processor::OPCode0xA3; + m_OPCodes[0xA4] = &Processor::OPCode0xA4; + m_OPCodes[0xA5] = &Processor::OPCode0xA5; + m_OPCodes[0xA6] = &Processor::OPCode0xA6; + m_OPCodes[0xA7] = &Processor::OPCode0xA7; + m_OPCodes[0xA8] = &Processor::OPCode0xA8; + m_OPCodes[0xA9] = &Processor::OPCode0xA9; + m_OPCodes[0xAA] = &Processor::OPCode0xAA; + m_OPCodes[0xAB] = &Processor::OPCode0xAB; + m_OPCodes[0xAC] = &Processor::OPCode0xAC; + m_OPCodes[0xAD] = &Processor::OPCode0xAD; + m_OPCodes[0xAE] = &Processor::OPCode0xAE; + m_OPCodes[0xAF] = &Processor::OPCode0xAF; + + m_OPCodes[0xB0] = &Processor::OPCode0xB0; + m_OPCodes[0xB1] = &Processor::OPCode0xB1; + m_OPCodes[0xB2] = &Processor::OPCode0xB2; + m_OPCodes[0xB3] = &Processor::OPCode0xB3; + m_OPCodes[0xB4] = &Processor::OPCode0xB4; + m_OPCodes[0xB5] = &Processor::OPCode0xB5; + m_OPCodes[0xB6] = &Processor::OPCode0xB6; + m_OPCodes[0xB7] = &Processor::OPCode0xB7; + m_OPCodes[0xB8] = &Processor::OPCode0xB8; + m_OPCodes[0xB9] = &Processor::OPCode0xB9; + m_OPCodes[0xBA] = &Processor::OPCode0xBA; + m_OPCodes[0xBB] = &Processor::OPCode0xBB; + m_OPCodes[0xBC] = &Processor::OPCode0xBC; + m_OPCodes[0xBD] = &Processor::OPCode0xBD; + m_OPCodes[0xBE] = &Processor::OPCode0xBE; + m_OPCodes[0xBF] = &Processor::OPCode0xBF; + + m_OPCodes[0xC0] = &Processor::OPCode0xC0; + m_OPCodes[0xC1] = &Processor::OPCode0xC1; + m_OPCodes[0xC2] = &Processor::OPCode0xC2; + m_OPCodes[0xC3] = &Processor::OPCode0xC3; + m_OPCodes[0xC4] = &Processor::OPCode0xC4; + m_OPCodes[0xC5] = &Processor::OPCode0xC5; + m_OPCodes[0xC6] = &Processor::OPCode0xC6; + m_OPCodes[0xC7] = &Processor::OPCode0xC7; + m_OPCodes[0xC8] = &Processor::OPCode0xC8; + m_OPCodes[0xC9] = &Processor::OPCode0xC9; + m_OPCodes[0xCA] = &Processor::OPCode0xCA; + m_OPCodes[0xCB] = &Processor::OPCode0xCB; + m_OPCodes[0xCC] = &Processor::OPCode0xCC; + m_OPCodes[0xCD] = &Processor::OPCode0xCD; + m_OPCodes[0xCE] = &Processor::OPCode0xCE; + m_OPCodes[0xCF] = &Processor::OPCode0xCF; + + m_OPCodes[0xD0] = &Processor::OPCode0xD0; + m_OPCodes[0xD1] = &Processor::OPCode0xD1; + m_OPCodes[0xD2] = &Processor::OPCode0xD2; + m_OPCodes[0xD3] = &Processor::OPCode0xD3; + m_OPCodes[0xD4] = &Processor::OPCode0xD4; + m_OPCodes[0xD5] = &Processor::OPCode0xD5; + m_OPCodes[0xD6] = &Processor::OPCode0xD6; + m_OPCodes[0xD7] = &Processor::OPCode0xD7; + m_OPCodes[0xD8] = &Processor::OPCode0xD8; + m_OPCodes[0xD9] = &Processor::OPCode0xD9; + m_OPCodes[0xDA] = &Processor::OPCode0xDA; + m_OPCodes[0xDB] = &Processor::OPCode0xDB; + m_OPCodes[0xDC] = &Processor::OPCode0xDC; + m_OPCodes[0xDD] = &Processor::OPCode0xDD; + m_OPCodes[0xDE] = &Processor::OPCode0xDE; + m_OPCodes[0xDF] = &Processor::OPCode0xDF; + + m_OPCodes[0xE0] = &Processor::OPCode0xE0; + m_OPCodes[0xE1] = &Processor::OPCode0xE1; + m_OPCodes[0xE2] = &Processor::OPCode0xE2; + m_OPCodes[0xE3] = &Processor::OPCode0xE3; + m_OPCodes[0xE4] = &Processor::OPCode0xE4; + m_OPCodes[0xE5] = &Processor::OPCode0xE5; + m_OPCodes[0xE6] = &Processor::OPCode0xE6; + m_OPCodes[0xE7] = &Processor::OPCode0xE7; + m_OPCodes[0xE8] = &Processor::OPCode0xE8; + m_OPCodes[0xE9] = &Processor::OPCode0xE9; + m_OPCodes[0xEA] = &Processor::OPCode0xEA; + m_OPCodes[0xEB] = &Processor::OPCode0xEB; + m_OPCodes[0xEC] = &Processor::OPCode0xEC; + m_OPCodes[0xED] = &Processor::OPCode0xED; + m_OPCodes[0xEE] = &Processor::OPCode0xEE; + m_OPCodes[0xEF] = &Processor::OPCode0xEF; + + m_OPCodes[0xF0] = &Processor::OPCode0xF0; + m_OPCodes[0xF1] = &Processor::OPCode0xF1; + m_OPCodes[0xF2] = &Processor::OPCode0xF2; + m_OPCodes[0xF3] = &Processor::OPCode0xF3; + m_OPCodes[0xF4] = &Processor::OPCode0xF4; + m_OPCodes[0xF5] = &Processor::OPCode0xF5; + m_OPCodes[0xF6] = &Processor::OPCode0xF6; + m_OPCodes[0xF7] = &Processor::OPCode0xF7; + m_OPCodes[0xF8] = &Processor::OPCode0xF8; + m_OPCodes[0xF9] = &Processor::OPCode0xF9; + m_OPCodes[0xFA] = &Processor::OPCode0xFA; + m_OPCodes[0xFB] = &Processor::OPCode0xFB; + m_OPCodes[0xFC] = &Processor::OPCode0xFC; + m_OPCodes[0xFD] = &Processor::OPCode0xFD; + m_OPCodes[0xFE] = &Processor::OPCode0xFE; + m_OPCodes[0xFF] = &Processor::OPCode0xFF; + + + m_OPCodesCB[0x00] = &Processor::OPCodeCB0x00; + m_OPCodesCB[0x01] = &Processor::OPCodeCB0x01; + m_OPCodesCB[0x02] = &Processor::OPCodeCB0x02; + m_OPCodesCB[0x03] = &Processor::OPCodeCB0x03; + m_OPCodesCB[0x04] = &Processor::OPCodeCB0x04; + m_OPCodesCB[0x05] = &Processor::OPCodeCB0x05; + m_OPCodesCB[0x06] = &Processor::OPCodeCB0x06; + m_OPCodesCB[0x07] = &Processor::OPCodeCB0x07; + m_OPCodesCB[0x08] = &Processor::OPCodeCB0x08; + m_OPCodesCB[0x09] = &Processor::OPCodeCB0x09; + m_OPCodesCB[0x0A] = &Processor::OPCodeCB0x0A; + m_OPCodesCB[0x0B] = &Processor::OPCodeCB0x0B; + m_OPCodesCB[0x0C] = &Processor::OPCodeCB0x0C; + m_OPCodesCB[0x0D] = &Processor::OPCodeCB0x0D; + m_OPCodesCB[0x0E] = &Processor::OPCodeCB0x0E; + m_OPCodesCB[0x0F] = &Processor::OPCodeCB0x0F; + + m_OPCodesCB[0x10] = &Processor::OPCodeCB0x10; + m_OPCodesCB[0x11] = &Processor::OPCodeCB0x11; + m_OPCodesCB[0x12] = &Processor::OPCodeCB0x12; + m_OPCodesCB[0x13] = &Processor::OPCodeCB0x13; + m_OPCodesCB[0x14] = &Processor::OPCodeCB0x14; + m_OPCodesCB[0x15] = &Processor::OPCodeCB0x15; + m_OPCodesCB[0x16] = &Processor::OPCodeCB0x16; + m_OPCodesCB[0x17] = &Processor::OPCodeCB0x17; + m_OPCodesCB[0x18] = &Processor::OPCodeCB0x18; + m_OPCodesCB[0x19] = &Processor::OPCodeCB0x19; + m_OPCodesCB[0x1A] = &Processor::OPCodeCB0x1A; + m_OPCodesCB[0x1B] = &Processor::OPCodeCB0x1B; + m_OPCodesCB[0x1C] = &Processor::OPCodeCB0x1C; + m_OPCodesCB[0x1D] = &Processor::OPCodeCB0x1D; + m_OPCodesCB[0x1E] = &Processor::OPCodeCB0x1E; + m_OPCodesCB[0x1F] = &Processor::OPCodeCB0x1F; + + m_OPCodesCB[0x20] = &Processor::OPCodeCB0x20; + m_OPCodesCB[0x21] = &Processor::OPCodeCB0x21; + m_OPCodesCB[0x22] = &Processor::OPCodeCB0x22; + m_OPCodesCB[0x23] = &Processor::OPCodeCB0x23; + m_OPCodesCB[0x24] = &Processor::OPCodeCB0x24; + m_OPCodesCB[0x25] = &Processor::OPCodeCB0x25; + m_OPCodesCB[0x26] = &Processor::OPCodeCB0x26; + m_OPCodesCB[0x27] = &Processor::OPCodeCB0x27; + m_OPCodesCB[0x28] = &Processor::OPCodeCB0x28; + m_OPCodesCB[0x29] = &Processor::OPCodeCB0x29; + m_OPCodesCB[0x2A] = &Processor::OPCodeCB0x2A; + m_OPCodesCB[0x2B] = &Processor::OPCodeCB0x2B; + m_OPCodesCB[0x2C] = &Processor::OPCodeCB0x2C; + m_OPCodesCB[0x2D] = &Processor::OPCodeCB0x2D; + m_OPCodesCB[0x2E] = &Processor::OPCodeCB0x2E; + m_OPCodesCB[0x2F] = &Processor::OPCodeCB0x2F; + + m_OPCodesCB[0x30] = &Processor::OPCodeCB0x30; + m_OPCodesCB[0x31] = &Processor::OPCodeCB0x31; + m_OPCodesCB[0x32] = &Processor::OPCodeCB0x32; + m_OPCodesCB[0x33] = &Processor::OPCodeCB0x33; + m_OPCodesCB[0x34] = &Processor::OPCodeCB0x34; + m_OPCodesCB[0x35] = &Processor::OPCodeCB0x35; + m_OPCodesCB[0x36] = &Processor::OPCodeCB0x36; + m_OPCodesCB[0x37] = &Processor::OPCodeCB0x37; + m_OPCodesCB[0x38] = &Processor::OPCodeCB0x38; + m_OPCodesCB[0x39] = &Processor::OPCodeCB0x39; + m_OPCodesCB[0x3A] = &Processor::OPCodeCB0x3A; + m_OPCodesCB[0x3B] = &Processor::OPCodeCB0x3B; + m_OPCodesCB[0x3C] = &Processor::OPCodeCB0x3C; + m_OPCodesCB[0x3D] = &Processor::OPCodeCB0x3D; + m_OPCodesCB[0x3E] = &Processor::OPCodeCB0x3E; + m_OPCodesCB[0x3F] = &Processor::OPCodeCB0x3F; + + m_OPCodesCB[0x40] = &Processor::OPCodeCB0x40; + m_OPCodesCB[0x41] = &Processor::OPCodeCB0x41; + m_OPCodesCB[0x42] = &Processor::OPCodeCB0x42; + m_OPCodesCB[0x43] = &Processor::OPCodeCB0x43; + m_OPCodesCB[0x44] = &Processor::OPCodeCB0x44; + m_OPCodesCB[0x45] = &Processor::OPCodeCB0x45; + m_OPCodesCB[0x46] = &Processor::OPCodeCB0x46; + m_OPCodesCB[0x47] = &Processor::OPCodeCB0x47; + m_OPCodesCB[0x48] = &Processor::OPCodeCB0x48; + m_OPCodesCB[0x49] = &Processor::OPCodeCB0x49; + m_OPCodesCB[0x4A] = &Processor::OPCodeCB0x4A; + m_OPCodesCB[0x4B] = &Processor::OPCodeCB0x4B; + m_OPCodesCB[0x4C] = &Processor::OPCodeCB0x4C; + m_OPCodesCB[0x4D] = &Processor::OPCodeCB0x4D; + m_OPCodesCB[0x4E] = &Processor::OPCodeCB0x4E; + m_OPCodesCB[0x4F] = &Processor::OPCodeCB0x4F; + + m_OPCodesCB[0x50] = &Processor::OPCodeCB0x50; + m_OPCodesCB[0x51] = &Processor::OPCodeCB0x51; + m_OPCodesCB[0x52] = &Processor::OPCodeCB0x52; + m_OPCodesCB[0x53] = &Processor::OPCodeCB0x53; + m_OPCodesCB[0x54] = &Processor::OPCodeCB0x54; + m_OPCodesCB[0x55] = &Processor::OPCodeCB0x55; + m_OPCodesCB[0x56] = &Processor::OPCodeCB0x56; + m_OPCodesCB[0x57] = &Processor::OPCodeCB0x57; + m_OPCodesCB[0x58] = &Processor::OPCodeCB0x58; + m_OPCodesCB[0x59] = &Processor::OPCodeCB0x59; + m_OPCodesCB[0x5A] = &Processor::OPCodeCB0x5A; + m_OPCodesCB[0x5B] = &Processor::OPCodeCB0x5B; + m_OPCodesCB[0x5C] = &Processor::OPCodeCB0x5C; + m_OPCodesCB[0x5D] = &Processor::OPCodeCB0x5D; + m_OPCodesCB[0x5E] = &Processor::OPCodeCB0x5E; + m_OPCodesCB[0x5F] = &Processor::OPCodeCB0x5F; + + m_OPCodesCB[0x60] = &Processor::OPCodeCB0x60; + m_OPCodesCB[0x61] = &Processor::OPCodeCB0x61; + m_OPCodesCB[0x62] = &Processor::OPCodeCB0x62; + m_OPCodesCB[0x63] = &Processor::OPCodeCB0x63; + m_OPCodesCB[0x64] = &Processor::OPCodeCB0x64; + m_OPCodesCB[0x65] = &Processor::OPCodeCB0x65; + m_OPCodesCB[0x66] = &Processor::OPCodeCB0x66; + m_OPCodesCB[0x67] = &Processor::OPCodeCB0x67; + m_OPCodesCB[0x68] = &Processor::OPCodeCB0x68; + m_OPCodesCB[0x69] = &Processor::OPCodeCB0x69; + m_OPCodesCB[0x6A] = &Processor::OPCodeCB0x6A; + m_OPCodesCB[0x6B] = &Processor::OPCodeCB0x6B; + m_OPCodesCB[0x6C] = &Processor::OPCodeCB0x6C; + m_OPCodesCB[0x6D] = &Processor::OPCodeCB0x6D; + m_OPCodesCB[0x6E] = &Processor::OPCodeCB0x6E; + m_OPCodesCB[0x6F] = &Processor::OPCodeCB0x6F; + + m_OPCodesCB[0x70] = &Processor::OPCodeCB0x70; + m_OPCodesCB[0x71] = &Processor::OPCodeCB0x71; + m_OPCodesCB[0x72] = &Processor::OPCodeCB0x72; + m_OPCodesCB[0x73] = &Processor::OPCodeCB0x73; + m_OPCodesCB[0x74] = &Processor::OPCodeCB0x74; + m_OPCodesCB[0x75] = &Processor::OPCodeCB0x75; + m_OPCodesCB[0x76] = &Processor::OPCodeCB0x76; + m_OPCodesCB[0x77] = &Processor::OPCodeCB0x77; + m_OPCodesCB[0x78] = &Processor::OPCodeCB0x78; + m_OPCodesCB[0x79] = &Processor::OPCodeCB0x79; + m_OPCodesCB[0x7A] = &Processor::OPCodeCB0x7A; + m_OPCodesCB[0x7B] = &Processor::OPCodeCB0x7B; + m_OPCodesCB[0x7C] = &Processor::OPCodeCB0x7C; + m_OPCodesCB[0x7D] = &Processor::OPCodeCB0x7D; + m_OPCodesCB[0x7E] = &Processor::OPCodeCB0x7E; + m_OPCodesCB[0x7F] = &Processor::OPCodeCB0x7F; + + m_OPCodesCB[0x80] = &Processor::OPCodeCB0x80; + m_OPCodesCB[0x81] = &Processor::OPCodeCB0x81; + m_OPCodesCB[0x82] = &Processor::OPCodeCB0x82; + m_OPCodesCB[0x83] = &Processor::OPCodeCB0x83; + m_OPCodesCB[0x84] = &Processor::OPCodeCB0x84; + m_OPCodesCB[0x85] = &Processor::OPCodeCB0x85; + m_OPCodesCB[0x86] = &Processor::OPCodeCB0x86; + m_OPCodesCB[0x87] = &Processor::OPCodeCB0x87; + m_OPCodesCB[0x88] = &Processor::OPCodeCB0x88; + m_OPCodesCB[0x89] = &Processor::OPCodeCB0x89; + m_OPCodesCB[0x8A] = &Processor::OPCodeCB0x8A; + m_OPCodesCB[0x8B] = &Processor::OPCodeCB0x8B; + m_OPCodesCB[0x8C] = &Processor::OPCodeCB0x8C; + m_OPCodesCB[0x8D] = &Processor::OPCodeCB0x8D; + m_OPCodesCB[0x8E] = &Processor::OPCodeCB0x8E; + m_OPCodesCB[0x8F] = &Processor::OPCodeCB0x8F; + + m_OPCodesCB[0x90] = &Processor::OPCodeCB0x90; + m_OPCodesCB[0x91] = &Processor::OPCodeCB0x91; + m_OPCodesCB[0x92] = &Processor::OPCodeCB0x92; + m_OPCodesCB[0x93] = &Processor::OPCodeCB0x93; + m_OPCodesCB[0x94] = &Processor::OPCodeCB0x94; + m_OPCodesCB[0x95] = &Processor::OPCodeCB0x95; + m_OPCodesCB[0x96] = &Processor::OPCodeCB0x96; + m_OPCodesCB[0x97] = &Processor::OPCodeCB0x97; + m_OPCodesCB[0x98] = &Processor::OPCodeCB0x98; + m_OPCodesCB[0x99] = &Processor::OPCodeCB0x99; + m_OPCodesCB[0x9A] = &Processor::OPCodeCB0x9A; + m_OPCodesCB[0x9B] = &Processor::OPCodeCB0x9B; + m_OPCodesCB[0x9C] = &Processor::OPCodeCB0x9C; + m_OPCodesCB[0x9D] = &Processor::OPCodeCB0x9D; + m_OPCodesCB[0x9E] = &Processor::OPCodeCB0x9E; + m_OPCodesCB[0x9F] = &Processor::OPCodeCB0x9F; + + m_OPCodesCB[0xA0] = &Processor::OPCodeCB0xA0; + m_OPCodesCB[0xA1] = &Processor::OPCodeCB0xA1; + m_OPCodesCB[0xA2] = &Processor::OPCodeCB0xA2; + m_OPCodesCB[0xA3] = &Processor::OPCodeCB0xA3; + m_OPCodesCB[0xA4] = &Processor::OPCodeCB0xA4; + m_OPCodesCB[0xA5] = &Processor::OPCodeCB0xA5; + m_OPCodesCB[0xA6] = &Processor::OPCodeCB0xA6; + m_OPCodesCB[0xA7] = &Processor::OPCodeCB0xA7; + m_OPCodesCB[0xA8] = &Processor::OPCodeCB0xA8; + m_OPCodesCB[0xA9] = &Processor::OPCodeCB0xA9; + m_OPCodesCB[0xAA] = &Processor::OPCodeCB0xAA; + m_OPCodesCB[0xAB] = &Processor::OPCodeCB0xAB; + m_OPCodesCB[0xAC] = &Processor::OPCodeCB0xAC; + m_OPCodesCB[0xAD] = &Processor::OPCodeCB0xAD; + m_OPCodesCB[0xAE] = &Processor::OPCodeCB0xAE; + m_OPCodesCB[0xAF] = &Processor::OPCodeCB0xAF; + + m_OPCodesCB[0xB0] = &Processor::OPCodeCB0xB0; + m_OPCodesCB[0xB1] = &Processor::OPCodeCB0xB1; + m_OPCodesCB[0xB2] = &Processor::OPCodeCB0xB2; + m_OPCodesCB[0xB3] = &Processor::OPCodeCB0xB3; + m_OPCodesCB[0xB4] = &Processor::OPCodeCB0xB4; + m_OPCodesCB[0xB5] = &Processor::OPCodeCB0xB5; + m_OPCodesCB[0xB6] = &Processor::OPCodeCB0xB6; + m_OPCodesCB[0xB7] = &Processor::OPCodeCB0xB7; + m_OPCodesCB[0xB8] = &Processor::OPCodeCB0xB8; + m_OPCodesCB[0xB9] = &Processor::OPCodeCB0xB9; + m_OPCodesCB[0xBA] = &Processor::OPCodeCB0xBA; + m_OPCodesCB[0xBB] = &Processor::OPCodeCB0xBB; + m_OPCodesCB[0xBC] = &Processor::OPCodeCB0xBC; + m_OPCodesCB[0xBD] = &Processor::OPCodeCB0xBD; + m_OPCodesCB[0xBE] = &Processor::OPCodeCB0xBE; + m_OPCodesCB[0xBF] = &Processor::OPCodeCB0xBF; + + m_OPCodesCB[0xC0] = &Processor::OPCodeCB0xC0; + m_OPCodesCB[0xC1] = &Processor::OPCodeCB0xC1; + m_OPCodesCB[0xC2] = &Processor::OPCodeCB0xC2; + m_OPCodesCB[0xC3] = &Processor::OPCodeCB0xC3; + m_OPCodesCB[0xC4] = &Processor::OPCodeCB0xC4; + m_OPCodesCB[0xC5] = &Processor::OPCodeCB0xC5; + m_OPCodesCB[0xC6] = &Processor::OPCodeCB0xC6; + m_OPCodesCB[0xC7] = &Processor::OPCodeCB0xC7; + m_OPCodesCB[0xC8] = &Processor::OPCodeCB0xC8; + m_OPCodesCB[0xC9] = &Processor::OPCodeCB0xC9; + m_OPCodesCB[0xCA] = &Processor::OPCodeCB0xCA; + m_OPCodesCB[0xCB] = &Processor::OPCodeCB0xCB; + m_OPCodesCB[0xCC] = &Processor::OPCodeCB0xCC; + m_OPCodesCB[0xCD] = &Processor::OPCodeCB0xCD; + m_OPCodesCB[0xCE] = &Processor::OPCodeCB0xCE; + m_OPCodesCB[0xCF] = &Processor::OPCodeCB0xCF; + + m_OPCodesCB[0xD0] = &Processor::OPCodeCB0xD0; + m_OPCodesCB[0xD1] = &Processor::OPCodeCB0xD1; + m_OPCodesCB[0xD2] = &Processor::OPCodeCB0xD2; + m_OPCodesCB[0xD3] = &Processor::OPCodeCB0xD3; + m_OPCodesCB[0xD4] = &Processor::OPCodeCB0xD4; + m_OPCodesCB[0xD5] = &Processor::OPCodeCB0xD5; + m_OPCodesCB[0xD6] = &Processor::OPCodeCB0xD6; + m_OPCodesCB[0xD7] = &Processor::OPCodeCB0xD7; + m_OPCodesCB[0xD8] = &Processor::OPCodeCB0xD8; + m_OPCodesCB[0xD9] = &Processor::OPCodeCB0xD9; + m_OPCodesCB[0xDA] = &Processor::OPCodeCB0xDA; + m_OPCodesCB[0xDB] = &Processor::OPCodeCB0xDB; + m_OPCodesCB[0xDC] = &Processor::OPCodeCB0xDC; + m_OPCodesCB[0xDD] = &Processor::OPCodeCB0xDD; + m_OPCodesCB[0xDE] = &Processor::OPCodeCB0xDE; + m_OPCodesCB[0xDF] = &Processor::OPCodeCB0xDF; + + m_OPCodesCB[0xE0] = &Processor::OPCodeCB0xE0; + m_OPCodesCB[0xE1] = &Processor::OPCodeCB0xE1; + m_OPCodesCB[0xE2] = &Processor::OPCodeCB0xE2; + m_OPCodesCB[0xE3] = &Processor::OPCodeCB0xE3; + m_OPCodesCB[0xE4] = &Processor::OPCodeCB0xE4; + m_OPCodesCB[0xE5] = &Processor::OPCodeCB0xE5; + m_OPCodesCB[0xE6] = &Processor::OPCodeCB0xE6; + m_OPCodesCB[0xE7] = &Processor::OPCodeCB0xE7; + m_OPCodesCB[0xE8] = &Processor::OPCodeCB0xE8; + m_OPCodesCB[0xE9] = &Processor::OPCodeCB0xE9; + m_OPCodesCB[0xEA] = &Processor::OPCodeCB0xEA; + m_OPCodesCB[0xEB] = &Processor::OPCodeCB0xEB; + m_OPCodesCB[0xEC] = &Processor::OPCodeCB0xEC; + m_OPCodesCB[0xED] = &Processor::OPCodeCB0xED; + m_OPCodesCB[0xEE] = &Processor::OPCodeCB0xEE; + m_OPCodesCB[0xEF] = &Processor::OPCodeCB0xEF; + + m_OPCodesCB[0xF0] = &Processor::OPCodeCB0xF0; + m_OPCodesCB[0xF1] = &Processor::OPCodeCB0xF1; + m_OPCodesCB[0xF2] = &Processor::OPCodeCB0xF2; + m_OPCodesCB[0xF3] = &Processor::OPCodeCB0xF3; + m_OPCodesCB[0xF4] = &Processor::OPCodeCB0xF4; + m_OPCodesCB[0xF5] = &Processor::OPCodeCB0xF5; + m_OPCodesCB[0xF6] = &Processor::OPCodeCB0xF6; + m_OPCodesCB[0xF7] = &Processor::OPCodeCB0xF7; + m_OPCodesCB[0xF8] = &Processor::OPCodeCB0xF8; + m_OPCodesCB[0xF9] = &Processor::OPCodeCB0xF9; + m_OPCodesCB[0xFA] = &Processor::OPCodeCB0xFA; + m_OPCodesCB[0xFB] = &Processor::OPCodeCB0xFB; + m_OPCodesCB[0xFC] = &Processor::OPCodeCB0xFC; + m_OPCodesCB[0xFD] = &Processor::OPCodeCB0xFD; + m_OPCodesCB[0xFE] = &Processor::OPCodeCB0xFE; + m_OPCodesCB[0xFF] = &Processor::OPCodeCB0xFF; +} diff --git a/gearboy/src/Processor.h b/gearboy/src/Processor.h new file mode 100644 index 00000000..4525d5d0 --- /dev/null +++ b/gearboy/src/Processor.h @@ -0,0 +1,691 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#ifndef PROCESSOR_H +#define PROCESSOR_H + +#include <list> +#include "definitions.h" +#include "SixteenBitRegister.h" + +class Memory; + +class Processor +{ +public: + enum Interrupts + { + None_Interrupt = 0x00, + VBlank_Interrupt = 0x01, + LCDSTAT_Interrupt = 0x02, + Timer_Interrupt = 0x04, + Serial_Interrupt = 0x08, + Joypad_Interrupt = 0x10 + }; + + struct ProcessorState + { + SixteenBitRegister* AF; + SixteenBitRegister* BC; + SixteenBitRegister* DE; + SixteenBitRegister* HL; + SixteenBitRegister* SP; + SixteenBitRegister* PC; + bool* IME; + bool* Halt; + }; + +public: + Processor(Memory* pMemory); + ~Processor(); + void Init(); + void Reset(bool bCGB, bool bGBA); + u8 RunFor(u8 ticks); + void RequestInterrupt(Interrupts interrupt); + void ResetTIMACycles(); + void ResetDIVCycles(); + bool Halted() const; + bool DuringOpCode() const; + bool CGBSpeed() const; + void AddCycles(unsigned int cycles); + bool InterruptIsAboutToRaise(); + void SaveState(std::ostream& stream); + void LoadState(std::istream& stream); + void SetGameSharkCheat(const char* szCheat); + void ClearGameSharkCheats(); + ProcessorState* GetState(); + bool Disassemble(u16 address); + bool BreakpointHit(); + void RequestMemoryBreakpoint(); + void UpdateTimers(u8 ticks); + void UpdateSerial(u8 ticks); + +private: + typedef void (Processor::*OPCptr) (void); + OPCptr m_OPCodes[256]; + OPCptr m_OPCodesCB[256]; + Memory* m_pMemory; + SixteenBitRegister AF; + SixteenBitRegister BC; + SixteenBitRegister DE; + SixteenBitRegister HL; + SixteenBitRegister SP; + SixteenBitRegister PC; + bool m_bIME; + bool m_bHalt; + bool m_bBranchTaken; + bool m_bSkipPCBug; + unsigned int m_iCurrentClockCycles; + unsigned int m_iDIVCycles; + unsigned int m_iTIMACycles; + int m_iSerialBit; + int m_iSerialCycles; + int m_iIMECycles; + int m_iUnhaltCycles; + bool m_bCGB; + int m_iInterruptDelayCycles; + bool m_bCGBSpeed; + int m_iSpeedMultiplier; + int m_iAccurateOPCodeState; + u8 m_iReadCache; + bool m_bBreakpointHit; + bool m_bRequestMemBreakpoint; + + struct GameSharkCode + { + u8 type; + u16 address; + u8 value; + }; + std::list<GameSharkCode> m_GameSharkList; + + ProcessorState m_ProcessorState; + +private: + Processor::Interrupts InterruptPending(); + void ServeInterrupt(Interrupts interrupt); + void UpdateGameShark(); + void ClearAllFlags(); + void ToggleZeroFlagFromResult(u8 result); + void SetFlag(u8 flag); + void FlipFlag(u8 flag); + void ToggleFlag(u8 flag); + void UntoggleFlag(u8 flag); + bool IsSetFlag(u8 flag); + void StackPush(SixteenBitRegister* reg); + void StackPop(SixteenBitRegister* reg); + int AdjustedCycles(int cycles); + void InvalidOPCode(); + void OPCodes_LD(u8* reg1, u8 reg2); + void OPCodes_LD(u8* reg, u16 address); + void OPCodes_LD(u16 address, u8 reg); + void OPCodes_OR(u8 number); + void OPCodes_XOR(u8 number); + void OPCodes_AND(u8 number); + void OPCodes_CP(u8 number); + void OPCodes_INC(u8* reg); + void OPCodes_INC_HL(); + void OPCodes_DEC(u8* reg); + void OPCodes_DEC_HL(); + void OPCodes_ADD(u8 number); + void OPCodes_ADC(u8 number); + void OPCodes_SUB(u8 number); + void OPCodes_SBC(u8 number); + void OPCodes_ADD_HL(u16 number); + void OPCodes_ADD_SP(s8 number); + void OPCodes_SWAP_Register(u8* reg); + void OPCodes_SWAP_HL(); + void OPCodes_SLA(u8* reg); + void OPCodes_SLA_HL(); + void OPCodes_SRA(u8* reg); + void OPCodes_SRA_HL(); + void OPCodes_SRL(u8* reg); + void OPCodes_SRL_HL(); + void OPCodes_RLC(u8* reg, bool isRegisterA = false); + void OPCodes_RLC_HL(); + void OPCodes_RL(u8* reg, bool isRegisterA = false); + void OPCodes_RL_HL(); + void OPCodes_RRC(u8* reg, bool isRegisterA = false); + void OPCodes_RRC_HL(); + void OPCodes_RR(u8* reg, bool isRegisterA = false); + void OPCodes_RR_HL(); + void OPCodes_BIT(u8* reg, int bit); + void OPCodes_BIT_HL(int bit); + void OPCodes_SET(u8* reg, int bit); + void OPCodes_SET_HL(int bit); + void OPCodes_RES(u8* reg, int bit); + void OPCodes_RES_HL(int bit); + void InitOPCodeFunctors(); + void OPCode0x00(); + void OPCode0x01(); + void OPCode0x02(); + void OPCode0x03(); + void OPCode0x04(); + void OPCode0x05(); + void OPCode0x06(); + void OPCode0x07(); + void OPCode0x08(); + void OPCode0x09(); + void OPCode0x0A(); + void OPCode0x0B(); + void OPCode0x0C(); + void OPCode0x0D(); + void OPCode0x0E(); + void OPCode0x0F(); + void OPCode0x10(); + void OPCode0x11(); + void OPCode0x12(); + void OPCode0x13(); + void OPCode0x14(); + void OPCode0x15(); + void OPCode0x16(); + void OPCode0x17(); + void OPCode0x18(); + void OPCode0x19(); + void OPCode0x1A(); + void OPCode0x1B(); + void OPCode0x1C(); + void OPCode0x1D(); + void OPCode0x1E(); + void OPCode0x1F(); + void OPCode0x20(); + void OPCode0x21(); + void OPCode0x22(); + void OPCode0x23(); + void OPCode0x24(); + void OPCode0x25(); + void OPCode0x26(); + void OPCode0x27(); + void OPCode0x28(); + void OPCode0x29(); + void OPCode0x2A(); + void OPCode0x2B(); + void OPCode0x2C(); + void OPCode0x2D(); + void OPCode0x2E(); + void OPCode0x2F(); + void OPCode0x30(); + void OPCode0x31(); + void OPCode0x32(); + void OPCode0x33(); + void OPCode0x34(); + void OPCode0x35(); + void OPCode0x36(); + void OPCode0x37(); + void OPCode0x38(); + void OPCode0x39(); + void OPCode0x3A(); + void OPCode0x3B(); + void OPCode0x3C(); + void OPCode0x3D(); + void OPCode0x3E(); + void OPCode0x3F(); + void OPCode0x40(); + void OPCode0x41(); + void OPCode0x42(); + void OPCode0x43(); + void OPCode0x44(); + void OPCode0x45(); + void OPCode0x46(); + void OPCode0x47(); + void OPCode0x48(); + void OPCode0x49(); + void OPCode0x4A(); + void OPCode0x4B(); + void OPCode0x4C(); + void OPCode0x4D(); + void OPCode0x4E(); + void OPCode0x4F(); + void OPCode0x50(); + void OPCode0x51(); + void OPCode0x52(); + void OPCode0x53(); + void OPCode0x54(); + void OPCode0x55(); + void OPCode0x56(); + void OPCode0x57(); + void OPCode0x58(); + void OPCode0x59(); + void OPCode0x5A(); + void OPCode0x5B(); + void OPCode0x5C(); + void OPCode0x5D(); + void OPCode0x5E(); + void OPCode0x5F(); + void OPCode0x60(); + void OPCode0x61(); + void OPCode0x62(); + void OPCode0x63(); + void OPCode0x64(); + void OPCode0x65(); + void OPCode0x66(); + void OPCode0x67(); + void OPCode0x68(); + void OPCode0x69(); + void OPCode0x6A(); + void OPCode0x6B(); + void OPCode0x6C(); + void OPCode0x6D(); + void OPCode0x6E(); + void OPCode0x6F(); + void OPCode0x70(); + void OPCode0x71(); + void OPCode0x72(); + void OPCode0x73(); + void OPCode0x74(); + void OPCode0x75(); + void OPCode0x76(); + void OPCode0x77(); + void OPCode0x78(); + void OPCode0x79(); + void OPCode0x7A(); + void OPCode0x7B(); + void OPCode0x7C(); + void OPCode0x7D(); + void OPCode0x7E(); + void OPCode0x7F(); + void OPCode0x80(); + void OPCode0x81(); + void OPCode0x82(); + void OPCode0x83(); + void OPCode0x84(); + void OPCode0x85(); + void OPCode0x86(); + void OPCode0x87(); + void OPCode0x88(); + void OPCode0x89(); + void OPCode0x8A(); + void OPCode0x8B(); + void OPCode0x8C(); + void OPCode0x8D(); + void OPCode0x8E(); + void OPCode0x8F(); + void OPCode0x90(); + void OPCode0x91(); + void OPCode0x92(); + void OPCode0x93(); + void OPCode0x94(); + void OPCode0x95(); + void OPCode0x96(); + void OPCode0x97(); + void OPCode0x98(); + void OPCode0x99(); + void OPCode0x9A(); + void OPCode0x9B(); + void OPCode0x9C(); + void OPCode0x9D(); + void OPCode0x9E(); + void OPCode0x9F(); + void OPCode0xA0(); + void OPCode0xA1(); + void OPCode0xA2(); + void OPCode0xA3(); + void OPCode0xA4(); + void OPCode0xA5(); + void OPCode0xA6(); + void OPCode0xA7(); + void OPCode0xA8(); + void OPCode0xA9(); + void OPCode0xAA(); + void OPCode0xAB(); + void OPCode0xAC(); + void OPCode0xAD(); + void OPCode0xAE(); + void OPCode0xAF(); + void OPCode0xB0(); + void OPCode0xB1(); + void OPCode0xB2(); + void OPCode0xB3(); + void OPCode0xB4(); + void OPCode0xB5(); + void OPCode0xB6(); + void OPCode0xB7(); + void OPCode0xB8(); + void OPCode0xB9(); + void OPCode0xBA(); + void OPCode0xBB(); + void OPCode0xBC(); + void OPCode0xBD(); + void OPCode0xBE(); + void OPCode0xBF(); + void OPCode0xC0(); + void OPCode0xC1(); + void OPCode0xC2(); + void OPCode0xC3(); + void OPCode0xC4(); + void OPCode0xC5(); + void OPCode0xC6(); + void OPCode0xC7(); + void OPCode0xC8(); + void OPCode0xC9(); + void OPCode0xCA(); + void OPCode0xCB(); + void OPCode0xCC(); + void OPCode0xCD(); + void OPCode0xCE(); + void OPCode0xCF(); + void OPCode0xD0(); + void OPCode0xD1(); + void OPCode0xD2(); + void OPCode0xD3(); + void OPCode0xD4(); + void OPCode0xD5(); + void OPCode0xD6(); + void OPCode0xD7(); + void OPCode0xD8(); + void OPCode0xD9(); + void OPCode0xDA(); + void OPCode0xDB(); + void OPCode0xDC(); + void OPCode0xDD(); + void OPCode0xDE(); + void OPCode0xDF(); + void OPCode0xE0(); + void OPCode0xE1(); + void OPCode0xE2(); + void OPCode0xE3(); + void OPCode0xE4(); + void OPCode0xE5(); + void OPCode0xE6(); + void OPCode0xE7(); + void OPCode0xE8(); + void OPCode0xE9(); + void OPCode0xEA(); + void OPCode0xEB(); + void OPCode0xEC(); + void OPCode0xED(); + void OPCode0xEE(); + void OPCode0xEF(); + void OPCode0xF0(); + void OPCode0xF1(); + void OPCode0xF2(); + void OPCode0xF3(); + void OPCode0xF4(); + void OPCode0xF5(); + void OPCode0xF6(); + void OPCode0xF7(); + void OPCode0xF8(); + void OPCode0xF9(); + void OPCode0xFA(); + void OPCode0xFB(); + void OPCode0xFC(); + void OPCode0xFD(); + void OPCode0xFE(); + void OPCode0xFF(); + void OPCodeCB0x00(); + void OPCodeCB0x01(); + void OPCodeCB0x02(); + void OPCodeCB0x03(); + void OPCodeCB0x04(); + void OPCodeCB0x05(); + void OPCodeCB0x06(); + void OPCodeCB0x07(); + void OPCodeCB0x08(); + void OPCodeCB0x09(); + void OPCodeCB0x0A(); + void OPCodeCB0x0B(); + void OPCodeCB0x0C(); + void OPCodeCB0x0D(); + void OPCodeCB0x0E(); + void OPCodeCB0x0F(); + void OPCodeCB0x10(); + void OPCodeCB0x11(); + void OPCodeCB0x12(); + void OPCodeCB0x13(); + void OPCodeCB0x14(); + void OPCodeCB0x15(); + void OPCodeCB0x16(); + void OPCodeCB0x17(); + void OPCodeCB0x18(); + void OPCodeCB0x19(); + void OPCodeCB0x1A(); + void OPCodeCB0x1B(); + void OPCodeCB0x1C(); + void OPCodeCB0x1D(); + void OPCodeCB0x1E(); + void OPCodeCB0x1F(); + void OPCodeCB0x20(); + void OPCodeCB0x21(); + void OPCodeCB0x22(); + void OPCodeCB0x23(); + void OPCodeCB0x24(); + void OPCodeCB0x25(); + void OPCodeCB0x26(); + void OPCodeCB0x27(); + void OPCodeCB0x28(); + void OPCodeCB0x29(); + void OPCodeCB0x2A(); + void OPCodeCB0x2B(); + void OPCodeCB0x2C(); + void OPCodeCB0x2D(); + void OPCodeCB0x2E(); + void OPCodeCB0x2F(); + void OPCodeCB0x30(); + void OPCodeCB0x31(); + void OPCodeCB0x32(); + void OPCodeCB0x33(); + void OPCodeCB0x34(); + void OPCodeCB0x35(); + void OPCodeCB0x36(); + void OPCodeCB0x37(); + void OPCodeCB0x38(); + void OPCodeCB0x39(); + void OPCodeCB0x3A(); + void OPCodeCB0x3B(); + void OPCodeCB0x3C(); + void OPCodeCB0x3D(); + void OPCodeCB0x3E(); + void OPCodeCB0x3F(); + void OPCodeCB0x40(); + void OPCodeCB0x41(); + void OPCodeCB0x42(); + void OPCodeCB0x43(); + void OPCodeCB0x44(); + void OPCodeCB0x45(); + void OPCodeCB0x46(); + void OPCodeCB0x47(); + void OPCodeCB0x48(); + void OPCodeCB0x49(); + void OPCodeCB0x4A(); + void OPCodeCB0x4B(); + void OPCodeCB0x4C(); + void OPCodeCB0x4D(); + void OPCodeCB0x4E(); + void OPCodeCB0x4F(); + void OPCodeCB0x50(); + void OPCodeCB0x51(); + void OPCodeCB0x52(); + void OPCodeCB0x53(); + void OPCodeCB0x54(); + void OPCodeCB0x55(); + void OPCodeCB0x56(); + void OPCodeCB0x57(); + void OPCodeCB0x58(); + void OPCodeCB0x59(); + void OPCodeCB0x5A(); + void OPCodeCB0x5B(); + void OPCodeCB0x5C(); + void OPCodeCB0x5D(); + void OPCodeCB0x5E(); + void OPCodeCB0x5F(); + void OPCodeCB0x60(); + void OPCodeCB0x61(); + void OPCodeCB0x62(); + void OPCodeCB0x63(); + void OPCodeCB0x64(); + void OPCodeCB0x65(); + void OPCodeCB0x66(); + void OPCodeCB0x67(); + void OPCodeCB0x68(); + void OPCodeCB0x69(); + void OPCodeCB0x6A(); + void OPCodeCB0x6B(); + void OPCodeCB0x6C(); + void OPCodeCB0x6D(); + void OPCodeCB0x6E(); + void OPCodeCB0x6F(); + void OPCodeCB0x70(); + void OPCodeCB0x71(); + void OPCodeCB0x72(); + void OPCodeCB0x73(); + void OPCodeCB0x74(); + void OPCodeCB0x75(); + void OPCodeCB0x76(); + void OPCodeCB0x77(); + void OPCodeCB0x78(); + void OPCodeCB0x79(); + void OPCodeCB0x7A(); + void OPCodeCB0x7B(); + void OPCodeCB0x7C(); + void OPCodeCB0x7D(); + void OPCodeCB0x7E(); + void OPCodeCB0x7F(); + void OPCodeCB0x80(); + void OPCodeCB0x81(); + void OPCodeCB0x82(); + void OPCodeCB0x83(); + void OPCodeCB0x84(); + void OPCodeCB0x85(); + void OPCodeCB0x86(); + void OPCodeCB0x87(); + void OPCodeCB0x88(); + void OPCodeCB0x89(); + void OPCodeCB0x8A(); + void OPCodeCB0x8B(); + void OPCodeCB0x8C(); + void OPCodeCB0x8D(); + void OPCodeCB0x8E(); + void OPCodeCB0x8F(); + void OPCodeCB0x90(); + void OPCodeCB0x91(); + void OPCodeCB0x92(); + void OPCodeCB0x93(); + void OPCodeCB0x94(); + void OPCodeCB0x95(); + void OPCodeCB0x96(); + void OPCodeCB0x97(); + void OPCodeCB0x98(); + void OPCodeCB0x99(); + void OPCodeCB0x9A(); + void OPCodeCB0x9B(); + void OPCodeCB0x9C(); + void OPCodeCB0x9D(); + void OPCodeCB0x9E(); + void OPCodeCB0x9F(); + void OPCodeCB0xA0(); + void OPCodeCB0xA1(); + void OPCodeCB0xA2(); + void OPCodeCB0xA3(); + void OPCodeCB0xA4(); + void OPCodeCB0xA5(); + void OPCodeCB0xA6(); + void OPCodeCB0xA7(); + void OPCodeCB0xA8(); + void OPCodeCB0xA9(); + void OPCodeCB0xAA(); + void OPCodeCB0xAB(); + void OPCodeCB0xAC(); + void OPCodeCB0xAD(); + void OPCodeCB0xAE(); + void OPCodeCB0xAF(); + void OPCodeCB0xB0(); + void OPCodeCB0xB1(); + void OPCodeCB0xB2(); + void OPCodeCB0xB3(); + void OPCodeCB0xB4(); + void OPCodeCB0xB5(); + void OPCodeCB0xB6(); + void OPCodeCB0xB7(); + void OPCodeCB0xB8(); + void OPCodeCB0xB9(); + void OPCodeCB0xBA(); + void OPCodeCB0xBB(); + void OPCodeCB0xBC(); + void OPCodeCB0xBD(); + void OPCodeCB0xBE(); + void OPCodeCB0xBF(); + void OPCodeCB0xC0(); + void OPCodeCB0xC1(); + void OPCodeCB0xC2(); + void OPCodeCB0xC3(); + void OPCodeCB0xC4(); + void OPCodeCB0xC5(); + void OPCodeCB0xC6(); + void OPCodeCB0xC7(); + void OPCodeCB0xC8(); + void OPCodeCB0xC9(); + void OPCodeCB0xCA(); + void OPCodeCB0xCB(); + void OPCodeCB0xCC(); + void OPCodeCB0xCD(); + void OPCodeCB0xCE(); + void OPCodeCB0xCF(); + void OPCodeCB0xD0(); + void OPCodeCB0xD1(); + void OPCodeCB0xD2(); + void OPCodeCB0xD3(); + void OPCodeCB0xD4(); + void OPCodeCB0xD5(); + void OPCodeCB0xD6(); + void OPCodeCB0xD7(); + void OPCodeCB0xD8(); + void OPCodeCB0xD9(); + void OPCodeCB0xDA(); + void OPCodeCB0xDB(); + void OPCodeCB0xDC(); + void OPCodeCB0xDD(); + void OPCodeCB0xDE(); + void OPCodeCB0xDF(); + void OPCodeCB0xE0(); + void OPCodeCB0xE1(); + void OPCodeCB0xE2(); + void OPCodeCB0xE3(); + void OPCodeCB0xE4(); + void OPCodeCB0xE5(); + void OPCodeCB0xE6(); + void OPCodeCB0xE7(); + void OPCodeCB0xE8(); + void OPCodeCB0xE9(); + void OPCodeCB0xEA(); + void OPCodeCB0xEB(); + void OPCodeCB0xEC(); + void OPCodeCB0xED(); + void OPCodeCB0xEE(); + void OPCodeCB0xEF(); + void OPCodeCB0xF0(); + void OPCodeCB0xF1(); + void OPCodeCB0xF2(); + void OPCodeCB0xF3(); + void OPCodeCB0xF4(); + void OPCodeCB0xF5(); + void OPCodeCB0xF6(); + void OPCodeCB0xF7(); + void OPCodeCB0xF8(); + void OPCodeCB0xF9(); + void OPCodeCB0xFA(); + void OPCodeCB0xFB(); + void OPCodeCB0xFC(); + void OPCodeCB0xFD(); + void OPCodeCB0xFE(); + void OPCodeCB0xFF(); +}; + +#include "Processor_inline.h" + +#endif /* PROCESSOR_H */ diff --git a/gearboy/src/Processor_inline.h b/gearboy/src/Processor_inline.h new file mode 100644 index 00000000..bc1876d8 --- /dev/null +++ b/gearboy/src/Processor_inline.h @@ -0,0 +1,676 @@ +#ifndef PROCESSOR_INLINE_H +#define PROCESSOR_INLINE_H + +#include "definitions.h" +#include "Memory.h" + +inline bool Processor::InterruptIsAboutToRaise() +{ + u8 ie_reg = m_pMemory->Retrieve(0xFFFF); + u8 if_reg = m_pMemory->Retrieve(0xFF0F); + + return (if_reg & ie_reg & 0x1F) != 0; +} + +inline Processor::Interrupts Processor::InterruptPending() +{ + u8 ie_reg = m_pMemory->Retrieve(0xFFFF); + u8 if_reg = m_pMemory->Retrieve(0xFF0F); + u8 ie_if = if_reg & ie_reg; + + if ((ie_if & 0x1F) == 0) + { + return None_Interrupt; + } + else if ((ie_if & 0x01) && (m_iInterruptDelayCycles <= 0)) + { + return VBlank_Interrupt; + } + else if (ie_if & 0x02) + { + return LCDSTAT_Interrupt; + } + else if (ie_if & 0x04) + { + return Timer_Interrupt; + } + else if (ie_if & 0x08) + { + return Serial_Interrupt; + } + else if (ie_if & 0x10) + { + return Joypad_Interrupt; + } + + return None_Interrupt; +} + +inline void Processor::RequestInterrupt(Interrupts interrupt) +{ + m_pMemory->Load(0xFF0F, m_pMemory->Retrieve(0xFF0F) | interrupt); + + if ((interrupt == VBlank_Interrupt) && !m_bCGBSpeed) + { + m_iInterruptDelayCycles = 4; + } +} + +inline void Processor::ResetTIMACycles() +{ + m_iTIMACycles = 0; + m_pMemory->Load(0xFF05, m_pMemory->Retrieve(0xFF06)); +} + +inline void Processor::ResetDIVCycles() +{ + m_iDIVCycles = 0; + m_pMemory->Load(0xFF04, 0x00); +} + +inline bool Processor::Halted() const +{ + return m_bHalt; +} + +inline bool Processor::DuringOpCode() const +{ + return m_iAccurateOPCodeState != 0; +} + +inline bool Processor::CGBSpeed() const +{ + return m_bCGBSpeed; +} + +inline void Processor::AddCycles(unsigned int cycles) +{ + m_iCurrentClockCycles += cycles; +} + +inline void Processor::ClearAllFlags() +{ + SetFlag(FLAG_NONE); +} + +inline void Processor::ToggleZeroFlagFromResult(u8 result) +{ + if (result == 0) + ToggleFlag(FLAG_ZERO); +} + +inline void Processor::SetFlag(u8 flag) +{ + AF.SetLow(flag); +} + +inline void Processor::FlipFlag(u8 flag) +{ + AF.SetLow(AF.GetLow() ^ flag); +} + +inline void Processor::ToggleFlag(u8 flag) +{ + AF.SetLow(AF.GetLow() | flag); +} + +inline void Processor::UntoggleFlag(u8 flag) +{ + AF.SetLow(AF.GetLow() & (~flag)); +} + +inline bool Processor::IsSetFlag(u8 flag) +{ + return (AF.GetLow() & flag) != 0; +} + +inline void Processor::StackPush(SixteenBitRegister* reg) +{ + SP.Decrement(); + m_pMemory->Write(SP.GetValue(), reg->GetHigh()); + SP.Decrement(); + m_pMemory->Write(SP.GetValue(), reg->GetLow()); +} + +inline void Processor::StackPop(SixteenBitRegister* reg) +{ + reg->SetLow(m_pMemory->Read(SP.GetValue())); + SP.Increment(); + reg->SetHigh(m_pMemory->Read(SP.GetValue())); + SP.Increment(); +} + +inline int Processor::AdjustedCycles(int cycles) +{ + if (!cycles) return cycles; + return cycles >> m_iSpeedMultiplier; +} + +inline void Processor::InvalidOPCode() +{ + Log("--> ** INVALID OP Code"); +} + +inline void Processor::OPCodes_LD(u8* reg1, u8 reg2) +{ + *reg1 = reg2; +} + +inline void Processor::OPCodes_LD(u8* reg, u16 address) +{ + *reg = m_pMemory->Read(address); +} + +inline void Processor::OPCodes_LD(u16 address, u8 reg) +{ + m_pMemory->Write(address, reg); +} + +inline void Processor::OPCodes_OR(u8 number) +{ + u8 result = AF.GetHigh() | number; + AF.SetHigh(result); + ClearAllFlags(); + ToggleZeroFlagFromResult(result); +} + +inline void Processor::OPCodes_XOR(u8 number) +{ + u8 result = AF.GetHigh() ^ number; + AF.SetHigh(result); + ClearAllFlags(); + ToggleZeroFlagFromResult(result); +} + +inline void Processor::OPCodes_AND(u8 number) +{ + u8 result = AF.GetHigh() & number; + AF.SetHigh(result); + SetFlag(FLAG_HALF); + ToggleZeroFlagFromResult(result); +} + +inline void Processor::OPCodes_CP(u8 number) +{ + SetFlag(FLAG_SUB); + if (AF.GetHigh() < number) + { + ToggleFlag(FLAG_CARRY); + } + if (AF.GetHigh() == number) + { + ToggleFlag(FLAG_ZERO); + } + if (((AF.GetHigh() - number) & 0xF) > (AF.GetHigh() & 0xF)) + { + ToggleFlag(FLAG_HALF); + } +} + +inline void Processor::OPCodes_INC(u8* reg) +{ + u8 result = *reg + 1; + *reg = result; + IsSetFlag(FLAG_CARRY) ? SetFlag(FLAG_CARRY) : ClearAllFlags(); + ToggleZeroFlagFromResult(result); + if ((result & 0x0F) == 0x00) + { + ToggleFlag(FLAG_HALF); + } +} + +inline void Processor::OPCodes_INC_HL() +{ + if (m_iAccurateOPCodeState == 1) + { + m_iReadCache = m_pMemory->Read(HL.GetValue()) + 1; + return; + } + m_pMemory->Write(HL.GetValue(), m_iReadCache); + IsSetFlag(FLAG_CARRY) ? SetFlag(FLAG_CARRY) : ClearAllFlags(); + ToggleZeroFlagFromResult(m_iReadCache); + if ((m_iReadCache & 0x0F) == 0x00) + { + ToggleFlag(FLAG_HALF); + } +} + +inline void Processor::OPCodes_DEC(u8* reg) +{ + u8 result = *reg - 1; + *reg = result; + IsSetFlag(FLAG_CARRY) ? SetFlag(FLAG_CARRY) : ClearAllFlags(); + ToggleFlag(FLAG_SUB); + ToggleZeroFlagFromResult(result); + if ((result & 0x0F) == 0x0F) + { + ToggleFlag(FLAG_HALF); + } +} + +inline void Processor::OPCodes_DEC_HL() +{ + if (m_iAccurateOPCodeState == 1) + { + m_iReadCache = m_pMemory->Read(HL.GetValue()) - 1; + return; + } + m_pMemory->Write(HL.GetValue(), m_iReadCache); + IsSetFlag(FLAG_CARRY) ? SetFlag(FLAG_CARRY) : ClearAllFlags(); + ToggleFlag(FLAG_SUB); + ToggleZeroFlagFromResult(m_iReadCache); + if ((m_iReadCache & 0x0F) == 0x0F) + { + ToggleFlag(FLAG_HALF); + } +} + +inline void Processor::OPCodes_ADD(u8 number) +{ + int result = AF.GetHigh() + number; + int carrybits = AF.GetHigh() ^ number ^ result; + AF.SetHigh(static_cast<u8> (result)); + ClearAllFlags(); + ToggleZeroFlagFromResult(static_cast<u8> (result)); + if ((carrybits & 0x100) != 0) + { + ToggleFlag(FLAG_CARRY); + } + if ((carrybits & 0x10) != 0) + { + ToggleFlag(FLAG_HALF); + } +} + +inline void Processor::OPCodes_ADC(u8 number) +{ + int carry = IsSetFlag(FLAG_CARRY) ? 1 : 0; + int result = AF.GetHigh() + number + carry; + ClearAllFlags(); + ToggleZeroFlagFromResult(static_cast<u8> (result)); + if (result > 0xFF) + { + ToggleFlag(FLAG_CARRY); + } + if (((AF.GetHigh()& 0x0F) + (number & 0x0F) + carry) > 0x0F) + { + ToggleFlag(FLAG_HALF); + } + AF.SetHigh(static_cast<u8> (result)); +} + +inline void Processor::OPCodes_SUB(u8 number) +{ + int result = AF.GetHigh() - number; + int carrybits = AF.GetHigh() ^ number ^ result; + AF.SetHigh(static_cast<u8> (result)); + SetFlag(FLAG_SUB); + ToggleZeroFlagFromResult(static_cast<u8> (result)); + if ((carrybits & 0x100) != 0) + { + ToggleFlag(FLAG_CARRY); + } + if ((carrybits & 0x10) != 0) + { + ToggleFlag(FLAG_HALF); + } +} + +inline void Processor::OPCodes_SBC(u8 number) +{ + int carry = IsSetFlag(FLAG_CARRY) ? 1 : 0; + int result = AF.GetHigh() - number - carry; + SetFlag(FLAG_SUB); + ToggleZeroFlagFromResult(static_cast<u8> (result)); + if (result < 0) + { + ToggleFlag(FLAG_CARRY); + } + if (((AF.GetHigh() & 0x0F) - (number & 0x0F) - carry) < 0) + { + ToggleFlag(FLAG_HALF); + } + AF.SetHigh(static_cast<u8> (result)); +} + +inline void Processor::OPCodes_ADD_HL(u16 number) +{ + int result = HL.GetValue() + number; + IsSetFlag(FLAG_ZERO) ? SetFlag(FLAG_ZERO) : ClearAllFlags(); + if (result & 0x10000) + { + ToggleFlag(FLAG_CARRY); + } + if ((HL.GetValue() ^ number ^ (result & 0xFFFF)) & 0x1000) + { + ToggleFlag(FLAG_HALF); + } + HL.SetValue(static_cast<u16> (result)); +} + +inline void Processor::OPCodes_ADD_SP(s8 number) +{ + int result = SP.GetValue() + number; + ClearAllFlags(); + if (((SP.GetValue() ^ number ^ (result & 0xFFFF)) & 0x100) == 0x100) + { + ToggleFlag(FLAG_CARRY); + } + if (((SP.GetValue() ^ number ^ (result & 0xFFFF)) & 0x10) == 0x10) + { + ToggleFlag(FLAG_HALF); + } + SP.SetValue(static_cast<u16> (result)); +} + +inline void Processor::OPCodes_SWAP_Register(u8* reg) +{ + u8 low_half = *reg & 0x0F; + u8 high_half = (*reg >> 4) & 0x0F; + *reg = (low_half << 4) + high_half; + ClearAllFlags(); + ToggleZeroFlagFromResult(*reg); +} + +inline void Processor::OPCodes_SWAP_HL() +{ + if (m_iAccurateOPCodeState == 1) + { + m_iReadCache = m_pMemory->Read(HL.GetValue()); + return; + } + u8 low_half = m_iReadCache & 0x0F; + u8 high_half = (m_iReadCache >> 4) & 0x0F; + m_iReadCache = (low_half << 4) + high_half; + m_pMemory->Write(HL.GetValue(), m_iReadCache); + ClearAllFlags(); + ToggleZeroFlagFromResult(m_iReadCache); +} + +inline void Processor::OPCodes_SLA(u8* reg) +{ + (*reg & 0x80) != 0 ? SetFlag(FLAG_CARRY) : ClearAllFlags(); + u8 result = *reg << 1; + *reg = result; + ToggleZeroFlagFromResult(result); +} + +inline void Processor::OPCodes_SLA_HL() +{ + if (m_iAccurateOPCodeState == 1) + { + m_iReadCache = m_pMemory->Read(HL.GetValue()); + return; + } + (m_iReadCache & 0x80) != 0 ? SetFlag(FLAG_CARRY) : ClearAllFlags(); + m_iReadCache <<= 1; + m_pMemory->Write(HL.GetValue(), m_iReadCache); + ToggleZeroFlagFromResult(m_iReadCache); +} + +inline void Processor::OPCodes_SRA(u8* reg) +{ + u8 result = *reg; + (result & 0x01) != 0 ? SetFlag(FLAG_CARRY) : ClearAllFlags(); + if ((result & 0x80) != 0) + { + result >>= 1; + result |= 0x80; + } + else + { + result >>= 1; + } + *reg = result; + ToggleZeroFlagFromResult(result); +} + +inline void Processor::OPCodes_SRA_HL() +{ + if (m_iAccurateOPCodeState == 1) + { + m_iReadCache = m_pMemory->Read(HL.GetValue()); + return; + } + (m_iReadCache & 0x01) != 0 ? SetFlag(FLAG_CARRY) : ClearAllFlags(); + if ((m_iReadCache & 0x80) != 0) + { + m_iReadCache >>= 1; + m_iReadCache |= 0x80; + } + else + { + m_iReadCache >>= 1; + } + m_pMemory->Write(HL.GetValue(), m_iReadCache); + ToggleZeroFlagFromResult(m_iReadCache); +} + +inline void Processor::OPCodes_SRL(u8* reg) +{ + u8 result = *reg; + (result & 0x01) != 0 ? SetFlag(FLAG_CARRY) : ClearAllFlags(); + result >>= 1; + *reg = result; + ToggleZeroFlagFromResult(result); +} + +inline void Processor::OPCodes_SRL_HL() +{ + if (m_iAccurateOPCodeState == 1) + { + m_iReadCache = m_pMemory->Read(HL.GetValue()); + return; + } + (m_iReadCache & 0x01) != 0 ? SetFlag(FLAG_CARRY) : ClearAllFlags(); + m_iReadCache >>= 1; + m_pMemory->Write(HL.GetValue(), m_iReadCache); + ToggleZeroFlagFromResult(m_iReadCache); +} + +inline void Processor::OPCodes_RLC(u8* reg, bool isRegisterA) +{ + u8 result = *reg; + if ((result & 0x80) != 0) + { + SetFlag(FLAG_CARRY); + result <<= 1; + result |= 0x1; + } + else + { + ClearAllFlags(); + result <<= 1; + } + *reg = result; + if (!isRegisterA) + { + ToggleZeroFlagFromResult(result); + } +} + +inline void Processor::OPCodes_RLC_HL() +{ + if (m_iAccurateOPCodeState == 1) + { + m_iReadCache = m_pMemory->Read(HL.GetValue()); + return; + } + if ((m_iReadCache & 0x80) != 0) + { + SetFlag(FLAG_CARRY); + m_iReadCache <<= 1; + m_iReadCache |= 0x1; + } + else + { + ClearAllFlags(); + m_iReadCache <<= 1; + } + m_pMemory->Write(HL.GetValue(), m_iReadCache); + ToggleZeroFlagFromResult(m_iReadCache); +} + +inline void Processor::OPCodes_RL(u8* reg, bool isRegisterA) +{ + u8 carry = IsSetFlag(FLAG_CARRY) ? 1 : 0; + u8 result = *reg; + ((result & 0x80) != 0) ? SetFlag(FLAG_CARRY) : ClearAllFlags(); + result <<= 1; + result |= carry; + *reg = result; + if (!isRegisterA) + { + ToggleZeroFlagFromResult(result); + } +} + +inline void Processor::OPCodes_RL_HL() +{ + if (m_iAccurateOPCodeState == 1) + { + m_iReadCache = m_pMemory->Read(HL.GetValue()); + return; + } + u8 carry = IsSetFlag(FLAG_CARRY) ? 1 : 0; + ((m_iReadCache & 0x80) != 0) ? SetFlag(FLAG_CARRY) : ClearAllFlags(); + m_iReadCache <<= 1; + m_iReadCache |= carry; + m_pMemory->Write(HL.GetValue(), m_iReadCache); + ToggleZeroFlagFromResult(m_iReadCache); +} + +inline void Processor::OPCodes_RRC(u8* reg, bool isRegisterA) +{ + u8 result = *reg; + if ((result & 0x01) != 0) + { + SetFlag(FLAG_CARRY); + result >>= 1; + result |= 0x80; + } + else + { + ClearAllFlags(); + result >>= 1; + } + *reg = result; + if (!isRegisterA) + { + ToggleZeroFlagFromResult(result); + } +} + +inline void Processor::OPCodes_RRC_HL() +{ + if (m_iAccurateOPCodeState == 1) + { + m_iReadCache = m_pMemory->Read(HL.GetValue()); + return; + } + if ((m_iReadCache & 0x01) != 0) + { + SetFlag(FLAG_CARRY); + m_iReadCache >>= 1; + m_iReadCache |= 0x80; + } + else + { + ClearAllFlags(); + m_iReadCache >>= 1; + } + m_pMemory->Write(HL.GetValue(), m_iReadCache); + ToggleZeroFlagFromResult(m_iReadCache); +} + +inline void Processor::OPCodes_RR(u8* reg, bool isRegisterA) +{ + u8 carry = IsSetFlag(FLAG_CARRY) ? 0x80 : 0x00; + u8 result = *reg; + ((result & 0x01) != 0) ? SetFlag(FLAG_CARRY) : ClearAllFlags(); + result >>= 1; + result |= carry; + *reg = result; + if (!isRegisterA) + { + ToggleZeroFlagFromResult(result); + } +} + +inline void Processor::OPCodes_RR_HL() +{ + if (m_iAccurateOPCodeState == 1) + { + m_iReadCache = m_pMemory->Read(HL.GetValue()); + return; + } + u8 carry = IsSetFlag(FLAG_CARRY) ? 0x80 : 0x00; + ((m_iReadCache & 0x01) != 0) ? SetFlag(FLAG_CARRY) : ClearAllFlags(); + m_iReadCache >>= 1; + m_iReadCache |= carry; + m_pMemory->Write(HL.GetValue(), m_iReadCache); + ToggleZeroFlagFromResult(m_iReadCache); +} + +inline void Processor::OPCodes_BIT(u8* reg, int bit) +{ + if (((*reg >> bit) & 0x01) == 0) + { + ToggleFlag(FLAG_ZERO); + } + else + { + UntoggleFlag(FLAG_ZERO); + } + ToggleFlag(FLAG_HALF); + UntoggleFlag(FLAG_SUB); +} + +inline void Processor::OPCodes_BIT_HL(int bit) +{ + if (((m_pMemory->Read(HL.GetValue()) >> bit) & 0x01) == 0) + { + ToggleFlag(FLAG_ZERO); + } + else + { + UntoggleFlag(FLAG_ZERO); + } + ToggleFlag(FLAG_HALF); + UntoggleFlag(FLAG_SUB); +} + +inline void Processor::OPCodes_SET(u8* reg, int bit) +{ + *reg = (*reg | (0x1 << bit)); +} + +inline void Processor::OPCodes_SET_HL(int bit) +{ + if (m_iAccurateOPCodeState == 1) + { + m_iReadCache = m_pMemory->Read(HL.GetValue()); + return; + } + m_iReadCache |= (0x1 << bit); + m_pMemory->Write(HL.GetValue(), m_iReadCache); +} + +inline void Processor::OPCodes_RES(u8* reg, int bit) +{ + *reg = (*reg & (~(0x1 << bit))); +} + +inline void Processor::OPCodes_RES_HL(int bit) +{ + if (m_iAccurateOPCodeState == 1) + { + m_iReadCache = m_pMemory->Read(HL.GetValue()); + return; + } + m_iReadCache &= ~(0x1 << bit); + m_pMemory->Write(HL.GetValue(), m_iReadCache); +} + +#endif /* PROCESSOR_INLINE_H */ + diff --git a/gearboy/src/RomOnlyMemoryRule.cpp b/gearboy/src/RomOnlyMemoryRule.cpp new file mode 100644 index 00000000..66f026ff --- /dev/null +++ b/gearboy/src/RomOnlyMemoryRule.cpp @@ -0,0 +1,155 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#include "RomOnlyMemoryRule.h" +#include "Video.h" +#include "Memory.h" +#include "Processor.h" +#include "Input.h" +#include "Cartridge.h" + +RomOnlyMemoryRule::RomOnlyMemoryRule(Processor* pProcessor, + Memory* pMemory, Video* pVideo, Input* pInput, + Cartridge* pCartridge, Audio* pAudio) : MemoryRule(pProcessor, +pMemory, pVideo, pInput, pCartridge, pAudio) +{ + Reset(false); +} + +RomOnlyMemoryRule::~RomOnlyMemoryRule() +{ +} + +void RomOnlyMemoryRule::Reset(bool bCGB) +{ + m_bCGB = bCGB; +} + +u8 RomOnlyMemoryRule::PerformRead(u16 address) +{ + if (address >= 0xA000 && address < 0xC000) + { + if (m_pCartridge->GetRAMSize() > 0) + return m_pMemory->Retrieve(address); + else + { + Log("--> ** Attempting to read from RAM without ram in cart %X", address); + return 0xFF; + } + } + else + return m_pMemory->Retrieve(address); +} + +void RomOnlyMemoryRule::PerformWrite(u16 address, u8 value) +{ + if (address < 0x8000) + { + // ROM + Log("--> ** Attempting to write on ROM address %X %X", address, value); + } + else if (address >= 0xA000 && address < 0xC000) + { + if (m_pCartridge->GetRAMSize() > 0) + { + m_pMemory->Load(address, value); + } + else + { + Log("--> ** Attempting to write to RAM without ram in cart %X %X", address, value); + } + } + else + m_pMemory->Load(address, value); +} + +void RomOnlyMemoryRule::SaveRam(std::ostream &file) +{ + Log("RomOnlyMemoryRule save RAM..."); + + for (int i = 0xA000; i < 0xC000; i++) + { + u8 ram_byte = m_pMemory->Retrieve(i); + file.write(reinterpret_cast<const char*> (&ram_byte), 1); + } + + Log("RomOnlyMemoryRule save RAM done"); +} + +bool RomOnlyMemoryRule::LoadRam(std::istream &file, s32 fileSize) +{ + Log("RomOnlyMemoryRule load RAM..."); + + if ((fileSize > 0) && (fileSize != 0x2000)) + { + Log("RomOnlyMemoryRule incorrect size. Expected: %d Found: %d", 0x2000, fileSize); + return false; + } + + for (int i = 0xA000; i < 0xC000; i++) + { + u8 ram_byte = 0; + file.read(reinterpret_cast<char*> (&ram_byte), 1); + m_pMemory->Load(i, ram_byte); + } + + Log("RomOnlyMemoryRule load RAM done"); + + return true; +} + +size_t RomOnlyMemoryRule::GetRamSize() +{ + return m_pCartridge->GetRAMBankCount() * 0x2000; +} + +u8* RomOnlyMemoryRule::GetRamBanks() +{ + return m_pMemory->GetMemoryMap() + 0xA000; +} + +u8* RomOnlyMemoryRule::GetCurrentRamBank() +{ + return m_pMemory->GetMemoryMap() + 0xA000; +} + +int RomOnlyMemoryRule::GetCurrentRamBankIndex() +{ + return 0; +} + +u8* RomOnlyMemoryRule::GetCurrentRomBank1() +{ + return m_pMemory->GetMemoryMap() + 0x4000; +} + +int RomOnlyMemoryRule::GetCurrentRomBank1Index() +{ + return 1; +} + +u8* RomOnlyMemoryRule::GetRomBank0() +{ + return m_pMemory->GetMemoryMap() + 0x0000; +} + +int RomOnlyMemoryRule::GetCurrentRomBank0Index() +{ + return 0; +} diff --git a/gearboy/src/RomOnlyMemoryRule.h b/gearboy/src/RomOnlyMemoryRule.h new file mode 100644 index 00000000..53079e3b --- /dev/null +++ b/gearboy/src/RomOnlyMemoryRule.h @@ -0,0 +1,46 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#ifndef ROMONLYMEMORYRULE_H +#define ROMONLYMEMORYRULE_H + +#include "MemoryRule.h" + +class RomOnlyMemoryRule : public MemoryRule +{ +public: + RomOnlyMemoryRule(Processor* pProcessor, Memory* pMemory, + Video* pVideo, Input* pInput, Cartridge* pCartridge, Audio* pAudio); + virtual ~RomOnlyMemoryRule(); + virtual u8 PerformRead(u16 address); + virtual void PerformWrite(u16 address, u8 value); + virtual void Reset(bool bCGB); + virtual void SaveRam(std::ostream &file); + virtual bool LoadRam(std::istream &file, s32 fileSize); + virtual size_t GetRamSize(); + virtual u8* GetRamBanks(); + virtual u8* GetCurrentRamBank(); + virtual int GetCurrentRamBankIndex(); + virtual u8* GetRomBank0(); + virtual int GetCurrentRomBank0Index(); + virtual u8* GetCurrentRomBank1(); + virtual int GetCurrentRomBank1Index(); +}; + +#endif /* ROMONLYMEMORYRULE_H */ diff --git a/gearboy/src/SixteenBitRegister.h b/gearboy/src/SixteenBitRegister.h new file mode 100644 index 00000000..71902d64 --- /dev/null +++ b/gearboy/src/SixteenBitRegister.h @@ -0,0 +1,109 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#ifndef SIXTEENBITREGISTER_H +#define SIXTEENBITREGISTER_H + +#include "definitions.h" + +class SixteenBitRegister +{ +public: + SixteenBitRegister() { } + void SetLow(u8 low); + u8 GetLow() const; + void SetHigh(u8 high); + u8 GetHigh() const; + u8* GetHighRegister(); + u8* GetLowRegister(); + void SetValue(u16 value); + u16 GetValue() const; + void Increment(); + void Decrement(); + +private: + union sixteenBit + { + u16 v; + struct + { +#ifdef IS_LITTLE_ENDIAN + uint8_t low; + uint8_t high; +#else + uint8_t high; + uint8_t low; +#endif + }; + } m_Value; +}; + + +inline void SixteenBitRegister::SetLow(u8 low) +{ + m_Value.low = low; +} + +inline u8 SixteenBitRegister::GetLow() const +{ + return m_Value.low; +} + +inline void SixteenBitRegister::SetHigh(u8 high) +{ + m_Value.high = high; +} + +inline u8 SixteenBitRegister::GetHigh() const +{ + return m_Value.high; +} + +inline u8* SixteenBitRegister::GetHighRegister() +{ + return &m_Value.high; +} + +inline u8* SixteenBitRegister::GetLowRegister() +{ + return &m_Value.low; +} + +inline void SixteenBitRegister::SetValue(u16 value) +{ + m_Value.v = value; +} + +inline u16 SixteenBitRegister::GetValue() const +{ + return m_Value.v; +} + +inline void SixteenBitRegister::Increment() +{ + m_Value.v++; +} + +inline void SixteenBitRegister::Decrement() +{ + m_Value.v--; +} + +#endif /* SIXTEENBITREGISTER_H */ + diff --git a/gearboy/src/Video.cpp b/gearboy/src/Video.cpp new file mode 100644 index 00000000..6ddb466f --- /dev/null +++ b/gearboy/src/Video.cpp @@ -0,0 +1,906 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#include "Video.h" +#include "Memory.h" +#include "Processor.h" + +Video::Video(Memory* pMemory, Processor* pProcessor) +{ + m_pMemory = pMemory; + m_pMemory->SetVideo(this); + m_pProcessor = pProcessor; + InitPointer(m_pFrameBuffer); + InitPointer(m_pColorFrameBuffer); + InitPointer(m_pSpriteXCacheBuffer); + InitPointer(m_pColorCacheBuffer); + m_iStatusMode = 0; + m_iStatusModeCounter = 0; + m_iStatusModeCounterAux = 0; + m_iStatusModeLYCounter = 0; + m_iScreenEnableDelayCycles = 0; + m_iStatusVBlankLine = 0; + m_iWindowLine = 0; + m_iPixelCounter = 0; + m_iTileCycleCounter = 0; + m_bScreenEnabled = true; + m_bCGB = false; + m_bScanLineTransfered = false; + m_iHideFrames = 0; + m_IRQ48Signal = 0; + m_pixelFormat = GB_PIXEL_RGB565; +} + +Video::~Video() +{ + SafeDeleteArray(m_pSpriteXCacheBuffer); + SafeDeleteArray(m_pColorCacheBuffer); + SafeDeleteArray(m_pFrameBuffer); +} + +void Video::Init() +{ + m_pFrameBuffer = new u8[GAMEBOY_WIDTH * GAMEBOY_HEIGHT]; + m_pSpriteXCacheBuffer = new int[GAMEBOY_WIDTH * GAMEBOY_HEIGHT]; + m_pColorCacheBuffer = new u8[GAMEBOY_WIDTH * GAMEBOY_HEIGHT]; + Reset(false); +} + +void Video::Reset(bool bCGB) +{ + for (int i = 0; i < (GAMEBOY_WIDTH * GAMEBOY_HEIGHT); i++) + m_pSpriteXCacheBuffer[i] = m_pFrameBuffer[i] = m_pColorCacheBuffer[i] = 0; + + for (int p = 0; p < 8; p++) + for (int c = 0; c < 4; c++) + { + m_CGBBackgroundPalettes[p][c][0] = m_CGBSpritePalettes[p][c][0] = 0x0000; + m_CGBBackgroundPalettes[p][c][1] = m_CGBSpritePalettes[p][c][1] = 0x0000; + } + + m_iStatusMode = 1; + m_iStatusModeCounter = 0; + m_iStatusModeCounterAux = 0; + m_iStatusModeLYCounter = 144; + m_iScreenEnableDelayCycles = 0; + m_iStatusVBlankLine = 0; + m_iWindowLine = 0; + m_iPixelCounter = 0; + m_iTileCycleCounter = 0; + m_bScreenEnabled = true; + m_bScanLineTransfered = false; + m_bCGB = bCGB; + m_iHideFrames = 0; + m_IRQ48Signal = 0; +} + +bool Video::Tick(unsigned int &clockCycles, u16* pColorFrameBuffer, GB_Color_Format pixelFormat) +{ + m_pColorFrameBuffer = pColorFrameBuffer; + m_pixelFormat = pixelFormat; + + bool vblank = false; + m_iStatusModeCounter += clockCycles; + + if (m_bScreenEnabled) + { + switch (m_iStatusMode) + { + // During H-BLANK + case 0: + { + if (m_iStatusModeCounter >= 204) + { + m_iStatusModeCounter -= 204; + m_iStatusMode = 2; + + m_iStatusModeLYCounter++; + m_pMemory->Load(0xFF44, m_iStatusModeLYCounter); + CompareLYToLYC(); + + if (m_bCGB && m_pMemory->IsHDMAEnabled() && (!m_pProcessor->Halted() || m_pProcessor->InterruptIsAboutToRaise())) + { + unsigned int cycles = m_pMemory->PerformHDMA(); + m_iStatusModeCounter += cycles; + clockCycles += cycles; + } + + if (m_iStatusModeLYCounter == 144) + { + m_iStatusMode = 1; + m_iStatusVBlankLine = 0; + m_iStatusModeCounterAux = m_iStatusModeCounter; + + m_pProcessor->RequestInterrupt(Processor::VBlank_Interrupt); + + m_IRQ48Signal &= 0x09; + u8 stat = m_pMemory->Retrieve(0xFF41); + if (IsSetBit(stat, 4)) + { + if (!IsSetBit(m_IRQ48Signal, 0) && !IsSetBit(m_IRQ48Signal, 3)) + { + m_pProcessor->RequestInterrupt(Processor::LCDSTAT_Interrupt); + } + m_IRQ48Signal = SetBit(m_IRQ48Signal, 1); + } + m_IRQ48Signal &= 0x0E; + + if (m_iHideFrames > 0) + m_iHideFrames--; + else + vblank = true; + + m_iWindowLine = 0; + } + else + { + m_IRQ48Signal &= 0x09; + u8 stat = m_pMemory->Retrieve(0xFF41); + if (IsSetBit(stat, 5)) + { + if (m_IRQ48Signal == 0) + { + m_pProcessor->RequestInterrupt(Processor::LCDSTAT_Interrupt); + } + m_IRQ48Signal = SetBit(m_IRQ48Signal, 2); + } + m_IRQ48Signal &= 0x0E; + } + + UpdateStatRegister(); + } + break; + } + // During V-BLANK + case 1: + { + m_iStatusModeCounterAux += clockCycles; + + if (m_iStatusModeCounterAux >= 456) + { + m_iStatusModeCounterAux -= 456; + m_iStatusVBlankLine++; + + if (m_iStatusVBlankLine <= 9) + { + m_iStatusModeLYCounter++; + m_pMemory->Load(0xFF44, m_iStatusModeLYCounter); + CompareLYToLYC(); + } + } + + if ((m_iStatusModeCounter >= 4104) && (m_iStatusModeCounterAux >= 4) && (m_iStatusModeLYCounter == 153)) + { + m_iStatusModeLYCounter = 0; + m_pMemory->Load(0xFF44, m_iStatusModeLYCounter); + CompareLYToLYC(); + } + + if (m_iStatusModeCounter >= 4560) + { + m_iStatusModeCounter -= 4560; + m_iStatusMode = 2; + UpdateStatRegister(); + m_IRQ48Signal &= 0x07; + + + m_IRQ48Signal &= 0x0A; + u8 stat = m_pMemory->Retrieve(0xFF41); + if (IsSetBit(stat, 5)) + { + if (m_IRQ48Signal == 0) + { + m_pProcessor->RequestInterrupt(Processor::LCDSTAT_Interrupt); + } + m_IRQ48Signal = SetBit(m_IRQ48Signal, 2); + } + m_IRQ48Signal &= 0x0D; + } + break; + } + // During searching OAM RAM + case 2: + { + if (m_iStatusModeCounter >= 80) + { + m_iStatusModeCounter -= 80; + m_iStatusMode = 3; + m_bScanLineTransfered = false; + m_IRQ48Signal &= 0x08; + UpdateStatRegister(); + } + break; + } + // During transfering data to LCD driver + case 3: + { +#ifndef PERFORMANCE + if (m_iPixelCounter < 160) + { + m_iTileCycleCounter += clockCycles; + u8 lcdc = m_pMemory->Retrieve(0xFF40); + + if (m_bScreenEnabled && IsSetBit(lcdc, 7)) + { + while (m_iTileCycleCounter >= 3) + { + if (IsValidPointer(m_pColorFrameBuffer)) + { + RenderBG(m_iStatusModeLYCounter, m_iPixelCounter); + } + m_iPixelCounter += 4; + m_iTileCycleCounter -= 3; + + if (m_iPixelCounter >= 160) + { + break; + } + } + } + } +#endif + + if (m_iStatusModeCounter >= 160 && !m_bScanLineTransfered) + { + ScanLine(m_iStatusModeLYCounter); + m_bScanLineTransfered = true; + } + + if (m_iStatusModeCounter >= 172) + { + m_iPixelCounter = 0; + m_iStatusModeCounter -= 172; + m_iStatusMode = 0; + m_iTileCycleCounter = 0; + UpdateStatRegister(); + + m_IRQ48Signal &= 0x08; + u8 stat = m_pMemory->Retrieve(0xFF41); + if (IsSetBit(stat, 3)) + { + if (!IsSetBit(m_IRQ48Signal, 3)) + { + m_pProcessor->RequestInterrupt(Processor::LCDSTAT_Interrupt); + } + m_IRQ48Signal = SetBit(m_IRQ48Signal, 0); + } + } + break; + } + } + } + // Screen disabled + else + { + if (m_iScreenEnableDelayCycles > 0) + { + m_iScreenEnableDelayCycles -= clockCycles; + + if (m_iScreenEnableDelayCycles <= 0) + { + m_iScreenEnableDelayCycles = 0; + m_bScreenEnabled = true; + m_iHideFrames = 3; + m_iStatusMode = 0; + m_iStatusModeCounter = 0; + m_iStatusModeCounterAux = 0; + m_iStatusModeLYCounter = 0; + m_iWindowLine = 0; + m_iStatusVBlankLine = 0; + m_iPixelCounter = 0; + m_iTileCycleCounter = 0; + m_pMemory->Load(0xFF44, m_iStatusModeLYCounter); + m_IRQ48Signal = 0; + + u8 stat = m_pMemory->Retrieve(0xFF41); + if (IsSetBit(stat, 5)) + { + m_pProcessor->RequestInterrupt(Processor::LCDSTAT_Interrupt); + m_IRQ48Signal = SetBit(m_IRQ48Signal, 2); + } + + CompareLYToLYC(); + } + } + else if (m_iStatusModeCounter >= 70224) + { + m_iStatusModeCounter -= 70224; + vblank = true; + } + } + return vblank; +} + +void Video::EnableScreen() +{ + if (!m_bScreenEnabled) + { + m_iScreenEnableDelayCycles = 244; + } +} + +void Video::DisableScreen() +{ + m_bScreenEnabled = false; + m_pMemory->Load(0xFF44, 0x00); + u8 stat = m_pMemory->Retrieve(0xFF41); + stat &= 0x7C; + m_pMemory->Load(0xFF41, stat); + m_iStatusMode = 0; + m_iStatusModeCounter = 0; + m_iStatusModeCounterAux = 0; + m_iStatusModeLYCounter = 0; + m_IRQ48Signal = 0; +} + +bool Video::IsScreenEnabled() const +{ + return m_bScreenEnabled; +} + +const u8* Video::GetFrameBuffer() const +{ + return m_pFrameBuffer; +} + +void Video::UpdatePaletteToSpecification(bool background, u8 value) +{ + bool hl = IsSetBit(value, 0); + int index = (value >> 1) & 0x03; + int pal = (value >> 3) & 0x07; + + u16 color = (background ? m_CGBBackgroundPalettes[pal][index][0] : m_CGBSpritePalettes[pal][index][0]); + + m_pMemory->Load(background ? 0xFF69 : 0xFF6B, hl ? (color >> 8) & 0xFF : color & 0xFF); +} + +void Video::SetColorPalette(bool background, u8 value) +{ + u8 ps = background ? m_pMemory->Retrieve(0xFF68) : m_pMemory->Retrieve(0xFF6A); + bool hl = IsSetBit(ps, 0); + int index = (ps >> 1) & 0x03; + int pal = (ps >> 3) & 0x07; + bool increment = IsSetBit(ps, 7); + + if (increment) + { + u8 address = ps & 0x3F; + address++; + address &= 0x3F; + ps = (ps & 0x80) | address; + m_pMemory->Load(background ? 0xFF68 : 0xFF6A, ps); + UpdatePaletteToSpecification(background, ps); + } + + u16* palette_color_gbc = background ? &m_CGBBackgroundPalettes[pal][index][0] : &m_CGBSpritePalettes[pal][index][0]; + u16* palette_color_final = background ? &m_CGBBackgroundPalettes[pal][index][1] : &m_CGBSpritePalettes[pal][index][1]; + + *palette_color_gbc = hl ? (*palette_color_gbc & 0x00FF) | (value << 8) : (*palette_color_gbc & 0xFF00) | value; + + u8 red_5bit = *palette_color_gbc & 0x1F; + u8 blue_5bit = (*palette_color_gbc >> 10) & 0x1F; + + switch (m_pixelFormat) + { + case GB_PIXEL_RGB565: + { + u8 green_6bit = (*palette_color_gbc >> 4) & 0x3E; + *palette_color_final = (red_5bit << 11) | (green_6bit << 5) | blue_5bit; + break; + } + case GB_PIXEL_BGR565: + { + u8 green_6bit = (*palette_color_gbc >> 4) & 0x3E; + *palette_color_final = (blue_5bit << 11) | (green_6bit << 5) | red_5bit; + break; + } + case GB_PIXEL_RGB555: + { + u8 green_5bit = (*palette_color_gbc >> 5) & 0x1F; + *palette_color_final = 0x8000 | (red_5bit << 10) | (green_5bit << 5) | blue_5bit; + break; + } + case GB_PIXEL_BGR555: + { + u8 green_5bit = (*palette_color_gbc >> 5) & 0x1F; + *palette_color_final = 0x8000 | (blue_5bit << 10) | (green_5bit << 5) | red_5bit; + break; + } + } + +} + +int Video::GetCurrentStatusMode() const +{ + return m_iStatusMode; +} + +void Video::ResetWindowLine() +{ + u8 wy = m_pMemory->Retrieve(0xFF4A); + + if ((m_iWindowLine == 0) && (m_iStatusModeLYCounter < 144) && (m_iStatusModeLYCounter > wy)) + m_iWindowLine = 144; +} + +void Video::ScanLine(int line) +{ + if (IsValidPointer(m_pColorFrameBuffer)) + { + u8 lcdc = m_pMemory->Retrieve(0xFF40); + + if (m_bScreenEnabled && IsSetBit(lcdc, 7)) + { +#ifdef PERFORMANCE + RenderBG(line, 0); +#endif + RenderWindow(line); + RenderSprites(line); + } + else + { + int line_width = (line * GAMEBOY_WIDTH); + if (m_bCGB) + { + for (int x = 0; x < GAMEBOY_WIDTH; x++) + m_pColorFrameBuffer[line_width + x] = 0x8000; + } + else + { + for (int x = 0; x < GAMEBOY_WIDTH; x++) + m_pFrameBuffer[line_width + x] = 0; + } + } + } +} + +void Video::RenderBG(int line, int pixel) +{ + u8 lcdc = m_pMemory->Retrieve(0xFF40); + int line_width = (line * GAMEBOY_WIDTH); + + if (m_bCGB || IsSetBit(lcdc, 0)) + { +#ifdef PERFORMANCE + int pixels_to_render = 160; +#else + int pixels_to_render = 4; +#endif + int offset_x_init = pixel & 0x7; + int offset_x_end = offset_x_init + pixels_to_render; + int screen_tile = pixel >> 3; + int tile_start_addr = IsSetBit(lcdc, 4) ? 0x8000 : 0x8800; + int map_start_addr = IsSetBit(lcdc, 3) ? 0x9C00 : 0x9800; + u8 scroll_x = m_pMemory->Retrieve(0xFF43); + u8 scroll_y = m_pMemory->Retrieve(0xFF42); + u8 line_scrolled = line + scroll_y; + int line_scrolled_32 = (line_scrolled >> 3) << 5; + int tile_pixel_y = line_scrolled & 0x7; + int tile_pixel_y_2 = tile_pixel_y << 1; + int tile_pixel_y_flip_2 = (7 - tile_pixel_y) << 1; + u8 palette = m_pMemory->Retrieve(0xFF47); + + for (int offset_x = offset_x_init; offset_x < offset_x_end; offset_x++) + { + int screen_pixel_x = (screen_tile << 3) + offset_x; + u8 map_pixel_x = screen_pixel_x + scroll_x; + int map_tile_x = map_pixel_x >> 3; + int map_tile_offset_x = map_pixel_x & 0x7; + u16 map_tile_addr = map_start_addr + line_scrolled_32 + map_tile_x; + int map_tile = 0; + + if (tile_start_addr == 0x8800) + { + map_tile = static_cast<s8> (m_pMemory->Retrieve(map_tile_addr)); + map_tile += 128; + } + else + { + map_tile = m_pMemory->Retrieve(map_tile_addr); + } + + u8 cgb_tile_attr = m_bCGB ? m_pMemory->ReadCGBLCDRAM(map_tile_addr, true) : 0; + u8 cgb_tile_pal = m_bCGB ? (cgb_tile_attr & 0x07) : 0; + bool cgb_tile_bank = m_bCGB ? IsSetBit(cgb_tile_attr, 3) : false; + bool cgb_tile_xflip = m_bCGB ? IsSetBit(cgb_tile_attr, 5) : false; + bool cgb_tile_yflip = m_bCGB ? IsSetBit(cgb_tile_attr, 6) : false; + int map_tile_16 = map_tile << 4; + u8 byte1 = 0; + u8 byte2 = 0; + int final_pixely_2 = cgb_tile_yflip ? tile_pixel_y_flip_2 : tile_pixel_y_2; + int tile_address = tile_start_addr + map_tile_16 + final_pixely_2; + + if (cgb_tile_bank) + { + byte1 = m_pMemory->ReadCGBLCDRAM(tile_address, true); + byte2 = m_pMemory->ReadCGBLCDRAM(tile_address + 1, true); + } + else + { + byte1 = m_pMemory->Retrieve(tile_address); + byte2 = m_pMemory->Retrieve(tile_address + 1); + } + + int pixel_x_in_tile = map_tile_offset_x; + + if (cgb_tile_xflip) + { + pixel_x_in_tile = 7 - pixel_x_in_tile; + } + int pixel_x_in_tile_bit = 0x1 << (7 - pixel_x_in_tile); + int pixel_data = (byte1 & pixel_x_in_tile_bit) ? 1 : 0; + pixel_data |= (byte2 & pixel_x_in_tile_bit) ? 2 : 0; + + int index = line_width + screen_pixel_x; + m_pColorCacheBuffer[index] = pixel_data & 0x03; + + if (m_bCGB) + { + bool cgb_tile_priority = IsSetBit(cgb_tile_attr, 7) && IsSetBit(lcdc, 0); + if (cgb_tile_priority && (pixel_data != 0)) + m_pColorCacheBuffer[index] = SetBit(m_pColorCacheBuffer[index], 2); + m_pColorFrameBuffer[index] = m_CGBBackgroundPalettes[cgb_tile_pal][pixel_data][1]; + } + else + { + u8 color = (palette >> (pixel_data << 1)) & 0x03; + m_pColorFrameBuffer[index] = m_pFrameBuffer[index] = color; + } + } + } + else + { + for (int x = 0; x < 4; x++) + { + int position = line_width + pixel + x; + m_pFrameBuffer[position] = 0; + m_pColorCacheBuffer[position] = 0; + } + } +} + +void Video::RenderWindow(int line) +{ + if (m_iWindowLine > 143) + return; + + u8 lcdc = m_pMemory->Retrieve(0xFF40); + if (!IsSetBit(lcdc, 5)) + return; + + int wx = m_pMemory->Retrieve(0xFF4B) - 7; + if (wx > 159) + return; + + u8 wy = m_pMemory->Retrieve(0xFF4A); + if ((wy > 143) || (wy > line)) + return; + + int tiles = IsSetBit(lcdc, 4) ? 0x8000 : 0x8800; + int map = IsSetBit(lcdc, 6) ? 0x9C00 : 0x9800; + int lineAdjusted = m_iWindowLine; + int y_32 = (lineAdjusted >> 3) << 5; + int pixely = lineAdjusted & 0x7; + int pixely_2 = pixely << 1; + int pixely_2_flip = (7 - pixely) << 1; + int line_width = (line * GAMEBOY_WIDTH); + u8 palette = m_pMemory->Retrieve(0xFF47); + + for (int x = 0; x < 32; x++) + { + int tile = 0; + + if (tiles == 0x8800) + { + tile = static_cast<s8> (m_pMemory->Retrieve(map + y_32 + x)); + tile += 128; + } + else + { + tile = m_pMemory->Retrieve(map + y_32 + x); + } + + u8 cgb_tile_attr = m_bCGB ? m_pMemory->ReadCGBLCDRAM(map + y_32 + x, true) : 0; + u8 cgb_tile_pal = m_bCGB ? (cgb_tile_attr & 0x07) : 0; + bool cgb_tile_bank = m_bCGB ? IsSetBit(cgb_tile_attr, 3) : false; + bool cgb_tile_xflip = m_bCGB ? IsSetBit(cgb_tile_attr, 5) : false; + bool cgb_tile_yflip = m_bCGB ? IsSetBit(cgb_tile_attr, 6) : false; + int mapOffsetX = x << 3; + int tile_16 = tile << 4; + u8 byte1 = 0; + u8 byte2 = 0; + int final_pixely_2 = (m_bCGB && cgb_tile_yflip) ? pixely_2_flip : pixely_2; + int tile_address = tiles + tile_16 + final_pixely_2; + + if (m_bCGB && cgb_tile_bank) + { + byte1 = m_pMemory->ReadCGBLCDRAM(tile_address, true); + byte2 = m_pMemory->ReadCGBLCDRAM(tile_address + 1, true); + } + else + { + byte1 = m_pMemory->Retrieve(tile_address); + byte2 = m_pMemory->Retrieve(tile_address + 1); + } + + for (int pixelx = 0; pixelx < 8; pixelx++) + { + int bufferX = (mapOffsetX + pixelx + wx); + + if (bufferX < 0 || bufferX >= GAMEBOY_WIDTH) + continue; + + int pixelx_pos = pixelx; + + if (m_bCGB && cgb_tile_xflip) + { + pixelx_pos = 7 - pixelx_pos; + } + + int pixel = (byte1 & (0x1 << (7 - pixelx_pos))) ? 1 : 0; + pixel |= (byte2 & (0x1 << (7 - pixelx_pos))) ? 2 : 0; + + int position = line_width + bufferX; + m_pColorCacheBuffer[position] = pixel & 0x03; + + if (m_bCGB) + { + bool cgb_tile_priority = IsSetBit(cgb_tile_attr, 7) && IsSetBit(lcdc, 0); + if (cgb_tile_priority && (pixel != 0)) + m_pColorCacheBuffer[position] = SetBit(m_pColorCacheBuffer[position], 2); + m_pColorFrameBuffer[position] = m_CGBBackgroundPalettes[cgb_tile_pal][pixel][1]; + } + else + { + u8 color = (palette >> (pixel << 1)) & 0x03; + m_pColorFrameBuffer[position] = m_pFrameBuffer[position] = color; + } + } + } + m_iWindowLine++; +} + +void Video::RenderSprites(int line) +{ + u8 lcdc = m_pMemory->Retrieve(0xFF40); + + if (!IsSetBit(lcdc, 1)) + return; + + int sprite_height = IsSetBit(lcdc, 2) ? 16 : 8; + int line_width = (line * GAMEBOY_WIDTH); + + bool visible_sprites[40]; + int sprite_limit = 0; + + for (int sprite = 0; sprite < 40; sprite++) + { + int sprite_4 = sprite << 2; + int sprite_y = m_pMemory->Retrieve(0xFE00 + sprite_4) - 16; + + if ((sprite_y > line) || ((sprite_y + sprite_height) <= line)) + { + visible_sprites[sprite] = false; + continue; + } + + sprite_limit++; + + visible_sprites[sprite] = sprite_limit <= 10; + } + + for (int sprite = 39; sprite >= 0; sprite--) + { + if (!visible_sprites[sprite]) + continue; + + int sprite_4 = sprite << 2; + int sprite_x = m_pMemory->Retrieve(0xFE00 + sprite_4 + 1) - 8; + + if ((sprite_x < -7) || (sprite_x >= GAMEBOY_WIDTH)) + continue; + + int sprite_y = m_pMemory->Retrieve(0xFE00 + sprite_4) - 16; + int sprite_tile_16 = (m_pMemory->Retrieve(0xFE00 + sprite_4 + 2) + & ((sprite_height == 16) ? 0xFE : 0xFF)) << 4; + u8 sprite_flags = m_pMemory->Retrieve(0xFE00 + sprite_4 + 3); + int sprite_pallette = IsSetBit(sprite_flags, 4) ? 1 : 0; + u8 palette = m_pMemory->Retrieve(sprite_pallette ? 0xFF49 : 0xFF48); + bool xflip = IsSetBit(sprite_flags, 5); + bool yflip = IsSetBit(sprite_flags, 6); + bool aboveBG = (!IsSetBit(sprite_flags, 7)); + bool cgb_tile_bank = IsSetBit(sprite_flags, 3); + int cgb_tile_pal = sprite_flags & 0x07; + int tiles = 0x8000; + int pixel_y = yflip ? ((sprite_height == 16) ? 15 : 7) - (line - sprite_y) : line - sprite_y; + u8 byte1 = 0; + u8 byte2 = 0; + int pixel_y_2 = 0; + int offset = 0; + + if (sprite_height == 16 && (pixel_y >= 8)) + { + pixel_y_2 = (pixel_y - 8) << 1; + offset = 16; + } + else + pixel_y_2 = pixel_y << 1; + + int tile_address = tiles + sprite_tile_16 + pixel_y_2 + offset; + + if (m_bCGB && cgb_tile_bank) + { + byte1 = m_pMemory->ReadCGBLCDRAM(tile_address, true); + byte2 = m_pMemory->ReadCGBLCDRAM(tile_address + 1, true); + } + else + { + byte1 = m_pMemory->Retrieve(tile_address); + byte2 = m_pMemory->Retrieve(tile_address + 1); + } + + for (int pixelx = 0; pixelx < 8; pixelx++) + { + int pixel = (byte1 & (0x01 << (xflip ? pixelx : 7 - pixelx))) ? 1 : 0; + pixel |= (byte2 & (0x01 << (xflip ? pixelx : 7 - pixelx))) ? 2 : 0; + + if (pixel == 0) + continue; + + int bufferX = (sprite_x + pixelx); + + if (bufferX < 0 || bufferX >= GAMEBOY_WIDTH) + continue; + + int position = line_width + bufferX; + u8 color_cache = m_pColorCacheBuffer[position]; + + if (m_bCGB) + { + if (IsSetBit(color_cache, 2)) + continue; + } + else + { + int sprite_x_cache = m_pSpriteXCacheBuffer[position]; + if (IsSetBit(color_cache, 3) && (sprite_x_cache < sprite_x)) + continue; + } + + if (!aboveBG && (color_cache & 0x03)) + continue; + + m_pColorCacheBuffer[position] = SetBit(color_cache, 3); + m_pSpriteXCacheBuffer[position] = sprite_x; + if (m_bCGB) + { + m_pColorFrameBuffer[position] = m_CGBSpritePalettes[cgb_tile_pal][pixel][1]; + } + else + { + u8 color = (palette >> (pixel << 1)) & 0x03; + m_pColorFrameBuffer[position] = m_pFrameBuffer[position] = color; + } + } + } +} + +void Video::UpdateStatRegister() +{ + // Updates the STAT register with current mode + u8 stat = m_pMemory->Retrieve(0xFF41); + m_pMemory->Load(0xFF41, (stat & 0xFC) | (m_iStatusMode & 0x3)); +} + +void Video::CompareLYToLYC() +{ + if (m_bScreenEnabled) + { + u8 lyc = m_pMemory->Retrieve(0xFF45); + u8 stat = m_pMemory->Retrieve(0xFF41); + + if (lyc == m_iStatusModeLYCounter) + { + stat = SetBit(stat, 2); + if (IsSetBit(stat, 6)) + { + if (m_IRQ48Signal == 0) + { + m_pProcessor->RequestInterrupt(Processor::LCDSTAT_Interrupt); + } + m_IRQ48Signal = SetBit(m_IRQ48Signal, 3); + } + } + else + { + stat = UnsetBit(stat, 2); + m_IRQ48Signal = UnsetBit(m_IRQ48Signal, 3); + } + + m_pMemory->Load(0xFF41, stat); + } +} + +u8 Video::GetIRQ48Signal() const +{ + return m_IRQ48Signal; +} + +void Video::SetIRQ48Signal(u8 signal) +{ + m_IRQ48Signal = signal; +} + +void Video::SaveState(std::ostream& stream) +{ + using namespace std; + + stream.write(reinterpret_cast<const char*> (m_pFrameBuffer), GAMEBOY_WIDTH * GAMEBOY_HEIGHT); + stream.write(reinterpret_cast<const char*> (m_pSpriteXCacheBuffer), sizeof(int) * GAMEBOY_WIDTH * GAMEBOY_HEIGHT); + stream.write(reinterpret_cast<const char*> (m_pColorCacheBuffer), GAMEBOY_WIDTH * GAMEBOY_HEIGHT); + stream.write(reinterpret_cast<const char*> (&m_iStatusMode), sizeof(m_iStatusMode)); + stream.write(reinterpret_cast<const char*> (&m_iStatusModeCounter), sizeof(m_iStatusModeCounter)); + stream.write(reinterpret_cast<const char*> (&m_iStatusModeCounterAux), sizeof(m_iStatusModeCounterAux)); + stream.write(reinterpret_cast<const char*> (&m_iStatusModeLYCounter), sizeof(m_iStatusModeLYCounter)); + stream.write(reinterpret_cast<const char*> (&m_iScreenEnableDelayCycles), sizeof(m_iScreenEnableDelayCycles)); + stream.write(reinterpret_cast<const char*> (&m_iStatusVBlankLine), sizeof(m_iStatusVBlankLine)); + stream.write(reinterpret_cast<const char*> (&m_iPixelCounter), sizeof(m_iPixelCounter)); + stream.write(reinterpret_cast<const char*> (&m_iTileCycleCounter), sizeof(m_iTileCycleCounter)); + stream.write(reinterpret_cast<const char*> (&m_bScreenEnabled), sizeof(m_bScreenEnabled)); + stream.write(reinterpret_cast<const char*> (m_CGBSpritePalettes), sizeof(m_CGBSpritePalettes)); + stream.write(reinterpret_cast<const char*> (m_CGBBackgroundPalettes), sizeof(m_CGBBackgroundPalettes)); + stream.write(reinterpret_cast<const char*> (&m_bScanLineTransfered), sizeof(m_bScanLineTransfered)); + stream.write(reinterpret_cast<const char*> (&m_iWindowLine), sizeof(m_iWindowLine)); + stream.write(reinterpret_cast<const char*> (&m_iHideFrames), sizeof(m_iHideFrames)); + stream.write(reinterpret_cast<const char*> (&m_IRQ48Signal), sizeof(m_IRQ48Signal)); +} + +void Video::LoadState(std::istream& stream) +{ + using namespace std; + + stream.read(reinterpret_cast<char*> (m_pFrameBuffer), GAMEBOY_WIDTH * GAMEBOY_HEIGHT); + stream.read(reinterpret_cast<char*> (m_pSpriteXCacheBuffer), sizeof(int) * GAMEBOY_WIDTH * GAMEBOY_HEIGHT); + stream.read(reinterpret_cast<char*> (m_pColorCacheBuffer), GAMEBOY_WIDTH * GAMEBOY_HEIGHT); + stream.read(reinterpret_cast<char*> (&m_iStatusMode), sizeof(m_iStatusMode)); + stream.read(reinterpret_cast<char*> (&m_iStatusModeCounter), sizeof(m_iStatusModeCounter)); + stream.read(reinterpret_cast<char*> (&m_iStatusModeCounterAux), sizeof(m_iStatusModeCounterAux)); + stream.read(reinterpret_cast<char*> (&m_iStatusModeLYCounter), sizeof(m_iStatusModeLYCounter)); + stream.read(reinterpret_cast<char*> (&m_iScreenEnableDelayCycles), sizeof(m_iScreenEnableDelayCycles)); + stream.read(reinterpret_cast<char*> (&m_iStatusVBlankLine), sizeof(m_iStatusVBlankLine)); + stream.read(reinterpret_cast<char*> (&m_iPixelCounter), sizeof(m_iPixelCounter)); + stream.read(reinterpret_cast<char*> (&m_iTileCycleCounter), sizeof(m_iTileCycleCounter)); + stream.read(reinterpret_cast<char*> (&m_bScreenEnabled), sizeof(m_bScreenEnabled)); + stream.read(reinterpret_cast<char*> (m_CGBSpritePalettes), sizeof(m_CGBSpritePalettes)); + stream.read(reinterpret_cast<char*> (m_CGBBackgroundPalettes), sizeof(m_CGBBackgroundPalettes)); + stream.read(reinterpret_cast<char*> (&m_bScanLineTransfered), sizeof(m_bScanLineTransfered)); + stream.read(reinterpret_cast<char*> (&m_iWindowLine), sizeof(m_iWindowLine)); + stream.read(reinterpret_cast<char*> (&m_iHideFrames), sizeof(m_iHideFrames)); + stream.read(reinterpret_cast<char*> (&m_IRQ48Signal), sizeof(m_IRQ48Signal)); +} + +PaletteMatrix Video::GetCGBBackgroundPalettes() +{ + return &m_CGBBackgroundPalettes; +} + +PaletteMatrix Video::GetCGBSpritePalettes() +{ + return &m_CGBSpritePalettes; +} diff --git a/gearboy/src/Video.h b/gearboy/src/Video.h new file mode 100644 index 00000000..b7180e93 --- /dev/null +++ b/gearboy/src/Video.h @@ -0,0 +1,87 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#ifndef VIDEO_H +#define VIDEO_H + +#include "definitions.h" + +class Memory; +class Processor; + +typedef u16 (*PaletteMatrix)[8][4][2]; + +class Video +{ +public: + Video(Memory* pMemory, Processor* pProcessor); + ~Video(); + void Init(); + void Reset(bool bCGB); + bool Tick(unsigned int &clockCycles, u16* pColorFrameBuffer, GB_Color_Format pixelFormat); + void EnableScreen(); + void DisableScreen(); + bool IsScreenEnabled() const; + const u8* GetFrameBuffer() const; + void UpdatePaletteToSpecification(bool background, u8 value); + void SetColorPalette(bool background, u8 value); + int GetCurrentStatusMode() const; + void ResetWindowLine(); + void CompareLYToLYC(); + u8 GetIRQ48Signal() const; + void SetIRQ48Signal(u8 signal); + void SaveState(std::ostream& stream); + void LoadState(std::istream& stream); + PaletteMatrix GetCGBBackgroundPalettes(); + PaletteMatrix GetCGBSpritePalettes(); + +private: + void ScanLine(int line); + void RenderBG(int line, int pixel); + void RenderWindow(int line); + void RenderSprites(int line); + void UpdateStatRegister(); + +private: + Memory* m_pMemory; + Processor* m_pProcessor; + u8* m_pFrameBuffer; + u16* m_pColorFrameBuffer; + int* m_pSpriteXCacheBuffer; + u8* m_pColorCacheBuffer; + int m_iStatusMode; + int m_iStatusModeCounter; + int m_iStatusModeCounterAux; + int m_iStatusModeLYCounter; + int m_iScreenEnableDelayCycles; + int m_iStatusVBlankLine; + int m_iPixelCounter; + int m_iTileCycleCounter; + bool m_bScreenEnabled; + bool m_bCGB; + u16 m_CGBSpritePalettes[8][4][2]; + u16 m_CGBBackgroundPalettes[8][4][2]; + bool m_bScanLineTransfered; + int m_iWindowLine; + int m_iHideFrames; + u8 m_IRQ48Signal; + GB_Color_Format m_pixelFormat; +}; + +#endif /* VIDEO_H */ diff --git a/gearboy/src/audio/Blip_Buffer.cpp b/gearboy/src/audio/Blip_Buffer.cpp new file mode 100644 index 00000000..85070daf --- /dev/null +++ b/gearboy/src/audio/Blip_Buffer.cpp @@ -0,0 +1,463 @@ +// Blip_Buffer 0.4.1. http://www.slack.net/~ant/ + +#include "Blip_Buffer.h" + +#include <assert.h> +#include <limits.h> +#include <string.h> +#include <stdlib.h> +#include <math.h> + +/* Copyright (C) 2003-2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +// TODO: use scoped for variables in treble_eq() + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +int const silent_buf_size = 1; // size used for Silent_Blip_Buffer + +Blip_Buffer::Blip_Buffer() +{ + factor_ = (blip_ulong)LONG_MAX; + buffer_ = 0; + buffer_size_ = 0; + sample_rate_ = 0; + bass_shift_ = 0; + clock_rate_ = 0; + bass_freq_ = 16; + length_ = 0; + + // assumptions code makes about implementation-defined features + #ifndef NDEBUG + // right shift of negative value preserves sign + buf_t_ i = -0x7FFFFFFE; + assert( (i >> 1) == -0x3FFFFFFF ); + + // casting to short truncates to 16 bits and sign-extends + i = 0x18000; + assert( (short) i == -0x8000 ); + #endif + + clear(); +} + +Blip_Buffer::~Blip_Buffer() +{ + if ( buffer_size_ != silent_buf_size ) + free( buffer_ ); +} + +Silent_Blip_Buffer::Silent_Blip_Buffer() +{ + factor_ = 0; + buffer_ = buf; + buffer_size_ = silent_buf_size; + clear(); +} + +void Blip_Buffer::clear( int entire_buffer ) +{ + offset_ = 0; + reader_accum_ = 0; + modified_ = 0; + if ( buffer_ ) + { + long count = (entire_buffer ? buffer_size_ : samples_avail()); + memset( buffer_, 0, (count + blip_buffer_extra_) * sizeof (buf_t_) ); + } +} + +Blip_Buffer::blargg_err_t Blip_Buffer::set_sample_rate( long new_rate, int msec ) +{ + if ( buffer_size_ == silent_buf_size ) + { + assert( 0 ); + return "Internal (tried to resize Silent_Blip_Buffer)"; + } + + // start with maximum length that resampled time can represent + long new_size = (ULONG_MAX >> BLIP_BUFFER_ACCURACY) - blip_buffer_extra_ - 64; + if ( msec != blip_max_length ) + { + long s = (new_rate * (msec + 1) + 999) / 1000; + if ( s < new_size ) + new_size = s; + else + assert( 0 ); // fails if requested buffer length exceeds limit + } + + if ( buffer_size_ != new_size ) + { + void* p = realloc( buffer_, (new_size + blip_buffer_extra_) * sizeof *buffer_ ); + if ( !p ) + return "Out of memory"; + buffer_ = (buf_t_*) p; + } + + buffer_size_ = (int)new_size; + assert( buffer_size_ != silent_buf_size ); // size should never happen to match this + + // update things based on the sample rate + sample_rate_ = new_rate; + length_ = (int)(new_size * 1000 / new_rate - 1); + if ( msec ) + assert( length_ == msec ); // ensure length is same as that passed in + + // update these since they depend on sample rate + if ( clock_rate_ ) + clock_rate( clock_rate_ ); + bass_freq( bass_freq_ ); + + clear(); + + return 0; // success +} + +blip_resampled_time_t Blip_Buffer::clock_rate_factor( long rate ) const +{ + double ratio = (double) sample_rate_ / rate; + blip_long factor = (blip_long) floor( ratio * (1L << BLIP_BUFFER_ACCURACY) + 0.5 ); + assert( factor > 0 || !sample_rate_ ); // fails if clock/output ratio is too large + return (blip_resampled_time_t) factor; +} + +void Blip_Buffer::bass_freq( int freq ) +{ + bass_freq_ = freq; + int shift = 31; + if ( freq > 0 ) + { + shift = 13; + long f = (freq << 16) / sample_rate_; + while ( (f >>= 1) && --shift ) { } + } + bass_shift_ = shift; +} + +void Blip_Buffer::end_frame( blip_time_t t ) +{ + offset_ += t * factor_; + assert( samples_avail() <= (long) buffer_size_ ); // fails if time is past end of buffer +} + +long Blip_Buffer::count_samples( blip_time_t t ) const +{ + blip_resampled_time_t last_sample = resampled_time( t ) >> BLIP_BUFFER_ACCURACY; + blip_resampled_time_t first_sample = offset_ >> BLIP_BUFFER_ACCURACY; + return long (last_sample - first_sample); +} + +blip_time_t Blip_Buffer::count_clocks( long count ) const +{ + if ( !factor_ ) + { + assert( 0 ); // sample rate and clock rates must be set first + return 0; + } + + if ( count > buffer_size_ ) + count = buffer_size_; + blip_resampled_time_t time = (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY; + return (blip_time_t) ((time - offset_ + factor_ - 1) / factor_); +} + +void Blip_Buffer::remove_samples( long count ) +{ + if ( count ) + { + remove_silence( count ); + + // copy remaining samples to beginning and clear old samples + long remain = samples_avail() + blip_buffer_extra_; + memmove( buffer_, buffer_ + count, remain * sizeof *buffer_ ); + memset( buffer_ + remain, 0, count * sizeof *buffer_ ); + } +} + +// Blip_Synth_ + +Blip_Synth_Fast_::Blip_Synth_Fast_() +{ + buf = 0; + last_amp = 0; + delta_factor = 0; +} + +void Blip_Synth_Fast_::volume_unit( double new_unit ) +{ + delta_factor = int (new_unit * (1L << blip_sample_bits) + 0.5); +} + +#if !BLIP_BUFFER_FAST + +Blip_Synth_::Blip_Synth_( short* p, int w ) : + impulses( p ), + width( w ) +{ + volume_unit_ = 0.0; + kernel_unit = 0; + buf = 0; + last_amp = 0; + delta_factor = 0; +} + +#undef PI +#define PI 3.1415926535897932384626433832795029 + +static void gen_sinc( float* out, int count, double oversample, double treble, double cutoff ) +{ + if ( cutoff >= 0.999 ) + cutoff = 0.999; + + if ( treble < -300.0 ) + treble = -300.0; + if ( treble > 5.0 ) + treble = 5.0; + + double const maxh = 4096.0; + double const rolloff = pow( 10.0, 1.0 / (maxh * 20.0) * treble / (1.0 - cutoff) ); + double const pow_a_n = pow( rolloff, maxh - maxh * cutoff ); + double const to_angle = PI / 2 / maxh / oversample; + for ( int i = 0; i < count; i++ ) + { + double angle = ((i - count) * 2 + 1) * to_angle; + double c = rolloff * cos( (maxh - 1.0) * angle ) - cos( maxh * angle ); + double cos_nc_angle = cos( maxh * cutoff * angle ); + double cos_nc1_angle = cos( (maxh * cutoff - 1.0) * angle ); + double cos_angle = cos( angle ); + + c = c * pow_a_n - rolloff * cos_nc1_angle + cos_nc_angle; + double d = 1.0 + rolloff * (rolloff - cos_angle - cos_angle); + double b = 2.0 - cos_angle - cos_angle; + double a = 1.0 - cos_angle - cos_nc_angle + cos_nc1_angle; + + out [i] = (float) ((a * d + c * b) / (b * d)); // a / b + c / d + } +} + +void blip_eq_t::generate( float* out, int count ) const +{ + // lower cutoff freq for narrow kernels with their wider transition band + // (8 points->1.49, 16 points->1.15) + double oversample = blip_res * 2.25 / count + 0.85; + double half_rate = sample_rate * 0.5; + if ( cutoff_freq ) + oversample = half_rate / cutoff_freq; + double cutoff = rolloff_freq * oversample / half_rate; + + gen_sinc( out, count, blip_res * oversample, treble, cutoff ); + + // apply (half of) hamming window + double to_fraction = PI / (count - 1); + for ( int i = count; i--; ) + out [i] *= 0.54f - 0.46f * (float) cos( i * to_fraction ); +} + +void Blip_Synth_::adjust_impulse() +{ + // sum pairs for each phase and add error correction to end of first half + int const size = impulses_size(); + for ( int p = blip_res; p-- >= blip_res / 2; ) + { + int p2 = blip_res - 2 - p; + long error = kernel_unit; + for ( int i = 1; i < size; i += blip_res ) + { + error -= impulses [i + p ]; + error -= impulses [i + p2]; + } + if ( p == p2 ) + error /= 2; // phase = 0.5 impulse uses same half for both sides + impulses [size - blip_res + p] += (short) error; + //printf( "error: %ld\n", error ); + } + + //for ( int i = blip_res; i--; printf( "\n" ) ) + // for ( int j = 0; j < width / 2; j++ ) + // printf( "%5ld,", impulses [j * blip_res + i + 1] ); +} + +void Blip_Synth_::treble_eq( blip_eq_t const& eq ) +{ + float fimpulse [blip_res / 2 * (blip_widest_impulse_ - 1) + blip_res * 2]; + + int const half_size = blip_res / 2 * (width - 1); + eq.generate( &fimpulse [blip_res], half_size ); + + int i; + + // need mirror slightly past center for calculation + for ( i = blip_res; i--; ) + fimpulse [blip_res + half_size + i] = fimpulse [blip_res + half_size - 1 - i]; + + // starts at 0 + for ( i = 0; i < blip_res; i++ ) + fimpulse [i] = 0.0f; + + // find rescale factor + double total = 0.0; + for ( i = 0; i < half_size; i++ ) + total += fimpulse [blip_res + i]; + + //double const base_unit = 44800.0 - 128 * 18; // allows treble up to +0 dB + //double const base_unit = 37888.0; // allows treble to +5 dB + double const base_unit = 32768.0; // necessary for blip_unscaled to work + double rescale = base_unit / 2 / total; + kernel_unit = (long) base_unit; + + // integrate, first difference, rescale, convert to int + double sum = 0.0; + double next = 0.0; + int const size = this->impulses_size(); + for ( i = 0; i < size; i++ ) + { + impulses [i] = (short) (int) floor( (next - sum) * rescale + 0.5 ); + sum += fimpulse [i]; + next += fimpulse [i + blip_res]; + } + adjust_impulse(); + + // volume might require rescaling + double vol = volume_unit_; + if ( vol ) + { + volume_unit_ = 0.0; + volume_unit( vol ); + } +} + +void Blip_Synth_::volume_unit( double new_unit ) +{ + if ( new_unit != volume_unit_ ) + { + // use default eq if it hasn't been set yet + if ( !kernel_unit ) + treble_eq( -8.0 ); + + volume_unit_ = new_unit; + double factor = new_unit * (1L << blip_sample_bits) / kernel_unit; + + if ( factor > 0.0 ) + { + int shift = 0; + + // if unit is really small, might need to attenuate kernel + while ( factor < 2.0 ) + { + shift++; + factor *= 2.0; + } + + if ( shift ) + { + kernel_unit >>= shift; + assert( kernel_unit > 0 ); // fails if volume unit is too low + + // keep values positive to avoid round-towards-zero of sign-preserving + // right shift for negative values + long offset = 0x8000 + (1 << (shift - 1)); + long offset2 = 0x8000 >> shift; + for ( int i = impulses_size(); i--; ) + impulses [i] = (short) (int) (((impulses [i] + offset) >> shift) - offset2); + adjust_impulse(); + } + } + delta_factor = (int) floor( factor + 0.5 ); + //printf( "delta_factor: %d, kernel_unit: %d\n", delta_factor, kernel_unit ); + } +} +#endif + +long Blip_Buffer::read_samples( blip_sample_t* out_, long max_samples, int stereo ) +{ + long count = samples_avail(); + if ( count > max_samples ) + count = max_samples; + + if ( count ) + { + int const bass = BLIP_READER_BASS( *this ); + BLIP_READER_BEGIN( reader, *this ); + BLIP_READER_ADJ_( reader, count ); + blip_sample_t* BLIP_RESTRICT out = out_ + count; + blip_long offset = (blip_long) -count; + + if ( !stereo ) + { + do + { + blip_long s = BLIP_READER_READ( reader ); + BLIP_READER_NEXT_IDX_( reader, bass, offset ); + BLIP_CLAMP( s, s ); + out [offset] = (blip_sample_t) s; + } + while ( ++offset ); + } + else + { + do + { + blip_long s = BLIP_READER_READ( reader ); + BLIP_READER_NEXT_IDX_( reader, bass, offset ); + BLIP_CLAMP( s, s ); + out [offset * 2] = (blip_sample_t) s; + } + while ( ++offset ); + } + + BLIP_READER_END( reader, *this ); + + remove_samples( count ); + } + return count; +} + +void Blip_Buffer::mix_samples( blip_sample_t const* in, long count ) +{ + if ( buffer_size_ == silent_buf_size ) + { + assert( 0 ); + return; + } + + buf_t_* out = buffer_ + (offset_ >> BLIP_BUFFER_ACCURACY) + blip_widest_impulse_ / 2; + + int const sample_shift = blip_sample_bits - 16; + int prev = 0; + while ( count-- ) + { + blip_long s = (blip_long) *in++ << sample_shift; + *out += s - prev; + prev = s; + ++out; + } + *out -= prev; +} + +void Blip_Buffer::save_state( blip_buffer_state_t* out ) +{ + assert( samples_avail() == 0 ); + out->offset_ = offset_; + out->reader_accum_ = reader_accum_; + memcpy( out->buf, &buffer_ [offset_ >> BLIP_BUFFER_ACCURACY], sizeof out->buf ); +} + +void Blip_Buffer::load_state( blip_buffer_state_t const& in ) +{ + clear( false ); + + offset_ = in.offset_; + reader_accum_ = in.reader_accum_; + memcpy( buffer_, in.buf, sizeof in.buf ); +} diff --git a/gearboy/src/audio/Blip_Buffer.h b/gearboy/src/audio/Blip_Buffer.h new file mode 100644 index 00000000..699bff7d --- /dev/null +++ b/gearboy/src/audio/Blip_Buffer.h @@ -0,0 +1,556 @@ +// Band-limited sound synthesis buffer + +// Blip_Buffer 0.4.1 +#ifndef BLIP_BUFFER_H +#define BLIP_BUFFER_H + + // internal + #include <limits.h> + #if INT_MAX < 0x7FFFFFFF || LONG_MAX == 0x7FFFFFFF + typedef long blip_long; + typedef unsigned long blip_ulong; + #else + typedef int blip_long; + typedef unsigned blip_ulong; + #endif + +// Time unit at source clock rate +typedef blip_long blip_time_t; + +// Output samples are 16-bit signed, with a range of -32768 to 32767 +typedef short blip_sample_t; +enum { blip_sample_max = 32767 }; + +struct blip_buffer_state_t; + +class Blip_Buffer { +public: + typedef const char* blargg_err_t; + + // Sets output sample rate and buffer length in milliseconds (1/1000 sec, defaults + // to 1/4 second) and clears buffer. If there isn't enough memory, leaves buffer + // untouched and returns "Out of memory", otherwise returns NULL. + blargg_err_t set_sample_rate( long samples_per_sec, int msec_length = 1000 / 4 ); + + // Sets number of source time units per second + void clock_rate( long clocks_per_sec ); + + // Ends current time frame of specified duration and makes its samples available + // (along with any still-unread samples) for reading with read_samples(). Begins + // a new time frame at the end of the current frame. + void end_frame( blip_time_t time ); + + // Reads at most 'max_samples' out of buffer into 'dest', removing them from + // the buffer. Returns number of samples actually read and removed. If stereo is + // true, increments 'dest' one extra time after writing each sample, to allow + // easy interleving of two channels into a stereo output buffer. + long read_samples( blip_sample_t* dest, long max_samples, int stereo = 0 ); + +// Additional features + + // Removes all available samples and clear buffer to silence. If 'entire_buffer' is + // false, just clears out any samples waiting rather than the entire buffer. + void clear( int entire_buffer = 1 ); + + // Number of samples available for reading with read_samples() + long samples_avail() const; + + // Removes 'count' samples from those waiting to be read + void remove_samples( long count ); + + // Sets frequency high-pass filter frequency, where higher values reduce bass more + void bass_freq( int frequency ); + + // Current output sample rate + long sample_rate() const; + + // Length of buffer in milliseconds + int length() const; + + // Number of source time units per second + long clock_rate() const; + +// Experimental features + + // Saves state, including high-pass filter and tails of last deltas. + // All samples must have been read from buffer before calling this. + void save_state( blip_buffer_state_t* out ); + + // Loads state. State must have been saved from Blip_Buffer with same + // settings during same run of program. States can NOT be stored on disk. + // Clears buffer before loading state. + void load_state( blip_buffer_state_t const& in ); + + // Number of samples delay from synthesis to samples read out + int output_latency() const; + + // Counts number of clocks needed until 'count' samples will be available. + // If buffer can't even hold 'count' samples, returns number of clocks until + // buffer becomes full. + blip_time_t count_clocks( long count ) const; + + // Number of raw samples that can be mixed within frame of specified duration. + long count_samples( blip_time_t duration ) const; + + // Mixes in 'count' samples from 'buf_in' + void mix_samples( blip_sample_t const* buf_in, long count ); + + + // Signals that sound has been added to buffer. Could be done automatically in + // Blip_Synth, but that would affect performance more, as you can arrange that + // this is called only once per time frame rather than for every delta. + void set_modified() { modified_ = this; } + + // not documented yet + blip_ulong unsettled() const; + Blip_Buffer* clear_modified() { Blip_Buffer* b = modified_; modified_ = 0; return b; } + void remove_silence( long count ); + typedef blip_ulong blip_resampled_time_t; + blip_resampled_time_t resampled_duration( int t ) const { return t * factor_; } + blip_resampled_time_t resampled_time( blip_time_t t ) const { return t * factor_ + offset_; } + blip_resampled_time_t clock_rate_factor( long clock_rate ) const; +public: + Blip_Buffer(); + ~Blip_Buffer(); + + // Deprecated + typedef blip_resampled_time_t resampled_time_t; + blargg_err_t sample_rate( long r ) { return set_sample_rate( r ); } + blargg_err_t sample_rate( long r, int msec ) { return set_sample_rate( r, msec ); } +private: + // noncopyable + Blip_Buffer( const Blip_Buffer& ); + Blip_Buffer& operator = ( const Blip_Buffer& ); +public: + typedef blip_long buf_t_; + blip_ulong factor_; + blip_resampled_time_t offset_; + buf_t_* buffer_; + blip_long buffer_size_; + blip_long reader_accum_; + int bass_shift_; +private: + long sample_rate_; + long clock_rate_; + int bass_freq_; + int length_; + Blip_Buffer* modified_; // non-zero = true (more optimal than using bool, heh) + friend class Blip_Reader; +}; + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +// Number of bits in resample ratio fraction. Higher values give a more accurate ratio +// but reduce maximum buffer size. +#ifndef BLIP_BUFFER_ACCURACY + #define BLIP_BUFFER_ACCURACY 16 +#endif + +// Number bits in phase offset. Fewer than 6 bits (64 phase offsets) results in +// noticeable broadband noise when synthesizing high frequency square waves. +// Affects size of Blip_Synth objects since they store the waveform directly. +#ifndef BLIP_PHASE_BITS + #if BLIP_BUFFER_FAST + #define BLIP_PHASE_BITS 8 + #else + #define BLIP_PHASE_BITS 6 + #endif +#endif + + // Internal + typedef blip_ulong blip_resampled_time_t; + int const blip_widest_impulse_ = 16; + int const blip_buffer_extra_ = blip_widest_impulse_ + 2; + int const blip_res = 1 << BLIP_PHASE_BITS; + class blip_eq_t; + + class Blip_Synth_Fast_ { + public: + Blip_Buffer* buf; + int last_amp; + int delta_factor; + + void volume_unit( double ); + Blip_Synth_Fast_(); + void treble_eq( blip_eq_t const& ) { } + }; + + class Blip_Synth_ { + public: + Blip_Buffer* buf; + int last_amp; + int delta_factor; + + void volume_unit( double ); + Blip_Synth_( short* impulses, int width ); + void treble_eq( blip_eq_t const& ); + private: + double volume_unit_; + short* const impulses; + int const width; + blip_long kernel_unit; + int impulses_size() const { return blip_res / 2 * width + 1; } + void adjust_impulse(); + }; + +// Quality level, better = slower. In general, use blip_good_quality. +const int blip_med_quality = 8; +const int blip_good_quality = 12; +const int blip_high_quality = 16; + +// Range specifies the greatest expected change in amplitude. Calculate it +// by finding the difference between the maximum and minimum expected +// amplitudes (max - min). +template<int quality,int range> +class Blip_Synth { +public: + // Sets overall volume of waveform + void volume( double v ) { impl.volume_unit( v * (1.0 / (range < 0 ? -range : range)) ); } + + // Configures low-pass filter (see blip_buffer.txt) + void treble_eq( blip_eq_t const& eq ) { impl.treble_eq( eq ); } + + // Gets/sets Blip_Buffer used for output + Blip_Buffer* output() const { return impl.buf; } + void output( Blip_Buffer* b ) { impl.buf = b; impl.last_amp = 0; } + + // Updates amplitude of waveform at given time. Using this requires a separate + // Blip_Synth for each waveform. + void update( blip_time_t time, int amplitude ); + +// Low-level interface + + // Adds an amplitude transition of specified delta, optionally into specified buffer + // rather than the one set with output(). Delta can be positive or negative. + // The actual change in amplitude is delta * (volume / range) + void offset( blip_time_t, int delta, Blip_Buffer* ) const; + void offset( blip_time_t t, int delta ) const { offset( t, delta, impl.buf ); } + + // Works directly in terms of fractional output samples. Contact author for more info. + void offset_resampled( blip_resampled_time_t, int delta, Blip_Buffer* ) const; + + // Same as offset(), except code is inlined for higher performance + void offset_inline( blip_time_t t, int delta, Blip_Buffer* buf ) const { + offset_resampled( t * buf->factor_ + buf->offset_, delta, buf ); + } + void offset_inline( blip_time_t t, int delta ) const { + offset_resampled( t * impl.buf->factor_ + impl.buf->offset_, delta, impl.buf ); + } + +private: +#if BLIP_BUFFER_FAST + Blip_Synth_Fast_ impl; +#else + Blip_Synth_ impl; + typedef short imp_t; + imp_t impulses [blip_res * (quality / 2) + 1]; +public: + Blip_Synth() : impl( impulses, quality ) { } +#endif +}; + +// Low-pass equalization parameters +class blip_eq_t { +public: + // Logarithmic rolloff to treble dB at half sampling rate. Negative values reduce + // treble, small positive values (0 to 5.0) increase treble. + blip_eq_t( double treble_db = 0 ); + + // See blip_buffer.txt + blip_eq_t( double treble, long rolloff_freq, long sample_rate, long cutoff_freq = 0 ); + +private: + double treble; + long rolloff_freq; + long sample_rate; + long cutoff_freq; + void generate( float* out, int count ) const; + friend class Blip_Synth_; +}; + +int const blip_sample_bits = 30; + +// Dummy Blip_Buffer to direct sound output to, for easy muting without +// having to stop sound code. +class Silent_Blip_Buffer : public Blip_Buffer { + buf_t_ buf [blip_buffer_extra_ + 1]; +public: + // The following cannot be used (an assertion will fail if attempted): + blargg_err_t set_sample_rate( long samples_per_sec, int msec_length ); + blip_time_t count_clocks( long count ) const; + void mix_samples( blip_sample_t const* buf, long count ); + + Silent_Blip_Buffer(); +}; + + #if __GNUC__ >= 3 || _MSC_VER >= 1100 + #define BLIP_RESTRICT __restrict + #else + #define BLIP_RESTRICT + #endif + +// Optimized reading from Blip_Buffer, for use in custom sample output + +// Begins reading from buffer. Name should be unique to the current block. +#define BLIP_READER_BEGIN( name, blip_buffer ) \ + const Blip_Buffer::buf_t_* BLIP_RESTRICT name##_reader_buf = (blip_buffer).buffer_;\ + blip_long name##_reader_accum = (blip_buffer).reader_accum_ + +// Gets value to pass to BLIP_READER_NEXT() +#define BLIP_READER_BASS( blip_buffer ) ((blip_buffer).bass_shift_) + +// Constant value to use instead of BLIP_READER_BASS(), for slightly more optimal +// code at the cost of having no bass control +int const blip_reader_default_bass = 9; + +// Current sample +#define BLIP_READER_READ( name ) (name##_reader_accum >> (blip_sample_bits - 16)) + +// Current raw sample in full internal resolution +#define BLIP_READER_READ_RAW( name ) (name##_reader_accum) + +// Advances to next sample +#define BLIP_READER_NEXT( name, bass ) \ + (void) (name##_reader_accum += *name##_reader_buf++ - (name##_reader_accum >> (bass))) + +// Ends reading samples from buffer. The number of samples read must now be removed +// using Blip_Buffer::remove_samples(). +#define BLIP_READER_END( name, blip_buffer ) \ + (void) ((blip_buffer).reader_accum_ = name##_reader_accum) + + +// experimental +#define BLIP_READER_ADJ_( name, offset ) (name##_reader_buf += offset) + +blip_long const blip_reader_idx_factor = sizeof (Blip_Buffer::buf_t_); + +#define BLIP_READER_NEXT_IDX_( name, bass, idx ) {\ + name##_reader_accum -= name##_reader_accum >> (bass);\ + name##_reader_accum += name##_reader_buf [(idx)];\ +} + +#define BLIP_READER_NEXT_RAW_IDX_( name, bass, idx ) {\ + name##_reader_accum -= name##_reader_accum >> (bass);\ + name##_reader_accum +=\ + *(Blip_Buffer::buf_t_ const*) ((char const*) name##_reader_buf + (idx));\ +} + +// Compatibility with older version +const long blip_unscaled = 65535; +const int blip_low_quality = blip_med_quality; +const int blip_best_quality = blip_high_quality; + +// Deprecated; use BLIP_READER macros as follows: +// Blip_Reader r; r.begin( buf ); -> BLIP_READER_BEGIN( r, buf ); +// int bass = r.begin( buf ) -> BLIP_READER_BEGIN( r, buf ); int bass = BLIP_READER_BASS( buf ); +// r.read() -> BLIP_READER_READ( r ) +// r.read_raw() -> BLIP_READER_READ_RAW( r ) +// r.next( bass ) -> BLIP_READER_NEXT( r, bass ) +// r.next() -> BLIP_READER_NEXT( r, blip_reader_default_bass ) +// r.end( buf ) -> BLIP_READER_END( r, buf ) +class Blip_Reader { +public: + int begin( Blip_Buffer& ); + blip_long read() const { return accum >> (blip_sample_bits - 16); } + blip_long read_raw() const { return accum; } + void next( int bass_shift = 9 ) { accum += *buf++ - (accum >> bass_shift); } + void end( Blip_Buffer& b ) { b.reader_accum_ = accum; } +private: + const Blip_Buffer::buf_t_* buf; + blip_long accum; +}; + +#if defined (_M_IX86) || defined (_M_IA64) || defined (__i486__) || \ + defined (__x86_64__) || defined (__ia64__) || defined (__i386__) + #define BLIP_CLAMP_( in ) in < -0x8000 || 0x7FFF < in +#else + #define BLIP_CLAMP_( in ) (blip_sample_t) in != in +#endif + +// Clamp sample to blip_sample_t range +#define BLIP_CLAMP( sample, out )\ + { if ( BLIP_CLAMP_( (sample) ) ) (out) = ((sample) >> 24) ^ 0x7FFF; } + +struct blip_buffer_state_t +{ + blip_resampled_time_t offset_; + blip_long reader_accum_; + blip_long buf [blip_buffer_extra_]; +}; + +// End of public interface + +#ifndef assert + #include <assert.h> +#endif + +template<int quality,int range> +inline void Blip_Synth<quality,range>::offset_resampled( blip_resampled_time_t time, + int delta, Blip_Buffer* blip_buf ) const +{ + // If this assertion fails, it means that an attempt was made to add a delta + // at a negative time or past the end of the buffer. + assert( (blip_long) (time >> BLIP_BUFFER_ACCURACY) < blip_buf->buffer_size_ ); + + delta *= impl.delta_factor; + blip_long* BLIP_RESTRICT buf = blip_buf->buffer_ + (time >> BLIP_BUFFER_ACCURACY); + int phase = (int) (time >> (BLIP_BUFFER_ACCURACY - BLIP_PHASE_BITS) & (blip_res - 1)); + +#if BLIP_BUFFER_FAST + blip_long left = buf [0] + delta; + + // Kind of crappy, but doing shift after multiply results in overflow. + // Alternate way of delaying multiply by delta_factor results in worse + // sub-sample resolution. + blip_long right = (delta >> BLIP_PHASE_BITS) * phase; + left -= right; + right += buf [1]; + + buf [0] = left; + buf [1] = right; +#else + + int const fwd = (blip_widest_impulse_ - quality) / 2; + int const rev = fwd + quality - 2; + int const mid = quality / 2 - 1; + + imp_t const* BLIP_RESTRICT imp = impulses + blip_res - phase; + + #if defined (_M_IX86) || defined (_M_IA64) || defined (__i486__) || \ + defined (__x86_64__) || defined (__ia64__) || defined (__i386__) + + // this straight forward version gave in better code on GCC for x86 + + #define ADD_IMP( out, in ) \ + buf [out] += (blip_long) imp [blip_res * (in)] * delta + + #define BLIP_FWD( i ) {\ + ADD_IMP( fwd + i, i );\ + ADD_IMP( fwd + 1 + i, i + 1 );\ + } + #define BLIP_REV( r ) {\ + ADD_IMP( rev - r, r + 1 );\ + ADD_IMP( rev + 1 - r, r );\ + } + + BLIP_FWD( 0 ) + if ( quality > 8 ) BLIP_FWD( 2 ) + if ( quality > 12 ) BLIP_FWD( 4 ) + { + ADD_IMP( fwd + mid - 1, mid - 1 ); + ADD_IMP( fwd + mid , mid ); + imp = impulses + phase; + } + if ( quality > 12 ) BLIP_REV( 6 ) + if ( quality > 8 ) BLIP_REV( 4 ) + BLIP_REV( 2 ) + + ADD_IMP( rev , 1 ); + ADD_IMP( rev + 1, 0 ); + + #undef ADD_IMP + + #else + + // for RISC processors, help compiler by reading ahead of writes + + #define BLIP_FWD( i ) {\ + blip_long t0 = i0 * delta + buf [fwd + i];\ + blip_long t1 = imp [blip_res * (i + 1)] * delta + buf [fwd + 1 + i];\ + i0 = imp [blip_res * (i + 2)];\ + buf [fwd + i] = t0;\ + buf [fwd + 1 + i] = t1;\ + } + #define BLIP_REV( r ) {\ + blip_long t0 = i0 * delta + buf [rev - r];\ + blip_long t1 = imp [blip_res * r] * delta + buf [rev + 1 - r];\ + i0 = imp [blip_res * (r - 1)];\ + buf [rev - r] = t0;\ + buf [rev + 1 - r] = t1;\ + } + + blip_long i0 = *imp; + BLIP_FWD( 0 ) + if ( quality > 8 ) BLIP_FWD( 2 ) + if ( quality > 12 ) BLIP_FWD( 4 ) + { + blip_long t0 = i0 * delta + buf [fwd + mid - 1]; + blip_long t1 = imp [blip_res * mid] * delta + buf [fwd + mid ]; + imp = impulses + phase; + i0 = imp [blip_res * mid]; + buf [fwd + mid - 1] = t0; + buf [fwd + mid ] = t1; + } + if ( quality > 12 ) BLIP_REV( 6 ) + if ( quality > 8 ) BLIP_REV( 4 ) + BLIP_REV( 2 ) + + blip_long t0 = i0 * delta + buf [rev ]; + blip_long t1 = *imp * delta + buf [rev + 1]; + buf [rev ] = t0; + buf [rev + 1] = t1; + #endif + +#endif +} + +#undef BLIP_FWD +#undef BLIP_REV + +template<int quality,int range> +#if BLIP_BUFFER_FAST + inline +#endif +void Blip_Synth<quality,range>::offset( blip_time_t t, int delta, Blip_Buffer* buf ) const +{ + offset_resampled( t * buf->factor_ + buf->offset_, delta, buf ); +} + +template<int quality,int range> +#if BLIP_BUFFER_FAST + inline +#endif +void Blip_Synth<quality,range>::update( blip_time_t t, int amp ) +{ + int delta = amp - impl.last_amp; + impl.last_amp = amp; + offset_resampled( t * impl.buf->factor_ + impl.buf->offset_, delta, impl.buf ); +} + +inline blip_eq_t::blip_eq_t( double t ) : + treble( t ), rolloff_freq( 0 ), sample_rate( 44100 ), cutoff_freq( 0 ) { } +inline blip_eq_t::blip_eq_t( double t, long rf, long sr, long cf ) : + treble( t ), rolloff_freq( rf ), sample_rate( sr ), cutoff_freq( cf ) { } + +inline int Blip_Buffer::length() const { return length_; } +inline long Blip_Buffer::samples_avail() const { return (long) (offset_ >> BLIP_BUFFER_ACCURACY); } +inline long Blip_Buffer::sample_rate() const { return sample_rate_; } +inline int Blip_Buffer::output_latency() const { return blip_widest_impulse_ / 2; } +inline long Blip_Buffer::clock_rate() const { return clock_rate_; } +inline void Blip_Buffer::clock_rate( long cps ) { factor_ = clock_rate_factor( clock_rate_ = cps ); } + +inline int Blip_Reader::begin( Blip_Buffer& blip_buf ) +{ + buf = blip_buf.buffer_; + accum = blip_buf.reader_accum_; + return blip_buf.bass_shift_; +} + +inline void Blip_Buffer::remove_silence( long count ) +{ + // fails if you try to remove more samples than available + assert( count <= samples_avail() ); + offset_ -= (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY; +} + +inline blip_ulong Blip_Buffer::unsettled() const +{ + return reader_accum_ >> (blip_sample_bits - 16); +} + +int const blip_max_length = 0; +int const blip_default_length = 250; // 1/4 second + +#endif diff --git a/gearboy/src/audio/Blip_Synth.h b/gearboy/src/audio/Blip_Synth.h new file mode 100644 index 00000000..c1bdc396 --- /dev/null +++ b/gearboy/src/audio/Blip_Synth.h @@ -0,0 +1,208 @@ + +// Blip_Synth and Blip_Wave are waveform transition synthesizers for adding +// waveforms to a Blip_Buffer. + +// Blip_Buffer 0.3.4. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef BLIP_SYNTH_H +#define BLIP_SYNTH_H + +#ifndef BLIP_BUFFER_H + #include "Blip_Buffer.h" +#endif + +// Quality level. Higher levels are slower, and worse in a few cases. +// Use blip_good_quality as a starting point. +const int blip_low_quality = 1; +const int blip_med_quality = 2; +const int blip_good_quality = 3; +const int blip_high_quality = 4; + +// Blip_Synth is a transition waveform synthesizer which adds band-limited +// offsets (transitions) into a Blip_Buffer. For a simpler interface, use +// Blip_Wave (below). +// +// Range specifies the greatest expected offset that will occur. For a +// waveform that goes between +amp and -amp, range should be amp * 2 (half +// that if it only goes between +amp and 0). When range is large, a higher +// accuracy scheme is used; to force this even when range is small, pass +// the negative of range (i.e. -range). +template<int quality,int range> +class Blip_Synth { + BOOST_STATIC_ASSERT( 1 <= quality && quality <= 5 ); + BOOST_STATIC_ASSERT( -32768 <= range && range <= 32767 ); + enum { + abs_range = (range < 0) ? -range : range, + fine_mode = (range > 512 || range < 0), + width = (quality < 5 ? quality * 4 : Blip_Buffer::widest_impulse_), + res = 1 << blip_res_bits_, + impulse_size = width / 2 * (fine_mode + 1), + base_impulses_size = width / 2 * (res / 2 + 1), + fine_bits = (fine_mode ? (abs_range <= 64 ? 2 : abs_range <= 128 ? 3 : + abs_range <= 256 ? 4 : abs_range <= 512 ? 5 : abs_range <= 1024 ? 6 : + abs_range <= 2048 ? 7 : 8) : 0) + }; + blip_pair_t_ impulses [impulse_size * res * 2 + base_impulses_size]; + Blip_Impulse_ impulse; + void init() { impulse.init( impulses, width, res, fine_bits ); } +public: + Blip_Synth() { init(); } + Blip_Synth( double volume ) { init(); this->volume( volume ); } + + // Configure low-pass filter (see notes.txt). Not optimized for real-time control + void treble_eq( const blip_eq_t& eq ) { impulse.treble_eq( eq ); } + + // Set volume of a transition at amplitude 'range' by setting volume_unit + // to v / range + void volume( double v ) { impulse.volume_unit( v * (1.0 / abs_range) ); } + + // Set base volume unit of transitions, where 1.0 is a full swing between the + // positive and negative extremes. Not optimized for real-time control. + void volume_unit( double unit ) { impulse.volume_unit( unit ); } + + // Default Blip_Buffer used for output when none is specified for a given call + Blip_Buffer* output() const { return impulse.buf; } + void output( Blip_Buffer* b ) { impulse.buf = b; } + + // Add an amplitude offset (transition) with a magnitude of delta * volume_unit + // into the specified buffer (default buffer if none specified) at the + // specified source time. Delta can be positive or negative. To increase + // performance by inlining code at the call site, use offset_inline(). + void offset( blip_time_t, int delta, Blip_Buffer* ) const; + + void offset_resampled( blip_resampled_time_t, int delta, Blip_Buffer* ) const; + void offset_resampled( blip_resampled_time_t t, int o ) const { + offset_resampled( t, o, impulse.buf ); + } + void offset( blip_time_t t, int delta ) const { + offset( t, delta, impulse.buf ); + } + void offset_inline( blip_time_t time, int delta, Blip_Buffer* buf ) const { + offset_resampled( time * buf->factor_ + buf->offset_, delta, buf ); + } + void offset_inline( blip_time_t time, int delta ) const { + offset_inline( time, delta, impulse.buf ); + } +}; + +// Blip_Wave is a synthesizer for adding a *single* waveform to a Blip_Buffer. +// A wave is built from a series of delays and new amplitudes. This provides a +// simpler interface than Blip_Synth, nothing more. +template<int quality,int range> +class Blip_Wave { + Blip_Synth<quality,range> synth; + blip_time_t time_; + int last_amp; + void init() { time_ = 0; last_amp = 0; } +public: + // Start wave at time 0 and amplitude 0 + Blip_Wave() { init(); } + Blip_Wave( double volume ) { init(); this->volume( volume ); } + + // See Blip_Synth for description + void volume( double v ) { synth.volume( v ); } + void volume_unit( double v ) { synth.volume_unit( v ); } + void treble_eq( const blip_eq_t& eq){ synth.treble_eq( eq ); } + Blip_Buffer* output() const { return synth.output(); } + void output( Blip_Buffer* b ) { synth.output( b ); if ( !b ) time_ = last_amp = 0; } + + // Current time in frame + blip_time_t time() const { return time_; } + void time( blip_time_t t ) { time_ = t; } + + // Current amplitude of wave + int amplitude() const { return last_amp; } + void amplitude( int ); + + // Move forward by 't' time units + void delay( blip_time_t t ) { time_ += t; } + + // End time frame of specified duration. Localize time to new frame. + // If wave hadn't been run to end of frame, start it at beginning of new frame. + void end_frame( blip_time_t duration ) + { + time_ -= duration; + if ( time_ < 0 ) + time_ = 0; + } +}; + +// End of public interface + +template<int quality,int range> +void Blip_Wave<quality,range>::amplitude( int amp ) { + int delta = amp - last_amp; + last_amp = amp; + synth.offset_inline( time_, delta ); +} + +template<int quality,int range> +inline void Blip_Synth<quality,range>::offset_resampled( blip_resampled_time_t time, + int delta, Blip_Buffer* blip_buf ) const +{ + typedef blip_pair_t_ pair_t; + + unsigned sample_index = (time >> BLIP_BUFFER_ACCURACY) & ~1; + assert(( (void)"Blip_Synth/Blip_wave: Went past end of buffer", + sample_index < blip_buf->buffer_size_ )); + enum { const_offset = Blip_Buffer::widest_impulse_ / 2 - width / 2 }; + pair_t* buf = (pair_t*) &blip_buf->buffer_ [const_offset + sample_index]; + + enum { shift = BLIP_BUFFER_ACCURACY - blip_res_bits_ }; + enum { mask = res * 2 - 1 }; + const pair_t* imp = &impulses [((time >> shift) & mask) * impulse_size]; + + pair_t offset = impulse.offset * delta; + + if ( !fine_bits ) + { + // normal mode + for ( int n = width / 4; n; --n ) + { + pair_t t0 = buf [0] - offset; + pair_t t1 = buf [1] - offset; + + t0 += imp [0] * delta; + t1 += imp [1] * delta; + imp += 2; + + buf [0] = t0; + buf [1] = t1; + buf += 2; + } + } + else + { + // fine mode + enum { sub_range = 1 << fine_bits }; + delta += sub_range / 2; + int delta2 = (delta & (sub_range - 1)) - sub_range / 2; + delta >>= fine_bits; + + for ( int n = width / 4; n; --n ) + { + pair_t t0 = buf [0] - offset; + pair_t t1 = buf [1] - offset; + + t0 += imp [0] * delta2; + t0 += imp [1] * delta; + + t1 += imp [2] * delta2; + t1 += imp [3] * delta; + + imp += 4; + + buf [0] = t0; + buf [1] = t1; + buf += 2; + } + } +} + +template<int quality,int range> +void Blip_Synth<quality,range>::offset( blip_time_t time, int delta, Blip_Buffer* buf ) const { + offset_resampled( time * buf->factor_ + buf->offset_, delta, buf ); +} + +#endif + diff --git a/gearboy/src/audio/Effects_Buffer.cpp b/gearboy/src/audio/Effects_Buffer.cpp new file mode 100644 index 00000000..2a6f96b9 --- /dev/null +++ b/gearboy/src/audio/Effects_Buffer.cpp @@ -0,0 +1,638 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Effects_Buffer.h" + +#include <string.h> + +/* Copyright (C) 2006-2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +int const fixed_shift = 12; +#define TO_FIXED( f ) fixed_t ((f) * ((fixed_t) 1 << fixed_shift)) +#define FROM_FIXED( f ) ((f) >> fixed_shift) + +int const max_read = 2560; // determines minimum delay + +Effects_Buffer::Effects_Buffer( int max_bufs, long echo_size_ ) : Multi_Buffer( stereo ) +{ + echo_size = (int)max( max_read * (long) stereo, echo_size_ & ~1 ); + clock_rate_ = 0; + bass_freq_ = 90; + bufs = 0; + bufs_size = 0; + bufs_max = max( max_bufs, (int) extra_chans ); + no_echo = true; + no_effects = true; + + // defaults + config_.enabled = false; + config_.delay [0] = 120; + config_.delay [1] = 122; + config_.feedback = 0.2f; + config_.treble = 0.4f; + + static float const sep = 0.8f; + config_.side_chans [0].pan = -sep; + config_.side_chans [1].pan = +sep; + config_.side_chans [0].vol = 1.0f; + config_.side_chans [1].vol = 1.0f; + + memset( &s, 0, sizeof s ); + clear(); +} + +Effects_Buffer::~Effects_Buffer() +{ + delete_bufs(); +} + +// avoid using new [] +blargg_err_t Effects_Buffer::new_bufs( int size ) +{ + bufs = (buf_t*) malloc( size * sizeof *bufs ); + CHECK_ALLOC( bufs ); + for ( int i = 0; i < size; i++ ) + new (bufs + i) buf_t; + bufs_size = size; + return 0; +} + +void Effects_Buffer::delete_bufs() +{ + if ( bufs ) + { + for ( int i = bufs_size; --i >= 0; ) + bufs [i].~buf_t(); + free( bufs ); + bufs = 0; + } + bufs_size = 0; +} + +blargg_err_t Effects_Buffer::set_sample_rate( long rate, int msec ) +{ + // extra to allow farther past-the-end pointers + mixer.samples_read = 0; + RETURN_ERR( echo.resize( echo_size + stereo ) ); + return Multi_Buffer::set_sample_rate( rate, msec ); +} + +void Effects_Buffer::clock_rate( long rate ) +{ + clock_rate_ = rate; + for ( int i = bufs_size; --i >= 0; ) + bufs [i].clock_rate( clock_rate_ ); +} + +void Effects_Buffer::bass_freq( int freq ) +{ + bass_freq_ = freq; + for ( int i = bufs_size; --i >= 0; ) + bufs [i].bass_freq( bass_freq_ ); +} + +blargg_err_t Effects_Buffer::set_channel_count( int count, int const* types ) +{ + RETURN_ERR( Multi_Buffer::set_channel_count( count, types ) ); + + delete_bufs(); + + mixer.samples_read = 0; + + RETURN_ERR( chans.resize( count + extra_chans ) ); + + RETURN_ERR( new_bufs( min( bufs_max, count + extra_chans ) ) ); + + for ( int i = bufs_size; --i >= 0; ) + RETURN_ERR( bufs [i].set_sample_rate( sample_rate(), length() ) ); + + for ( int i = (int)chans.size(); --i >= 0; ) + { + chan_t& ch = chans [i]; + ch.cfg.vol = 1.0f; + ch.cfg.pan = 0.0f; + ch.cfg.surround = false; + ch.cfg.echo = false; + } + // side channels with echo + chans [2].cfg.echo = true; + chans [3].cfg.echo = true; + + clock_rate( clock_rate_ ); + bass_freq( bass_freq_ ); + apply_config(); + clear(); + + return 0; +} + +void Effects_Buffer::clear_echo() +{ + if ( echo.size() ) + memset( echo.begin(), 0, echo.size() * sizeof echo [0] ); +} + +void Effects_Buffer::clear() +{ + echo_pos = 0; + s.low_pass [0] = 0; + s.low_pass [1] = 0; + mixer.samples_read = 0; + + for ( int i = bufs_size; --i >= 0; ) + bufs [i].clear(); + clear_echo(); +} + +Effects_Buffer::channel_t Effects_Buffer::channel( int i ) +{ + i += extra_chans; + require( extra_chans <= i && i < (int) chans.size() ); + return chans [i].channel; +} + + +// Configuration + +// 3 wave positions with/without surround, 2 multi (one with same config as wave) +int const simple_bufs = 3 * 2 + 2 - 1; + +Simple_Effects_Buffer::Simple_Effects_Buffer() : + Effects_Buffer( extra_chans + simple_bufs, 18 * 1024L ) +{ + config_.echo = 0.20f; + config_.stereo = 0.20f; + config_.surround = true; + config_.enabled = false; +} + +void Simple_Effects_Buffer::apply_config() +{ + Effects_Buffer::config_t& c = Effects_Buffer::config(); + + c.enabled = config_.enabled; + if ( c.enabled ) + { + c.delay [0] = 120; + c.delay [1] = 122; + c.feedback = config_.echo * 0.7f; + c.treble = 0.6f - 0.3f * config_.echo; + + float sep = config_.stereo + 0.80f; + if ( sep > 1.0f ) + sep = 1.0f; + + c.side_chans [0].pan = -sep; + c.side_chans [1].pan = +sep; + + for ( int i = channel_count(); --i >= 0; ) + { + chan_config_t& ch = Effects_Buffer::chan_config( i ); + + ch.pan = 0.0f; + ch.surround = config_.surround; + ch.echo = false; + + int const type = (channel_types() ? channel_types() [i] : 0); + if ( !(type & noise_type) ) + { + int index = (type & type_index_mask) % 6 - 3; + if ( index < 0 ) + { + index += 3; + ch.surround = false; + ch.echo = true; + } + if ( index >= 1 ) + { + ch.pan = config_.stereo; + if ( index == 1 ) + ch.pan = -ch.pan; + } + } + else if ( type & 1 ) + { + ch.surround = false; + } + } + } + + Effects_Buffer::apply_config(); +} + +int Effects_Buffer::min_delay() const +{ + require( sample_rate() ); + return max_read * 1000L / sample_rate(); +} + +int Effects_Buffer::max_delay() const +{ + require( sample_rate() ); + return (echo_size / stereo - max_read) * 1000L / sample_rate(); +} + +void Effects_Buffer::apply_config() +{ + int i; + + if ( !bufs_size ) + return; + + s.treble = TO_FIXED( config_.treble ); + + bool echo_dirty = false; + + fixed_t old_feedback = s.feedback; + s.feedback = TO_FIXED( config_.feedback ); + if ( !old_feedback && s.feedback ) + echo_dirty = true; + + // delays + for ( i = stereo; --i >= 0; ) + { + long delay = config_.delay [i] * sample_rate() / 1000 * stereo; + delay = max( delay, long (max_read * stereo) ); + delay = min( delay, long (echo_size - max_read * stereo) ); + if ( s.delay [i] != delay ) + { + s.delay [i] = delay; + echo_dirty = true; + } + } + + // side channels + for ( i = 2; --i >= 0; ) + { + chans [i+2].cfg.vol = chans [i].cfg.vol = config_.side_chans [i].vol * 0.5f; + chans [i+2].cfg.pan = chans [i].cfg.pan = config_.side_chans [i].pan; + } + + // convert volumes + for ( i = (int)chans.size(); --i >= 0; ) + { + chan_t& ch = chans [i]; + ch.vol [0] = TO_FIXED( ch.cfg.vol - ch.cfg.vol * ch.cfg.pan ); + ch.vol [1] = TO_FIXED( ch.cfg.vol + ch.cfg.vol * ch.cfg.pan ); + if ( ch.cfg.surround ) + ch.vol [0] = -ch.vol [0]; + } + + assign_buffers(); + + // set side channels + for ( i = (int)chans.size(); --i >= 0; ) + { + chan_t& ch = chans [i]; + ch.channel.left = chans [ch.cfg.echo*2 ].channel.center; + ch.channel.right = chans [ch.cfg.echo*2+1].channel.center; + } + + bool old_echo = !no_echo && !no_effects; + + // determine whether effects and echo are needed at all + no_effects = true; + no_echo = true; + for ( i = (int)chans.size(); --i >= extra_chans; ) + { + chan_t& ch = chans [i]; + if ( ch.cfg.echo && s.feedback ) + no_echo = false; + + if ( ch.vol [0] != TO_FIXED( 1 ) || ch.vol [1] != TO_FIXED( 1 ) ) + no_effects = false; + } + if ( !no_echo ) + no_effects = false; + + if ( chans [0].vol [0] != TO_FIXED( 1 ) || + chans [0].vol [1] != TO_FIXED( 0 ) || + chans [1].vol [0] != TO_FIXED( 0 ) || + chans [1].vol [1] != TO_FIXED( 1 ) ) + no_effects = false; + + if ( !config_.enabled ) + no_effects = true; + + if ( no_effects ) + { + for ( i = (int)chans.size(); --i >= 0; ) + { + chan_t& ch = chans [i]; + ch.channel.center = &bufs [2]; + ch.channel.left = &bufs [0]; + ch.channel.right = &bufs [1]; + } + } + + mixer.bufs [0] = &bufs [0]; + mixer.bufs [1] = &bufs [1]; + mixer.bufs [2] = &bufs [2]; + + if ( echo_dirty || (!old_echo && (!no_echo && !no_effects)) ) + clear_echo(); + + channels_changed(); +} + +void Effects_Buffer::assign_buffers() +{ + // assign channels to buffers + int buf_count = 0; + for ( int i = 0; i < (int) chans.size(); i++ ) + { + // put second two side channels at end to give priority to main channels + // in case closest matching is necessary + int x = i; + if ( i > 1 ) + x += 2; + if ( x >= (int) chans.size() ) + x -= (chans.size() - 2); + chan_t& ch = chans [x]; + + int b = 0; + for ( ; b < buf_count; b++ ) + { + if ( ch.vol [0] == bufs [b].vol [0] && + ch.vol [1] == bufs [b].vol [1] && + (ch.cfg.echo == bufs [b].echo || !s.feedback) ) + break; + } + + if ( b >= buf_count ) + { + if ( buf_count < bufs_max ) + { + bufs [b].vol [0] = ch.vol [0]; + bufs [b].vol [1] = ch.vol [1]; + bufs [b].echo = ch.cfg.echo; + buf_count++; + } + else + { + // TODO: this is a mess, needs refinement + dprintf( "Effects_Buffer ran out of buffers; using closest match\n" ); + b = 0; + fixed_t best_dist = TO_FIXED( 8 ); + for ( int h = buf_count; --h >= 0; ) + { + #define CALC_LEVELS( vols, sum, diff, surround ) \ + fixed_t sum, diff;\ + bool surround = false;\ + {\ + fixed_t vol_0 = vols [0];\ + if ( vol_0 < 0 ) vol_0 = -vol_0, surround = true;\ + fixed_t vol_1 = vols [1];\ + if ( vol_1 < 0 ) vol_1 = -vol_1, surround = true;\ + sum = vol_0 + vol_1;\ + diff = vol_0 - vol_1;\ + } + CALC_LEVELS( ch.vol, ch_sum, ch_diff, ch_surround ); + CALC_LEVELS( bufs [h].vol, buf_sum, buf_diff, buf_surround ); + + fixed_t dist = abs( ch_sum - buf_sum ) + abs( ch_diff - buf_diff ); + + if ( ch_surround != buf_surround ) + dist += TO_FIXED( 1 ) / 2; + + if ( s.feedback && ch.cfg.echo != bufs [h].echo ) + dist += TO_FIXED( 1 ) / 2; + + if ( best_dist > dist ) + { + best_dist = dist; + b = h; + } + } + } + } + + //dprintf( "ch %d->buf %d\n", x, b ); + ch.channel.center = &bufs [b]; + } +} + + +// Mixing + +void Effects_Buffer::end_frame( blip_time_t time ) +{ + for ( int i = bufs_size; --i >= 0; ) + bufs [i].end_frame( time ); +} + +long Effects_Buffer::read_samples( blip_sample_t* out, long out_size ) +{ + out_size = min( out_size, samples_avail() ); + + int pair_count = int (out_size >> 1); + require( pair_count * stereo == out_size ); // must read an even number of samples + if ( pair_count ) + { + if ( no_effects ) + { + mixer.read_pairs( out, pair_count ); + } + else + { + int pairs_remain = pair_count; + do + { + // mix at most max_read pairs at a time + int count = max_read; + if ( count > pairs_remain ) + count = pairs_remain; + + if ( no_echo ) + { + // optimization: clear echo here to keep mix_effects() a leaf function + echo_pos = 0; + memset( echo.begin(), 0, count * stereo * sizeof echo [0] ); + } + mix_effects( out, count ); + + blargg_long new_echo_pos = echo_pos + count * stereo; + if ( new_echo_pos >= echo_size ) + new_echo_pos -= echo_size; + echo_pos = new_echo_pos; + assert( echo_pos < echo_size ); + + out += count * stereo; + mixer.samples_read += count; + pairs_remain -= count; + } + while ( pairs_remain ); + } + + if ( samples_avail() <= 0 || immediate_removal() ) + { + for ( int i = bufs_size; --i >= 0; ) + { + buf_t& b = bufs [i]; + // TODO: might miss non-silence settling since it checks END of last read + if ( b.non_silent() ) + b.remove_samples( mixer.samples_read ); + else + b.remove_silence( mixer.samples_read ); + } + mixer.samples_read = 0; + } + } + return out_size; +} + +void Effects_Buffer::mix_effects( blip_sample_t* out_, int pair_count ) +{ + typedef fixed_t stereo_fixed_t [stereo]; + + // add channels with echo, do echo, add channels without echo, then convert to 16-bit and output + int echo_phase = 1; + do + { + // mix any modified buffers + { + buf_t* buf = bufs; + int bufs_remain = bufs_size; + do + { + if ( buf->non_silent() && ( buf->echo == !!echo_phase ) ) + { + stereo_fixed_t* BLIP_RESTRICT out = (stereo_fixed_t*) &echo [echo_pos]; + int const bass = BLIP_READER_BASS( *buf ); + BLIP_READER_BEGIN( in, *buf ); + BLIP_READER_ADJ_( in, mixer.samples_read ); + fixed_t const vol_0 = buf->vol [0]; + fixed_t const vol_1 = buf->vol [1]; + + int count = unsigned (echo_size - echo_pos) / stereo; + int remain = pair_count; + if ( count > remain ) + count = remain; + do + { + remain -= count; + BLIP_READER_ADJ_( in, count ); + + out += count; + int offset = -count; + do + { + fixed_t s = BLIP_READER_READ( in ); + BLIP_READER_NEXT_IDX_( in, bass, offset ); + + out [offset] [0] += s * vol_0; + out [offset] [1] += s * vol_1; + } + while ( ++offset ); + + out = (stereo_fixed_t*) echo.begin(); + count = remain; + } + while ( remain ); + + BLIP_READER_END( in, *buf ); + } + buf++; + } + while ( --bufs_remain ); + } + + // add echo + if ( echo_phase && !no_echo ) + { + fixed_t const feedback = s.feedback; + fixed_t const treble = s.treble; + + int i = 1; + do + { + fixed_t low_pass = s.low_pass [i]; + + fixed_t* echo_end = &echo [echo_size + i]; + fixed_t const* BLIP_RESTRICT in_pos = &echo [echo_pos + i]; + blargg_long out_offset = (int)(echo_pos + i + s.delay [i]); + if ( out_offset >= echo_size ) + out_offset -= echo_size; + assert( out_offset < echo_size ); + fixed_t* BLIP_RESTRICT out_pos = &echo [out_offset]; + + // break into up to three chunks to avoid having to handle wrap-around + // in middle of core loop + int remain = pair_count; + do + { + fixed_t const* pos = in_pos; + if ( pos < out_pos ) + pos = out_pos; + int count = blargg_ulong ((char*) echo_end - (char const*) pos) / + unsigned (stereo * sizeof (fixed_t)); + if ( count > remain ) + count = remain; + remain -= count; + + in_pos += count * stereo; + out_pos += count * stereo; + int offset = -count; + do + { + low_pass += FROM_FIXED( in_pos [offset * stereo] - low_pass ) * treble; + out_pos [offset * stereo] = FROM_FIXED( low_pass ) * feedback; + } + while ( ++offset ); + + if ( in_pos >= echo_end ) in_pos -= echo_size; + if ( out_pos >= echo_end ) out_pos -= echo_size; + } + while ( remain ); + + s.low_pass [i] = low_pass; + } + while ( --i >= 0 ); + } + } + while ( --echo_phase >= 0 ); + + // clamp to 16 bits + { + stereo_fixed_t const* BLIP_RESTRICT in = (stereo_fixed_t*) &echo [echo_pos]; + typedef blip_sample_t stereo_blip_sample_t [stereo]; + stereo_blip_sample_t* BLIP_RESTRICT out = (stereo_blip_sample_t*) out_; + int count = unsigned (echo_size - echo_pos) / (unsigned) stereo; + int remain = pair_count; + if ( count > remain ) + count = remain; + do + { + remain -= count; + in += count; + out += count; + int offset = -count; + do + { + fixed_t in_0 = FROM_FIXED( in [offset] [0] ); + fixed_t in_1 = FROM_FIXED( in [offset] [1] ); + + BLIP_CLAMP( in_0, in_0 ); + out [offset] [0] = (blip_sample_t) in_0; + + BLIP_CLAMP( in_1, in_1 ); + out [offset] [1] = (blip_sample_t) in_1; + } + while ( ++offset ); + + in = (stereo_fixed_t*) echo.begin(); + count = remain; + } + while ( remain ); + } +} diff --git a/gearboy/src/audio/Effects_Buffer.h b/gearboy/src/audio/Effects_Buffer.h new file mode 100644 index 00000000..790325d9 --- /dev/null +++ b/gearboy/src/audio/Effects_Buffer.h @@ -0,0 +1,143 @@ +// Multi-channel effects buffer with echo and individual panning for each channel + +// Game_Music_Emu $vers +#ifndef EFFECTS_BUFFER_H +#define EFFECTS_BUFFER_H + +#include "Multi_Buffer.h" + +// See Simple_Effects_Buffer (below) for a simpler interface + +class Effects_Buffer : public Multi_Buffer { +public: + // To reduce memory usage, fewer buffers can be used (with a best-fit + // approach if there are too few), and maximum echo delay can be reduced + Effects_Buffer( int max_bufs = 32, long echo_size = 24 * 1024L ); + + struct pan_vol_t + { + float vol; // 0.0 = silent, 0.5 = half volume, 1.0 = normal + float pan; // -1.0 = left, 0.0 = center, +1.0 = right + }; + + // Global configuration + struct config_t + { + bool enabled; // false = disable all effects + + // Current sound is echoed at adjustable left/right delay, + // with reduced treble and volume (feedback). + float treble; // 1.0 = full treble, 0.1 = very little, 0.0 = silent + int delay [2]; // left, right delays (msec) + float feedback; // 0.0 = no echo, 0.5 = each echo half previous, 1.0 = cacophony + pan_vol_t side_chans [2]; // left and right side channel volume and pan + }; + config_t& config() { return config_; } + + // Limits of delay (msec) + int min_delay() const; + int max_delay() const; + + // Per-channel configuration. Two or more channels with matching parameters are + // optimized to internally use the same buffer. + struct chan_config_t : pan_vol_t + { + // (inherited from pan_vol_t) + //float vol; // these only affect center channel + //float pan; + bool surround; // if true, negates left volume to put sound in back + bool echo; // false = channel doesn't have any echo + }; + chan_config_t& chan_config( int i ) { return chans [i + extra_chans].cfg; } + + // Apply any changes made to config() and chan_config() + virtual void apply_config(); + +public: + ~Effects_Buffer(); + blargg_err_t set_sample_rate( long samples_per_sec, int msec = blip_default_length ); + blargg_err_t set_channel_count( int, int const* = 0 ); + void clock_rate( long ); + void bass_freq( int ); + void clear(); + channel_t channel( int ); + void end_frame( blip_time_t ); + long read_samples( blip_sample_t*, long ); + long samples_avail() const { return (bufs [0].samples_avail() - mixer.samples_read) * 2; } + enum { stereo = 2 }; + typedef blargg_long fixed_t; +protected: + enum { extra_chans = stereo * stereo }; +private: + config_t config_; + long clock_rate_; + int bass_freq_; + + blargg_long echo_size; + + struct chan_t + { + fixed_t vol [stereo]; + chan_config_t cfg; + channel_t channel; + }; + blargg_vector<chan_t> chans; + + struct buf_t : Tracked_Blip_Buffer + { + fixed_t vol [stereo]; + bool echo; + + void* operator new ( size_t, void* p ) { return p; } + void operator delete ( void* ) { } + + ~buf_t() { } + }; + buf_t* bufs; + int bufs_size; + int bufs_max; // bufs_size <= bufs_max, to limit memory usage + Stereo_Mixer mixer; + + struct { + long delay [stereo]; + fixed_t treble; + fixed_t feedback; + fixed_t low_pass [stereo]; + } s; + + blargg_vector<fixed_t> echo; + blargg_long echo_pos; + + bool no_effects; + bool no_echo; + + void assign_buffers(); + void clear_echo(); + void mix_effects( blip_sample_t* out, int pair_count ); + blargg_err_t new_bufs( int size ); + void delete_bufs(); +}; + +// Simpler interface and lower memory usage +class Simple_Effects_Buffer : public Effects_Buffer { +public: + struct config_t + { + bool enabled; // false = disable all effects + float echo; // 0.0 = none, 1.0 = lots + float stereo; // 0.0 = channels in center, 1.0 = channels on left/right + bool surround; // true = put some channels in back + }; + config_t& config() { return config_; } + + // Apply any changes made to config() + void apply_config(); + +public: + Simple_Effects_Buffer(); +private: + config_t config_; + void chan_config(); // hide +}; + +#endif diff --git a/gearboy/src/audio/Gb_Apu.cpp b/gearboy/src/audio/Gb_Apu.cpp new file mode 100644 index 00000000..77c004d7 --- /dev/null +++ b/gearboy/src/audio/Gb_Apu.cpp @@ -0,0 +1,395 @@ +// Gb_Snd_Emu 0.2.0. http://www.slack.net/~ant/ + +#include "Gb_Apu.h" + +/* Copyright (C) 2003-2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +unsigned const vol_reg = 0xFF24; +unsigned const stereo_reg = 0xFF25; +unsigned const status_reg = 0xFF26; +unsigned const wave_ram = 0xFF30; + +int const power_mask = 0x80; + +void Gb_Apu::treble_eq( blip_eq_t const& eq ) +{ + good_synth.treble_eq( eq ); + med_synth .treble_eq( eq ); +} + +inline int Gb_Apu::calc_output( int osc ) const +{ + int bits = regs [stereo_reg - start_addr] >> osc; + return (bits >> 3 & 2) | (bits & 1); +} + +void Gb_Apu::set_output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right, int osc ) +{ + // Must be silent (all NULL), mono (left and right NULL), or stereo (none NULL) + require( !center || (center && !left && !right) || (center && left && right) ); + require( (unsigned) osc <= osc_count ); // fails if you pass invalid osc index + + if ( !center || !left || !right ) + { + left = center; + right = center; + } + + int i = (unsigned) osc % osc_count; + do + { + Gb_Osc& o = *oscs [i]; + o.outputs [1] = right; + o.outputs [2] = left; + o.outputs [3] = center; + o.output = o.outputs [calc_output( i )]; + } + while ( ++i < osc ); +} + +void Gb_Apu::synth_volume( int iv ) +{ + double v = volume_ * 0.60 / osc_count / 15 /*steps*/ / 8 /*master vol range*/ * iv; + good_synth.volume( v ); + med_synth .volume( v ); +} + +void Gb_Apu::apply_volume() +{ + // TODO: Doesn't handle differing left and right volumes (panning). + // Not worth the complexity. + int data = regs [vol_reg - start_addr]; + int left = data >> 4 & 7; + int right = data & 7; + //if ( data & 0x88 ) dprintf( "Vin: %02X\n", data & 0x88 ); + //if ( left != right ) dprintf( "l: %d r: %d\n", left, right ); + synth_volume( max( left, right ) + 1 ); +} + +void Gb_Apu::volume( double v ) +{ + if ( volume_ != v ) + { + volume_ = v; + apply_volume(); + } +} + +void Gb_Apu::reset_regs() +{ + for ( int i = 0; i < 0x20; i++ ) + regs [i] = 0; + + square1.reset(); + square2.reset(); + wave .reset(); + noise .reset(); + + apply_volume(); +} + +void Gb_Apu::reset_lengths() +{ + square1.length_ctr = 64; + square2.length_ctr = 64; + wave .length_ctr = 256; + noise .length_ctr = 64; +} + +void Gb_Apu::reduce_clicks( bool reduce ) +{ + reduce_clicks_ = reduce; + + // Click reduction makes DAC off generate same output as volume 0 + int dac_off_amp = 0; + if ( reduce && wave.mode != mode_agb ) // AGB already eliminates clicks + dac_off_amp = -Gb_Osc::dac_bias; + + for ( int i = 0; i < osc_count; i++ ) + oscs [i]->dac_off_amp = dac_off_amp; + + // AGB always eliminates clicks on wave channel using same method + if ( wave.mode == mode_agb ) + wave.dac_off_amp = -Gb_Osc::dac_bias; +} + +void Gb_Apu::reset( mode_t mode, bool agb_wave ) +{ + // Hardware mode + if ( agb_wave ) + mode = mode_agb; // using AGB wave features implies AGB hardware + wave.agb_mask = agb_wave ? 0xFF : 0; + for ( int i = 0; i < osc_count; i++ ) + oscs [i]->mode = mode; + reduce_clicks( reduce_clicks_ ); + + // Reset state + frame_time = 0; + last_time = 0; + frame_phase = 0; + + reset_regs(); + reset_lengths(); + + // Load initial wave RAM + static byte const initial_wave [2] [16] = { + {0x84,0x40,0x43,0xAA,0x2D,0x78,0x92,0x3C,0x60,0x59,0x59,0xB0,0x34,0xB8,0x2E,0xDA}, + {0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF}, + }; + for ( int b = 2; --b >= 0; ) + { + // Init both banks (does nothing if not in AGB mode) + // TODO: verify that this works + write_register( 0, 0xFF1A, b * 0x40 ); + for ( unsigned i = 0; i < sizeof initial_wave [0]; i++ ) + write_register( 0, i + wave_ram, initial_wave [(mode != mode_dmg)] [i] ); + } +} + +void Gb_Apu::set_tempo( double t ) +{ + frame_period = 4194304 / 512; // 512 Hz + if ( t != 1.0 ) + frame_period = blip_time_t (frame_period / t); +} + +Gb_Apu::Gb_Apu() +{ + wave.wave_ram = ®s [wave_ram - start_addr]; + + oscs [0] = &square1; + oscs [1] = &square2; + oscs [2] = &wave; + oscs [3] = &noise; + + for ( int i = osc_count; --i >= 0; ) + { + Gb_Osc& o = *oscs [i]; + o.regs = ®s [i * 5]; + o.output = 0; + o.outputs [0] = 0; + o.outputs [1] = 0; + o.outputs [2] = 0; + o.outputs [3] = 0; + o.good_synth = &good_synth; + o.med_synth = &med_synth; + } + + reduce_clicks_ = false; + set_tempo( 1.0 ); + volume_ = 1.0; + reset(); +} + +void Gb_Apu::run_until_( blip_time_t end_time ) +{ + while ( true ) + { + // run oscillators + blip_time_t time = end_time; + if ( time > frame_time ) + time = frame_time; + + square1.run( last_time, time ); + square2.run( last_time, time ); + wave .run( last_time, time ); + noise .run( last_time, time ); + last_time = time; + + if ( time == end_time ) + break; + + // run frame sequencer + frame_time += frame_period * Gb_Osc::clk_mul; + switch ( frame_phase++ ) + { + case 2: + case 6: + // 128 Hz + square1.clock_sweep(); + /* fall through */ + case 0: + case 4: + // 256 Hz + square1.clock_length(); + square2.clock_length(); + wave .clock_length(); + noise .clock_length(); + break; + + case 7: + // 64 Hz + frame_phase = 0; + square1.clock_envelope(); + square2.clock_envelope(); + noise .clock_envelope(); + } + } +} + +inline void Gb_Apu::run_until( blip_time_t time ) +{ + //require( time >= last_time ); // end_time must not be before previous time + if ( time > last_time ) + run_until_( time ); +} + +void Gb_Apu::end_frame( blip_time_t end_time ) +{ + if ( end_time > last_time ) + run_until( end_time ); + + frame_time -= end_time; + assert( frame_time >= 0 ); + + last_time -= end_time; + assert( last_time >= 0 ); +} + +void Gb_Apu::silence_osc( Gb_Osc& o ) +{ + int delta = -o.last_amp; + if ( delta ) + { + o.last_amp = 0; + if ( o.output ) + { + o.output->set_modified(); + med_synth.offset( last_time, delta, o.output ); + } + } +} + +void Gb_Apu::apply_stereo() +{ + for ( int i = osc_count; --i >= 0; ) + { + Gb_Osc& o = *oscs [i]; + Blip_Buffer* out = o.outputs [calc_output( i )]; + if ( o.output != out ) + { + silence_osc( o ); + o.output = out; + } + } +} + +void Gb_Apu::write_register( blip_time_t time, unsigned addr, int data ) +{ + require( (unsigned) data < 0x100 ); + + int reg = addr - start_addr; + if ( (unsigned) reg >= register_count ) + { + require( false ); + return; + } + + if ( addr < status_reg && !(regs [status_reg - start_addr] & power_mask) ) + { + // Power is off + + // length counters can only be written in DMG mode + if ( wave.mode != mode_dmg || (reg != 1 && reg != 5+1 && reg != 10+1 && reg != 15+1) ) + return; + + if ( reg < 10 ) + data &= 0x3F; // clear square duty + } + + run_until( time ); + + if ( addr >= wave_ram ) + { + wave.write( addr, data ); + } + else + { + int old_data = regs [reg]; + regs [reg] = data; + + if ( addr < vol_reg ) + { + // Oscillator + write_osc( reg / 5, reg, old_data, data ); + } + else if ( addr == vol_reg && data != old_data ) + { + // Master volume + for ( int i = osc_count; --i >= 0; ) + silence_osc( *oscs [i] ); + + apply_volume(); + } + else if ( addr == stereo_reg ) + { + // Stereo panning + apply_stereo(); + } + else if ( addr == status_reg && (data ^ old_data) & power_mask ) + { + // Power control + frame_phase = 0; + for ( int i = osc_count; --i >= 0; ) + silence_osc( *oscs [i] ); + + reset_regs(); + if ( wave.mode != mode_dmg ) + reset_lengths(); + + regs [status_reg - start_addr] = data; + } + } +} + +int Gb_Apu::read_register( blip_time_t time, unsigned addr ) +{ + run_until( time ); + + int reg = addr - start_addr; + if ( (unsigned) reg >= register_count ) + { + require( false ); + return 0; + } + + if ( addr >= wave_ram ) + return wave.read( addr ); + + // Value read back has some bits always set + static byte const masks [] = { + 0x80,0x3F,0x00,0xFF,0xBF, + 0xFF,0x3F,0x00,0xFF,0xBF, + 0x7F,0xFF,0x9F,0xFF,0xBF, + 0xFF,0xFF,0x00,0x00,0xBF, + 0x00,0x00,0x70, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF + }; + int mask = masks [reg]; + if ( wave.agb_mask && (reg == 10 || reg == 12) ) + mask = 0x1F; // extra implemented bits in wave regs on AGB + int data = regs [reg] | mask; + + // Status register + if ( addr == status_reg ) + { + data &= 0xF0; + data |= (int) square1.enabled << 0; + data |= (int) square2.enabled << 1; + data |= (int) wave .enabled << 2; + data |= (int) noise .enabled << 3; + } + + return data; +} diff --git a/gearboy/src/audio/Gb_Apu.h b/gearboy/src/audio/Gb_Apu.h new file mode 100644 index 00000000..3f132096 --- /dev/null +++ b/gearboy/src/audio/Gb_Apu.h @@ -0,0 +1,182 @@ +// Nintendo Game Boy sound hardware emulator with save state support + +// Gb_Snd_Emu 0.2.0 +#ifndef GB_APU_H +#define GB_APU_H + +#include "Gb_Oscs.h" + +struct gb_apu_state_t; + +class Gb_Apu { +public: +// Basics + + // Clock rate that sound hardware runs at. + enum { clock_rate = 4194304 * GB_APU_OVERCLOCK }; + + // Sets buffer(s) to generate sound into. If left and right are NULL, output is mono. + // If all are NULL, no output is generated but other emulation still runs. + // If chan is specified, only that channel's output is changed, otherwise all are. + enum { osc_count = 4 }; // 0: Square 1, 1: Square 2, 2: Wave, 3: Noise + void set_output( Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL, + int chan = osc_count ); + + // Resets hardware to initial power on state BEFORE boot ROM runs. Mode selects + // sound hardware. Additional AGB wave features are enabled separately. + enum mode_t { + mode_dmg, // Game Boy monochrome + mode_cgb, // Game Boy Color + mode_agb // Game Boy Advance + }; + void reset( mode_t mode = mode_cgb, bool agb_wave = false ); + + // Reads and writes must be within the start_addr to end_addr range, inclusive. + // Addresses outside this range are not mapped to the sound hardware. + enum { start_addr = 0xFF10 }; + enum { end_addr = 0xFF3F }; + enum { register_count = end_addr - start_addr + 1 }; + + // Times are specified as the number of clocks since the beginning of the + // current time frame. + + // Emulates CPU write of data to addr at specified time. + void write_register( blip_time_t time, unsigned addr, int data ); + + // Emulates CPU read from addr at specified time. + int read_register( blip_time_t time, unsigned addr ); + + // Emulates sound hardware up to specified time, ends current time frame, then + // starts a new frame at time 0. + void end_frame( blip_time_t frame_length ); + +// Sound adjustments + + // Sets overall volume, where 1.0 is normal. + void volume( double ); + + // If true, reduces clicking by disabling DAC biasing. Note that this reduces + // emulation accuracy, since the clicks are authentic. + void reduce_clicks( bool reduce = true ); + + // Sets treble equalization. + void treble_eq( blip_eq_t const& ); + + // Treble and bass values for various hardware. + enum { + speaker_treble = -47, // speaker on system + speaker_bass = 2000, + dmg_treble = 0, // headphones on each system + dmg_bass = 30, + cgb_treble = 0, + cgb_bass = 300, // CGB has much less bass + agb_treble = 0, + agb_bass = 30 + }; + + // Sets frame sequencer rate, where 1.0 is normal. Meant for adjusting the + // tempo in a game music player. + void set_tempo( double ); + +// Save states + + // Saves full emulation state to state_out. Data format is portable and + // includes some extra space to avoid expansion in case more state needs + // to be stored in the future. + void save_state( gb_apu_state_t* state_out ); + + // Loads state. You should call reset() BEFORE this. + blargg_err_t load_state( gb_apu_state_t const& in ); + +public: + Gb_Apu(); + + // Use set_output() in place of these + BLARGG_DEPRECATED void output ( Blip_Buffer* c ) { set_output( c, c, c ); } + BLARGG_DEPRECATED void output ( Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) { set_output( c, l, r ); } + BLARGG_DEPRECATED void osc_output( int i, Blip_Buffer* c ) { set_output( c, c, c, i ); } + BLARGG_DEPRECATED void osc_output( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) { set_output( c, l, r, i ); } + +private: + // noncopyable + Gb_Apu( const Gb_Apu& ); + Gb_Apu& operator = ( const Gb_Apu& ); + + Gb_Osc* oscs [osc_count]; + blip_time_t last_time; // time sound emulator has been run to + blip_time_t frame_period; // clocks between each frame sequencer step + double volume_; + bool reduce_clicks_; + + Gb_Sweep_Square square1; + Gb_Square square2; + Gb_Wave wave; + Gb_Noise noise; + blip_time_t frame_time; // time of next frame sequencer action + int frame_phase; // phase of next frame sequencer step + enum { regs_size = register_count + 0x10 }; + BOOST::uint8_t regs [regs_size];// last values written to registers + + // large objects after everything else + Gb_Osc::Good_Synth good_synth; + Gb_Osc::Med_Synth med_synth; + + void reset_lengths(); + void reset_regs(); + int calc_output( int osc ) const; + void apply_stereo(); + void apply_volume(); + void synth_volume( int ); + void run_until_( blip_time_t ); + void run_until( blip_time_t ); + void silence_osc( Gb_Osc& ); + void write_osc( int index, int reg, int old_data, int data ); + const char* save_load( gb_apu_state_t*, bool save ); + void save_load2( gb_apu_state_t*, bool save ); + friend class Gb_Apu_Tester; +}; + +// Format of save state. Should be stable across versions of the library, +// with earlier versions properly opening later save states. Includes some +// room for expansion so the state size shouldn't increase. +struct gb_apu_state_t +{ +#if GB_APU_CUSTOM_STATE + // Values stored as plain int so your code can read/write them easily. + // Structure can NOT be written to disk, since format is not portable. + typedef int val_t; +#else + // Values written in portable little-endian format, allowing structure + // to be written directly to disk. + typedef unsigned char val_t [4]; +#endif + + enum { format0 = 0x50414247 }; + + val_t format; // format of all following data + val_t version; // later versions just add fields to end + + unsigned char regs [0x40]; + val_t frame_time; + val_t frame_phase; + + val_t sweep_freq; + val_t sweep_delay; + val_t sweep_enabled; + val_t sweep_neg; + val_t noise_divider; + val_t wave_buf; + + val_t delay [4]; + val_t length_ctr [4]; + val_t phase [4]; + val_t enabled [4]; + + val_t env_delay [3]; + val_t env_volume [3]; + val_t env_enabled [3]; + + val_t unused [13]; // for future expansion +}; + +#endif diff --git a/gearboy/src/audio/Gb_Apu_State.cpp b/gearboy/src/audio/Gb_Apu_State.cpp new file mode 100644 index 00000000..6a523460 --- /dev/null +++ b/gearboy/src/audio/Gb_Apu_State.cpp @@ -0,0 +1,119 @@ +// Gb_Snd_Emu $vers. http://www.slack.net/~ant/ + +#include "Gb_Apu.h" + +#include <string.h> + +/* Copyright (C) 2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +#if GB_APU_CUSTOM_STATE + #define REFLECT( x, y ) (save ? (io->y) = (x) : (x) = (io->y) ) +#else + #define REFLECT( x, y ) (save ? set_val( io->y, x ) : (void) ((x) = get_val( io->y ))) + + static blargg_ulong get_val( byte const* p ) + { + return p [3] * 0x1000000 + p [2] * 0x10000 + p [1] * 0x100 + p [0]; + } + + static void set_val( byte* p, blargg_ulong n ) + { + p [0] = (byte) (n ); + p [1] = (byte) (n >> 8); + p [2] = (byte) (n >> 16); + p [3] = (byte) (n >> 24); + } +#endif + +inline const char* Gb_Apu::save_load( gb_apu_state_t* io, bool save ) +{ + #if !GB_APU_CUSTOM_STATE + assert( sizeof (gb_apu_state_t) == 256 ); + #endif + + int format = io->format0; + REFLECT( format, format ); + if ( format != io->format0 ) + return "Unsupported sound save state format"; + + int version = 0; + REFLECT( version, version ); + + // Registers and wave RAM + assert( regs_size == sizeof io->regs ); + if ( save ) + memcpy( io->regs, regs, sizeof io->regs ); + else + memcpy( regs, io->regs, sizeof regs ); + + // Frame sequencer + REFLECT( frame_time, frame_time ); + REFLECT( frame_phase, frame_phase ); + + REFLECT( square1.sweep_freq, sweep_freq ); + REFLECT( square1.sweep_delay, sweep_delay ); + REFLECT( square1.sweep_enabled, sweep_enabled ); + REFLECT( square1.sweep_neg, sweep_neg ); + + REFLECT( noise.divider, noise_divider ); + REFLECT( wave.sample_buf, wave_buf ); + + return 0; +} + +// second function to avoid inline limits of some compilers +inline void Gb_Apu::save_load2( gb_apu_state_t* io, bool save ) +{ + for ( int i = osc_count; --i >= 0; ) + { + Gb_Osc& osc = *oscs [i]; + REFLECT( osc.delay, delay [i] ); + REFLECT( osc.length_ctr, length_ctr [i] ); + REFLECT( osc.phase, phase [i] ); + REFLECT( osc.enabled, enabled [i] ); + + if ( i != 2 ) + { + int j = min( i, 2 ); + Gb_Env& env = STATIC_CAST(Gb_Env&,osc); + REFLECT( env.env_delay, env_delay [j] ); + REFLECT( env.volume, env_volume [j] ); + REFLECT( env.env_enabled, env_enabled [j] ); + } + } +} + +void Gb_Apu::save_state( gb_apu_state_t* out ) +{ + (void) save_load( out, true ); + save_load2( out, true ); + + #if !GB_APU_CUSTOM_STATE + memset( out->unused, 0, sizeof out->unused ); + #endif +} + +blargg_err_t Gb_Apu::load_state( gb_apu_state_t const& in ) +{ + RETURN_ERR( save_load( CONST_CAST(gb_apu_state_t*,&in), false ) ); + save_load2( CONST_CAST(gb_apu_state_t*,&in), false ); + + apply_stereo(); + synth_volume( 0 ); // suppress output for the moment + run_until_( last_time ); // get last_amp updated + apply_volume(); // now use correct volume + + return 0; +} + diff --git a/gearboy/src/audio/Gb_Oscs.cpp b/gearboy/src/audio/Gb_Oscs.cpp new file mode 100644 index 00000000..72ec8049 --- /dev/null +++ b/gearboy/src/audio/Gb_Oscs.cpp @@ -0,0 +1,665 @@ +// Gb_Snd_Emu 0.2.0. http://www.slack.net/~ant/ + +#include "Gb_Apu.h" + +/* Copyright (C) 2003-2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +bool const cgb_02 = false; // enables bug in early CGB units that causes problems in some games +bool const cgb_05 = false; // enables CGB-05 zombie behavior + +int const trigger_mask = 0x80; +int const length_enabled = 0x40; + +void Gb_Osc::reset() +{ + output = 0; + last_amp = 0; + delay = 0; + phase = 0; + enabled = false; +} + +inline void Gb_Osc::update_amp( blip_time_t time, int new_amp ) +{ + output->set_modified(); + int delta = new_amp - last_amp; + if ( delta ) + { + last_amp = new_amp; + med_synth->offset( time, delta, output ); + } +} + +// Units + +void Gb_Osc::clock_length() +{ + if ( (regs [4] & length_enabled) && length_ctr ) + { + if ( --length_ctr <= 0 ) + enabled = false; + } +} + +inline int Gb_Env::reload_env_timer() +{ + int raw = regs [2] & 7; + env_delay = (raw ? raw : 8); + return raw; +} + +void Gb_Env::clock_envelope() +{ + if ( env_enabled && --env_delay <= 0 && reload_env_timer() ) + { + int v = volume + (regs [2] & 0x08 ? +1 : -1); + if ( 0 <= v && v <= 15 ) + volume = v; + else + env_enabled = false; + } +} + +inline void Gb_Sweep_Square::reload_sweep_timer() +{ + sweep_delay = (regs [0] & period_mask) >> 4; + if ( !sweep_delay ) + sweep_delay = 8; +} + +void Gb_Sweep_Square::calc_sweep( bool update ) +{ + int const shift = regs [0] & shift_mask; + int const delta = sweep_freq >> shift; + sweep_neg = (regs [0] & 0x08) != 0; + int const freq = sweep_freq + (sweep_neg ? -delta : delta); + + if ( freq > 0x7FF ) + { + enabled = false; + } + else if ( shift && update ) + { + sweep_freq = freq; + + regs [3] = freq & 0xFF; + regs [4] = (regs [4] & ~0x07) | (freq >> 8 & 0x07); + } +} + +void Gb_Sweep_Square::clock_sweep() +{ + if ( --sweep_delay <= 0 ) + { + reload_sweep_timer(); + if ( sweep_enabled && (regs [0] & period_mask) ) + { + calc_sweep( true ); + calc_sweep( false ); + } + } +} + +int Gb_Wave::access( unsigned addr ) const +{ + if ( enabled && mode != Gb_Apu::mode_agb ) + { + addr = phase & (bank_size - 1); + if ( mode == Gb_Apu::mode_dmg ) + { + addr++; + if ( delay > clk_mul ) + return -1; // can only access within narrow time window while playing + } + addr >>= 1; + } + return addr & 0x0F; +} + +// write_register + +int Gb_Osc::write_trig( int frame_phase, int max_len, int old_data ) +{ + int data = regs [4]; + + if ( (frame_phase & 1) && !(old_data & length_enabled) && length_ctr ) + { + if ( (data & length_enabled) || cgb_02 ) + length_ctr--; + } + + if ( data & trigger_mask ) + { + enabled = true; + if ( !length_ctr ) + { + length_ctr = max_len; + if ( (frame_phase & 1) && (data & length_enabled) ) + length_ctr--; + } + } + + if ( !length_ctr ) + enabled = false; + + return data & trigger_mask; +} + +inline void Gb_Env::zombie_volume( int old, int data ) +{ + int v = volume; + if ( mode == Gb_Apu::mode_agb || cgb_05 ) + { + // CGB-05 behavior, very close to AGB behavior as well + if ( (old ^ data) & 8 ) + { + if ( !(old & 8) ) + { + v++; + if ( old & 7 ) + v++; + } + + v = 16 - v; + } + else if ( (old & 0x0F) == 8 ) + { + v++; + } + } + else + { + // CGB-04&02 behavior, very close to MGB behavior as well + if ( !(old & 7) && env_enabled ) + v++; + else if ( !(old & 8) ) + v += 2; + + if ( (old ^ data) & 8 ) + v = 16 - v; + } + volume = v & 0x0F; +} + +bool Gb_Env::write_register( int frame_phase, int reg, int old, int data ) +{ + int const max_len = 64; + + switch ( reg ) + { + case 1: + length_ctr = max_len - (data & (max_len - 1)); + break; + + case 2: + if ( !dac_enabled() ) + enabled = false; + + zombie_volume( old, data ); + + if ( (data & 7) && env_delay == 8 ) + { + env_delay = 1; + clock_envelope(); // TODO: really happens at next length clock + } + break; + + case 4: + if ( write_trig( frame_phase, max_len, old ) ) + { + volume = regs [2] >> 4; + reload_env_timer(); + env_enabled = true; + if ( frame_phase == 7 ) + env_delay++; + if ( !dac_enabled() ) + enabled = false; + return true; + } + } + return false; +} + +bool Gb_Square::write_register( int frame_phase, int reg, int old_data, int data ) +{ + bool result = Gb_Env::write_register( frame_phase, reg, old_data, data ); + if ( result ) + delay = (delay & (4 * clk_mul - 1)) + period(); + return result; +} + +inline void Gb_Noise::write_register( int frame_phase, int reg, int old_data, int data ) +{ + if ( Gb_Env::write_register( frame_phase, reg, old_data, data ) ) + { + phase = 0x7FFF; + delay += 8 * clk_mul; + } +} + +inline void Gb_Sweep_Square::write_register( int frame_phase, int reg, int old_data, int data ) +{ + if ( reg == 0 && sweep_enabled && sweep_neg && !(data & 0x08) ) + enabled = false; // sweep negate disabled after used + + if ( Gb_Square::write_register( frame_phase, reg, old_data, data ) ) + { + sweep_freq = frequency(); + sweep_neg = false; + reload_sweep_timer(); + sweep_enabled = (regs [0] & (period_mask | shift_mask)) != 0; + if ( regs [0] & shift_mask ) + calc_sweep( false ); + } +} + +void Gb_Wave::corrupt_wave() +{ + int pos = ((phase + 1) & (bank_size - 1)) >> 1; + if ( pos < 4 ) + wave_ram [0] = wave_ram [pos]; + else + for ( int i = 4; --i >= 0; ) + wave_ram [i] = wave_ram [(pos & ~3) + i]; +} + +inline void Gb_Wave::write_register( int frame_phase, int reg, int old_data, int data ) +{ + int const max_len = 256; + + switch ( reg ) + { + case 0: + if ( !dac_enabled() ) + enabled = false; + break; + + case 1: + length_ctr = max_len - data; + break; + + case 4: + bool was_enabled = enabled; + if ( write_trig( frame_phase, max_len, old_data ) ) + { + if ( !dac_enabled() ) + enabled = false; + else if ( mode == Gb_Apu::mode_dmg && was_enabled && + (unsigned) (delay - 2 * clk_mul) < 2 * clk_mul ) + corrupt_wave(); + + phase = 0; + delay = period() + 6 * clk_mul; + } + } +} + +void Gb_Apu::write_osc( int index, int reg, int old_data, int data ) +{ + reg -= index * 5; + switch ( index ) + { + case 0: square1.write_register( frame_phase, reg, old_data, data ); break; + case 1: square2.write_register( frame_phase, reg, old_data, data ); break; + case 2: wave .write_register( frame_phase, reg, old_data, data ); break; + case 3: noise .write_register( frame_phase, reg, old_data, data ); break; + } +} + +// Synthesis + +void Gb_Square::run( blip_time_t time, blip_time_t end_time ) +{ + // Calc duty and phase + static byte const duty_offsets [4] = { 1, 1, 3, 7 }; + static byte const duties [4] = { 1, 2, 4, 6 }; + int const duty_code = regs [1] >> 6; + int duty_offset = duty_offsets [duty_code]; + int duty = duties [duty_code]; + if ( mode == Gb_Apu::mode_agb ) + { + // AGB uses inverted duty + duty_offset -= duty; + duty = 8 - duty; + } + int ph = (this->phase + duty_offset) & 7; + + // Determine what will be generated + int vol = 0; + Blip_Buffer* const out = this->output; + if ( out ) + { + int amp = dac_off_amp; + if ( dac_enabled() ) + { + if ( enabled ) + vol = this->volume; + + amp = -dac_bias; + if ( mode == Gb_Apu::mode_agb ) + amp = -(vol >> 1); + + // Play inaudible frequencies as constant amplitude + if ( frequency() >= 0x7FA && delay < 32 * clk_mul ) + { + amp += (vol * duty) >> 3; + vol = 0; + } + + if ( ph < duty ) + { + amp += vol; + vol = -vol; + } + } + update_amp( time, amp ); + } + + // Generate wave + time += delay; + if ( time < end_time ) + { + int const per = this->period(); + if ( !vol ) + { + // Maintain phase when not playing + int count = (end_time - time + per - 1) / per; + ph += count; // will be masked below + time += (blip_time_t) count * per; + } + else + { + // Output amplitude transitions + int delta = vol; + do + { + ph = (ph + 1) & 7; + if ( ph == 0 || ph == duty ) + { + good_synth->offset_inline( time, delta, out ); + delta = -delta; + } + time += per; + } + while ( time < end_time ); + + if ( delta != vol ) + last_amp -= delta; + } + this->phase = (ph - duty_offset) & 7; + } + delay = time - end_time; +} + +// Quickly runs LFSR for a large number of clocks. For use when noise is generating +// no sound. +static unsigned run_lfsr( unsigned s, unsigned mask, int count ) +{ + bool const optimized = true; // set to false to use only unoptimized loop in middle + + // optimization used in several places: + // ((s & (1 << b)) << n) ^ ((s & (1 << b)) << (n + 1)) = (s & (1 << b)) * (3 << n) + + if ( mask == 0x4000 && optimized ) + { + if ( count >= 32767 ) + count %= 32767; + + // Convert from Fibonacci to Galois configuration, + // shifted left 1 bit + s ^= (s & 1) * 0x8000; + + // Each iteration is equivalent to clocking LFSR 255 times + while ( (count -= 255) > 0 ) + s ^= ((s & 0xE) << 12) ^ ((s & 0xE) << 11) ^ (s >> 3); + count += 255; + + // Each iteration is equivalent to clocking LFSR 15 times + // (interesting similarity to single clocking below) + while ( (count -= 15) > 0 ) + s ^= ((s & 2) * (3 << 13)) ^ (s >> 1); + count += 15; + + // Remaining singles + while ( --count >= 0 ) + s = ((s & 2) * (3 << 13)) ^ (s >> 1); + + // Convert back to Fibonacci configuration + s &= 0x7FFF; + } + else if ( count < 8 || !optimized ) + { + // won't fully replace upper 8 bits, so have to do the unoptimized way + while ( --count >= 0 ) + s = (s >> 1 | mask) ^ (mask & (0 - ((s - 1) & 2))); + } + else + { + if ( count > 127 ) + { + count %= 127; + if ( !count ) + count = 127; // must run at least once + } + + // Need to keep one extra bit of history + s = s << 1 & 0xFF; + + // Convert from Fibonacci to Galois configuration, + // shifted left 2 bits + s ^= (s & 2) * 0x80; + + // Each iteration is equivalent to clocking LFSR 7 times + // (interesting similarity to single clocking below) + while ( (count -= 7) > 0 ) + s ^= ((s & 4) * (3 << 5)) ^ (s >> 1); + count += 7; + + // Remaining singles + while ( --count >= 0 ) + s = ((s & 4) * (3 << 5)) ^ (s >> 1); + + // Convert back to Fibonacci configuration and + // repeat last 8 bits above significant 7 + s = (s << 7 & 0x7F80) | (s >> 1 & 0x7F); + } + + return s; +} + +void Gb_Noise::run( blip_time_t time, blip_time_t end_time ) +{ + // Determine what will be generated + int vol = 0; + Blip_Buffer* const out = this->output; + if ( out ) + { + int amp = dac_off_amp; + if ( dac_enabled() ) + { + if ( enabled ) + vol = this->volume; + + amp = -dac_bias; + if ( mode == Gb_Apu::mode_agb ) + amp = -(vol >> 1); + + if ( !(phase & 1) ) + { + amp += vol; + vol = -vol; + } + } + + // AGB negates final output + if ( mode == Gb_Apu::mode_agb ) + { + vol = -vol; + amp = -amp; + } + + update_amp( time, amp ); + } + + // Run timer and calculate time of next LFSR clock + static byte const period1s [8] = { 1, 2, 4, 6, 8, 10, 12, 14 }; + int const period1 = period1s [regs [3] & 7] * clk_mul; + { + int extra = (end_time - time) - delay; + int const per2 = this->period2(); + time += delay + ((divider ^ (per2 >> 1)) & (per2 - 1)) * period1; + + int count = (extra < 0 ? 0 : (extra + period1 - 1) / period1); + divider = (divider - count) & period2_mask; + delay = count * period1 - extra; + } + + // Generate wave + if ( time < end_time ) + { + unsigned const mask = this->lfsr_mask(); + unsigned bits = this->phase; + + int per = period2( period1 * 8 ); + if ( period2_index() >= 0xE ) + { + time = end_time; + } + else if ( !vol ) + { + // Maintain phase when not playing + int count = (end_time - time + per - 1) / per; + time += (blip_time_t) count * per; + bits = run_lfsr( bits, ~mask, count ); + } + else + { + // Output amplitude transitions + int delta = -vol; + do + { + unsigned changed = bits + 1; + bits = bits >> 1 & mask; + if ( changed & 2 ) + { + bits |= ~mask; + delta = -delta; + med_synth->offset_inline( time, delta, out ); + } + time += per; + } + while ( time < end_time ); + + if ( delta == vol ) + last_amp += delta; + } + this->phase = bits; + } +} + +void Gb_Wave::run( blip_time_t time, blip_time_t end_time ) +{ + // Calc volume + static byte const volumes [8] = { 0, 4, 2, 1, 3, 3, 3, 3 }; + int const volume_shift = 2; + int const volume_idx = regs [2] >> 5 & (agb_mask | 3); // 2 bits on DMG/CGB, 3 on AGB + int const volume_mul = volumes [volume_idx]; + + // Determine what will be generated + int playing = false; + Blip_Buffer* const out = this->output; + if ( out ) + { + int amp = dac_off_amp; + if ( dac_enabled() ) + { + // Play inaudible frequencies as constant amplitude + amp = 8 << 4; // really depends on average of all samples in wave + + // if delay is larger, constant amplitude won't start yet + if ( frequency() <= 0x7FB || delay > 15 * clk_mul ) + { + if ( volume_mul ) + playing = (int) enabled; + + amp = (sample_buf << (phase << 2 & 4) & 0xF0) * playing; + } + + amp = ((amp * volume_mul) >> (volume_shift + 4)) - dac_bias; + } + update_amp( time, amp ); + } + + // Generate wave + time += delay; + if ( time < end_time ) + { + byte const* wave = this->wave_ram; + + // wave size and bank + int const size20_mask = 0x20; + int const flags = regs [0] & agb_mask; + int const wave_mask = (flags & size20_mask) | 0x1F; + int swap_banks = 0; + if ( flags & bank40_mask ) + { + swap_banks = flags & size20_mask; + wave += bank_size/2 - (swap_banks >> 1); + } + + int ph = this->phase ^ swap_banks; + ph = (ph + 1) & wave_mask; // pre-advance + + int const per = this->period(); + if ( !playing ) + { + // Maintain phase when not playing + int count = (end_time - time + per - 1) / per; + ph += count; // will be masked below + time += (blip_time_t) count * per; + } + else + { + // Output amplitude transitions + int lamp = this->last_amp + dac_bias; + do + { + // Extract nybble + int nybble = wave [ph >> 1] << (ph << 2 & 4) & 0xF0; + ph = (ph + 1) & wave_mask; + + // Scale by volume + int amp = (nybble * volume_mul) >> (volume_shift + 4); + + int delta = amp - lamp; + if ( delta ) + { + lamp = amp; + med_synth->offset_inline( time, delta, out ); + } + time += per; + } + while ( time < end_time ); + this->last_amp = lamp - dac_bias; + } + ph = (ph - 1) & wave_mask; // undo pre-advance and mask position + + // Keep track of last byte read + if ( enabled ) + sample_buf = wave [ph >> 1]; + + this->phase = ph ^ swap_banks; // undo swapped banks + } + delay = time - end_time; +} diff --git a/gearboy/src/audio/Gb_Oscs.h b/gearboy/src/audio/Gb_Oscs.h new file mode 100644 index 00000000..138802de --- /dev/null +++ b/gearboy/src/audio/Gb_Oscs.h @@ -0,0 +1,191 @@ +// Private oscillators used by Gb_Apu + +// Gb_Snd_Emu 0.2.0 +#ifndef GB_OSCS_H +#define GB_OSCS_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +#ifndef GB_APU_OVERCLOCK + #define GB_APU_OVERCLOCK 1 +#endif + +#if GB_APU_OVERCLOCK & (GB_APU_OVERCLOCK - 1) + #error "GB_APU_OVERCLOCK must be a power of 2" +#endif + +class Gb_Osc { +protected: + + // 11-bit frequency in NRx3 and NRx4 + int frequency() const { return (regs [4] & 7) * 0x100 + regs [3]; } + + void update_amp( blip_time_t, int new_amp ); + int write_trig( int frame_phase, int max_len, int old_data ); +public: + + enum { clk_mul = GB_APU_OVERCLOCK }; + enum { dac_bias = 7 }; + + Blip_Buffer* outputs [4];// NULL, right, left, center + Blip_Buffer* output; // where to output sound + BOOST::uint8_t* regs; // osc's 5 registers + int mode; // mode_dmg, mode_cgb, mode_agb + int dac_off_amp;// amplitude when DAC is off + int last_amp; // current amplitude in Blip_Buffer + typedef Blip_Synth<blip_good_quality,1> Good_Synth; + typedef Blip_Synth<blip_med_quality ,1> Med_Synth; + Good_Synth const* good_synth; + Med_Synth const* med_synth; + + int delay; // clocks until frequency timer expires + int length_ctr; // length counter + unsigned phase; // waveform phase (or equivalent) + bool enabled; // internal enabled flag + + void clock_length(); + void reset(); +}; + +class Gb_Env : public Gb_Osc { +public: + Gb_Env() : env_delay(0), env_enabled(false) {} + int env_delay; + int volume; + bool env_enabled; + + void clock_envelope(); + bool write_register( int frame_phase, int reg, int old_data, int data ); + + void reset() + { + env_delay = 0; + volume = 0; + Gb_Osc::reset(); + } +protected: + // Non-zero if DAC is enabled + int dac_enabled() const { return regs [2] & 0xF8; } +private: + void zombie_volume( int old, int data ); + int reload_env_timer(); +}; + +class Gb_Square : public Gb_Env { +public: + bool write_register( int frame_phase, int reg, int old_data, int data ); + void run( blip_time_t, blip_time_t ); + + void reset() + { + Gb_Env::reset(); + delay = 0x40000000; // TODO: something less hacky (never clocked until first trigger) + } +private: + // Frequency timer period + int period() const { return (2048 - frequency()) * (4 * clk_mul); } +}; + +class Gb_Sweep_Square : public Gb_Square { +public: + int sweep_freq; + int sweep_delay; + bool sweep_enabled; + bool sweep_neg; + + void clock_sweep(); + void write_register( int frame_phase, int reg, int old_data, int data ); + + void reset() + { + sweep_freq = 0; + sweep_delay = 0; + sweep_enabled = false; + sweep_neg = false; + Gb_Square::reset(); + } +private: + enum { period_mask = 0x70 }; + enum { shift_mask = 0x07 }; + + void calc_sweep( bool update ); + void reload_sweep_timer(); +}; + +class Gb_Noise : public Gb_Env { +public: + + int divider; // noise has more complex frequency divider setup + + void run( blip_time_t, blip_time_t ); + void write_register( int frame_phase, int reg, int old_data, int data ); + + void reset() + { + divider = 0; + Gb_Env::reset(); + delay = 4 * clk_mul; // TODO: remove? + } +private: + enum { period2_mask = 0x1FFFF }; + + int period2_index() const { return regs [3] >> 4; } + int period2( int base = 8 ) const { return base << period2_index(); } + unsigned lfsr_mask() const { return (regs [3] & 0x08) ? ~0x4040 : ~0x4000; } +}; + +class Gb_Wave : public Gb_Osc { +public: + int sample_buf; // last wave RAM byte read (hardware has this as well) + + void write_register( int frame_phase, int reg, int old_data, int data ); + void run( blip_time_t, blip_time_t ); + + // Reads/writes wave RAM + int read( unsigned addr ) const; + void write( unsigned addr, int data ); + + void reset() + { + sample_buf = 0; + Gb_Osc::reset(); + } + +private: + enum { bank40_mask = 0x40 }; + enum { bank_size = 32 }; + + int agb_mask; // 0xFF if AGB features enabled, 0 otherwise + BOOST::uint8_t* wave_ram; // 32 bytes (64 nybbles), stored in APU + + friend class Gb_Apu; + + // Frequency timer period + int period() const { return (2048 - frequency()) * (2 * clk_mul); } + + // Non-zero if DAC is enabled + int dac_enabled() const { return regs [0] & 0x80; } + + void corrupt_wave(); + + BOOST::uint8_t* wave_bank() const { return &wave_ram [(~regs [0] & bank40_mask) >> 2 & agb_mask]; } + + // Wave index that would be accessed, or -1 if no access would occur + int access( unsigned addr ) const; +}; + +inline int Gb_Wave::read( unsigned addr ) const +{ + int index = access( addr ); + return (index < 0 ? 0xFF : wave_bank() [index]); +} + +inline void Gb_Wave::write( unsigned addr, int data ) +{ + int index = access( addr ); + if ( index >= 0 ) + wave_bank() [index] = data;; +} + +#endif diff --git a/gearboy/src/audio/Multi_Buffer.cpp b/gearboy/src/audio/Multi_Buffer.cpp new file mode 100644 index 00000000..c1ba0b01 --- /dev/null +++ b/gearboy/src/audio/Multi_Buffer.cpp @@ -0,0 +1,281 @@ +// Blip_Buffer 0.4.1. http://www.slack.net/~ant/ + +#include "Multi_Buffer.h" + +/* Copyright (C) 2003-2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +Multi_Buffer::Multi_Buffer( int spf ) : samples_per_frame_( spf ) +{ + length_ = 0; + sample_rate_ = 0; + channels_changed_count_ = 1; + channel_types_ = 0; + channel_count_ = 0; + immediate_removal_ = true; +} + +Multi_Buffer::channel_t Multi_Buffer::channel( int /*index*/ ) +{ + static channel_t const ch = { 0, 0, 0 }; + return ch; +} + +// Silent_Buffer + +Silent_Buffer::Silent_Buffer() : Multi_Buffer( 1 ) // 0 channels would probably confuse +{ + // TODO: better to use empty Blip_Buffer so caller never has to check for NULL? + chan.left = 0; + chan.center = 0; + chan.right = 0; +} + +// Mono_Buffer + +Mono_Buffer::Mono_Buffer() : Multi_Buffer( 1 ) +{ + chan.center = &buf; + chan.left = &buf; + chan.right = &buf; +} + +Mono_Buffer::~Mono_Buffer() { } + +blargg_err_t Mono_Buffer::set_sample_rate( long rate, int msec ) +{ + RETURN_ERR( buf.set_sample_rate( rate, msec ) ); + return Multi_Buffer::set_sample_rate( buf.sample_rate(), buf.length() ); +} + + +// Tracked_Blip_Buffer + +Tracked_Blip_Buffer::Tracked_Blip_Buffer() +{ + last_non_silence = 0; +} + +void Tracked_Blip_Buffer::clear() +{ + last_non_silence = 0; + Blip_Buffer::clear(); +} + +void Tracked_Blip_Buffer::end_frame( blip_time_t t ) +{ + Blip_Buffer::end_frame( t ); + if ( clear_modified() ) + last_non_silence = (int)samples_avail() + blip_buffer_extra_; +} + +blip_ulong Tracked_Blip_Buffer::non_silent() const +{ + return last_non_silence | unsettled(); +} + +inline void Tracked_Blip_Buffer::remove_( long n ) +{ + if ( (last_non_silence -= n) < 0 ) + last_non_silence = 0; +} + +void Tracked_Blip_Buffer::remove_silence( long n ) +{ + remove_( n ); + Blip_Buffer::remove_silence( n ); +} + +void Tracked_Blip_Buffer::remove_samples( long n ) +{ + remove_( n ); + Blip_Buffer::remove_samples( n ); +} + +void Tracked_Blip_Buffer::remove_all_samples() +{ + long avail = samples_avail(); + if ( !non_silent() ) + remove_silence( avail ); + else + remove_samples( avail ); +} + +long Tracked_Blip_Buffer::read_samples( blip_sample_t* out, long count ) +{ + count = Blip_Buffer::read_samples( out, count ); + remove_( count ); + return count; +} + +// Stereo_Buffer + +int const stereo = 2; + +Stereo_Buffer::Stereo_Buffer() : Multi_Buffer( 2 ) +{ + chan.center = mixer.bufs [2] = &bufs [2]; + chan.left = mixer.bufs [0] = &bufs [0]; + chan.right = mixer.bufs [1] = &bufs [1]; + mixer.samples_read = 0; +} + +Stereo_Buffer::~Stereo_Buffer() { } + +blargg_err_t Stereo_Buffer::set_sample_rate( long rate, int msec ) +{ + mixer.samples_read = 0; + for ( int i = bufs_size; --i >= 0; ) + RETURN_ERR( bufs [i].set_sample_rate( rate, msec ) ); + return Multi_Buffer::set_sample_rate( bufs [0].sample_rate(), bufs [0].length() ); +} + +void Stereo_Buffer::clock_rate( long rate ) +{ + for ( int i = bufs_size; --i >= 0; ) + bufs [i].clock_rate( rate ); +} + +void Stereo_Buffer::bass_freq( int bass ) +{ + for ( int i = bufs_size; --i >= 0; ) + bufs [i].bass_freq( bass ); +} + +void Stereo_Buffer::clear() +{ + mixer.samples_read = 0; + for ( int i = bufs_size; --i >= 0; ) + bufs [i].clear(); +} + +void Stereo_Buffer::end_frame( blip_time_t time ) +{ + for ( int i = bufs_size; --i >= 0; ) + bufs [i].end_frame( time ); +} + +long Stereo_Buffer::read_samples( blip_sample_t* out, long out_size ) +{ + require( (out_size & 1) == 0 ); // must read an even number of samples + out_size = min( out_size, samples_avail() ); + + int pair_count = int (out_size >> 1); + if ( pair_count ) + { + mixer.read_pairs( out, pair_count ); + + if ( samples_avail() <= 0 || immediate_removal() ) + { + for ( int i = bufs_size; --i >= 0; ) + { + buf_t& b = bufs [i]; + // TODO: might miss non-silence settling since it checks END of last read + if ( !b.non_silent() ) + b.remove_silence( mixer.samples_read ); + else + b.remove_samples( mixer.samples_read ); + } + mixer.samples_read = 0; + } + } + return out_size; +} + + +// Stereo_Mixer + +// mixers use a single index value to improve performance on register-challenged processors +// offset goes from negative to zero + +void Stereo_Mixer::read_pairs( blip_sample_t* out, int count ) +{ + // TODO: if caller never marks buffers as modified, uses mono + // except that buffer isn't cleared, so caller can encounter + // subtle problems and not realize the cause. + samples_read += count; + if ( bufs [0]->non_silent() | bufs [1]->non_silent() ) + mix_stereo( out, count ); + else + mix_mono( out, count ); +} + +void Stereo_Mixer::mix_mono( blip_sample_t* out_, int count ) +{ + int const bass = BLIP_READER_BASS( *bufs [2] ); + BLIP_READER_BEGIN( center, *bufs [2] ); + BLIP_READER_ADJ_( center, samples_read ); + + typedef blip_sample_t stereo_blip_sample_t [stereo]; + stereo_blip_sample_t* BLIP_RESTRICT out = (stereo_blip_sample_t*) out_ + count; + int offset = -count; + do + { + blargg_long s = BLIP_READER_READ( center ); + BLIP_READER_NEXT_IDX_( center, bass, offset ); + BLIP_CLAMP( s, s ); + + out [offset] [0] = (blip_sample_t) s; + out [offset] [1] = (blip_sample_t) s; + } + while ( ++offset ); + + BLIP_READER_END( center, *bufs [2] ); +} + +void Stereo_Mixer::mix_stereo( blip_sample_t* out_, int count ) +{ + blip_sample_t* BLIP_RESTRICT out = out_ + count * stereo; + + // do left + center and right + center separately to reduce register load + Tracked_Blip_Buffer* const* buf = &bufs [2]; + while ( true ) // loop runs twice + { + --buf; + --out; + + int const bass = BLIP_READER_BASS( *bufs [2] ); + BLIP_READER_BEGIN( side, **buf ); + BLIP_READER_BEGIN( center, *bufs [2] ); + + BLIP_READER_ADJ_( side, samples_read ); + BLIP_READER_ADJ_( center, samples_read ); + + int offset = -count; + do + { + blargg_long s = BLIP_READER_READ_RAW( center ) + BLIP_READER_READ_RAW( side ); + s >>= blip_sample_bits - 16; + BLIP_READER_NEXT_IDX_( side, bass, offset ); + BLIP_READER_NEXT_IDX_( center, bass, offset ); + BLIP_CLAMP( s, s ); + + ++offset; // before write since out is decremented to slightly before end + out [offset * stereo] = (blip_sample_t) s; + } + while ( offset ); + + BLIP_READER_END( side, **buf ); + + if ( buf != bufs ) + continue; + + // only end center once + BLIP_READER_END( center, *bufs [2] ); + break; + } +} diff --git a/gearboy/src/audio/Multi_Buffer.h b/gearboy/src/audio/Multi_Buffer.h new file mode 100644 index 00000000..50b0a81a --- /dev/null +++ b/gearboy/src/audio/Multi_Buffer.h @@ -0,0 +1,204 @@ +// Multi-channel sound buffer interface, and basic mono and stereo buffers + +// Blip_Buffer 0.4.1 +#ifndef MULTI_BUFFER_H +#define MULTI_BUFFER_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +// Interface to one or more Blip_Buffers mapped to one or more channels +// consisting of left, center, and right buffers. +class Multi_Buffer { +public: + Multi_Buffer( int samples_per_frame ); + virtual ~Multi_Buffer() { } + + // Sets the number of channels available and optionally their types + // (type information used by Effects_Buffer) + enum { type_index_mask = 0xFF }; + enum { wave_type = 0x100, noise_type = 0x200, mixed_type = wave_type | noise_type }; + virtual blargg_err_t set_channel_count( int, int const* types = 0 ); + int channel_count() const { return channel_count_; } + + // Gets indexed channel, from 0 to channel count - 1 + struct channel_t { + Blip_Buffer* center; + Blip_Buffer* left; + Blip_Buffer* right; + }; + virtual channel_t channel( int index ) BLARGG_PURE( ; ) + + // See Blip_Buffer.h + virtual blargg_err_t set_sample_rate( long rate, int msec = blip_default_length ) BLARGG_PURE( ; ) + virtual void clock_rate( long ) BLARGG_PURE( { } ) + virtual void bass_freq( int ) BLARGG_PURE( { } ) + virtual void clear() BLARGG_PURE( { } ) + long sample_rate() const; + + // Length of buffer, in milliseconds + int length() const; + + // See Blip_Buffer.h + virtual void end_frame( blip_time_t ) BLARGG_PURE( { } ) + + // Number of samples per output frame (1 = mono, 2 = stereo) + int samples_per_frame() const; + + // Count of changes to channel configuration. Incremented whenever + // a change is made to any of the Blip_Buffers for any channel. + unsigned channels_changed_count() { return channels_changed_count_; } + + // See Blip_Buffer.h + virtual long read_samples( blip_sample_t*, long ) BLARGG_PURE( { return 0; } ) + virtual long samples_avail() const BLARGG_PURE( { return 0; } ) + +public: + BLARGG_DISABLE_NOTHROW + void disable_immediate_removal() { immediate_removal_ = false; } +protected: + bool immediate_removal() const { return immediate_removal_; } + int const* channel_types() const { return channel_types_; } + void channels_changed() { channels_changed_count_++; } +private: + // noncopyable + Multi_Buffer( const Multi_Buffer& ); + Multi_Buffer& operator = ( const Multi_Buffer& ); + + unsigned channels_changed_count_; + long sample_rate_; + int length_; + int channel_count_; + int const samples_per_frame_; + int const* channel_types_; + bool immediate_removal_; +}; + +// Uses a single buffer and outputs mono samples. +class Mono_Buffer : public Multi_Buffer { + Blip_Buffer buf; + channel_t chan; +public: + // Buffer used for all channels + Blip_Buffer* center() { return &buf; } + +public: + Mono_Buffer(); + ~Mono_Buffer(); + blargg_err_t set_sample_rate( long rate, int msec = blip_default_length ); + void clock_rate( long rate ) { buf.clock_rate( rate ); } + void bass_freq( int freq ) { buf.bass_freq( freq ); } + void clear() { buf.clear(); } + long samples_avail() const { return buf.samples_avail(); } + long read_samples( blip_sample_t* p, long s ) { return buf.read_samples( p, s ); } + channel_t channel( int ) { return chan; } + void end_frame( blip_time_t t ) { buf.end_frame( t ); } +}; + + class Tracked_Blip_Buffer : public Blip_Buffer { + public: + // Non-zero if buffer still has non-silent samples in it. Requires that you call + // set_modified() appropriately. + blip_ulong non_silent() const; + + // remove_samples( samples_avail() ) + void remove_all_samples(); + + public: + BLARGG_DISABLE_NOTHROW + + long read_samples( blip_sample_t*, long ); + void remove_silence( long ); + void remove_samples( long ); + Tracked_Blip_Buffer(); + void clear(); + void end_frame( blip_time_t ); + private: + blip_long last_non_silence; + void remove_( long ); + }; + + class Stereo_Mixer { + public: + Tracked_Blip_Buffer* bufs [3]; + blargg_long samples_read; + + Stereo_Mixer() : samples_read( 0 ) { } + void read_pairs( blip_sample_t* out, int count ); + private: + void mix_mono ( blip_sample_t* out, int pair_count ); + void mix_stereo( blip_sample_t* out, int pair_count ); + }; + +// Uses three buffers (one for center) and outputs stereo sample pairs. +class Stereo_Buffer : public Multi_Buffer { +public: + + // Buffers used for all channels + Blip_Buffer* center() { return &bufs [2]; } + Blip_Buffer* left() { return &bufs [0]; } + Blip_Buffer* right() { return &bufs [1]; } + +public: + Stereo_Buffer(); + ~Stereo_Buffer(); + blargg_err_t set_sample_rate( long, int msec = blip_default_length ); + void clock_rate( long ); + void bass_freq( int ); + void clear(); + channel_t channel( int ) { return chan; } + void end_frame( blip_time_t ); + + long samples_avail() const { return (bufs [0].samples_avail() - mixer.samples_read) * 2; } + long read_samples( blip_sample_t*, long ); + +private: + enum { bufs_size = 3 }; + typedef Tracked_Blip_Buffer buf_t; + buf_t bufs [bufs_size]; + Stereo_Mixer mixer; + channel_t chan; +}; + +// Silent_Buffer generates no samples, useful where no sound is wanted +class Silent_Buffer : public Multi_Buffer { + channel_t chan; +public: + Silent_Buffer(); + blargg_err_t set_sample_rate( long rate, int msec = blip_default_length ); + void clock_rate( long ) { } + void bass_freq( int ) { } + void clear() { } + channel_t channel( int ) { return chan; } + void end_frame( blip_time_t ) { } + long samples_avail() const { return 0; } + long read_samples( blip_sample_t*, long ) { return 0; } +}; + + +inline blargg_err_t Multi_Buffer::set_sample_rate( long rate, int msec ) +{ + sample_rate_ = rate; + length_ = msec; + return 0; +} + +inline blargg_err_t Silent_Buffer::set_sample_rate( long rate, int msec ) +{ + return Multi_Buffer::set_sample_rate( rate, msec ); +} + +inline int Multi_Buffer::samples_per_frame() const { return samples_per_frame_; } + +inline long Multi_Buffer::sample_rate() const { return sample_rate_; } + +inline int Multi_Buffer::length() const { return length_; } + +inline blargg_err_t Multi_Buffer::set_channel_count( int n, int const* types ) +{ + channel_count_ = n; + channel_types_ = types; + return 0; +} + +#endif diff --git a/gearboy/src/audio/blargg_common.h b/gearboy/src/audio/blargg_common.h new file mode 100644 index 00000000..1203d387 --- /dev/null +++ b/gearboy/src/audio/blargg_common.h @@ -0,0 +1,206 @@ +// Sets up common environment for Shay Green's libraries. +// To change configuration options, modify blargg_config.h, not this file. + +// Gb_Snd_Emu 0.2.0 +#ifndef BLARGG_COMMON_H +#define BLARGG_COMMON_H + +#include <stddef.h> +#include <stdlib.h> +#include <assert.h> +#include <limits.h> + +#undef BLARGG_COMMON_H +// allow blargg_config.h to #include blargg_common.h +#include "blargg_config.h" +#ifndef BLARGG_COMMON_H +#define BLARGG_COMMON_H + +// BLARGG_RESTRICT: equivalent to restrict, where supported +#if __GNUC__ >= 3 || _MSC_VER >= 1100 + #define BLARGG_RESTRICT __restrict +#else + #define BLARGG_RESTRICT +#endif + +// STATIC_CAST(T,expr): Used in place of static_cast<T> (expr) +// CONST_CAST( T,expr): Used in place of const_cast<T> (expr) +#ifndef STATIC_CAST + #if __GNUC__ >= 4 + #define STATIC_CAST(T,expr) static_cast<T> (expr) + #define CONST_CAST( T,expr) const_cast<T> (expr) + #else + #define STATIC_CAST(T,expr) ((T) (expr)) + #define CONST_CAST( T,expr) ((T) (expr)) + #endif +#endif + +// blargg_err_t (0 on success, otherwise error string) +#ifndef blargg_err_t + typedef const char* blargg_err_t; +#endif + +// blargg_vector - very lightweight vector of POD types (no constructor/destructor) +template<class T> +class blargg_vector { + T* begin_; + size_t size_; +public: + blargg_vector() : begin_( 0 ), size_( 0 ) { } + ~blargg_vector() { free( begin_ ); } + size_t size() const { return size_; } + T* begin() const { return begin_; } + T* end() const { return begin_ + size_; } + blargg_err_t resize( size_t n ) + { + // TODO: blargg_common.cpp to hold this as an outline function, ugh + void* p = realloc( begin_, n * sizeof (T) ); + if ( p ) + begin_ = (T*) p; + else if ( n > size_ ) // realloc failure only a problem if expanding + return "Out of memory"; + size_ = n; + return 0; + } + void clear() { void* p = begin_; begin_ = 0; size_ = 0; free( p ); } + T& operator [] ( size_t n ) const + { + assert( n <= size_ ); // <= to allow past-the-end value + return begin_ [n]; + } +}; + +#ifndef BLARGG_DISABLE_NOTHROW + // throw spec mandatory in ISO C++ if operator new can return NULL + #if __cplusplus >= 199711 || __GNUC__ >= 3 + #define BLARGG_THROWS( spec ) throw spec + #else + #define BLARGG_THROWS( spec ) + #endif + #define BLARGG_DISABLE_NOTHROW \ + void* operator new ( size_t s ) BLARGG_THROWS(()) { return malloc( s ); }\ + void operator delete ( void* p ) { free( p ); } + #define BLARGG_NEW new +#else + #include <new> + #define BLARGG_NEW new (std::nothrow) +#endif + +// BLARGG_4CHAR('a','b','c','d') = 'abcd' (four character integer constant) +#define BLARGG_4CHAR( a, b, c, d ) \ + ((a&0xFF)*0x1000000 + (b&0xFF)*0x10000 + (c&0xFF)*0x100 + (d&0xFF)) + +// BOOST_STATIC_ASSERT( expr ): Generates compile error if expr is 0. +#ifndef BOOST_STATIC_ASSERT + #ifdef _MSC_VER + // MSVC6 (_MSC_VER < 1300) fails for use of __LINE__ when /Zl is specified + #define BOOST_STATIC_ASSERT( expr ) \ + void blargg_failed_( int (*arg) [2 / (int) !!(expr) - 1] ) + #else + // Some other compilers fail when declaring same function multiple times in class, + // so differentiate them by line + #define BOOST_STATIC_ASSERT( expr ) \ + void blargg_failed_( int (*arg) [2 / !!(expr) - 1] [__LINE__] ) + #endif +#endif + +// BLARGG_COMPILER_HAS_BOOL: If 0, provides bool support for old compiler. If 1, +// compiler is assumed to support bool. If undefined, availability is determined. +#ifndef BLARGG_COMPILER_HAS_BOOL + #if defined (__MWERKS__) + #if !__option(bool) + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif + #elif defined (_MSC_VER) + #if _MSC_VER < 1100 + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif + #elif defined (__GNUC__) + // supports bool + #elif __cplusplus < 199711 + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif +#endif +#if defined (BLARGG_COMPILER_HAS_BOOL) && !BLARGG_COMPILER_HAS_BOOL + // If you get errors here, modify your blargg_config.h file + typedef int bool; + const bool true = 1; + const bool false = 0; +#endif + +// blargg_long/blargg_ulong = at least 32 bits, int if it's big enough + +#if INT_MAX < 0x7FFFFFFF || LONG_MAX == 0x7FFFFFFF + typedef long blargg_long; +#else + typedef int blargg_long; +#endif + +#if UINT_MAX < 0xFFFFFFFF || ULONG_MAX == 0xFFFFFFFF + typedef unsigned long blargg_ulong; +#else + typedef unsigned blargg_ulong; +#endif + +// BOOST::int8_t etc. + +// HAVE_STDINT_H: If defined, use <stdint.h> for int8_t etc. +#if defined (HAVE_STDINT_H) + #include <stdint.h> + #define BOOST + +// HAVE_INTTYPES_H: If defined, use <stdint.h> for int8_t etc. +#elif defined (HAVE_INTTYPES_H) + #include <inttypes.h> + #define BOOST + +#else + struct BOOST + { + #if UCHAR_MAX == 0xFF && SCHAR_MAX == 0x7F + typedef signed char int8_t; + typedef unsigned char uint8_t; + #else + // No suitable 8-bit type available + typedef struct see_blargg_common_h int8_t; + typedef struct see_blargg_common_h uint8_t; + #endif + + #if USHRT_MAX == 0xFFFF + typedef short int16_t; + typedef unsigned short uint16_t; + #else + // No suitable 16-bit type available + typedef struct see_blargg_common_h int16_t; + typedef struct see_blargg_common_h uint16_t; + #endif + + #if ULONG_MAX == 0xFFFFFFFF + typedef long int32_t; + typedef unsigned long uint32_t; + #elif UINT_MAX == 0xFFFFFFFF + typedef int int32_t; + typedef unsigned int uint32_t; + #else + // No suitable 32-bit type available + typedef struct see_blargg_common_h int32_t; + typedef struct see_blargg_common_h uint32_t; + #endif + }; +#endif + +#if __GNUC__ >= 3 + #define BLARGG_DEPRECATED __attribute__ ((deprecated)) +#else + #define BLARGG_DEPRECATED +#endif + +// Use in place of "= 0;" for a pure virtual, since these cause calls to std C++ lib. +// During development, BLARGG_PURE( x ) expands to = 0; +// virtual int func() BLARGG_PURE( { return 0; } ) +#ifndef BLARGG_PURE + #define BLARGG_PURE( def ) def +#endif + +#endif +#endif diff --git a/gearboy/src/audio/blargg_config.h b/gearboy/src/audio/blargg_config.h new file mode 100644 index 00000000..d577a1c5 --- /dev/null +++ b/gearboy/src/audio/blargg_config.h @@ -0,0 +1,33 @@ +// $package user configuration file. Don't replace when updating library. + +#ifndef BLARGG_CONFIG_H +#define BLARGG_CONFIG_H + +// Uncomment to have Gb_Apu run at 4x normal clock rate (16777216 Hz), useful in +// a Game Boy Advance emulator. +//#define GB_APU_OVERCLOCK 4 + +#define GB_APU_CUSTOM_STATE 1 + +// Uncomment to enable platform-specific (and possibly non-portable) optimizations. +//#define BLARGG_NONPORTABLE 1 + +// Uncomment if automatic byte-order determination doesn't work +//#define BLARGG_BIG_ENDIAN 1 + +// Uncomment to use zlib for transparent decompression of gzipped files +//#define HAVE_ZLIB_H + +// Uncomment if you get errors in the bool section of blargg_common.h +//#define BLARGG_COMPILER_HAS_BOOL 1 + +// Uncomment to disable out-of-memory exceptions +//#include <memory> +//#define BLARGG_NEW new (std::nothrow) + +// Use standard config.h if present +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#endif diff --git a/gearboy/src/audio/blargg_source.h b/gearboy/src/audio/blargg_source.h new file mode 100644 index 00000000..ddef37d6 --- /dev/null +++ b/gearboy/src/audio/blargg_source.h @@ -0,0 +1,92 @@ +/* Included at the beginning of library source files, AFTER all other #include lines. +Sets up helpful macros and services used in my source code. Since this is only "active" +in my source code, I don't have to worry about polluting the global namespace with +unprefixed names. */ + +// Gb_Snd_Emu 0.2.0 +#ifndef BLARGG_SOURCE_H +#define BLARGG_SOURCE_H + +// The following four macros are for debugging only. Some or all might be defined +// to do nothing, depending on the circumstances. Described is what happens when +// a particular macro is defined to do something. When defined to do nothing, the +// macros do NOT evaluate their argument(s). + +// If expr is false, prints file and line number, then aborts program. Meant for +// checking internal state and consistency. A failed assertion indicates a bug +// in MY code. +// +// void assert( bool expr ); +#include <assert.h> + +// If expr is false, prints file and line number, then aborts program. Meant for +// checking caller-supplied parameters and operations that are outside the control +// of the module. A failed requirement probably indicates a bug in YOUR code. +// +// void require( bool expr ); +#undef require +#define require( expr ) assert( expr ) + +// Like printf() except output goes to debugging console/file. +// +// void dprintf( const char* format, ... ); +static inline void blargg_dprintf_( const char*, ... ) { } +#undef dprintf +#define dprintf (1) ? (void) 0 : blargg_dprintf_ + +// If expr is false, prints file and line number to debug console/log, then +// continues execution normally. Meant for flagging potential problems or things +// that should be looked into, but that aren't serious problems. +// +// void check( bool expr ); +#undef check +#define check( expr ) ((void) 0) + +// If expr yields non-NULL error string, returns it from current function, +// otherwise continues normally. +#undef RETURN_ERR +#define RETURN_ERR( expr ) do { \ + blargg_err_t blargg_return_err_ = (expr); \ + if ( blargg_return_err_ ) return blargg_return_err_; \ + } while ( 0 ) + +// If ptr is NULL, returns "Out of memory" error string, otherwise continues normally. +#undef CHECK_ALLOC +#define CHECK_ALLOC( ptr ) do { if ( (ptr) == 0 ) return "Out of memory"; } while ( 0 ) + +// The usual min/max functions for built-in types. +// +// template<typename T> T min( T x, T y ) { return x < y ? x : y; } +// template<typename T> T max( T x, T y ) { return x > y ? x : y; } +#define BLARGG_DEF_MIN_MAX( type ) \ + static inline type blargg_min( type x, type y ) { if ( y < x ) x = y; return x; }\ + static inline type blargg_max( type x, type y ) { if ( x < y ) x = y; return x; } + +BLARGG_DEF_MIN_MAX( int ) +BLARGG_DEF_MIN_MAX( unsigned ) +BLARGG_DEF_MIN_MAX( long ) +BLARGG_DEF_MIN_MAX( unsigned long ) +BLARGG_DEF_MIN_MAX( float ) +BLARGG_DEF_MIN_MAX( double ) + +#undef min +#define min blargg_min + +#undef max +#define max blargg_max + +// typedef unsigned char byte; +typedef unsigned char blargg_byte; +#undef byte +#define byte blargg_byte + +// deprecated +#define BLARGG_CHECK_ALLOC CHECK_ALLOC +#define BLARGG_RETURN_ERR RETURN_ERR + +// BLARGG_SOURCE_BEGIN: If defined, #included, allowing redefition of dprintf and check +#ifdef BLARGG_SOURCE_BEGIN + #include BLARGG_SOURCE_BEGIN +#endif + +#endif diff --git a/gearboy/src/definitions.h b/gearboy/src/definitions.h new file mode 100644 index 00000000..ba5b4e29 --- /dev/null +++ b/gearboy/src/definitions.h @@ -0,0 +1,167 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#ifndef DEFINITIONS_H +#define DEFINITIONS_H + +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include <iostream> +#include <fstream> +#include <sstream> + +#ifdef DEBUG +#define DEBUG_GEARBOY 1 +#endif + +#if defined(PS2) || defined(PSP) +#define PERFORMANCE +#endif + +#define GEARBOY_TITLE "Gearboy" +#define GEARBOY_VERSION "3.4.1" + +#ifndef EMULATOR_BUILD +#define EMULATOR_BUILD "undefined" +#endif + +#ifndef NULL +#define NULL 0 +#endif + +#ifdef _WIN32 +#define BLARGG_USE_NAMESPACE 1 +#endif + +//#define GEARBOY_DISABLE_DISASSEMBLER + +#define MAX_ROM_SIZE 0x800000 + +#define SafeDelete(pointer) if(pointer != NULL) {delete pointer; pointer = NULL;} +#define SafeDeleteArray(pointer) if(pointer != NULL) {delete [] pointer; pointer = NULL;} + +#define InitPointer(pointer) ((pointer) = NULL) +#define IsValidPointer(pointer) ((pointer) != NULL) + +#if defined(MSB_FIRST) || defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +#define IS_BIG_ENDIAN +#else +#define IS_LITTLE_ENDIAN +#endif + +typedef uint8_t u8; +typedef int8_t s8; +typedef uint16_t u16; +typedef int16_t s16; +typedef uint32_t u32; +typedef int32_t s32; +typedef uint64_t u64; +typedef int64_t s64; + +typedef void (*RamChangedCallback) (void); + +#define FLAG_ZERO 0x80 +#define FLAG_SUB 0x40 +#define FLAG_HALF 0x20 +#define FLAG_CARRY 0x10 +#define FLAG_NONE 0 + +#define GAMEBOY_WIDTH 160 +#define GAMEBOY_HEIGHT 144 + +#define AUDIO_BUFFER_SIZE 4096 + +#define SAVESTATE_MAGIC 0x28011983 + +struct GB_Color +{ + u8 red; + u8 green; + u8 blue; +}; + +enum GB_Color_Format +{ + GB_PIXEL_RGB565, + GB_PIXEL_RGB555, + GB_PIXEL_BGR565, + GB_PIXEL_BGR555 +}; + +enum Gameboy_Keys +{ + A_Key = 4, + B_Key = 5, + Start_Key = 7, + Select_Key = 6, + Right_Key = 0, + Left_Key = 1, + Up_Key = 2, + Down_Key = 3 +}; + +#ifdef DEBUG_GEARBOY + #ifdef __ANDROID__ + #include <android/log.h> + #define printf(...) __android_log_print(ANDROID_LOG_DEBUG, "GEARBOY", __VA_ARGS__); + #endif +#define Log(msg, ...) (Log_func(msg, ##__VA_ARGS__)) +#else +#define Log(msg, ...) +#endif + +inline void Log_func(const char* const msg, ...) +{ + static int count = 1; + char szBuf[512]; + + va_list args; + va_start(args, msg); + vsprintf(szBuf, msg, args); + va_end(args); + + printf("%d: %s\n", count, szBuf); + + count++; +} + +inline u8 SetBit(const u8 value, const u8 bit) +{ + return value | static_cast<u8>(0x01 << bit); +} + +inline u8 UnsetBit(const u8 value, const u8 bit) +{ + return value & (~(0x01 << bit)); +} + +inline bool IsSetBit(const u8 value, const u8 bit) +{ + return (value & (0x01 << bit)) != 0; +} + +inline int AsHex(const char c) +{ + return c >= 'A' ? c - 'A' + 0xA : c - '0'; +} + +#endif /* DEFINITIONS_H */ diff --git a/gearboy/src/gearboy.h b/gearboy/src/gearboy.h new file mode 100644 index 00000000..ee2d1b26 --- /dev/null +++ b/gearboy/src/gearboy.h @@ -0,0 +1,34 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#ifndef GEARBOY_H +#define GEARBOY_H + +#include "definitions.h" +#include "GearboyCore.h" +#include "Memory.h" +#include "Processor.h" +#include "Cartridge.h" +#include "Audio.h" +#include "Video.h" +#include "SixteenBitRegister.h" +#include "MemoryRule.h" + +#endif /* GEARBOY_H */ + diff --git a/gearboy/src/miniz/miniz.c b/gearboy/src/miniz/miniz.c new file mode 100644 index 00000000..87bdedb1 --- /dev/null +++ b/gearboy/src/miniz/miniz.c @@ -0,0 +1,7733 @@ +#include "miniz.h" +/************************************************************************** + * + * Copyright 2013-2014 RAD Game Tools and Valve Software + * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + + + +typedef unsigned char mz_validate_uint16[sizeof(mz_uint16) == 2 ? 1 : -1]; +typedef unsigned char mz_validate_uint32[sizeof(mz_uint32) == 4 ? 1 : -1]; +typedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1]; + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- zlib-style API's */ + +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len) +{ + mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16); + size_t block_len = buf_len % 5552; + if (!ptr) + return MZ_ADLER32_INIT; + while (buf_len) + { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) + { + s1 += ptr[0], s2 += s1; + s1 += ptr[1], s2 += s1; + s1 += ptr[2], s2 += s1; + s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; + s1 += ptr[5], s2 += s1; + s1 += ptr[6], s2 += s1; + s1 += ptr[7], s2 += s1; + } + for (; i < block_len; ++i) + s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; + buf_len -= block_len; + block_len = 5552; + } + return (s2 << 16) + s1; +} + +/* Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ */ +#if 0 + mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) + { + static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; + mz_uint32 crcu32 = (mz_uint32)crc; + if (!ptr) + return MZ_CRC32_INIT; + crcu32 = ~crcu32; + while (buf_len--) + { + mz_uint8 b = *ptr++; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; + } + return ~crcu32; + } +#elif defined(USE_EXTERNAL_MZCRC) +/* If USE_EXTERNAL_CRC is defined, an external module will export the + * mz_crc32() symbol for us to use, e.g. an SSE-accelerated version. + * Depending on the impl, it may be necessary to ~ the input/output crc values. + */ +mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len); +#else +/* Faster, but larger CPU cache footprint. + */ +mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) +{ + static const mz_uint32 s_crc_table[256] = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, + 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, + 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, + 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, + 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, + 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, + 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, + 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, + 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, + 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, + 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, + 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, + 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, + 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, + 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, + 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, + 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, + 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, + 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, + 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, + 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, + 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, + 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, + 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, + 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, + 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, + 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, + 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, + 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, + 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, + 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + mz_uint32 crc32 = (mz_uint32)crc ^ 0xFFFFFFFF; + const mz_uint8 *pByte_buf = (const mz_uint8 *)ptr; + + while (buf_len >= 4) + { + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[1]) & 0xFF]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[2]) & 0xFF]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[3]) & 0xFF]; + pByte_buf += 4; + buf_len -= 4; + } + + while (buf_len) + { + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF]; + ++pByte_buf; + --buf_len; + } + + return ~crc32; +} +#endif + +void mz_free(void *p) +{ + MZ_FREE(p); +} + +MINIZ_EXPORT void *miniz_def_alloc_func(void *opaque, size_t items, size_t size) +{ + (void)opaque, (void)items, (void)size; + return MZ_MALLOC(items * size); +} +MINIZ_EXPORT void miniz_def_free_func(void *opaque, void *address) +{ + (void)opaque, (void)address; + MZ_FREE(address); +} +MINIZ_EXPORT void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size) +{ + (void)opaque, (void)address, (void)items, (void)size; + return MZ_REALLOC(address, items * size); +} + +const char *mz_version(void) +{ + return MZ_VERSION; +} + +#ifndef MINIZ_NO_ZLIB_APIS + +int mz_deflateInit(mz_streamp pStream, int level) +{ + return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY); +} + +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy) +{ + tdefl_compressor *pComp; + mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy); + + if (!pStream) + return MZ_STREAM_ERROR; + if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))) + return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = MZ_ADLER32_INIT; + pStream->msg = NULL; + pStream->reserved = 0; + pStream->total_in = 0; + pStream->total_out = 0; + if (!pStream->zalloc) + pStream->zalloc = miniz_def_alloc_func; + if (!pStream->zfree) + pStream->zfree = miniz_def_free_func; + + pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) + return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pComp; + + if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY) + { + mz_deflateEnd(pStream); + return MZ_PARAM_ERROR; + } + + return MZ_OK; +} + +int mz_deflateReset(mz_streamp pStream) +{ + if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree)) + return MZ_STREAM_ERROR; + pStream->total_in = pStream->total_out = 0; + tdefl_init((tdefl_compressor *)pStream->state, NULL, NULL, ((tdefl_compressor *)pStream->state)->m_flags); + return MZ_OK; +} + +int mz_deflate(mz_streamp pStream, int flush) +{ + size_t in_bytes, out_bytes; + mz_ulong orig_total_in, orig_total_out; + int mz_status = MZ_OK; + + if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out)) + return MZ_STREAM_ERROR; + if (!pStream->avail_out) + return MZ_BUF_ERROR; + + if (flush == MZ_PARTIAL_FLUSH) + flush = MZ_SYNC_FLUSH; + + if (((tdefl_compressor *)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE) + return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR; + + orig_total_in = pStream->total_in; + orig_total_out = pStream->total_out; + for (;;) + { + tdefl_status defl_status; + in_bytes = pStream->avail_in; + out_bytes = pStream->avail_out; + + defl_status = tdefl_compress((tdefl_compressor *)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush); + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tdefl_get_adler32((tdefl_compressor *)pStream->state); + + pStream->next_out += (mz_uint)out_bytes; + pStream->avail_out -= (mz_uint)out_bytes; + pStream->total_out += (mz_uint)out_bytes; + + if (defl_status < 0) + { + mz_status = MZ_STREAM_ERROR; + break; + } + else if (defl_status == TDEFL_STATUS_DONE) + { + mz_status = MZ_STREAM_END; + break; + } + else if (!pStream->avail_out) + break; + else if ((!pStream->avail_in) && (flush != MZ_FINISH)) + { + if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out)) + break; + return MZ_BUF_ERROR; /* Can't make forward progress without some input. + */ + } + } + return mz_status; +} + +int mz_deflateEnd(mz_streamp pStream) +{ + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) + { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len) +{ + (void)pStream; + /* This is really over conservative. (And lame, but it's actually pretty tricky to compute a true upper bound given the way tdefl's blocking works.) */ + return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5); +} + +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level) +{ + int status; + mz_stream stream; + memset(&stream, 0, sizeof(stream)); + + /* In case mz_ulong is 64-bits (argh I hate longs). */ + if ((source_len | *pDest_len) > 0xFFFFFFFFU) + return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_deflateInit(&stream, level); + if (status != MZ_OK) + return status; + + status = mz_deflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) + { + mz_deflateEnd(&stream); + return (status == MZ_OK) ? MZ_BUF_ERROR : status; + } + + *pDest_len = stream.total_out; + return mz_deflateEnd(&stream); +} + +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +{ + return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION); +} + +mz_ulong mz_compressBound(mz_ulong source_len) +{ + return mz_deflateBound(NULL, source_len); +} + +typedef struct +{ + tinfl_decompressor m_decomp; + mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; + int m_window_bits; + mz_uint8 m_dict[TINFL_LZ_DICT_SIZE]; + tinfl_status m_last_status; +} inflate_state; + +int mz_inflateInit2(mz_streamp pStream, int window_bits) +{ + inflate_state *pDecomp; + if (!pStream) + return MZ_STREAM_ERROR; + if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)) + return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = 0; + pStream->msg = NULL; + pStream->total_in = 0; + pStream->total_out = 0; + pStream->reserved = 0; + if (!pStream->zalloc) + pStream->zalloc = miniz_def_alloc_func; + if (!pStream->zfree) + pStream->zfree = miniz_def_free_func; + + pDecomp = (inflate_state *)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state)); + if (!pDecomp) + return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pDecomp; + + tinfl_init(&pDecomp->m_decomp); + pDecomp->m_dict_ofs = 0; + pDecomp->m_dict_avail = 0; + pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; + pDecomp->m_first_call = 1; + pDecomp->m_has_flushed = 0; + pDecomp->m_window_bits = window_bits; + + return MZ_OK; +} + +int mz_inflateInit(mz_streamp pStream) +{ + return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS); +} + +int mz_inflateReset(mz_streamp pStream) +{ + inflate_state *pDecomp; + if (!pStream) + return MZ_STREAM_ERROR; + + pStream->data_type = 0; + pStream->adler = 0; + pStream->msg = NULL; + pStream->total_in = 0; + pStream->total_out = 0; + pStream->reserved = 0; + + pDecomp = (inflate_state *)pStream->state; + + tinfl_init(&pDecomp->m_decomp); + pDecomp->m_dict_ofs = 0; + pDecomp->m_dict_avail = 0; + pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; + pDecomp->m_first_call = 1; + pDecomp->m_has_flushed = 0; + /* pDecomp->m_window_bits = window_bits */; + + return MZ_OK; +} + +int mz_inflate(mz_streamp pStream, int flush) +{ + inflate_state *pState; + mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32; + size_t in_bytes, out_bytes, orig_avail_in; + tinfl_status status; + + if ((!pStream) || (!pStream->state)) + return MZ_STREAM_ERROR; + if (flush == MZ_PARTIAL_FLUSH) + flush = MZ_SYNC_FLUSH; + if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) + return MZ_STREAM_ERROR; + + pState = (inflate_state *)pStream->state; + if (pState->m_window_bits > 0) + decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER; + orig_avail_in = pStream->avail_in; + + first_call = pState->m_first_call; + pState->m_first_call = 0; + if (pState->m_last_status < 0) + return MZ_DATA_ERROR; + + if (pState->m_has_flushed && (flush != MZ_FINISH)) + return MZ_STREAM_ERROR; + pState->m_has_flushed |= (flush == MZ_FINISH); + + if ((flush == MZ_FINISH) && (first_call)) + { + /* MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. */ + decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF; + in_bytes = pStream->avail_in; + out_bytes = pStream->avail_out; + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags); + pState->m_last_status = status; + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + pStream->next_out += (mz_uint)out_bytes; + pStream->avail_out -= (mz_uint)out_bytes; + pStream->total_out += (mz_uint)out_bytes; + + if (status < 0) + return MZ_DATA_ERROR; + else if (status != TINFL_STATUS_DONE) + { + pState->m_last_status = TINFL_STATUS_FAILED; + return MZ_BUF_ERROR; + } + return MZ_STREAM_END; + } + /* flush != MZ_FINISH then we must assume there's more input. */ + if (flush != MZ_FINISH) + decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT; + + if (pState->m_dict_avail) + { + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; + pStream->avail_out -= n; + pStream->total_out += n; + pState->m_dict_avail -= n; + pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; + } + + for (;;) + { + in_bytes = pStream->avail_in; + out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs; + + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags); + pState->m_last_status = status; + + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + + pState->m_dict_avail = (mz_uint)out_bytes; + + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; + pStream->avail_out -= n; + pStream->total_out += n; + pState->m_dict_avail -= n; + pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + + if (status < 0) + return MZ_DATA_ERROR; /* Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). */ + else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in)) + return MZ_BUF_ERROR; /* Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. */ + else if (flush == MZ_FINISH) + { + /* The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. */ + if (status == TINFL_STATUS_DONE) + return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END; + /* status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong. */ + else if (!pStream->avail_out) + return MZ_BUF_ERROR; + } + else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail)) + break; + } + + return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; +} + +int mz_inflateEnd(mz_streamp pStream) +{ + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) + { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} +int mz_uncompress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong *pSource_len) +{ + mz_stream stream; + int status; + memset(&stream, 0, sizeof(stream)); + + /* In case mz_ulong is 64-bits (argh I hate longs). */ + if ((*pSource_len | *pDest_len) > 0xFFFFFFFFU) + return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)*pSource_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_inflateInit(&stream); + if (status != MZ_OK) + return status; + + status = mz_inflate(&stream, MZ_FINISH); + *pSource_len = *pSource_len - stream.avail_in; + if (status != MZ_STREAM_END) + { + mz_inflateEnd(&stream); + return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status; + } + *pDest_len = stream.total_out; + + return mz_inflateEnd(&stream); +} + +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +{ + return mz_uncompress2(pDest, pDest_len, pSource, &source_len); +} + +const char *mz_error(int err) +{ + static struct + { + int m_err; + const char *m_pDesc; + } s_error_descs[] = + { + { MZ_OK, "" }, { MZ_STREAM_END, "stream end" }, { MZ_NEED_DICT, "need dictionary" }, { MZ_ERRNO, "file error" }, { MZ_STREAM_ERROR, "stream error" }, { MZ_DATA_ERROR, "data error" }, { MZ_MEM_ERROR, "out of memory" }, { MZ_BUF_ERROR, "buf error" }, { MZ_VERSION_ERROR, "version error" }, { MZ_PARAM_ERROR, "parameter error" } + }; + mz_uint i; + for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i) + if (s_error_descs[i].m_err == err) + return s_error_descs[i].m_pDesc; + return NULL; +} + +#endif /*MINIZ_NO_ZLIB_APIS */ + +#ifdef __cplusplus +} +#endif + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to <http://unlicense.org/> +*/ +/************************************************************************** + * + * Copyright 2013-2014 RAD Game Tools and Valve Software + * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + + + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- Low-level Compression (independent from all decompression API's) */ + +/* Purposely making these tables static for faster init and thread safety. */ +static const mz_uint16 s_tdefl_len_sym[256] = + { + 257, 258, 259, 260, 261, 262, 263, 264, 265, 265, 266, 266, 267, 267, 268, 268, 269, 269, 269, 269, 270, 270, 270, 270, 271, 271, 271, 271, 272, 272, 272, 272, + 273, 273, 273, 273, 273, 273, 273, 273, 274, 274, 274, 274, 274, 274, 274, 274, 275, 275, 275, 275, 275, 275, 275, 275, 276, 276, 276, 276, 276, 276, 276, 276, + 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, + 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, + 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, + 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, + 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, + 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 285 + }; + +static const mz_uint8 s_tdefl_len_extra[256] = + { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0 + }; + +static const mz_uint8 s_tdefl_small_dist_sym[512] = + { + 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17 + }; + +static const mz_uint8 s_tdefl_small_dist_extra[512] = + { + 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7 + }; + +static const mz_uint8 s_tdefl_large_dist_sym[128] = + { + 0, 0, 18, 19, 20, 20, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29 + }; + +static const mz_uint8 s_tdefl_large_dist_extra[128] = + { + 0, 0, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13 + }; + +/* Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted values. */ +typedef struct +{ + mz_uint16 m_key, m_sym_index; +} tdefl_sym_freq; +static tdefl_sym_freq *tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq *pSyms0, tdefl_sym_freq *pSyms1) +{ + mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; + tdefl_sym_freq *pCur_syms = pSyms0, *pNew_syms = pSyms1; + MZ_CLEAR_OBJ(hist); + for (i = 0; i < num_syms; i++) + { + mz_uint freq = pSyms0[i].m_key; + hist[freq & 0xFF]++; + hist[256 + ((freq >> 8) & 0xFF)]++; + } + while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) + total_passes--; + for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8) + { + const mz_uint32 *pHist = &hist[pass << 8]; + mz_uint offsets[256], cur_ofs = 0; + for (i = 0; i < 256; i++) + { + offsets[i] = cur_ofs; + cur_ofs += pHist[i]; + } + for (i = 0; i < num_syms; i++) + pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i]; + { + tdefl_sym_freq *t = pCur_syms; + pCur_syms = pNew_syms; + pNew_syms = t; + } + } + return pCur_syms; +} + +/* tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. */ +static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n) +{ + int root, leaf, next, avbl, used, dpth; + if (n == 0) + return; + else if (n == 1) + { + A[0].m_key = 1; + return; + } + A[0].m_key += A[1].m_key; + root = 0; + leaf = 2; + for (next = 1; next < n - 1; next++) + { + if (leaf >= n || A[root].m_key < A[leaf].m_key) + { + A[next].m_key = A[root].m_key; + A[root++].m_key = (mz_uint16)next; + } + else + A[next].m_key = A[leaf++].m_key; + if (leaf >= n || (root < next && A[root].m_key < A[leaf].m_key)) + { + A[next].m_key = (mz_uint16)(A[next].m_key + A[root].m_key); + A[root++].m_key = (mz_uint16)next; + } + else + A[next].m_key = (mz_uint16)(A[next].m_key + A[leaf++].m_key); + } + A[n - 2].m_key = 0; + for (next = n - 3; next >= 0; next--) + A[next].m_key = A[A[next].m_key].m_key + 1; + avbl = 1; + used = dpth = 0; + root = n - 2; + next = n - 1; + while (avbl > 0) + { + while (root >= 0 && (int)A[root].m_key == dpth) + { + used++; + root--; + } + while (avbl > used) + { + A[next--].m_key = (mz_uint16)(dpth); + avbl--; + } + avbl = 2 * used; + dpth++; + used = 0; + } +} + +/* Limits canonical Huffman code table's max code size. */ +enum +{ + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 +}; +static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, int code_list_len, int max_code_size) +{ + int i; + mz_uint32 total = 0; + if (code_list_len <= 1) + return; + for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) + pNum_codes[max_code_size] += pNum_codes[i]; + for (i = max_code_size; i > 0; i--) + total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i)); + while (total != (1UL << max_code_size)) + { + pNum_codes[max_code_size]--; + for (i = max_code_size - 1; i > 0; i--) + if (pNum_codes[i]) + { + pNum_codes[i]--; + pNum_codes[i + 1] += 2; + break; + } + total--; + } +} + +static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int table_len, int code_size_limit, int static_table) +{ + int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; + mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; + MZ_CLEAR_OBJ(num_codes); + if (static_table) + { + for (i = 0; i < table_len; i++) + num_codes[d->m_huff_code_sizes[table_num][i]]++; + } + else + { + tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], *pSyms; + int num_used_syms = 0; + const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0]; + for (i = 0; i < table_len; i++) + if (pSym_count[i]) + { + syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i]; + syms0[num_used_syms++].m_sym_index = (mz_uint16)i; + } + + pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1); + tdefl_calculate_minimum_redundancy(pSyms, num_used_syms); + + for (i = 0; i < num_used_syms; i++) + num_codes[pSyms[i].m_key]++; + + tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit); + + MZ_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); + MZ_CLEAR_OBJ(d->m_huff_codes[table_num]); + for (i = 1, j = num_used_syms; i <= code_size_limit; i++) + for (l = num_codes[i]; l > 0; l--) + d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i); + } + + next_code[1] = 0; + for (j = 0, i = 2; i <= code_size_limit; i++) + next_code[i] = j = ((j + num_codes[i - 1]) << 1); + + for (i = 0; i < table_len; i++) + { + mz_uint rev_code = 0, code, code_size; + if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) + continue; + code = next_code[code_size]++; + for (l = code_size; l > 0; l--, code >>= 1) + rev_code = (rev_code << 1) | (code & 1); + d->m_huff_codes[table_num][i] = (mz_uint16)rev_code; + } +} + +#define TDEFL_PUT_BITS(b, l) \ + do \ + { \ + mz_uint bits = b; \ + mz_uint len = l; \ + MZ_ASSERT(bits <= ((1U << len) - 1U)); \ + d->m_bit_buffer |= (bits << d->m_bits_in); \ + d->m_bits_in += len; \ + while (d->m_bits_in >= 8) \ + { \ + if (d->m_pOutput_buf < d->m_pOutput_buf_end) \ + *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \ + d->m_bit_buffer >>= 8; \ + d->m_bits_in -= 8; \ + } \ + } \ + MZ_MACRO_END + +#define TDEFL_RLE_PREV_CODE_SIZE() \ + { \ + if (rle_repeat_count) \ + { \ + if (rle_repeat_count < 3) \ + { \ + d->m_huff_count[2][prev_code_size] = (mz_uint16)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \ + while (rle_repeat_count--) \ + packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \ + } \ + else \ + { \ + d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 16; \ + packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_repeat_count - 3); \ + } \ + rle_repeat_count = 0; \ + } \ + } + +#define TDEFL_RLE_ZERO_CODE_SIZE() \ + { \ + if (rle_z_count) \ + { \ + if (rle_z_count < 3) \ + { \ + d->m_huff_count[2][0] = (mz_uint16)(d->m_huff_count[2][0] + rle_z_count); \ + while (rle_z_count--) \ + packed_code_sizes[num_packed_code_sizes++] = 0; \ + } \ + else if (rle_z_count <= 10) \ + { \ + d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 17; \ + packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 3); \ + } \ + else \ + { \ + d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 18; \ + packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 11); \ + } \ + rle_z_count = 0; \ + } \ + } + +static mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + +static void tdefl_start_dynamic_block(tdefl_compressor *d) +{ + int num_lit_codes, num_dist_codes, num_bit_lengths; + mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index; + mz_uint8 code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], prev_code_size = 0xFF; + + d->m_huff_count[0][256] = 1; + + tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE); + tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE); + + for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) + if (d->m_huff_code_sizes[0][num_lit_codes - 1]) + break; + for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) + if (d->m_huff_code_sizes[1][num_dist_codes - 1]) + break; + + memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes); + memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes); + total_code_sizes_to_pack = num_lit_codes + num_dist_codes; + num_packed_code_sizes = 0; + rle_z_count = 0; + rle_repeat_count = 0; + + memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2); + for (i = 0; i < total_code_sizes_to_pack; i++) + { + mz_uint8 code_size = code_sizes_to_pack[i]; + if (!code_size) + { + TDEFL_RLE_PREV_CODE_SIZE(); + if (++rle_z_count == 138) + { + TDEFL_RLE_ZERO_CODE_SIZE(); + } + } + else + { + TDEFL_RLE_ZERO_CODE_SIZE(); + if (code_size != prev_code_size) + { + TDEFL_RLE_PREV_CODE_SIZE(); + d->m_huff_count[2][code_size] = (mz_uint16)(d->m_huff_count[2][code_size] + 1); + packed_code_sizes[num_packed_code_sizes++] = code_size; + } + else if (++rle_repeat_count == 6) + { + TDEFL_RLE_PREV_CODE_SIZE(); + } + } + prev_code_size = code_size; + } + if (rle_repeat_count) + { + TDEFL_RLE_PREV_CODE_SIZE(); + } + else + { + TDEFL_RLE_ZERO_CODE_SIZE(); + } + + tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE); + + TDEFL_PUT_BITS(2, 2); + + TDEFL_PUT_BITS(num_lit_codes - 257, 5); + TDEFL_PUT_BITS(num_dist_codes - 1, 5); + + for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) + if (d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]]) + break; + num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1)); + TDEFL_PUT_BITS(num_bit_lengths - 4, 4); + for (i = 0; (int)i < num_bit_lengths; i++) + TDEFL_PUT_BITS(d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3); + + for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes;) + { + mz_uint code = packed_code_sizes[packed_code_sizes_index++]; + MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2); + TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]); + if (code >= 16) + TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]); + } +} + +static void tdefl_start_static_block(tdefl_compressor *d) +{ + mz_uint i; + mz_uint8 *p = &d->m_huff_code_sizes[0][0]; + + for (i = 0; i <= 143; ++i) + *p++ = 8; + for (; i <= 255; ++i) + *p++ = 9; + for (; i <= 279; ++i) + *p++ = 7; + for (; i <= 287; ++i) + *p++ = 8; + + memset(d->m_huff_code_sizes[1], 5, 32); + + tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE); + tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE); + + TDEFL_PUT_BITS(1, 2); +} + +static const mz_uint mz_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) +{ + mz_uint flags; + mz_uint8 *pLZ_codes; + mz_uint8 *pOutput_buf = d->m_pOutput_buf; + mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf; + mz_uint64 bit_buffer = d->m_bit_buffer; + mz_uint bits_in = d->m_bits_in; + +#define TDEFL_PUT_BITS_FAST(b, l) \ + { \ + bit_buffer |= (((mz_uint64)(b)) << bits_in); \ + bits_in += (l); \ + } + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; flags >>= 1) + { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + + if (flags & 1) + { + mz_uint s0, s1, n0, n1, sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], match_dist = *(const mz_uint16 *)(pLZ_codes + 1); + pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); + + /* This sequence coaxes MSVC into using cmov's vs. jmp's. */ + s0 = s_tdefl_small_dist_sym[match_dist & 511]; + n0 = s_tdefl_small_dist_extra[match_dist & 511]; + s1 = s_tdefl_large_dist_sym[match_dist >> 8]; + n1 = s_tdefl_large_dist_extra[match_dist >> 8]; + sym = (match_dist < 512) ? s0 : s1; + num_extra_bits = (match_dist < 512) ? n0 : n1; + + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } + else + { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) + { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) + { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } + } + } + + if (pOutput_buf >= d->m_pOutput_buf_end) + return MZ_FALSE; + + *(mz_uint64 *)pOutput_buf = bit_buffer; + pOutput_buf += (bits_in >> 3); + bit_buffer >>= (bits_in & ~7); + bits_in &= 7; + } + +#undef TDEFL_PUT_BITS_FAST + + d->m_pOutput_buf = pOutput_buf; + d->m_bits_in = 0; + d->m_bit_buffer = 0; + + while (bits_in) + { + mz_uint32 n = MZ_MIN(bits_in, 16); + TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n); + bit_buffer >>= n; + bits_in -= n; + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#else +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) +{ + mz_uint flags; + mz_uint8 *pLZ_codes; + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; flags >>= 1) + { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + if (flags & 1) + { + mz_uint sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); + pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); + + if (match_dist < 512) + { + sym = s_tdefl_small_dist_sym[match_dist]; + num_extra_bits = s_tdefl_small_dist_extra[match_dist]; + } + else + { + sym = s_tdefl_large_dist_sym[match_dist >> 8]; + num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8]; + } + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } + else + { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS */ + +static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block) +{ + if (static_block) + tdefl_start_static_block(d); + else + tdefl_start_dynamic_block(d); + return tdefl_compress_lz_codes(d); +} + +static int tdefl_flush_block(tdefl_compressor *d, int flush) +{ + mz_uint saved_bit_buf, saved_bits_in; + mz_uint8 *pSaved_output_buf; + mz_bool comp_block_succeeded = MZ_FALSE; + int n, use_raw_block = ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size; + mz_uint8 *pOutput_buf_start = ((d->m_pPut_buf_func == NULL) && ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) : d->m_output_buf; + + d->m_pOutput_buf = pOutput_buf_start; + d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16; + + MZ_ASSERT(!d->m_output_flush_remaining); + d->m_output_flush_ofs = 0; + d->m_output_flush_remaining = 0; + + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left); + d->m_pLZ_code_buf -= (d->m_num_flags_left == 8); + + if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index)) + { + TDEFL_PUT_BITS(0x78, 8); + TDEFL_PUT_BITS(0x01, 8); + } + + TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1); + + pSaved_output_buf = d->m_pOutput_buf; + saved_bit_buf = d->m_bit_buffer; + saved_bits_in = d->m_bits_in; + + if (!use_raw_block) + comp_block_succeeded = tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48)); + + /* If the block gets expanded, forget the current contents of the output buffer and send a raw block instead. */ + if (((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= d->m_total_lz_bytes))) && + ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size)) + { + mz_uint i; + d->m_pOutput_buf = pSaved_output_buf; + d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + TDEFL_PUT_BITS(0, 2); + if (d->m_bits_in) + { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF) + { + TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16); + } + for (i = 0; i < d->m_total_lz_bytes; ++i) + { + TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8); + } + } + /* Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes. */ + else if (!comp_block_succeeded) + { + d->m_pOutput_buf = pSaved_output_buf; + d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + tdefl_compress_block(d, MZ_TRUE); + } + + if (flush) + { + if (flush == TDEFL_FINISH) + { + if (d->m_bits_in) + { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER) + { + mz_uint i, a = d->m_adler32; + for (i = 0; i < 4; i++) + { + TDEFL_PUT_BITS((a >> 24) & 0xFF, 8); + a <<= 8; + } + } + } + else + { + mz_uint i, z = 0; + TDEFL_PUT_BITS(0, 3); + if (d->m_bits_in) + { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + for (i = 2; i; --i, z ^= 0xFFFF) + { + TDEFL_PUT_BITS(z & 0xFFFF, 16); + } + } + } + + MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end); + + memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; + d->m_pLZ_flags = d->m_lz_code_buf; + d->m_num_flags_left = 8; + d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes; + d->m_total_lz_bytes = 0; + d->m_block_index++; + + if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0) + { + if (d->m_pPut_buf_func) + { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user)) + return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED); + } + else if (pOutput_buf_start == d->m_output_buf) + { + int bytes_to_copy = (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs)); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy); + d->m_out_buf_ofs += bytes_to_copy; + if ((n -= bytes_to_copy) != 0) + { + d->m_output_flush_ofs = bytes_to_copy; + d->m_output_flush_remaining = n; + } + } + else + { + d->m_out_buf_ofs += n; + } + } + + return d->m_output_flush_remaining; +} + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES +#ifdef MINIZ_UNALIGNED_USE_MEMCPY +static mz_uint16 TDEFL_READ_UNALIGNED_WORD(const mz_uint8* p) +{ + mz_uint16 ret; + memcpy(&ret, p, sizeof(mz_uint16)); + return ret; +} +static mz_uint16 TDEFL_READ_UNALIGNED_WORD2(const mz_uint16* p) +{ + mz_uint16 ret; + memcpy(&ret, p, sizeof(mz_uint16)); + return ret; +} +#else +#define TDEFL_READ_UNALIGNED_WORD(p) *(const mz_uint16 *)(p) +#define TDEFL_READ_UNALIGNED_WORD2(p) *(const mz_uint16 *)(p) +#endif +static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) +{ + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint16 *s = (const mz_uint16 *)(d->m_dict + pos), *p, *q; + mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), s01 = TDEFL_READ_UNALIGNED_WORD2(s); + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); + if (max_match_len <= match_len) + return; + for (;;) + { + for (;;) + { + if (--num_probes_left == 0) + return; +#define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ + return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) \ + break; + TDEFL_PROBE; + TDEFL_PROBE; + TDEFL_PROBE; + } + if (!dist) + break; + q = (const mz_uint16 *)(d->m_dict + probe_pos); + if (TDEFL_READ_UNALIGNED_WORD2(q) != s01) + continue; + p = s; + probe_len = 32; + do + { + } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && + (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0)); + if (!probe_len) + { + *pMatch_dist = dist; + *pMatch_len = MZ_MIN(max_match_len, (mz_uint)TDEFL_MAX_MATCH_LEN); + break; + } + else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q)) > match_len) + { + *pMatch_dist = dist; + if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len) + break; + c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]); + } + } +} +#else +static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) +{ + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint8 *s = d->m_dict + pos, *p, *q; + mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1]; + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); + if (max_match_len <= match_len) + return; + for (;;) + { + for (;;) + { + if (--num_probes_left == 0) + return; +#define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ + return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) \ + break; + TDEFL_PROBE; + TDEFL_PROBE; + TDEFL_PROBE; + } + if (!dist) + break; + p = s; + q = d->m_dict + probe_pos; + for (probe_len = 0; probe_len < max_match_len; probe_len++) + if (*p++ != *q++) + break; + if (probe_len > match_len) + { + *pMatch_dist = dist; + if ((*pMatch_len = match_len = probe_len) == max_match_len) + return; + c0 = d->m_dict[pos + match_len]; + c1 = d->m_dict[pos + match_len - 1]; + } + } +} +#endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES */ + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN +#ifdef MINIZ_UNALIGNED_USE_MEMCPY +static mz_uint32 TDEFL_READ_UNALIGNED_WORD32(const mz_uint8* p) +{ + mz_uint32 ret; + memcpy(&ret, p, sizeof(mz_uint32)); + return ret; +} +#else +#define TDEFL_READ_UNALIGNED_WORD32(p) *(const mz_uint32 *)(p) +#endif +static mz_bool tdefl_compress_fast(tdefl_compressor *d) +{ + /* Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio. */ + mz_uint lookahead_pos = d->m_lookahead_pos, lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, total_lz_bytes = d->m_total_lz_bytes, num_flags_left = d->m_num_flags_left; + mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags; + mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + + while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size))) + { + const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096; + mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size); + d->m_src_buf_left -= num_bytes_to_process; + lookahead_size += num_bytes_to_process; + + while (num_bytes_to_process) + { + mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process); + memcpy(d->m_dict + dst_pos, d->m_pSrc, n); + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos)); + d->m_pSrc += n; + dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK; + num_bytes_to_process -= n; + } + + dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size); + if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE)) + break; + + while (lookahead_size >= 4) + { + mz_uint cur_match_dist, cur_match_len = 1; + mz_uint8 *pCur_dict = d->m_dict + cur_pos; + mz_uint first_trigram = TDEFL_READ_UNALIGNED_WORD32(pCur_dict) & 0xFFFFFF; + mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & TDEFL_LEVEL1_HASH_SIZE_MASK; + mz_uint probe_pos = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)lookahead_pos; + + if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= dict_size) && ((TDEFL_READ_UNALIGNED_WORD32(d->m_dict + (probe_pos &= TDEFL_LZ_DICT_SIZE_MASK)) & 0xFFFFFF) == first_trigram)) + { + const mz_uint16 *p = (const mz_uint16 *)pCur_dict; + const mz_uint16 *q = (const mz_uint16 *)(d->m_dict + probe_pos); + mz_uint32 probe_len = 32; + do + { + } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && + (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0)); + cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q); + if (!probe_len) + cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0; + + if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U))) + { + cur_match_len = 1; + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + else + { + mz_uint32 s0, s1; + cur_match_len = MZ_MIN(cur_match_len, lookahead_size); + + MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && (cur_match_dist <= TDEFL_LZ_DICT_SIZE)); + + cur_match_dist--; + + pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN); +#ifdef MINIZ_UNALIGNED_USE_MEMCPY + memcpy(&pLZ_code_buf[1], &cur_match_dist, sizeof(cur_match_dist)); +#else + *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist; +#endif + pLZ_code_buf += 3; + *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80); + + s0 = s_tdefl_small_dist_sym[cur_match_dist & 511]; + s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8]; + d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++; + + d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++; + } + } + else + { + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + + if (--num_flags_left == 0) + { + num_flags_left = 8; + pLZ_flags = pLZ_code_buf++; + } + + total_lz_bytes += cur_match_len; + lookahead_pos += cur_match_len; + dict_size = MZ_MIN(dict_size + cur_match_len, (mz_uint)TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK; + MZ_ASSERT(lookahead_size >= cur_match_len); + lookahead_size -= cur_match_len; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) + { + int n; + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; + pLZ_code_buf = d->m_pLZ_code_buf; + pLZ_flags = d->m_pLZ_flags; + num_flags_left = d->m_num_flags_left; + } + } + + while (lookahead_size) + { + mz_uint8 lit = d->m_dict[cur_pos]; + + total_lz_bytes++; + *pLZ_code_buf++ = lit; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + if (--num_flags_left == 0) + { + num_flags_left = 8; + pLZ_flags = pLZ_code_buf++; + } + + d->m_huff_count[0][lit]++; + + lookahead_pos++; + dict_size = MZ_MIN(dict_size + 1, (mz_uint)TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; + lookahead_size--; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) + { + int n; + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; + pLZ_code_buf = d->m_pLZ_code_buf; + pLZ_flags = d->m_pLZ_flags; + num_flags_left = d->m_num_flags_left; + } + } + } + + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + return MZ_TRUE; +} +#endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */ + +static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, mz_uint8 lit) +{ + d->m_total_lz_bytes++; + *d->m_pLZ_code_buf++ = lit; + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1); + if (--d->m_num_flags_left == 0) + { + d->m_num_flags_left = 8; + d->m_pLZ_flags = d->m_pLZ_code_buf++; + } + d->m_huff_count[0][lit]++; +} + +static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist) +{ + mz_uint32 s0, s1; + + MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && (match_dist <= TDEFL_LZ_DICT_SIZE)); + + d->m_total_lz_bytes += match_len; + + d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN); + + match_dist -= 1; + d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF); + d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8); + d->m_pLZ_code_buf += 3; + + *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80); + if (--d->m_num_flags_left == 0) + { + d->m_num_flags_left = 8; + d->m_pLZ_flags = d->m_pLZ_code_buf++; + } + + s0 = s_tdefl_small_dist_sym[match_dist & 511]; + s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127]; + d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++; + d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; +} + +static mz_bool tdefl_compress_normal(tdefl_compressor *d) +{ + const mz_uint8 *pSrc = d->m_pSrc; + size_t src_buf_left = d->m_src_buf_left; + tdefl_flush flush = d->m_flush; + + while ((src_buf_left) || ((flush) && (d->m_lookahead_size))) + { + mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos; + /* Update dictionary and hash chains. Keeps the lookahead size equal to TDEFL_MAX_MATCH_LEN. */ + if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1)) + { + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK, ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2; + mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK]; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size); + const mz_uint8 *pSrc_end = pSrc + num_bytes_to_process; + src_buf_left -= num_bytes_to_process; + d->m_lookahead_size += num_bytes_to_process; + while (pSrc != pSrc_end) + { + mz_uint8 c = *pSrc++; + d->m_dict[dst_pos] = c; + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)(ins_pos); + dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; + ins_pos++; + } + } + else + { + while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + { + mz_uint8 c = *pSrc++; + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + src_buf_left--; + d->m_dict[dst_pos] = c; + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN) + { + mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2; + mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << (TDEFL_LZ_HASH_SHIFT * 2)) ^ (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)(ins_pos); + } + } + } + d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size); + if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + break; + + /* Simple lazy/greedy parsing state machine. */ + len_to_move = 1; + cur_match_dist = 0; + cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1); + cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS)) + { + if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) + { + mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK]; + cur_match_len = 0; + while (cur_match_len < d->m_lookahead_size) + { + if (d->m_dict[cur_pos + cur_match_len] != c) + break; + cur_match_len++; + } + if (cur_match_len < TDEFL_MIN_MATCH_LEN) + cur_match_len = 0; + else + cur_match_dist = 1; + } + } + else + { + tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, &cur_match_dist, &cur_match_len); + } + if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U)) || (cur_pos == cur_match_dist) || ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5))) + { + cur_match_dist = cur_match_len = 0; + } + if (d->m_saved_match_len) + { + if (cur_match_len > d->m_saved_match_len) + { + tdefl_record_literal(d, (mz_uint8)d->m_saved_lit); + if (cur_match_len >= 128) + { + tdefl_record_match(d, cur_match_len, cur_match_dist); + d->m_saved_match_len = 0; + len_to_move = cur_match_len; + } + else + { + d->m_saved_lit = d->m_dict[cur_pos]; + d->m_saved_match_dist = cur_match_dist; + d->m_saved_match_len = cur_match_len; + } + } + else + { + tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist); + len_to_move = d->m_saved_match_len - 1; + d->m_saved_match_len = 0; + } + } + else if (!cur_match_dist) + tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]); + else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || (cur_match_len >= 128)) + { + tdefl_record_match(d, cur_match_len, cur_match_dist); + len_to_move = cur_match_len; + } + else + { + d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]; + d->m_saved_match_dist = cur_match_dist; + d->m_saved_match_len = cur_match_len; + } + /* Move the lookahead forward by len_to_move bytes. */ + d->m_lookahead_pos += len_to_move; + MZ_ASSERT(d->m_lookahead_size >= len_to_move); + d->m_lookahead_size -= len_to_move; + d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, (mz_uint)TDEFL_LZ_DICT_SIZE); + /* Check if it's time to flush the current LZ codes to the internal output buffer. */ + if ((d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) || + ((d->m_total_lz_bytes > 31 * 1024) && (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= d->m_total_lz_bytes) || (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS)))) + { + int n; + d->m_pSrc = pSrc; + d->m_src_buf_left = src_buf_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + } + } + + d->m_pSrc = pSrc; + d->m_src_buf_left = src_buf_left; + return MZ_TRUE; +} + +static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d) +{ + if (d->m_pIn_buf_size) + { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + } + + if (d->m_pOut_buf_size) + { + size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, n); + d->m_output_flush_ofs += (mz_uint)n; + d->m_output_flush_remaining -= (mz_uint)n; + d->m_out_buf_ofs += n; + + *d->m_pOut_buf_size = d->m_out_buf_ofs; + } + + return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush) +{ + if (!d) + { + if (pIn_buf_size) + *pIn_buf_size = 0; + if (pOut_buf_size) + *pOut_buf_size = 0; + return TDEFL_STATUS_BAD_PARAM; + } + + d->m_pIn_buf = pIn_buf; + d->m_pIn_buf_size = pIn_buf_size; + d->m_pOut_buf = pOut_buf; + d->m_pOut_buf_size = pOut_buf_size; + d->m_pSrc = (const mz_uint8 *)(pIn_buf); + d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0; + d->m_out_buf_ofs = 0; + d->m_flush = flush; + + if (((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || (d->m_prev_return_status != TDEFL_STATUS_OKAY) || + (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || (pIn_buf_size && *pIn_buf_size && !pIn_buf) || (pOut_buf_size && *pOut_buf_size && !pOut_buf)) + { + if (pIn_buf_size) + *pIn_buf_size = 0; + if (pOut_buf_size) + *pOut_buf_size = 0; + return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM); + } + d->m_wants_to_finish |= (flush == TDEFL_FINISH); + + if ((d->m_output_flush_remaining) || (d->m_finished)) + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) && + ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) && + ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == 0)) + { + if (!tdefl_compress_fast(d)) + return d->m_prev_return_status; + } + else +#endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */ + { + if (!tdefl_compress_normal(d)) + return d->m_prev_return_status; + } + + if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf)) + d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, d->m_pSrc - (const mz_uint8 *)pIn_buf); + + if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && (!d->m_output_flush_remaining)) + { + if (tdefl_flush_block(d, flush) < 0) + return d->m_prev_return_status; + d->m_finished = (flush == TDEFL_FINISH); + if (flush == TDEFL_FULL_FLUSH) + { + MZ_CLEAR_OBJ(d->m_hash); + MZ_CLEAR_OBJ(d->m_next); + d->m_dict_size = 0; + } + } + + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); +} + +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush) +{ + MZ_ASSERT(d->m_pPut_buf_func); + return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush); +} + +tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + d->m_pPut_buf_func = pPut_buf_func; + d->m_pPut_buf_user = pPut_buf_user; + d->m_flags = (mz_uint)(flags); + d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3; + d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0; + d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3; + if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) + MZ_CLEAR_OBJ(d->m_hash); + d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0; + d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0; + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; + d->m_pLZ_flags = d->m_lz_code_buf; + *d->m_pLZ_flags = 0; + d->m_num_flags_left = 8; + d->m_pOutput_buf = d->m_output_buf; + d->m_pOutput_buf_end = d->m_output_buf; + d->m_prev_return_status = TDEFL_STATUS_OKAY; + d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0; + d->m_adler32 = 1; + d->m_pIn_buf = NULL; + d->m_pOut_buf = NULL; + d->m_pIn_buf_size = NULL; + d->m_pOut_buf_size = NULL; + d->m_flush = TDEFL_NO_FLUSH; + d->m_pSrc = NULL; + d->m_src_buf_left = 0; + d->m_out_buf_ofs = 0; + if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) + MZ_CLEAR_OBJ(d->m_dict); + memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + return TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d) +{ + return d->m_prev_return_status; +} + +mz_uint32 tdefl_get_adler32(tdefl_compressor *d) +{ + return d->m_adler32; +} + +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + tdefl_compressor *pComp; + mz_bool succeeded; + if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) + return MZ_FALSE; + pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); + if (!pComp) + return MZ_FALSE; + succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY); + succeeded = succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE); + MZ_FREE(pComp); + return succeeded; +} + +typedef struct +{ + size_t m_size, m_capacity; + mz_uint8 *m_pBuf; + mz_bool m_expandable; +} tdefl_output_buffer; + +static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, void *pUser) +{ + tdefl_output_buffer *p = (tdefl_output_buffer *)pUser; + size_t new_size = p->m_size + len; + if (new_size > p->m_capacity) + { + size_t new_capacity = p->m_capacity; + mz_uint8 *pNew_buf; + if (!p->m_expandable) + return MZ_FALSE; + do + { + new_capacity = MZ_MAX(128U, new_capacity << 1U); + } while (new_size > new_capacity); + pNew_buf = (mz_uint8 *)MZ_REALLOC(p->m_pBuf, new_capacity); + if (!pNew_buf) + return MZ_FALSE; + p->m_pBuf = pNew_buf; + p->m_capacity = new_capacity; + } + memcpy((mz_uint8 *)p->m_pBuf + p->m_size, pBuf, len); + p->m_size = new_size; + return MZ_TRUE; +} + +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) +{ + tdefl_output_buffer out_buf; + MZ_CLEAR_OBJ(out_buf); + if (!pOut_len) + return MZ_FALSE; + else + *pOut_len = 0; + out_buf.m_expandable = MZ_TRUE; + if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) + return NULL; + *pOut_len = out_buf.m_size; + return out_buf.m_pBuf; +} + +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) +{ + tdefl_output_buffer out_buf; + MZ_CLEAR_OBJ(out_buf); + if (!pOut_buf) + return 0; + out_buf.m_pBuf = (mz_uint8 *)pOut_buf; + out_buf.m_capacity = out_buf_len; + if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) + return 0; + return out_buf.m_size; +} + +static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + +/* level may actually range from [0,10] (10 is a "hidden" max level, where we want a bit more compression and it's fine if throughput to fall off a cliff on some files). */ +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy) +{ + mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); + if (window_bits > 0) + comp_flags |= TDEFL_WRITE_ZLIB_HEADER; + + if (!level) + comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; + else if (strategy == MZ_FILTERED) + comp_flags |= TDEFL_FILTER_MATCHES; + else if (strategy == MZ_HUFFMAN_ONLY) + comp_flags &= ~TDEFL_MAX_PROBES_MASK; + else if (strategy == MZ_FIXED) + comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS; + else if (strategy == MZ_RLE) + comp_flags |= TDEFL_RLE_MATCHES; + + return comp_flags; +} + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4204) /* nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal) */ +#endif + +/* Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at + http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/. + This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck. */ +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip) +{ + /* Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined. */ + static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); + tdefl_output_buffer out_buf; + int i, bpl = w * num_chans, y, z; + mz_uint32 c; + *pLen_out = 0; + if (!pComp) + return NULL; + MZ_CLEAR_OBJ(out_buf); + out_buf.m_expandable = MZ_TRUE; + out_buf.m_capacity = 57 + MZ_MAX(64, (1 + bpl) * h); + if (NULL == (out_buf.m_pBuf = (mz_uint8 *)MZ_MALLOC(out_buf.m_capacity))) + { + MZ_FREE(pComp); + return NULL; + } + /* write dummy header */ + for (z = 41; z; --z) + tdefl_output_buffer_putter(&z, 1, &out_buf); + /* compress image data */ + tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER); + for (y = 0; y < h; ++y) + { + tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); + tdefl_compress_buffer(pComp, (mz_uint8 *)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH); + } + if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE) + { + MZ_FREE(pComp); + MZ_FREE(out_buf.m_pBuf); + return NULL; + } + /* write real header */ + *pLen_out = out_buf.m_size - 41; + { + static const mz_uint8 chans[] = { 0x00, 0x00, 0x04, 0x02, 0x06 }; + mz_uint8 pnghdr[41] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, + 0x0a, 0x1a, 0x0a, 0x00, 0x00, + 0x00, 0x0d, 0x49, 0x48, 0x44, + 0x52, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x49, 0x44, 0x41, + 0x54 }; + pnghdr[18] = (mz_uint8)(w >> 8); + pnghdr[19] = (mz_uint8)w; + pnghdr[22] = (mz_uint8)(h >> 8); + pnghdr[23] = (mz_uint8)h; + pnghdr[25] = chans[num_chans]; + pnghdr[33] = (mz_uint8)(*pLen_out >> 24); + pnghdr[34] = (mz_uint8)(*pLen_out >> 16); + pnghdr[35] = (mz_uint8)(*pLen_out >> 8); + pnghdr[36] = (mz_uint8)*pLen_out; + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, pnghdr + 12, 17); + for (i = 0; i < 4; ++i, c <<= 8) + ((mz_uint8 *)(pnghdr + 29))[i] = (mz_uint8)(c >> 24); + memcpy(out_buf.m_pBuf, pnghdr, 41); + } + /* write footer (IDAT CRC-32, followed by IEND chunk) */ + if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf)) + { + *pLen_out = 0; + MZ_FREE(pComp); + MZ_FREE(out_buf.m_pBuf); + return NULL; + } + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, out_buf.m_pBuf + 41 - 4, *pLen_out + 4); + for (i = 0; i < 4; ++i, c <<= 8) + (out_buf.m_pBuf + out_buf.m_size - 16)[i] = (mz_uint8)(c >> 24); + /* compute final size of file, grab compressed data buffer and return */ + *pLen_out += 57; + MZ_FREE(pComp); + return out_buf.m_pBuf; +} +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out) +{ + /* Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's where #defined out) */ + return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, MZ_FALSE); +} + +#ifndef MINIZ_NO_MALLOC +/* Allocate the tdefl_compressor and tinfl_decompressor structures in C so that */ +/* non-C language bindings to tdefL_ and tinfl_ API don't need to worry about */ +/* structure size and allocation mechanism. */ +tdefl_compressor *tdefl_compressor_alloc() +{ + return (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); +} + +void tdefl_compressor_free(tdefl_compressor *pComp) +{ + MZ_FREE(pComp); +} +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#ifdef __cplusplus +} +#endif + /************************************************************************** + * + * Copyright 2013-2014 RAD Game Tools and Valve Software + * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + + + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- Low-level Decompression (completely independent from all compression API's) */ + +#define TINFL_MEMCPY(d, s, l) memcpy(d, s, l) +#define TINFL_MEMSET(p, c, l) memset(p, c, l) + +#define TINFL_CR_BEGIN \ + switch (r->m_state) \ + { \ + case 0: +#define TINFL_CR_RETURN(state_index, result) \ + do \ + { \ + status = result; \ + r->m_state = state_index; \ + goto common_exit; \ + case state_index:; \ + } \ + MZ_MACRO_END +#define TINFL_CR_RETURN_FOREVER(state_index, result) \ + do \ + { \ + for (;;) \ + { \ + TINFL_CR_RETURN(state_index, result); \ + } \ + } \ + MZ_MACRO_END +#define TINFL_CR_FINISH } + +#define TINFL_GET_BYTE(state_index, c) \ + do \ + { \ + while (pIn_buf_cur >= pIn_buf_end) \ + { \ + TINFL_CR_RETURN(state_index, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS); \ + } \ + c = *pIn_buf_cur++; \ + } \ + MZ_MACRO_END + +#define TINFL_NEED_BITS(state_index, n) \ + do \ + { \ + mz_uint c; \ + TINFL_GET_BYTE(state_index, c); \ + bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ + num_bits += 8; \ + } while (num_bits < (mz_uint)(n)) +#define TINFL_SKIP_BITS(state_index, n) \ + do \ + { \ + if (num_bits < (mz_uint)(n)) \ + { \ + TINFL_NEED_BITS(state_index, n); \ + } \ + bit_buf >>= (n); \ + num_bits -= (n); \ + } \ + MZ_MACRO_END +#define TINFL_GET_BITS(state_index, b, n) \ + do \ + { \ + if (num_bits < (mz_uint)(n)) \ + { \ + TINFL_NEED_BITS(state_index, n); \ + } \ + b = bit_buf & ((1 << (n)) - 1); \ + bit_buf >>= (n); \ + num_bits -= (n); \ + } \ + MZ_MACRO_END + +/* TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. */ +/* It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a */ +/* Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the */ +/* bit buffer contains >=15 bits (deflate's max. Huffman code size). */ +#define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \ + do \ + { \ + temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ + if (temp >= 0) \ + { \ + code_len = temp >> 9; \ + if ((code_len) && (num_bits >= code_len)) \ + break; \ + } \ + else if (num_bits > TINFL_FAST_LOOKUP_BITS) \ + { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do \ + { \ + temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while ((temp < 0) && (num_bits >= (code_len + 1))); \ + if (temp >= 0) \ + break; \ + } \ + TINFL_GET_BYTE(state_index, c); \ + bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ + num_bits += 8; \ + } while (num_bits < 15); + +/* TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read */ +/* beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully */ +/* decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. */ +/* The slow path is only executed at the very end of the input buffer. */ +/* v1.16: The original macro handled the case at the very end of the passed-in input buffer, but we also need to handle the case where the user passes in 1+zillion bytes */ +/* following the deflate data and our non-conservative read-ahead path won't kick in here on this code. This is much trickier. */ +#define TINFL_HUFF_DECODE(state_index, sym, pHuff) \ + do \ + { \ + int temp; \ + mz_uint code_len, c; \ + if (num_bits < 15) \ + { \ + if ((pIn_buf_end - pIn_buf_cur) < 2) \ + { \ + TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ + } \ + else \ + { \ + bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); \ + pIn_buf_cur += 2; \ + num_bits += 16; \ + } \ + } \ + if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ + code_len = temp >> 9, temp &= 511; \ + else \ + { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do \ + { \ + temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while (temp < 0); \ + } \ + sym = temp; \ + bit_buf >>= code_len; \ + num_bits -= code_len; \ + } \ + MZ_MACRO_END + +tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags) +{ + static const int s_length_base[31] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 }; + static const int s_length_extra[31] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0 }; + static const int s_dist_base[32] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0 }; + static const int s_dist_extra[32] = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 }; + static const mz_uint8 s_length_dezigzag[19] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + static const int s_min_table_sizes[3] = { 257, 1, 4 }; + + tinfl_status status = TINFL_STATUS_FAILED; + mz_uint32 num_bits, dist, counter, num_extra; + tinfl_bit_buf_t bit_buf; + const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; + mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size; + size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start; + + /* Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). */ + if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) + { + *pIn_buf_size = *pOut_buf_size = 0; + return TINFL_STATUS_BAD_PARAM; + } + + num_bits = r->m_num_bits; + bit_buf = r->m_bit_buf; + dist = r->m_dist; + counter = r->m_counter; + num_extra = r->m_num_extra; + dist_from_out_buf_start = r->m_dist_from_out_buf_start; + TINFL_CR_BEGIN + + bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; + r->m_z_adler32 = r->m_check_adler32 = 1; + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + TINFL_GET_BYTE(1, r->m_zhdr0); + TINFL_GET_BYTE(2, r->m_zhdr1); + counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); + if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); + if (counter) + { + TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); + } + } + + do + { + TINFL_GET_BITS(3, r->m_final, 3); + r->m_type = r->m_final >> 1; + if (r->m_type == 0) + { + TINFL_SKIP_BITS(5, num_bits & 7); + for (counter = 0; counter < 4; ++counter) + { + if (num_bits) + TINFL_GET_BITS(6, r->m_raw_header[counter], 8); + else + TINFL_GET_BYTE(7, r->m_raw_header[counter]); + } + if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) + { + TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); + } + while ((counter) && (num_bits)) + { + TINFL_GET_BITS(51, dist, 8); + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = (mz_uint8)dist; + counter--; + } + while (counter) + { + size_t n; + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); + } + while (pIn_buf_cur >= pIn_buf_end) + { + TINFL_CR_RETURN(38, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS); + } + n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter); + TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); + pIn_buf_cur += n; + pOut_buf_cur += n; + counter -= (mz_uint)n; + } + } + else if (r->m_type == 3) + { + TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED); + } + else + { + if (r->m_type == 1) + { + mz_uint8 *p = r->m_tables[0].m_code_size; + mz_uint i; + r->m_table_sizes[0] = 288; + r->m_table_sizes[1] = 32; + TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); + for (i = 0; i <= 143; ++i) + *p++ = 8; + for (; i <= 255; ++i) + *p++ = 9; + for (; i <= 279; ++i) + *p++ = 7; + for (; i <= 287; ++i) + *p++ = 8; + } + else + { + for (counter = 0; counter < 3; counter++) + { + TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); + r->m_table_sizes[counter] += s_min_table_sizes[counter]; + } + MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); + for (counter = 0; counter < r->m_table_sizes[2]; counter++) + { + mz_uint s; + TINFL_GET_BITS(14, s, 3); + r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; + } + r->m_table_sizes[2] = 19; + } + for (; (int)r->m_type >= 0; r->m_type--) + { + int tree_next, tree_cur; + tinfl_huff_table *pTable; + mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; + pTable = &r->m_tables[r->m_type]; + MZ_CLEAR_OBJ(total_syms); + MZ_CLEAR_OBJ(pTable->m_look_up); + MZ_CLEAR_OBJ(pTable->m_tree); + for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) + total_syms[pTable->m_code_size[i]]++; + used_syms = 0, total = 0; + next_code[0] = next_code[1] = 0; + for (i = 1; i <= 15; ++i) + { + used_syms += total_syms[i]; + next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); + } + if ((65536 != total) && (used_syms > 1)) + { + TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); + } + for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index) + { + mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; + if (!code_size) + continue; + cur_code = next_code[code_size]++; + for (l = code_size; l > 0; l--, cur_code >>= 1) + rev_code = (rev_code << 1) | (cur_code & 1); + if (code_size <= TINFL_FAST_LOOKUP_BITS) + { + mz_int16 k = (mz_int16)((code_size << 9) | sym_index); + while (rev_code < TINFL_FAST_LOOKUP_SIZE) + { + pTable->m_look_up[rev_code] = k; + rev_code += (1 << code_size); + } + continue; + } + if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) + { + pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; + tree_cur = tree_next; + tree_next -= 2; + } + rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); + for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) + { + tree_cur -= ((rev_code >>= 1) & 1); + if (!pTable->m_tree[-tree_cur - 1]) + { + pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; + tree_cur = tree_next; + tree_next -= 2; + } + else + tree_cur = pTable->m_tree[-tree_cur - 1]; + } + tree_cur -= ((rev_code >>= 1) & 1); + pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index; + } + if (r->m_type == 2) + { + for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]);) + { + mz_uint s; + TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); + if (dist < 16) + { + r->m_len_codes[counter++] = (mz_uint8)dist; + continue; + } + if ((dist == 16) && (!counter)) + { + TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); + } + num_extra = "\02\03\07"[dist - 16]; + TINFL_GET_BITS(18, s, num_extra); + s += "\03\03\013"[dist - 16]; + TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); + counter += s; + } + if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) + { + TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); + } + TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); + TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); + } + } + for (;;) + { + mz_uint8 *pSrc; + for (;;) + { + if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) + { + TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); + if (counter >= 256) + break; + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = (mz_uint8)counter; + } + else + { + int sym2; + mz_uint code_len; +#if TINFL_USE_64BIT_BITBUF + if (num_bits < 30) + { + bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 4; + num_bits += 32; + } +#else + if (num_bits < 15) + { + bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 2; + num_bits += 16; + } +#endif + if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; + do + { + sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; + } while (sym2 < 0); + } + counter = sym2; + bit_buf >>= code_len; + num_bits -= code_len; + if (counter & 256) + break; + +#if !TINFL_USE_64BIT_BITBUF + if (num_bits < 15) + { + bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 2; + num_bits += 16; + } +#endif + if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; + do + { + sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; + } while (sym2 < 0); + } + bit_buf >>= code_len; + num_bits -= code_len; + + pOut_buf_cur[0] = (mz_uint8)counter; + if (sym2 & 256) + { + pOut_buf_cur++; + counter = sym2; + break; + } + pOut_buf_cur[1] = (mz_uint8)sym2; + pOut_buf_cur += 2; + } + } + if ((counter &= 511) == 256) + break; + + num_extra = s_length_extra[counter - 257]; + counter = s_length_base[counter - 257]; + if (num_extra) + { + mz_uint extra_bits; + TINFL_GET_BITS(25, extra_bits, num_extra); + counter += extra_bits; + } + + TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); + num_extra = s_dist_extra[dist]; + dist = s_dist_base[dist]; + if (num_extra) + { + mz_uint extra_bits; + TINFL_GET_BITS(27, extra_bits, num_extra); + dist += extra_bits; + } + + dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; + if ((dist == 0 || dist > dist_from_out_buf_start || dist_from_out_buf_start == 0) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + { + TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); + } + + pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask); + + if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) + { + while (counter--) + { + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask]; + } + continue; + } +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES + else if ((counter >= 9) && (counter <= dist)) + { + const mz_uint8 *pSrc_end = pSrc + (counter & ~7); + do + { +#ifdef MINIZ_UNALIGNED_USE_MEMCPY + memcpy(pOut_buf_cur, pSrc, sizeof(mz_uint32)*2); +#else + ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0]; + ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1]; +#endif + pOut_buf_cur += 8; + } while ((pSrc += 8) < pSrc_end); + if ((counter &= 7) < 3) + { + if (counter) + { + pOut_buf_cur[0] = pSrc[0]; + if (counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + continue; + } + } +#endif + while(counter>2) + { + pOut_buf_cur[0] = pSrc[0]; + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur[2] = pSrc[2]; + pOut_buf_cur += 3; + pSrc += 3; + counter -= 3; + } + if (counter > 0) + { + pOut_buf_cur[0] = pSrc[0]; + if (counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + } + } + } while (!(r->m_final & 1)); + + /* Ensure byte alignment and put back any bytes from the bitbuf if we've looked ahead too far on gzip, or other Deflate streams followed by arbitrary data. */ + /* I'm being super conservative here. A number of simplifications can be made to the byte alignment part, and the Adler32 check shouldn't ever need to worry about reading from the bitbuf now. */ + TINFL_SKIP_BITS(32, num_bits & 7); + while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8)) + { + --pIn_buf_cur; + num_bits -= 8; + } + bit_buf &= (tinfl_bit_buf_t)((((mz_uint64)1) << num_bits) - (mz_uint64)1); + MZ_ASSERT(!num_bits); /* if this assert fires then we've read beyond the end of non-deflate/zlib streams with following data (such as gzip streams). */ + + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + for (counter = 0; counter < 4; ++counter) + { + mz_uint s; + if (num_bits) + TINFL_GET_BITS(41, s, 8); + else + TINFL_GET_BYTE(42, s); + r->m_z_adler32 = (r->m_z_adler32 << 8) | s; + } + } + TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE); + + TINFL_CR_FINISH + +common_exit: + /* As long as we aren't telling the caller that we NEED more input to make forward progress: */ + /* Put back any bytes from the bitbuf in case we've looked ahead too far on gzip, or other Deflate streams followed by arbitrary data. */ + /* We need to be very careful here to NOT push back any bytes we definitely know we need to make forward progress, though, or we'll lock the caller up into an inf loop. */ + if ((status != TINFL_STATUS_NEEDS_MORE_INPUT) && (status != TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS)) + { + while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8)) + { + --pIn_buf_cur; + num_bits -= 8; + } + } + r->m_num_bits = num_bits; + r->m_bit_buf = bit_buf & (tinfl_bit_buf_t)((((mz_uint64)1) << num_bits) - (mz_uint64)1); + r->m_dist = dist; + r->m_counter = counter; + r->m_num_extra = num_extra; + r->m_dist_from_out_buf_start = dist_from_out_buf_start; + *pIn_buf_size = pIn_buf_cur - pIn_buf_next; + *pOut_buf_size = pOut_buf_cur - pOut_buf_next; + if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0)) + { + const mz_uint8 *ptr = pOut_buf_next; + size_t buf_len = *pOut_buf_size; + mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; + size_t block_len = buf_len % 5552; + while (buf_len) + { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) + { + s1 += ptr[0], s2 += s1; + s1 += ptr[1], s2 += s1; + s1 += ptr[2], s2 += s1; + s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; + s1 += ptr[5], s2 += s1; + s1 += ptr[6], s2 += s1; + s1 += ptr[7], s2 += s1; + } + for (; i < block_len; ++i) + s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; + buf_len -= block_len; + block_len = 5552; + } + r->m_check_adler32 = (s2 << 16) + s1; + if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) + status = TINFL_STATUS_ADLER32_MISMATCH; + } + return status; +} + +/* Higher level helper functions. */ +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) +{ + tinfl_decompressor decomp; + void *pBuf = NULL, *pNew_buf; + size_t src_buf_ofs = 0, out_buf_capacity = 0; + *pOut_len = 0; + tinfl_init(&decomp); + for (;;) + { + size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; + tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8 *)pBuf, pBuf ? (mz_uint8 *)pBuf + *pOut_len : NULL, &dst_buf_size, + (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) + { + MZ_FREE(pBuf); + *pOut_len = 0; + return NULL; + } + src_buf_ofs += src_buf_size; + *pOut_len += dst_buf_size; + if (status == TINFL_STATUS_DONE) + break; + new_out_buf_capacity = out_buf_capacity * 2; + if (new_out_buf_capacity < 128) + new_out_buf_capacity = 128; + pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity); + if (!pNew_buf) + { + MZ_FREE(pBuf); + *pOut_len = 0; + return NULL; + } + pBuf = pNew_buf; + out_buf_capacity = new_out_buf_capacity; + } + return pBuf; +} + +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) +{ + tinfl_decompressor decomp; + tinfl_status status; + tinfl_init(&decomp); + status = tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf, &src_buf_len, (mz_uint8 *)pOut_buf, (mz_uint8 *)pOut_buf, &out_buf_len, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len; +} + +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + int result = 0; + tinfl_decompressor decomp; + mz_uint8 *pDict = (mz_uint8 *)MZ_MALLOC(TINFL_LZ_DICT_SIZE); + size_t in_buf_ofs = 0, dict_ofs = 0; + if (!pDict) + return TINFL_STATUS_FAILED; + tinfl_init(&decomp); + for (;;) + { + size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; + tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, + (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); + in_buf_ofs += in_buf_size; + if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) + break; + if (status != TINFL_STATUS_HAS_MORE_OUTPUT) + { + result = (status == TINFL_STATUS_DONE); + break; + } + dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); + } + MZ_FREE(pDict); + *pIn_buf_size = in_buf_ofs; + return result; +} + +#ifndef MINIZ_NO_MALLOC +tinfl_decompressor *tinfl_decompressor_alloc() +{ + tinfl_decompressor *pDecomp = (tinfl_decompressor *)MZ_MALLOC(sizeof(tinfl_decompressor)); + if (pDecomp) + tinfl_init(pDecomp); + return pDecomp; +} + +void tinfl_decompressor_free(tinfl_decompressor *pDecomp) +{ + MZ_FREE(pDecomp); +} +#endif + +#ifdef __cplusplus +} +#endif + /************************************************************************** + * + * Copyright 2013-2014 RAD Game Tools and Valve Software + * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC + * Copyright 2016 Martin Raiber + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + + +#ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- .ZIP archive reading */ + +#ifdef MINIZ_NO_STDIO +#define MZ_FILE void * +#else +#include <sys/stat.h> + +#if defined(_MSC_VER) || defined(__MINGW64__) +static FILE *mz_fopen(const char *pFilename, const char *pMode) +{ + FILE *pFile = NULL; + fopen_s(&pFile, pFilename, pMode); + return pFile; +} +static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) +{ + FILE *pFile = NULL; + if (freopen_s(&pFile, pPath, pMode, pStream)) + return NULL; + return pFile; +} +#ifndef MINIZ_NO_TIME +#include <sys/utime.h> +#endif +#define MZ_FOPEN mz_fopen +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 _ftelli64 +#define MZ_FSEEK64 _fseeki64 +#define MZ_FILE_STAT_STRUCT _stat64 +#define MZ_FILE_STAT _stat64 +#define MZ_FFLUSH fflush +#define MZ_FREOPEN mz_freopen +#define MZ_DELETE_FILE remove +#elif defined(__MINGW32__) +#ifndef MINIZ_NO_TIME +#include <sys/utime.h> +#endif +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftello64 +#define MZ_FSEEK64 fseeko64 +#define MZ_FILE_STAT_STRUCT _stat +#define MZ_FILE_STAT _stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#elif defined(__TINYC__) +#ifndef MINIZ_NO_TIME +#include <sys/utime.h> +#endif +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftell +#define MZ_FSEEK64 fseek +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#elif defined(__USE_LARGEFILE64) /* gcc, clang */ +#ifndef MINIZ_NO_TIME +#include <utime.h> +#endif +#define MZ_FOPEN(f, m) fopen64(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftello64 +#define MZ_FSEEK64 fseeko64 +#define MZ_FILE_STAT_STRUCT stat64 +#define MZ_FILE_STAT stat64 +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(p, m, s) freopen64(p, m, s) +#define MZ_DELETE_FILE remove +#elif defined(__APPLE__) +#ifndef MINIZ_NO_TIME +#include <utime.h> +#endif +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftello +#define MZ_FSEEK64 fseeko +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(p, m, s) freopen(p, m, s) +#define MZ_DELETE_FILE remove + +#else +#pragma message("Using fopen, ftello, fseeko, stat() etc. path for file I/O - this path may not support large files.") +#ifndef MINIZ_NO_TIME +#include <utime.h> +#endif +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#ifdef __STRICT_ANSI__ +#define MZ_FTELL64 ftell +#define MZ_FSEEK64 fseek +#else +#define MZ_FTELL64 ftello +#define MZ_FSEEK64 fseeko +#endif +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#endif /* #ifdef _MSC_VER */ +#endif /* #ifdef MINIZ_NO_STDIO */ + +#define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c)) + +/* Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff. */ +enum +{ + /* ZIP archive identifiers and record sizes */ + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, + MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, + + /* ZIP64 archive identifier and record sizes */ + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06064b50, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG = 0x07064b50, + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE = 56, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE = 20, + MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID = 0x0001, + MZ_ZIP_DATA_DESCRIPTOR_ID = 0x08074b50, + MZ_ZIP_DATA_DESCRIPTER_SIZE64 = 24, + MZ_ZIP_DATA_DESCRIPTER_SIZE32 = 16, + + /* Central directory header record offsets */ + MZ_ZIP_CDH_SIG_OFS = 0, + MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, + MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, + MZ_ZIP_CDH_BIT_FLAG_OFS = 8, + MZ_ZIP_CDH_METHOD_OFS = 10, + MZ_ZIP_CDH_FILE_TIME_OFS = 12, + MZ_ZIP_CDH_FILE_DATE_OFS = 14, + MZ_ZIP_CDH_CRC32_OFS = 16, + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, + MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, + MZ_ZIP_CDH_EXTRA_LEN_OFS = 30, + MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, + MZ_ZIP_CDH_DISK_START_OFS = 34, + MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, + MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42, + + /* Local directory header offsets */ + MZ_ZIP_LDH_SIG_OFS = 0, + MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, + MZ_ZIP_LDH_BIT_FLAG_OFS = 6, + MZ_ZIP_LDH_METHOD_OFS = 8, + MZ_ZIP_LDH_FILE_TIME_OFS = 10, + MZ_ZIP_LDH_FILE_DATE_OFS = 12, + MZ_ZIP_LDH_CRC32_OFS = 14, + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22, + MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, + MZ_ZIP_LDH_EXTRA_LEN_OFS = 28, + MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR = 1 << 3, + + /* End of central directory offsets */ + MZ_ZIP_ECDH_SIG_OFS = 0, + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8, + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, + MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, + MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, + MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, + + /* ZIP64 End of central directory locator offsets */ + MZ_ZIP64_ECDL_SIG_OFS = 0, /* 4 bytes */ + MZ_ZIP64_ECDL_NUM_DISK_CDIR_OFS = 4, /* 4 bytes */ + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS = 8, /* 8 bytes */ + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS = 16, /* 4 bytes */ + + /* ZIP64 End of central directory header offsets */ + MZ_ZIP64_ECDH_SIG_OFS = 0, /* 4 bytes */ + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS = 4, /* 8 bytes */ + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS = 12, /* 2 bytes */ + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS = 14, /* 2 bytes */ + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS = 16, /* 4 bytes */ + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS = 20, /* 4 bytes */ + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 24, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS = 32, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_SIZE_OFS = 40, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_OFS_OFS = 48, /* 8 bytes */ + MZ_ZIP_VERSION_MADE_BY_DOS_FILESYSTEM_ID = 0, + MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG = 0x10, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED = 1, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG = 32, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION = 64, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED = 8192, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8 = 1 << 11 +}; + +typedef struct +{ + void *m_p; + size_t m_size, m_capacity; + mz_uint m_element_size; +} mz_zip_array; + +struct mz_zip_internal_state_tag +{ + mz_zip_array m_central_dir; + mz_zip_array m_central_dir_offsets; + mz_zip_array m_sorted_central_dir_offsets; + + /* The flags passed in when the archive is initially opened. */ + uint32_t m_init_flags; + + /* MZ_TRUE if the archive has a zip64 end of central directory headers, etc. */ + mz_bool m_zip64; + + /* MZ_TRUE if we found zip64 extended info in the central directory (m_zip64 will also be slammed to true too, even if we didn't find a zip64 end of central dir header, etc.) */ + mz_bool m_zip64_has_extended_info_fields; + + /* These fields are used by the file, FILE, memory, and memory/heap read/write helpers. */ + MZ_FILE *m_pFile; + mz_uint64 m_file_archive_start_ofs; + + void *m_pMem; + size_t m_mem_size; + size_t m_mem_capacity; +}; + +#define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size + +#if defined(DEBUG) || defined(_DEBUG) +static MZ_FORCEINLINE mz_uint mz_zip_array_range_check(const mz_zip_array *pArray, mz_uint index) +{ + MZ_ASSERT(index < pArray->m_size); + return index; +} +#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[mz_zip_array_range_check(array_ptr, index)] +#else +#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index] +#endif + +static MZ_FORCEINLINE void mz_zip_array_init(mz_zip_array *pArray, mz_uint32 element_size) +{ + memset(pArray, 0, sizeof(mz_zip_array)); + pArray->m_element_size = element_size; +} + +static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, mz_zip_array *pArray) +{ + pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p); + memset(pArray, 0, sizeof(mz_zip_array)); +} + +static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, mz_zip_array *pArray, size_t min_new_capacity, mz_uint growing) +{ + void *pNew_p; + size_t new_capacity = min_new_capacity; + MZ_ASSERT(pArray->m_element_size); + if (pArray->m_capacity >= min_new_capacity) + return MZ_TRUE; + if (growing) + { + new_capacity = MZ_MAX(1, pArray->m_capacity); + while (new_capacity < min_new_capacity) + new_capacity *= 2; + } + if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity))) + return MZ_FALSE; + pArray->m_p = pNew_p; + pArray->m_capacity = new_capacity; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_capacity, mz_uint growing) +{ + if (new_capacity > pArray->m_capacity) + { + if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) + return MZ_FALSE; + } + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_size, mz_uint growing) +{ + if (new_size > pArray->m_capacity) + { + if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) + return MZ_FALSE; + } + pArray->m_size = new_size; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, mz_zip_array *pArray, size_t n) +{ + return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE); +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, mz_zip_array *pArray, const void *pElements, size_t n) +{ + size_t orig_size = pArray->m_size; + if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE)) + return MZ_FALSE; + if (n > 0) + memcpy((mz_uint8 *)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size); + return MZ_TRUE; +} + +#ifndef MINIZ_NO_TIME +static MZ_TIME_T mz_zip_dos_to_time_t(int dos_time, int dos_date) +{ + struct tm tm; + memset(&tm, 0, sizeof(tm)); + tm.tm_isdst = -1; + tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; + tm.tm_mon = ((dos_date >> 5) & 15) - 1; + tm.tm_mday = dos_date & 31; + tm.tm_hour = (dos_time >> 11) & 31; + tm.tm_min = (dos_time >> 5) & 63; + tm.tm_sec = (dos_time << 1) & 62; + return mktime(&tm); +} + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS +static void mz_zip_time_t_to_dos_time(MZ_TIME_T time, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) +{ +#ifdef _MSC_VER + struct tm tm_struct; + struct tm *tm = &tm_struct; + errno_t err = localtime_s(tm, &time); + if (err) + { + *pDOS_date = 0; + *pDOS_time = 0; + return; + } +#else + struct tm *tm = localtime(&time); +#endif /* #ifdef _MSC_VER */ + + *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1)); + *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday); +} +#endif /* MINIZ_NO_ARCHIVE_WRITING_APIS */ + +#ifndef MINIZ_NO_STDIO +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS +static mz_bool mz_zip_get_file_modified_time(const char *pFilename, MZ_TIME_T *pTime) +{ + struct MZ_FILE_STAT_STRUCT file_stat; + + /* On Linux with x86 glibc, this call will fail on large files (I think >= 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. */ + if (MZ_FILE_STAT(pFilename, &file_stat) != 0) + return MZ_FALSE; + + *pTime = file_stat.st_mtime; + + return MZ_TRUE; +} +#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS*/ + +static mz_bool mz_zip_set_file_times(const char *pFilename, MZ_TIME_T access_time, MZ_TIME_T modified_time) +{ + struct utimbuf t; + + memset(&t, 0, sizeof(t)); + t.actime = access_time; + t.modtime = modified_time; + + return !utime(pFilename, &t); +} +#endif /* #ifndef MINIZ_NO_STDIO */ +#endif /* #ifndef MINIZ_NO_TIME */ + +static MZ_FORCEINLINE mz_bool mz_zip_set_error(mz_zip_archive *pZip, mz_zip_error err_num) +{ + if (pZip) + pZip->m_last_error = err_num; + return MZ_FALSE; +} + +static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint flags) +{ + (void)flags; + if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!pZip->m_pAlloc) + pZip->m_pAlloc = miniz_def_alloc_func; + if (!pZip->m_pFree) + pZip->m_pFree = miniz_def_free_func; + if (!pZip->m_pRealloc) + pZip->m_pRealloc = miniz_def_realloc_func; + + pZip->m_archive_size = 0; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + pZip->m_last_error = MZ_ZIP_NO_ERROR; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + pZip->m_pState->m_init_flags = flags; + pZip->m_pState->m_zip64 = MZ_FALSE; + pZip->m_pState->m_zip64_has_extended_info_fields = MZ_FALSE; + + pZip->m_zip_mode = MZ_ZIP_MODE_READING; + + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, mz_uint r_index) +{ + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; + const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index)); + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; + pR++; + } + return (pL == pE) ? (l_len < r_len) : (l < r); +} + +#define MZ_SWAP_UINT32(a, b) \ + do \ + { \ + mz_uint32 t = a; \ + a = b; \ + b = t; \ + } \ + MZ_MACRO_END + +/* Heap sort of lowercased filenames, used to help accelerate plain central directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.) */ +static void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices; + mz_uint32 start, end; + const mz_uint32 size = pZip->m_total_files; + + if (size <= 1U) + return; + + pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + + start = (size - 2U) >> 1U; + for (;;) + { + mz_uint64 child, root = start; + for (;;) + { + if ((child = (root << 1U) + 1U) >= size) + break; + child += (((child + 1U) < size) && (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U]))); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); + root = child; + } + if (!start) + break; + start--; + } + + end = size - 1; + while (end > 0) + { + mz_uint64 child, root = 0; + MZ_SWAP_UINT32(pIndices[end], pIndices[0]); + for (;;) + { + if ((child = (root << 1U) + 1U) >= end) + break; + child += (((child + 1U) < end) && mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U])); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); + root = child; + } + end--; + } +} + +static mz_bool mz_zip_reader_locate_header_sig(mz_zip_archive *pZip, mz_uint32 record_sig, mz_uint32 record_size, mz_int64 *pOfs) +{ + mz_int64 cur_file_ofs; + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; + mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + + /* Basic sanity checks - reject files which are too small */ + if (pZip->m_archive_size < record_size) + return MZ_FALSE; + + /* Find the record by scanning the file from the end towards the beginning. */ + cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0); + for (;;) + { + int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); + + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n) + return MZ_FALSE; + + for (i = n - 4; i >= 0; --i) + { + mz_uint s = MZ_READ_LE32(pBuf + i); + if (s == record_sig) + { + if ((pZip->m_archive_size - (cur_file_ofs + i)) >= record_size) + break; + } + } + + if (i >= 0) + { + cur_file_ofs += i; + break; + } + + /* Give up if we've searched the entire file, or we've gone back "too far" (~64kb) */ + if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (MZ_UINT16_MAX + record_size))) + return MZ_FALSE; + + cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); + } + + *pOfs = cur_file_ofs; + return MZ_TRUE; +} + +static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint flags) +{ + mz_uint cdir_size = 0, cdir_entries_on_this_disk = 0, num_this_disk = 0, cdir_disk_index = 0; + mz_uint64 cdir_ofs = 0; + mz_int64 cur_file_ofs = 0; + const mz_uint8 *p; + + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; + mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); + mz_uint32 zip64_end_of_central_dir_locator_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pZip64_locator = (mz_uint8 *)zip64_end_of_central_dir_locator_u32; + + mz_uint32 zip64_end_of_central_dir_header_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pZip64_end_of_central_dir = (mz_uint8 *)zip64_end_of_central_dir_header_u32; + + mz_uint64 zip64_end_of_central_dir_ofs = 0; + + /* Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there. */ + if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (!mz_zip_reader_locate_header_sig(pZip, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE, &cur_file_ofs)) + return mz_zip_set_error(pZip, MZ_ZIP_FAILED_FINDING_CENTRAL_DIR); + + /* Read and verify the end of central directory record. */ + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (cur_file_ofs >= (MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) + { + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs - MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE, pZip64_locator, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) + { + if (MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG) + { + zip64_end_of_central_dir_ofs = MZ_READ_LE64(pZip64_locator + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS); + if (zip64_end_of_central_dir_ofs > (pZip->m_archive_size - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (pZip->m_pRead(pZip->m_pIO_opaque, zip64_end_of_central_dir_ofs, pZip64_end_of_central_dir, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) + { + if (MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG) + { + pZip->m_pState->m_zip64 = MZ_TRUE; + } + } + } + } + } + + pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS); + cdir_entries_on_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); + num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); + cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); + cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS); + cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); + + if (pZip->m_pState->m_zip64) + { + mz_uint32 zip64_total_num_of_disks = MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS); + mz_uint64 zip64_cdir_total_entries = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS); + mz_uint64 zip64_cdir_total_entries_on_this_disk = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); + mz_uint64 zip64_size_of_end_of_central_dir_record = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS); + mz_uint64 zip64_size_of_central_directory = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_SIZE_OFS); + + if (zip64_size_of_end_of_central_dir_record < (MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - 12)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (zip64_total_num_of_disks != 1U) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + /* Check for miniz's practical limits */ + if (zip64_cdir_total_entries > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + pZip->m_total_files = (mz_uint32)zip64_cdir_total_entries; + + if (zip64_cdir_total_entries_on_this_disk > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + cdir_entries_on_this_disk = (mz_uint32)zip64_cdir_total_entries_on_this_disk; + + /* Check for miniz's current practical limits (sorry, this should be enough for millions of files) */ + if (zip64_size_of_central_directory > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + cdir_size = (mz_uint32)zip64_size_of_central_directory; + + num_this_disk = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS); + + cdir_disk_index = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS); + + cdir_ofs = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_OFS_OFS); + } + + if (pZip->m_total_files != cdir_entries_on_this_disk) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1))) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (cdir_size < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pZip->m_central_directory_file_ofs = cdir_ofs; + + if (pZip->m_total_files) + { + mz_uint i, n; + /* Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and possibly another to hold the sorted indices. */ + if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) || + (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (sort_central_dir) + { + if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + /* Now create an index into the central directory file records, do some basic sanity checking on each record */ + p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p; + for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) + { + mz_uint total_header_size, disk_index, bit_flags, filename_size, ext_data_size; + mz_uint64 comp_size, decomp_size, local_header_ofs; + + if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p); + + if (sort_central_dir) + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i; + + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + filename_size = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + ext_data_size = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); + + if ((!pZip->m_pState->m_zip64_has_extended_info_fields) && + (ext_data_size) && + (MZ_MAX(MZ_MAX(comp_size, decomp_size), local_header_ofs) == MZ_UINT32_MAX)) + { + /* Attempt to find zip64 extended information field in the entry's extra data */ + mz_uint32 extra_size_remaining = ext_data_size; + + if (extra_size_remaining) + { + const mz_uint8 *pExtra_data; + void* buf = NULL; + + if (MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + ext_data_size > n) + { + buf = MZ_MALLOC(ext_data_size); + if(buf==NULL) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size, buf, ext_data_size) != ext_data_size) + { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + pExtra_data = (mz_uint8*)buf; + } + else + { + pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size; + } + + do + { + mz_uint32 field_id; + mz_uint32 field_data_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + + if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining) + { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + /* Ok, the archive didn't have any zip64 headers but it uses a zip64 extended information field so mark it as zip64 anyway (this can occur with infozip's zip util when it reads compresses files from stdin). */ + pZip->m_pState->m_zip64 = MZ_TRUE; + pZip->m_pState->m_zip64_has_extended_info_fields = MZ_TRUE; + break; + } + + pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; + extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; + } while (extra_size_remaining); + + MZ_FREE(buf); + } + } + + /* I've seen archives that aren't marked as zip64 that uses zip64 ext data, argh */ + if ((comp_size != MZ_UINT32_MAX) && (decomp_size != MZ_UINT32_MAX)) + { + if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS); + if ((disk_index == MZ_UINT16_MAX) || ((disk_index != num_this_disk) && (disk_index != 1))) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (comp_size != MZ_UINT32_MAX) + { + if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + bit_flags = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + if (bit_flags & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + n -= total_header_size; + p += total_header_size; + } + } + + if (sort_central_dir) + mz_zip_reader_sort_central_dir_offsets_by_filename(pZip); + + return MZ_TRUE; +} + +void mz_zip_zero_struct(mz_zip_archive *pZip) +{ + if (pZip) + MZ_CLEAR_OBJ(*pZip); +} + +static mz_bool mz_zip_reader_end_internal(mz_zip_archive *pZip, mz_bool set_last_error) +{ + mz_bool status = MZ_TRUE; + + if (!pZip) + return MZ_FALSE; + + if ((!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + { + if (set_last_error) + pZip->m_last_error = MZ_ZIP_INVALID_PARAMETER; + + return MZ_FALSE; + } + + if (pZip->m_pState) + { + mz_zip_internal_state *pState = pZip->m_pState; + pZip->m_pState = NULL; + + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) + { + if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) + { + if (MZ_FCLOSE(pState->m_pFile) == EOF) + { + if (set_last_error) + pZip->m_last_error = MZ_ZIP_FILE_CLOSE_FAILED; + status = MZ_FALSE; + } + } + pState->m_pFile = NULL; + } +#endif /* #ifndef MINIZ_NO_STDIO */ + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + } + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + + return status; +} + +mz_bool mz_zip_reader_end(mz_zip_archive *pZip) +{ + return mz_zip_reader_end_internal(pZip, MZ_TRUE); +} +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags) +{ + if ((!pZip) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + + pZip->m_zip_type = MZ_ZIP_TYPE_USER; + pZip->m_archive_size = size; + + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + size_t s = (file_ofs >= pZip->m_archive_size) ? 0 : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n); + memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s); + return s; +} + +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags) +{ + if (!pMem) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + + pZip->m_zip_type = MZ_ZIP_TYPE_MEMORY; + pZip->m_archive_size = size; + pZip->m_pRead = mz_zip_mem_read_func; + pZip->m_pIO_opaque = pZip; + pZip->m_pNeeds_keepalive = NULL; + +#ifdef __cplusplus + pZip->m_pState->m_pMem = const_cast<void *>(pMem); +#else + pZip->m_pState->m_pMem = (void *)pMem; +#endif + + pZip->m_pState->m_mem_size = size; + + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + + file_ofs += pZip->m_pState->m_file_archive_start_ofs; + + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + + return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags) +{ + return mz_zip_reader_init_file_v2(pZip, pFilename, flags, 0, 0); +} + +mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size) +{ + mz_uint64 file_size; + MZ_FILE *pFile; + + if ((!pZip) || (!pFilename) || ((archive_size) && (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE))) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pFile = MZ_FOPEN(pFilename, "rb"); + if (!pFile) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + + file_size = archive_size; + if (!file_size) + { + if (MZ_FSEEK64(pFile, 0, SEEK_END)) + { + MZ_FCLOSE(pFile); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); + } + + file_size = MZ_FTELL64(pFile); + } + + /* TODO: Better sanity check archive_size and the # of actual remaining bytes */ + + if (file_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + { + MZ_FCLOSE(pFile); + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + } + + if (!mz_zip_reader_init_internal(pZip, flags)) + { + MZ_FCLOSE(pFile); + return MZ_FALSE; + } + + pZip->m_zip_type = MZ_ZIP_TYPE_FILE; + pZip->m_pRead = mz_zip_file_read_func; + pZip->m_pIO_opaque = pZip; + pZip->m_pState->m_pFile = pFile; + pZip->m_archive_size = file_size; + pZip->m_pState->m_file_archive_start_ofs = file_start_ofs; + + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags) +{ + mz_uint64 cur_file_ofs; + + if ((!pZip) || (!pFile)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + + cur_file_ofs = MZ_FTELL64(pFile); + + if (!archive_size) + { + if (MZ_FSEEK64(pFile, 0, SEEK_END)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); + + archive_size = MZ_FTELL64(pFile) - cur_file_ofs; + + if (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + } + + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + + pZip->m_zip_type = MZ_ZIP_TYPE_CFILE; + pZip->m_pRead = mz_zip_file_read_func; + + pZip->m_pIO_opaque = pZip; + pZip->m_pState->m_pFile = pFile; + pZip->m_archive_size = archive_size; + pZip->m_pState->m_file_archive_start_ofs = cur_file_ofs; + + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +#endif /* #ifndef MINIZ_NO_STDIO */ + +static MZ_FORCEINLINE const mz_uint8 *mz_zip_get_cdh(mz_zip_archive *pZip, mz_uint file_index) +{ + if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files)) + return NULL; + return &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); +} + +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint m_bit_flag; + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + return (m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION)) != 0; +} + +mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint bit_flag; + mz_uint method; + + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); + bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + + if ((method != 0) && (method != MZ_DEFLATED)) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + return MZ_FALSE; + } + + if (bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION)) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + return MZ_FALSE; + } + + if (bit_flag & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint filename_len, attribute_mapping_id, external_attr; + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_len) + { + if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/') + return MZ_TRUE; + } + + /* Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct. */ + /* Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field. */ + /* FIXME: Remove this check? Is it necessary - we already check the filename. */ + attribute_mapping_id = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS) >> 8; + (void)attribute_mapping_id; + + external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + if ((external_attr & MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG) != 0) + { + return MZ_TRUE; + } + + return MZ_FALSE; +} + +static mz_bool mz_zip_file_stat_internal(mz_zip_archive *pZip, mz_uint file_index, const mz_uint8 *pCentral_dir_header, mz_zip_archive_file_stat *pStat, mz_bool *pFound_zip64_extra_data) +{ + mz_uint n; + const mz_uint8 *p = pCentral_dir_header; + + if (pFound_zip64_extra_data) + *pFound_zip64_extra_data = MZ_FALSE; + + if ((!p) || (!pStat)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* Extract fields from the central directory record. */ + pStat->m_file_index = file_index; + pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index); + pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS); + pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS); + pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); +#ifndef MINIZ_NO_TIME + pStat->m_time = mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS)); +#endif + pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS); + pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS); + pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + + /* Copy as much of the filename and comment as possible. */ + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1); + memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pStat->m_filename[n] = '\0'; + + n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); + n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1); + pStat->m_comment_size = n; + memcpy(pStat->m_comment, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), n); + pStat->m_comment[n] = '\0'; + + /* Set some flags for convienance */ + pStat->m_is_directory = mz_zip_reader_is_file_a_directory(pZip, file_index); + pStat->m_is_encrypted = mz_zip_reader_is_file_encrypted(pZip, file_index); + pStat->m_is_supported = mz_zip_reader_is_file_supported(pZip, file_index); + + /* See if we need to read any zip64 extended information fields. */ + /* Confusingly, these zip64 fields can be present even on non-zip64 archives (Debian zip on a huge files from stdin piped to stdout creates them). */ + if (MZ_MAX(MZ_MAX(pStat->m_comp_size, pStat->m_uncomp_size), pStat->m_local_header_ofs) == MZ_UINT32_MAX) + { + /* Attempt to find zip64 extended information field in the entry's extra data */ + mz_uint32 extra_size_remaining = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); + + if (extra_size_remaining) + { + const mz_uint8 *pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + + do + { + mz_uint32 field_id; + mz_uint32 field_data_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + + if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + const mz_uint8 *pField_data = pExtra_data + sizeof(mz_uint16) * 2; + mz_uint32 field_data_remaining = field_data_size; + + if (pFound_zip64_extra_data) + *pFound_zip64_extra_data = MZ_TRUE; + + if (pStat->m_uncomp_size == MZ_UINT32_MAX) + { + if (field_data_remaining < sizeof(mz_uint64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pStat->m_uncomp_size = MZ_READ_LE64(pField_data); + pField_data += sizeof(mz_uint64); + field_data_remaining -= sizeof(mz_uint64); + } + + if (pStat->m_comp_size == MZ_UINT32_MAX) + { + if (field_data_remaining < sizeof(mz_uint64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pStat->m_comp_size = MZ_READ_LE64(pField_data); + pField_data += sizeof(mz_uint64); + field_data_remaining -= sizeof(mz_uint64); + } + + if (pStat->m_local_header_ofs == MZ_UINT32_MAX) + { + if (field_data_remaining < sizeof(mz_uint64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pStat->m_local_header_ofs = MZ_READ_LE64(pField_data); + pField_data += sizeof(mz_uint64); + field_data_remaining -= sizeof(mz_uint64); + } + + break; + } + + pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; + extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; + } while (extra_size_remaining); + } + } + + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_string_equal(const char *pA, const char *pB, mz_uint len, mz_uint flags) +{ + mz_uint i; + if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE) + return 0 == memcmp(pA, pB, len); + for (i = 0; i < len; ++i) + if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i])) + return MZ_FALSE; + return MZ_TRUE; +} + +static MZ_FORCEINLINE int mz_zip_filename_compare(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, const char *pR, mz_uint r_len) +{ + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; + pR++; + } + return (pL == pE) ? (int)(l_len - r_len) : (l - r); +} + +static mz_bool mz_zip_locate_file_binary_search(mz_zip_archive *pZip, const char *pFilename, mz_uint32 *pIndex) +{ + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const uint32_t size = pZip->m_total_files; + const mz_uint filename_len = (mz_uint)strlen(pFilename); + + if (pIndex) + *pIndex = 0; + + if (size) + { + /* yes I could use uint32_t's, but then we would have to add some special case checks in the loop, argh, and */ + /* honestly the major expense here on 32-bit CPU's will still be the filename compare */ + mz_int64 l = 0, h = (mz_int64)size - 1; + + while (l <= h) + { + mz_int64 m = l + ((h - l) >> 1); + uint32_t file_index = pIndices[(uint32_t)m]; + + int comp = mz_zip_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len); + if (!comp) + { + if (pIndex) + *pIndex = file_index; + return MZ_TRUE; + } + else if (comp < 0) + l = m + 1; + else + h = m - 1; + } + } + + return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND); +} + +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags) +{ + mz_uint32 index; + if (!mz_zip_reader_locate_file_v2(pZip, pName, pComment, flags, &index)) + return -1; + else + return (int)index; +} + +mz_bool mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *pIndex) +{ + mz_uint file_index; + size_t name_len, comment_len; + + if (pIndex) + *pIndex = 0; + + if ((!pZip) || (!pZip->m_pState) || (!pName)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* See if we can use a binary search */ + if (((pZip->m_pState->m_init_flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0) && + (pZip->m_zip_mode == MZ_ZIP_MODE_READING) && + ((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size)) + { + return mz_zip_locate_file_binary_search(pZip, pName, pIndex); + } + + /* Locate the entry by scanning the entire central directory */ + name_len = strlen(pName); + if (name_len > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + comment_len = pComment ? strlen(pComment) : 0; + if (comment_len > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + for (file_index = 0; file_index < pZip->m_total_files; file_index++) + { + const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); + mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); + const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + if (filename_len < name_len) + continue; + if (comment_len) + { + mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS); + const char *pFile_comment = pFilename + filename_len + file_extra_len; + if ((file_comment_len != comment_len) || (!mz_zip_string_equal(pComment, pFile_comment, file_comment_len, flags))) + continue; + } + if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len)) + { + int ofs = filename_len - 1; + do + { + if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':')) + break; + } while (--ofs >= 0); + ofs++; + pFilename += ofs; + filename_len -= ofs; + } + if ((filename_len == name_len) && (mz_zip_string_equal(pName, pFilename, filename_len, flags))) + { + if (pIndex) + *pIndex = file_index; + return MZ_TRUE; + } + } + + return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND); +} + +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +{ + int status = TINFL_STATUS_DONE; + mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail; + mz_zip_archive_file_stat file_stat; + void *pRead_buf; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + tinfl_decompressor inflator; + + if ((!pZip) || (!pZip->m_pState) || ((buf_size) && (!pBuf)) || ((user_read_buf_size) && (!pUser_read_buf)) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + /* A directory or zero length file */ + if ((file_stat.m_is_directory) || (!file_stat.m_comp_size)) + return MZ_TRUE; + + /* Encryption and patch files are not supported. */ + if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + /* This function only supports decompressing stored and deflate. */ + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + + /* Ensure supplied output buffer is large enough. */ + needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; + if (buf_size < needed_size) + return mz_zip_set_error(pZip, MZ_ZIP_BUF_TOO_SMALL); + + /* Read and parse the local directory entry. */ + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) + { + /* The file is stored or the caller has requested the compressed data. */ + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) == 0) + { + if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32) + return mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED); + } +#endif + + return MZ_TRUE; + } + + /* Decompress the file either directly from memory or from a file input buffer. */ + tinfl_init(&inflator); + + if (pZip->m_pState->m_pMem) + { + /* Read directly from the archive in memory. */ + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } + else if (pUser_read_buf) + { + /* Use a user provided read buffer. */ + if (!user_read_buf_size) + return MZ_FALSE; + pRead_buf = (mz_uint8 *)pUser_read_buf; + read_buf_size = user_read_buf_size; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + else + { + /* Temporarily allocate a read buffer. */ + read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); + if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + do + { + /* The size_t cast here should be OK because we've verified that the output buffer is >= file_stat.m_uncomp_size above */ + size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress(&inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0)); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + out_buf_ofs += out_buf_size; + } while (status == TINFL_STATUS_NEEDS_MORE_INPUT); + + if (status == TINFL_STATUS_DONE) + { + /* Make sure the entire file was decompressed, and check its CRC. */ + if (out_buf_ofs != file_stat.m_uncomp_size) + { + mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); + status = TINFL_STATUS_FAILED; + } +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + else if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32) + { + mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED); + status = TINFL_STATUS_FAILED; + } +#endif + } + + if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf)) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) + return MZ_FALSE; + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size); +} + +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags) +{ + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, NULL, 0); +} + +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags) +{ + return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0); +} + +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags) +{ + mz_uint64 comp_size, uncomp_size, alloc_size; + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + void *pBuf; + + if (pSize) + *pSize = 0; + + if (!p) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return NULL; + } + + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + + alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size; + if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) + { + mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + return NULL; + } + + if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + return NULL; + } + + if (!mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, flags)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return NULL; + } + + if (pSize) + *pSize = (size_t)alloc_size; + return pBuf; +} + +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) + { + if (pSize) + *pSize = 0; + return MZ_FALSE; + } + return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags); +} + +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) +{ + int status = TINFL_STATUS_DONE; +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + mz_uint file_crc32 = MZ_CRC32_INIT; +#endif + mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, cur_file_ofs; + mz_zip_archive_file_stat file_stat; + void *pRead_buf = NULL; + void *pWrite_buf = NULL; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + + if ((!pZip) || (!pZip->m_pState) || (!pCallback) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + /* A directory or zero length file */ + if ((file_stat.m_is_directory) || (!file_stat.m_comp_size)) + return MZ_TRUE; + + /* Encryption and patch files are not supported. */ + if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + /* This function only supports decompressing stored and deflate. */ + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + + /* Read and do some minimal validation of the local directory entry (this doesn't crack the zip64 stuff, which we already have from the central dir) */ + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + /* Decompress the file either directly from memory or from a file input buffer. */ + if (pZip->m_pState->m_pMem) + { + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } + else + { + read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) + { + /* The file is stored or the caller has requested the compressed data. */ + if (pZip->m_pState->m_pMem) + { + if (((sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)file_stat.m_comp_size) != file_stat.m_comp_size) + { + mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); + status = TINFL_STATUS_FAILED; + } + else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)file_stat.m_comp_size); +#endif + } + + cur_file_ofs += file_stat.m_comp_size; + out_buf_ofs += file_stat.m_comp_size; + comp_remaining = 0; + } + else + { + while (comp_remaining) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { + file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail); + } +#endif + + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + + cur_file_ofs += read_buf_avail; + out_buf_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + } + } + } + else + { + tinfl_decompressor inflator; + tinfl_init(&inflator); + + if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + status = TINFL_STATUS_FAILED; + } + else + { + do + { + mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress(&inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + + if (out_buf_size) + { + if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != out_buf_size) + { + mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + file_crc32 = (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size); +#endif + if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size) + { + mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + } + } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || (status == TINFL_STATUS_HAS_MORE_OUTPUT)); + } + } + + if ((status == TINFL_STATUS_DONE) && (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) + { + /* Make sure the entire file was decompressed, and check its CRC. */ + if (out_buf_ofs != file_stat.m_uncomp_size) + { + mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); + status = TINFL_STATUS_FAILED; + } +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + else if (file_crc32 != file_stat.m_crc32) + { + mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); + status = TINFL_STATUS_FAILED; + } +#endif + } + + if (!pZip->m_pState->m_pMem) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + + if (pWrite_buf) + pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) + return MZ_FALSE; + + return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, flags); +} + +mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags) +{ + mz_zip_reader_extract_iter_state *pState; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + + /* Argument sanity check */ + if ((!pZip) || (!pZip->m_pState)) + return NULL; + + /* Allocate an iterator status structure */ + pState = (mz_zip_reader_extract_iter_state*)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_reader_extract_iter_state)); + if (!pState) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + return NULL; + } + + /* Fetch file details */ + if (!mz_zip_reader_file_stat(pZip, file_index, &pState->file_stat)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + /* Encryption and patch files are not supported. */ + if (pState->file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + /* This function only supports decompressing stored and deflate. */ + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (pState->file_stat.m_method != 0) && (pState->file_stat.m_method != MZ_DEFLATED)) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + /* Init state - save args */ + pState->pZip = pZip; + pState->flags = flags; + + /* Init state - reset variables to defaults */ + pState->status = TINFL_STATUS_DONE; +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + pState->file_crc32 = MZ_CRC32_INIT; +#endif + pState->read_buf_ofs = 0; + pState->out_buf_ofs = 0; + pState->pRead_buf = NULL; + pState->pWrite_buf = NULL; + pState->out_blk_remain = 0; + + /* Read and parse the local directory entry. */ + pState->cur_file_ofs = pState->file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, pState->cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + pState->cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((pState->cur_file_ofs + pState->file_stat.m_comp_size) > pZip->m_archive_size) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + /* Decompress the file either directly from memory or from a file input buffer. */ + if (pZip->m_pState->m_pMem) + { + pState->pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + pState->cur_file_ofs; + pState->read_buf_size = pState->read_buf_avail = pState->file_stat.m_comp_size; + pState->comp_remaining = pState->file_stat.m_comp_size; + } + else + { + if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method))) + { + /* Decompression required, therefore intermediate read buffer required */ + pState->read_buf_size = MZ_MIN(pState->file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); + if (NULL == (pState->pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)pState->read_buf_size))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + } + else + { + /* Decompression not required - we will be reading directly into user buffer, no temp buf required */ + pState->read_buf_size = 0; + } + pState->read_buf_avail = 0; + pState->comp_remaining = pState->file_stat.m_comp_size; + } + + if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method))) + { + /* Decompression required, init decompressor */ + tinfl_init( &pState->inflator ); + + /* Allocate write buffer */ + if (NULL == (pState->pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + if (pState->pRead_buf) + pZip->m_pFree(pZip->m_pAlloc_opaque, pState->pRead_buf); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + } + + return pState; +} + +mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags) +{ + mz_uint32 file_index; + + /* Locate file index by name */ + if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) + return NULL; + + /* Construct iterator */ + return mz_zip_reader_extract_iter_new(pZip, file_index, flags); +} + +size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size) +{ + size_t copied_to_caller = 0; + + /* Argument sanity check */ + if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState) || (!pvBuf)) + return 0; + + if ((pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method)) + { + /* The file is stored or the caller has requested the compressed data, calc amount to return. */ + copied_to_caller = (size_t)MZ_MIN( buf_size, pState->comp_remaining ); + + /* Zip is in memory....or requires reading from a file? */ + if (pState->pZip->m_pState->m_pMem) + { + /* Copy data to caller's buffer */ + memcpy( pvBuf, pState->pRead_buf, copied_to_caller ); + pState->pRead_buf = ((mz_uint8*)pState->pRead_buf) + copied_to_caller; + } + else + { + /* Read directly into caller's buffer */ + if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pvBuf, copied_to_caller) != copied_to_caller) + { + /* Failed to read all that was asked for, flag failure and alert user */ + mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED); + pState->status = TINFL_STATUS_FAILED; + copied_to_caller = 0; + } + } + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + /* Compute CRC if not returning compressed data only */ + if (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, (const mz_uint8 *)pvBuf, copied_to_caller); +#endif + + /* Advance offsets, dec counters */ + pState->cur_file_ofs += copied_to_caller; + pState->out_buf_ofs += copied_to_caller; + pState->comp_remaining -= copied_to_caller; + } + else + { + do + { + /* Calc ptr to write buffer - given current output pos and block size */ + mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pState->pWrite_buf + (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + + /* Calc max output size - given current output pos and block size */ + size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + + if (!pState->out_blk_remain) + { + /* Read more data from file if none available (and reading from file) */ + if ((!pState->read_buf_avail) && (!pState->pZip->m_pState->m_pMem)) + { + /* Calc read size */ + pState->read_buf_avail = MZ_MIN(pState->read_buf_size, pState->comp_remaining); + if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pState->pRead_buf, (size_t)pState->read_buf_avail) != pState->read_buf_avail) + { + mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED); + pState->status = TINFL_STATUS_FAILED; + break; + } + + /* Advance offsets, dec counters */ + pState->cur_file_ofs += pState->read_buf_avail; + pState->comp_remaining -= pState->read_buf_avail; + pState->read_buf_ofs = 0; + } + + /* Perform decompression */ + in_buf_size = (size_t)pState->read_buf_avail; + pState->status = tinfl_decompress(&pState->inflator, (const mz_uint8 *)pState->pRead_buf + pState->read_buf_ofs, &in_buf_size, (mz_uint8 *)pState->pWrite_buf, pWrite_buf_cur, &out_buf_size, pState->comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); + pState->read_buf_avail -= in_buf_size; + pState->read_buf_ofs += in_buf_size; + + /* Update current output block size remaining */ + pState->out_blk_remain = out_buf_size; + } + + if (pState->out_blk_remain) + { + /* Calc amount to return. */ + size_t to_copy = MZ_MIN( (buf_size - copied_to_caller), pState->out_blk_remain ); + + /* Copy data to caller's buffer */ + memcpy( (uint8_t*)pvBuf + copied_to_caller, pWrite_buf_cur, to_copy ); + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + /* Perform CRC */ + pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, pWrite_buf_cur, to_copy); +#endif + + /* Decrement data consumed from block */ + pState->out_blk_remain -= to_copy; + + /* Inc output offset, while performing sanity check */ + if ((pState->out_buf_ofs += to_copy) > pState->file_stat.m_uncomp_size) + { + mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED); + pState->status = TINFL_STATUS_FAILED; + break; + } + + /* Increment counter of data copied to caller */ + copied_to_caller += to_copy; + } + } while ( (copied_to_caller < buf_size) && ((pState->status == TINFL_STATUS_NEEDS_MORE_INPUT) || (pState->status == TINFL_STATUS_HAS_MORE_OUTPUT)) ); + } + + /* Return how many bytes were copied into user buffer */ + return copied_to_caller; +} + +mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState) +{ + int status; + + /* Argument sanity check */ + if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState)) + return MZ_FALSE; + + /* Was decompression completed and requested? */ + if ((pState->status == TINFL_STATUS_DONE) && (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) + { + /* Make sure the entire file was decompressed, and check its CRC. */ + if (pState->out_buf_ofs != pState->file_stat.m_uncomp_size) + { + mz_zip_set_error(pState->pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); + pState->status = TINFL_STATUS_FAILED; + } +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + else if (pState->file_crc32 != pState->file_stat.m_crc32) + { + mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED); + pState->status = TINFL_STATUS_FAILED; + } +#endif + } + + /* Free buffers */ + if (!pState->pZip->m_pState->m_pMem) + pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pRead_buf); + if (pState->pWrite_buf) + pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pWrite_buf); + + /* Save status */ + status = pState->status; + + /* Free context */ + pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState); + + return status == TINFL_STATUS_DONE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n) +{ + (void)ofs; + + return MZ_FWRITE(pBuf, 1, n, (MZ_FILE *)pOpaque); +} + +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags) +{ + mz_bool status; + mz_zip_archive_file_stat file_stat; + MZ_FILE *pFile; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + if ((file_stat.m_is_directory) || (!file_stat.m_is_supported)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); + + pFile = MZ_FOPEN(pDst_filename, "wb"); + if (!pFile) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + + status = mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); + + if (MZ_FCLOSE(pFile) == EOF) + { + if (status) + mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); + + status = MZ_FALSE; + } + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO) + if (status) + mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time); +#endif + + return status; +} + +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index)) + return MZ_FALSE; + + return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags); +} + +mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *pFile, mz_uint flags) +{ + mz_zip_archive_file_stat file_stat; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + if ((file_stat.m_is_directory) || (!file_stat.m_is_supported)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); + + return mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); +} + +mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index)) + return MZ_FALSE; + + return mz_zip_reader_extract_to_cfile(pZip, file_index, pFile, flags); +} +#endif /* #ifndef MINIZ_NO_STDIO */ + +static size_t mz_zip_compute_crc32_callback(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_uint32 *p = (mz_uint32 *)pOpaque; + (void)file_ofs; + *p = (mz_uint32)mz_crc32(*p, (const mz_uint8 *)pBuf, n); + return n; +} + +mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags) +{ + mz_zip_archive_file_stat file_stat; + mz_zip_internal_state *pState; + const mz_uint8 *pCentral_dir_header; + mz_bool found_zip64_ext_data_in_cdir = MZ_FALSE; + mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + mz_uint64 local_header_ofs = 0; + mz_uint32 local_header_filename_len, local_header_extra_len, local_header_crc32; + mz_uint64 local_header_comp_size, local_header_uncomp_size; + mz_uint32 uncomp_crc32 = MZ_CRC32_INIT; + mz_bool has_data_descriptor; + mz_uint32 local_header_bit_flags; + + mz_zip_array file_data_array; + mz_zip_array_init(&file_data_array, 1); + + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (file_index > pZip->m_total_files) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + pCentral_dir_header = mz_zip_get_cdh(pZip, file_index); + + if (!mz_zip_file_stat_internal(pZip, file_index, pCentral_dir_header, &file_stat, &found_zip64_ext_data_in_cdir)) + return MZ_FALSE; + + /* A directory or zero length file */ + if ((file_stat.m_is_directory) || (!file_stat.m_uncomp_size)) + return MZ_TRUE; + + /* Encryption and patch files are not supported. */ + if (file_stat.m_is_encrypted) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + /* This function only supports stored and deflate. */ + if ((file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + + if (!file_stat.m_is_supported) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); + + /* Read and parse the local directory entry. */ + local_header_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + local_header_filename_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS); + local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS); + local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS); + local_header_crc32 = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_CRC32_OFS); + local_header_bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); + has_data_descriptor = (local_header_bit_flags & 8) != 0; + + if (local_header_filename_len != strlen(file_stat.m_filename)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if ((local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len + local_header_extra_len + file_stat.m_comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (!mz_zip_array_resize(pZip, &file_data_array, MZ_MAX(local_header_filename_len, local_header_extra_len), MZ_FALSE)) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + goto handle_failure; + } + + if (local_header_filename_len) + { + if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE, file_data_array.m_p, local_header_filename_len) != local_header_filename_len) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + goto handle_failure; + } + + /* I've seen 1 archive that had the same pathname, but used backslashes in the local dir and forward slashes in the central dir. Do we care about this? For now, this case will fail validation. */ + if (memcmp(file_stat.m_filename, file_data_array.m_p, local_header_filename_len) != 0) + { + mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + goto handle_failure; + } + } + + if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX))) + { + mz_uint32 extra_size_remaining = local_header_extra_len; + const mz_uint8 *pExtra_data = (const mz_uint8 *)file_data_array.m_p; + + if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len, file_data_array.m_p, local_header_extra_len) != local_header_extra_len) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + goto handle_failure; + } + + do + { + mz_uint32 field_id, field_data_size, field_total_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + goto handle_failure; + } + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + field_total_size = field_data_size + sizeof(mz_uint16) * 2; + + if (field_total_size > extra_size_remaining) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + goto handle_failure; + } + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32); + + if (field_data_size < sizeof(mz_uint64) * 2) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + goto handle_failure; + } + + local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data); + local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64)); + + found_zip64_ext_data_in_ldir = MZ_TRUE; + break; + } + + pExtra_data += field_total_size; + extra_size_remaining -= field_total_size; + } while (extra_size_remaining); + } + + /* TODO: parse local header extra data when local_header_comp_size is 0xFFFFFFFF! (big_descriptor.zip) */ + /* I've seen zips in the wild with the data descriptor bit set, but proper local header values and bogus data descriptors */ + if ((has_data_descriptor) && (!local_header_comp_size) && (!local_header_crc32)) + { + mz_uint8 descriptor_buf[32]; + mz_bool has_id; + const mz_uint8 *pSrc; + mz_uint32 file_crc32; + mz_uint64 comp_size = 0, uncomp_size = 0; + + mz_uint32 num_descriptor_uint32s = ((pState->m_zip64) || (found_zip64_ext_data_in_ldir)) ? 6 : 4; + + if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len + local_header_extra_len + file_stat.m_comp_size, descriptor_buf, sizeof(mz_uint32) * num_descriptor_uint32s) != (sizeof(mz_uint32) * num_descriptor_uint32s)) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + goto handle_failure; + } + + has_id = (MZ_READ_LE32(descriptor_buf) == MZ_ZIP_DATA_DESCRIPTOR_ID); + pSrc = has_id ? (descriptor_buf + sizeof(mz_uint32)) : descriptor_buf; + + file_crc32 = MZ_READ_LE32(pSrc); + + if ((pState->m_zip64) || (found_zip64_ext_data_in_ldir)) + { + comp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32)); + uncomp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32) + sizeof(mz_uint64)); + } + else + { + comp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32)); + uncomp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32) + sizeof(mz_uint32)); + } + + if ((file_crc32 != file_stat.m_crc32) || (comp_size != file_stat.m_comp_size) || (uncomp_size != file_stat.m_uncomp_size)) + { + mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + goto handle_failure; + } + } + else + { + if ((local_header_crc32 != file_stat.m_crc32) || (local_header_comp_size != file_stat.m_comp_size) || (local_header_uncomp_size != file_stat.m_uncomp_size)) + { + mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + goto handle_failure; + } + } + + mz_zip_array_clear(pZip, &file_data_array); + + if ((flags & MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY) == 0) + { + if (!mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_compute_crc32_callback, &uncomp_crc32, 0)) + return MZ_FALSE; + + /* 1 more check to be sure, although the extract checks too. */ + if (uncomp_crc32 != file_stat.m_crc32) + { + mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + return MZ_FALSE; + } + } + + return MZ_TRUE; + +handle_failure: + mz_zip_array_clear(pZip, &file_data_array); + return MZ_FALSE; +} + +mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags) +{ + mz_zip_internal_state *pState; + uint32_t i; + + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + /* Basic sanity checks */ + if (!pState->m_zip64) + { + if (pZip->m_total_files > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + if (pZip->m_archive_size > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + } + else + { + if (pZip->m_total_files >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + if (pState->m_central_dir.m_size >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + } + + for (i = 0; i < pZip->m_total_files; i++) + { + if (MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG & flags) + { + mz_uint32 found_index; + mz_zip_archive_file_stat stat; + + if (!mz_zip_reader_file_stat(pZip, i, &stat)) + return MZ_FALSE; + + if (!mz_zip_reader_locate_file_v2(pZip, stat.m_filename, NULL, 0, &found_index)) + return MZ_FALSE; + + /* This check can fail if there are duplicate filenames in the archive (which we don't check for when writing - that's up to the user) */ + if (found_index != i) + return mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + } + + if (!mz_zip_validate_file(pZip, i, flags)) + return MZ_FALSE; + } + + return MZ_TRUE; +} + +mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr) +{ + mz_bool success = MZ_TRUE; + mz_zip_archive zip; + mz_zip_error actual_err = MZ_ZIP_NO_ERROR; + + if ((!pMem) || (!size)) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_PARAMETER; + return MZ_FALSE; + } + + mz_zip_zero_struct(&zip); + + if (!mz_zip_reader_init_mem(&zip, pMem, size, flags)) + { + if (pErr) + *pErr = zip.m_last_error; + return MZ_FALSE; + } + + if (!mz_zip_validate_archive(&zip, flags)) + { + actual_err = zip.m_last_error; + success = MZ_FALSE; + } + + if (!mz_zip_reader_end_internal(&zip, success)) + { + if (!actual_err) + actual_err = zip.m_last_error; + success = MZ_FALSE; + } + + if (pErr) + *pErr = actual_err; + + return success; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr) +{ + mz_bool success = MZ_TRUE; + mz_zip_archive zip; + mz_zip_error actual_err = MZ_ZIP_NO_ERROR; + + if (!pFilename) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_PARAMETER; + return MZ_FALSE; + } + + mz_zip_zero_struct(&zip); + + if (!mz_zip_reader_init_file_v2(&zip, pFilename, flags, 0, 0)) + { + if (pErr) + *pErr = zip.m_last_error; + return MZ_FALSE; + } + + if (!mz_zip_validate_archive(&zip, flags)) + { + actual_err = zip.m_last_error; + success = MZ_FALSE; + } + + if (!mz_zip_reader_end_internal(&zip, success)) + { + if (!actual_err) + actual_err = zip.m_last_error; + success = MZ_FALSE; + } + + if (pErr) + *pErr = actual_err; + + return success; +} +#endif /* #ifndef MINIZ_NO_STDIO */ + +/* ------------------- .ZIP archive writing */ + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +static MZ_FORCEINLINE void mz_write_le16(mz_uint8 *p, mz_uint16 v) +{ + p[0] = (mz_uint8)v; + p[1] = (mz_uint8)(v >> 8); +} +static MZ_FORCEINLINE void mz_write_le32(mz_uint8 *p, mz_uint32 v) +{ + p[0] = (mz_uint8)v; + p[1] = (mz_uint8)(v >> 8); + p[2] = (mz_uint8)(v >> 16); + p[3] = (mz_uint8)(v >> 24); +} +static MZ_FORCEINLINE void mz_write_le64(mz_uint8 *p, mz_uint64 v) +{ + mz_write_le32(p, (mz_uint32)v); + mz_write_le32(p + sizeof(mz_uint32), (mz_uint32)(v >> 32)); +} + +#define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v)) +#define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v)) +#define MZ_WRITE_LE64(p, v) mz_write_le64((mz_uint8 *)(p), (mz_uint64)(v)) + +static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size); + + if (!n) + return 0; + + /* An allocation this big is likely to just fail on 32-bit systems, so don't even go there. */ + if ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF)) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); + return 0; + } + + if (new_size > pState->m_mem_capacity) + { + void *pNew_block; + size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity); + + while (new_capacity < new_size) + new_capacity *= 2; + + if (NULL == (pNew_block = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + return 0; + } + + pState->m_pMem = pNew_block; + pState->m_mem_capacity = new_capacity; + } + memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n); + pState->m_mem_size = (size_t)new_size; + return n; +} + +static mz_bool mz_zip_writer_end_internal(mz_zip_archive *pZip, mz_bool set_last_error) +{ + mz_zip_internal_state *pState; + mz_bool status = MZ_TRUE; + + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED))) + { + if (set_last_error) + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + pState = pZip->m_pState; + pZip->m_pState = NULL; + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) + { + if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) + { + if (MZ_FCLOSE(pState->m_pFile) == EOF) + { + if (set_last_error) + mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); + status = MZ_FALSE; + } + } + + pState->m_pFile = NULL; + } +#endif /* #ifndef MINIZ_NO_STDIO */ + + if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem); + pState->m_pMem = NULL; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + return status; +} + +mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags) +{ + mz_bool zip64 = (flags & MZ_ZIP_FLAG_WRITE_ZIP64) != 0; + + if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) + { + if (!pZip->m_pRead) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + } + + if (pZip->m_file_offset_alignment) + { + /* Ensure user specified file offset alignment is a power of 2. */ + if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + } + + if (!pZip->m_pAlloc) + pZip->m_pAlloc = miniz_def_alloc_func; + if (!pZip->m_pFree) + pZip->m_pFree = miniz_def_free_func; + if (!pZip->m_pRealloc) + pZip->m_pRealloc = miniz_def_realloc_func; + + pZip->m_archive_size = existing_size; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + + pZip->m_pState->m_zip64 = zip64; + pZip->m_pState->m_zip64_has_extended_info_fields = zip64; + + pZip->m_zip_type = MZ_ZIP_TYPE_USER; + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size) +{ + return mz_zip_writer_init_v2(pZip, existing_size, 0); +} + +mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags) +{ + pZip->m_pWrite = mz_zip_heap_write_func; + pZip->m_pNeeds_keepalive = NULL; + + if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) + pZip->m_pRead = mz_zip_mem_read_func; + + pZip->m_pIO_opaque = pZip; + + if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags)) + return MZ_FALSE; + + pZip->m_zip_type = MZ_ZIP_TYPE_HEAP; + + if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, size_to_reserve_at_beginning))) + { + if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, initial_allocation_size))) + { + mz_zip_writer_end_internal(pZip, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + pZip->m_pState->m_mem_capacity = initial_allocation_size; + } + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size) +{ + return mz_zip_writer_init_heap_v2(pZip, size_to_reserve_at_beginning, initial_allocation_size, 0); +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + + file_ofs += pZip->m_pState->m_file_archive_start_ofs; + + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); + return 0; + } + + return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning) +{ + return mz_zip_writer_init_file_v2(pZip, pFilename, size_to_reserve_at_beginning, 0); +} + +mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags) +{ + MZ_FILE *pFile; + + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pNeeds_keepalive = NULL; + + if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) + pZip->m_pRead = mz_zip_file_read_func; + + pZip->m_pIO_opaque = pZip; + + if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags)) + return MZ_FALSE; + + if (NULL == (pFile = MZ_FOPEN(pFilename, (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) ? "w+b" : "wb"))) + { + mz_zip_writer_end(pZip); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + } + + pZip->m_pState->m_pFile = pFile; + pZip->m_zip_type = MZ_ZIP_TYPE_FILE; + + if (size_to_reserve_at_beginning) + { + mz_uint64 cur_ofs = 0; + char buf[4096]; + + MZ_CLEAR_OBJ(buf); + + do + { + size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n) + { + mz_zip_writer_end(pZip); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + cur_ofs += n; + size_to_reserve_at_beginning -= n; + } while (size_to_reserve_at_beginning); + } + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags) +{ + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pNeeds_keepalive = NULL; + + if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) + pZip->m_pRead = mz_zip_file_read_func; + + pZip->m_pIO_opaque = pZip; + + if (!mz_zip_writer_init_v2(pZip, 0, flags)) + return MZ_FALSE; + + pZip->m_pState->m_pFile = pFile; + pZip->m_pState->m_file_archive_start_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + pZip->m_zip_type = MZ_ZIP_TYPE_CFILE; + + return MZ_TRUE; +} +#endif /* #ifndef MINIZ_NO_STDIO */ + +mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags) +{ + mz_zip_internal_state *pState; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (flags & MZ_ZIP_FLAG_WRITE_ZIP64) + { + /* We don't support converting a non-zip64 file to zip64 - this seems like more trouble than it's worth. (What about the existing 32-bit data descriptors that could follow the compressed data?) */ + if (!pZip->m_pState->m_zip64) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + } + + /* No sense in trying to write to an archive that's already at the support max size */ + if (pZip->m_pState->m_zip64) + { + if (pZip->m_total_files == MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + if (pZip->m_total_files == MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + if ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); + } + + pState = pZip->m_pState; + + if (pState->m_pFile) + { +#ifdef MINIZ_NO_STDIO + (void)pFilename; + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); +#else + if (pZip->m_pIO_opaque != pZip) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) + { + if (!pFilename) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* Archive is being read from stdio and was originally opened only for reading. Try to reopen as writable. */ + if (NULL == (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile))) + { + /* The mz_zip_archive is now in a bogus state because pState->m_pFile is NULL, so just close it. */ + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + } + } + + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pNeeds_keepalive = NULL; +#endif /* #ifdef MINIZ_NO_STDIO */ + } + else if (pState->m_pMem) + { + /* Archive lives in a memory block. Assume it's from the heap that we can resize using the realloc callback. */ + if (pZip->m_pIO_opaque != pZip) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState->m_mem_capacity = pState->m_mem_size; + pZip->m_pWrite = mz_zip_heap_write_func; + pZip->m_pNeeds_keepalive = NULL; + } + /* Archive is being read via a user provided read function - make sure the user has specified a write function too. */ + else if (!pZip->m_pWrite) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* Start writing new files at the archive's current central directory location. */ + /* TODO: We could add a flag that lets the user start writing immediately AFTER the existing central dir - this would be safer. */ + pZip->m_archive_size = pZip->m_central_directory_file_ofs; + pZip->m_central_directory_file_ofs = 0; + + /* Clear the sorted central dir offsets, they aren't useful or maintained now. */ + /* Even though we're now in write mode, files can still be extracted and verified, but file locates will be slow. */ + /* TODO: We could easily maintain the sorted central directory offsets. */ + mz_zip_array_clear(pZip, &pZip->m_pState->m_sorted_central_dir_offsets); + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename) +{ + return mz_zip_writer_init_from_reader_v2(pZip, pFilename, 0); +} + +/* TODO: pArchive_name is a terrible name here! */ +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags) +{ + return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, level_and_flags, 0, 0); +} + +typedef struct +{ + mz_zip_archive *m_pZip; + mz_uint64 m_cur_archive_file_ofs; + mz_uint64 m_comp_size; +} mz_zip_writer_add_state; + +static mz_bool mz_zip_writer_add_put_buf_callback(const void *pBuf, int len, void *pUser) +{ + mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser; + if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, pState->m_cur_archive_file_ofs, pBuf, len) != len) + return MZ_FALSE; + + pState->m_cur_archive_file_ofs += len; + pState->m_comp_size += len; + return MZ_TRUE; +} + +#define MZ_ZIP64_MAX_LOCAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 2) +#define MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 3) +static mz_uint32 mz_zip_writer_create_zip64_extra_data(mz_uint8 *pBuf, mz_uint64 *pUncomp_size, mz_uint64 *pComp_size, mz_uint64 *pLocal_header_ofs) +{ + mz_uint8 *pDst = pBuf; + mz_uint32 field_size = 0; + + MZ_WRITE_LE16(pDst + 0, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID); + MZ_WRITE_LE16(pDst + 2, 0); + pDst += sizeof(mz_uint16) * 2; + + if (pUncomp_size) + { + MZ_WRITE_LE64(pDst, *pUncomp_size); + pDst += sizeof(mz_uint64); + field_size += sizeof(mz_uint64); + } + + if (pComp_size) + { + MZ_WRITE_LE64(pDst, *pComp_size); + pDst += sizeof(mz_uint64); + field_size += sizeof(mz_uint64); + } + + if (pLocal_header_ofs) + { + MZ_WRITE_LE64(pDst, *pLocal_header_ofs); + pDst += sizeof(mz_uint64); + field_size += sizeof(mz_uint64); + } + + MZ_WRITE_LE16(pBuf + 2, field_size); + + return (mz_uint32)(pDst - pBuf); +} + +static mz_bool mz_zip_writer_create_local_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date) +{ + (void)pZip; + memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX)); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX)); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_create_central_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, + mz_uint16 filename_size, mz_uint16 extra_size, mz_uint16 comment_size, + mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, + mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, + mz_uint64 local_header_ofs, mz_uint32 ext_attributes) +{ + (void)pZip; + memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX)); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX)); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_MIN(local_header_ofs, MZ_UINT32_MAX)); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_add_to_central_dir(mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size, + const void *pExtra, mz_uint16 extra_size, const void *pComment, mz_uint16 comment_size, + mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, + mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, + mz_uint64 local_header_ofs, mz_uint32 ext_attributes, + const char *user_extra_data, mz_uint user_extra_data_len) +{ + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size; + size_t orig_central_dir_size = pState->m_central_dir.m_size; + mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + + if (!pZip->m_pState->m_zip64) + { + if (local_header_ofs > 0xFFFFFFFF) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); + } + + /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ + if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + user_extra_data_len + comment_size) >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + if (!mz_zip_writer_create_central_dir_header(pZip, central_dir_header, filename_size, (mz_uint16)(extra_size + user_extra_data_len), comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_header_ofs, ext_attributes)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, filename_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, extra_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, user_extra_data, user_extra_data_len)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, comment_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, ¢ral_dir_ofs, 1))) + { + /* Try to resize the central directory array back into its original state. */ + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name) +{ + /* Basic ZIP archive filename validity checks: Valid filenames cannot start with a forward slash, cannot contain a drive letter, and cannot use DOS-style backward slashes. */ + if (*pArchive_name == '/') + return MZ_FALSE; + + /* Making sure the name does not contain drive letters or DOS style backward slashes is the responsibility of the program using miniz*/ + + return MZ_TRUE; +} + +static mz_uint mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip) +{ + mz_uint32 n; + if (!pZip->m_file_offset_alignment) + return 0; + n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1)); + return (mz_uint)((pZip->m_file_offset_alignment - n) & (pZip->m_file_offset_alignment - 1)); +} + +static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, mz_uint64 cur_file_ofs, mz_uint32 n) +{ + char buf[4096]; + memset(buf, 0, MZ_MIN(sizeof(buf), n)); + while (n) + { + mz_uint32 s = MZ_MIN(sizeof(buf), n); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_file_ofs += s; + n -= s; + } + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + mz_uint64 uncomp_size, mz_uint32 uncomp_crc32) +{ + return mz_zip_writer_add_mem_ex_v2(pZip, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, uncomp_size, uncomp_crc32, NULL, NULL, 0, NULL, 0); +} + +mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, + mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified, + const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) +{ + mz_uint16 method = 0, dos_time = 0, dos_date = 0; + mz_uint level, ext_attributes = 0, num_alignment_padding_bytes; + mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + tdefl_compressor *pComp = NULL; + mz_bool store_data_uncompressed; + mz_zip_internal_state *pState; + mz_uint8 *pExtra_data = NULL; + mz_uint32 extra_size = 0; + mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE]; + mz_uint16 bit_flags = 0; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + + if (uncomp_size || (buf_size && !(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) + bit_flags |= MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR; + + if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME)) + bit_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8; + + level = level_and_flags & 0xF; + store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)); + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + if (pState->m_zip64) + { + if (pZip->m_total_files == MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + if (pZip->m_total_files == MZ_UINT16_MAX) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */ + } + if ((buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF)) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ + } + } + + if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); + +#ifndef MINIZ_NO_TIME + if (last_modified != NULL) + { + mz_zip_time_t_to_dos_time(*last_modified, &dos_time, &dos_date); + } + else + { + MZ_TIME_T cur_time; + time(&cur_time); + mz_zip_time_t_to_dos_time(cur_time, &dos_time, &dos_date); + } +#endif /* #ifndef MINIZ_NO_TIME */ + + if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { + uncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, buf_size); + uncomp_size = buf_size; + if (uncomp_size <= 3) + { + level = 0; + store_data_uncompressed = MZ_TRUE; + } + } + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ + if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE + comment_size) >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + if (!pState->m_zip64) + { + /* Bail early if the archive would obviously become too large */ + if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size + + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + user_extra_data_len + + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + user_extra_data_central_len + + MZ_ZIP_DATA_DESCRIPTER_SIZE32) > 0xFFFFFFFF) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ + } + } + + if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/')) + { + /* Set DOS Subdirectory attribute bit. */ + ext_attributes |= MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG; + + /* Subdirectories cannot contain data. */ + if ((buf_size) || (uncomp_size)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + } + + /* Try to do any allocations before writing to the archive, so if an allocation fails the file remains unmodified. (A good idea if we're doing an in-place modification.) */ + if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + (pState->m_zip64 ? MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE : 0))) || (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if ((!store_data_uncompressed) && (buf_size)) + { + if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + + local_dir_header_ofs += num_alignment_padding_bytes; + if (pZip->m_file_offset_alignment) + { + MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); + } + cur_archive_file_ofs += num_alignment_padding_bytes; + + MZ_CLEAR_OBJ(local_dir_header); + + if (!store_data_uncompressed || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { + method = MZ_DEFLATED; + } + + if (pState->m_zip64) + { + if (uncomp_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX) + { + pExtra_data = extra_data; + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len), 0, 0, 0, method, bit_flags, dos_time, dos_date)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + cur_archive_file_ofs += archive_name_size; + + if (pExtra_data != NULL) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += extra_size; + } + } + else + { + if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)user_extra_data_len, 0, 0, 0, method, bit_flags, dos_time, dos_date)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + cur_archive_file_ofs += archive_name_size; + } + + if (user_extra_data_len > 0) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += user_extra_data_len; + } + + if (store_data_uncompressed) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_file_ofs += buf_size; + comp_size = buf_size; + } + else if (buf_size) + { + mz_zip_writer_add_state state; + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) || + (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED); + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pComp = NULL; + + if (uncomp_size) + { + mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64]; + mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32; + + MZ_ASSERT(bit_flags & MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR); + + MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID); + MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32); + if (pExtra_data == NULL) + { + if (comp_size > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + MZ_WRITE_LE32(local_dir_footer + 8, comp_size); + MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size); + } + else + { + MZ_WRITE_LE64(local_dir_footer + 8, comp_size); + MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size); + local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64; + } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size) + return MZ_FALSE; + + cur_archive_file_ofs += local_dir_footer_size; + } + + if (pExtra_data != NULL) + { + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, pExtra_data, (mz_uint16)extra_size, pComment, + comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes, + user_extra_data_central, user_extra_data_central_len)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pArchive_name, mz_file_read_func read_callback, void* callback_opaque, mz_uint64 max_size, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) +{ + mz_uint16 gen_flags = (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE) ? 0 : MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR; + mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes; + mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0; + mz_uint64 local_dir_header_ofs, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = 0, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + mz_uint8 *pExtra_data = NULL; + mz_uint32 extra_size = 0; + mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE]; + mz_zip_internal_state *pState; + mz_uint64 file_ofs = 0, cur_archive_header_file_ofs; + + if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME)) + gen_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + level = level_and_flags & 0xF; + + /* Sanity checks */ + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + if ((!pState->m_zip64) && (max_size > MZ_UINT32_MAX)) + { + /* Source file is too large for non-zip64 */ + /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ + pState->m_zip64 = MZ_TRUE; + } + + /* We could support this, but why? */ + if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); + + if (pState->m_zip64) + { + if (pZip->m_total_files == MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + if (pZip->m_total_files == MZ_UINT16_MAX) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */ + } + } + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ + if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE + comment_size) >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + if (!pState->m_zip64) + { + /* Bail early if the archive would obviously become too large */ + if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + archive_name_size + comment_size + user_extra_data_len + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 1024 + + MZ_ZIP_DATA_DESCRIPTER_SIZE32 + user_extra_data_central_len) > 0xFFFFFFFF) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ + } + } + +#ifndef MINIZ_NO_TIME + if (pFile_time) + { + mz_zip_time_t_to_dos_time(*pFile_time, &dos_time, &dos_date); + } +#endif + + if (max_size <= 3) + level = 0; + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes)) + { + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_file_ofs += num_alignment_padding_bytes; + local_dir_header_ofs = cur_archive_file_ofs; + + if (pZip->m_file_offset_alignment) + { + MZ_ASSERT((cur_archive_file_ofs & (pZip->m_file_offset_alignment - 1)) == 0); + } + + if (max_size && level) + { + method = MZ_DEFLATED; + } + + MZ_CLEAR_OBJ(local_dir_header); + if (pState->m_zip64) + { + if (max_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX) + { + pExtra_data = extra_data; + if (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE) + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (max_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (max_size >= MZ_UINT32_MAX) ? &comp_size : NULL, + (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + else + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, NULL, + NULL, + (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len), 0, 0, 0, method, gen_flags, dos_time, dos_date)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_file_ofs += archive_name_size; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += extra_size; + } + else + { + if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)user_extra_data_len, 0, 0, 0, method, gen_flags, dos_time, dos_date)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_file_ofs += archive_name_size; + } + + if (user_extra_data_len > 0) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += user_extra_data_len; + } + + if (max_size) + { + void *pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE); + if (!pRead_buf) + { + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!level) + { + while (1) + { + size_t n = read_callback(callback_opaque, file_ofs, pRead_buf, MZ_ZIP_MAX_IO_BUF_SIZE); + if (n == 0) + break; + + if ((n > MZ_ZIP_MAX_IO_BUF_SIZE) || (file_ofs + n > max_size)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + file_ofs += n; + uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); + cur_archive_file_ofs += n; + } + uncomp_size = file_ofs; + comp_size = uncomp_size; + } + else + { + mz_bool result = MZ_FALSE; + mz_zip_writer_add_state state; + tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + } + + for (;;) + { + tdefl_status status; + tdefl_flush flush = TDEFL_NO_FLUSH; + + size_t n = read_callback(callback_opaque, file_ofs, pRead_buf, MZ_ZIP_MAX_IO_BUF_SIZE); + if ((n > MZ_ZIP_MAX_IO_BUF_SIZE) || (file_ofs + n > max_size)) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + break; + } + + file_ofs += n; + uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); + + if (pZip->m_pNeeds_keepalive != NULL && pZip->m_pNeeds_keepalive(pZip->m_pIO_opaque)) + flush = TDEFL_FULL_FLUSH; + + if (n == 0) + flush = TDEFL_FINISH; + + status = tdefl_compress_buffer(pComp, pRead_buf, n, flush); + if (status == TDEFL_STATUS_DONE) + { + result = MZ_TRUE; + break; + } + else if (status != TDEFL_STATUS_OKAY) + { + mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED); + break; + } + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + + if (!result) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return MZ_FALSE; + } + + uncomp_size = file_ofs; + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + } + + if (!(level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE)) + { + mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64]; + mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32; + + MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID); + MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32); + if (pExtra_data == NULL) + { + if (comp_size > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + MZ_WRITE_LE32(local_dir_footer + 8, comp_size); + MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size); + } + else + { + MZ_WRITE_LE64(local_dir_footer + 8, comp_size); + MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size); + local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64; + } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size) + return MZ_FALSE; + + cur_archive_file_ofs += local_dir_footer_size; + } + + if (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE) + { + if (pExtra_data != NULL) + { + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (max_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (max_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, + (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len), + (max_size >= MZ_UINT32_MAX) ? MZ_UINT32_MAX : uncomp_size, + (max_size >= MZ_UINT32_MAX) ? MZ_UINT32_MAX : comp_size, + uncomp_crc32, method, gen_flags, dos_time, dos_date)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + cur_archive_header_file_ofs = local_dir_header_ofs; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + if (pExtra_data != NULL) + { + cur_archive_header_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_header_file_ofs += archive_name_size; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, extra_data, extra_size) != extra_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_header_file_ofs += extra_size; + } + } + + if (pExtra_data != NULL) + { + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, pExtra_data, (mz_uint16)extra_size, pComment, comment_size, + uncomp_size, comp_size, uncomp_crc32, method, gen_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes, + user_extra_data_central, user_extra_data_central_len)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO + +static size_t mz_file_read_func_stdio(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + MZ_FILE *pSrc_file = (MZ_FILE *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pSrc_file); + + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pSrc_file, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + + return MZ_FREAD(pBuf, 1, n, pSrc_file); +} + +mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 max_size, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) +{ + return mz_zip_writer_add_read_buf_callback(pZip, pArchive_name, mz_file_read_func_stdio, pSrc_file, max_size, pFile_time, pComment, comment_size, level_and_flags, + user_extra_data, user_extra_data_len, user_extra_data_central, user_extra_data_central_len); +} + +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) +{ + MZ_FILE *pSrc_file = NULL; + mz_uint64 uncomp_size = 0; + MZ_TIME_T file_modified_time; + MZ_TIME_T *pFile_time = NULL; + mz_bool status; + + memset(&file_modified_time, 0, sizeof(file_modified_time)); + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO) + pFile_time = &file_modified_time; + if (!mz_zip_get_file_modified_time(pSrc_filename, &file_modified_time)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_STAT_FAILED); +#endif + + pSrc_file = MZ_FOPEN(pSrc_filename, "rb"); + if (!pSrc_file) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + + MZ_FSEEK64(pSrc_file, 0, SEEK_END); + uncomp_size = MZ_FTELL64(pSrc_file); + MZ_FSEEK64(pSrc_file, 0, SEEK_SET); + + status = mz_zip_writer_add_cfile(pZip, pArchive_name, pSrc_file, uncomp_size, pFile_time, pComment, comment_size, level_and_flags, NULL, 0, NULL, 0); + + MZ_FCLOSE(pSrc_file); + + return status; +} +#endif /* #ifndef MINIZ_NO_STDIO */ + +static mz_bool mz_zip_writer_update_zip64_extension_block(mz_zip_array *pNew_ext, mz_zip_archive *pZip, const mz_uint8 *pExt, uint32_t ext_len, mz_uint64 *pComp_size, mz_uint64 *pUncomp_size, mz_uint64 *pLocal_header_ofs, mz_uint32 *pDisk_start) +{ + /* + 64 should be enough for any new zip64 data */ + if (!mz_zip_array_reserve(pZip, pNew_ext, ext_len + 64, MZ_FALSE)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + mz_zip_array_resize(pZip, pNew_ext, 0, MZ_FALSE); + + if ((pUncomp_size) || (pComp_size) || (pLocal_header_ofs) || (pDisk_start)) + { + mz_uint8 new_ext_block[64]; + mz_uint8 *pDst = new_ext_block; + mz_write_le16(pDst, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID); + mz_write_le16(pDst + sizeof(mz_uint16), 0); + pDst += sizeof(mz_uint16) * 2; + + if (pUncomp_size) + { + mz_write_le64(pDst, *pUncomp_size); + pDst += sizeof(mz_uint64); + } + + if (pComp_size) + { + mz_write_le64(pDst, *pComp_size); + pDst += sizeof(mz_uint64); + } + + if (pLocal_header_ofs) + { + mz_write_le64(pDst, *pLocal_header_ofs); + pDst += sizeof(mz_uint64); + } + + if (pDisk_start) + { + mz_write_le32(pDst, *pDisk_start); + pDst += sizeof(mz_uint32); + } + + mz_write_le16(new_ext_block + sizeof(mz_uint16), (mz_uint16)((pDst - new_ext_block) - sizeof(mz_uint16) * 2)); + + if (!mz_zip_array_push_back(pZip, pNew_ext, new_ext_block, pDst - new_ext_block)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if ((pExt) && (ext_len)) + { + mz_uint32 extra_size_remaining = ext_len; + const mz_uint8 *pExtra_data = pExt; + + do + { + mz_uint32 field_id, field_data_size, field_total_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + field_total_size = field_data_size + sizeof(mz_uint16) * 2; + + if (field_total_size > extra_size_remaining) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (field_id != MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + if (!mz_zip_array_push_back(pZip, pNew_ext, pExtra_data, field_total_size)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + pExtra_data += field_total_size; + extra_size_remaining -= field_total_size; + } while (extra_size_remaining); + } + + return MZ_TRUE; +} + +/* TODO: This func is now pretty freakin complex due to zip64, split it up? */ +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index) +{ + mz_uint n, bit_flags, num_alignment_padding_bytes, src_central_dir_following_data_size; + mz_uint64 src_archive_bytes_remaining, local_dir_header_ofs; + mz_uint64 cur_src_file_ofs, cur_dst_file_ofs; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + mz_uint8 new_central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + size_t orig_central_dir_size; + mz_zip_internal_state *pState; + void *pBuf; + const mz_uint8 *pSrc_central_header; + mz_zip_archive_file_stat src_file_stat; + mz_uint32 src_filename_len, src_comment_len, src_ext_len; + mz_uint32 local_header_filename_size, local_header_extra_len; + mz_uint64 local_header_comp_size, local_header_uncomp_size; + mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE; + + /* Sanity checks */ + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pSource_zip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + /* Don't support copying files from zip64 archives to non-zip64, even though in some cases this is possible */ + if ((pSource_zip->m_pState->m_zip64) && (!pZip->m_pState->m_zip64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* Get pointer to the source central dir header and crack it */ + if (NULL == (pSrc_central_header = mz_zip_get_cdh(pSource_zip, src_file_index))) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_SIG_OFS) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + src_filename_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS); + src_comment_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS); + src_ext_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS); + src_central_dir_following_data_size = src_filename_len + src_ext_len + src_comment_len; + + /* TODO: We don't support central dir's >= MZ_UINT32_MAX bytes right now (+32 fudge factor in case we need to add more extra data) */ + if ((pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + 32) >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + if (!pState->m_zip64) + { + if (pZip->m_total_files == MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + /* TODO: Our zip64 support still has some 32-bit limits that may not be worth fixing. */ + if (pZip->m_total_files == MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + + if (!mz_zip_file_stat_internal(pSource_zip, src_file_index, pSrc_central_header, &src_file_stat, NULL)) + return MZ_FALSE; + + cur_src_file_ofs = src_file_stat.m_local_header_ofs; + cur_dst_file_ofs = pZip->m_archive_size; + + /* Read the source archive's local dir header */ + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + /* Compute the total size we need to copy (filename+extra data+compressed data) */ + local_header_filename_size = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS); + local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS); + local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS); + src_archive_bytes_remaining = local_header_filename_size + local_header_extra_len + src_file_stat.m_comp_size; + + /* Try to find a zip64 extended information field */ + if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX))) + { + mz_zip_array file_data_array; + const mz_uint8 *pExtra_data; + mz_uint32 extra_size_remaining = local_header_extra_len; + + mz_zip_array_init(&file_data_array, 1); + if (!mz_zip_array_resize(pZip, &file_data_array, local_header_extra_len, MZ_FALSE)) + { + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, src_file_stat.m_local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_size, file_data_array.m_p, local_header_extra_len) != local_header_extra_len) + { + mz_zip_array_clear(pZip, &file_data_array); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + pExtra_data = (const mz_uint8 *)file_data_array.m_p; + + do + { + mz_uint32 field_id, field_data_size, field_total_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + { + mz_zip_array_clear(pZip, &file_data_array); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + field_total_size = field_data_size + sizeof(mz_uint16) * 2; + + if (field_total_size > extra_size_remaining) + { + mz_zip_array_clear(pZip, &file_data_array); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32); + + if (field_data_size < sizeof(mz_uint64) * 2) + { + mz_zip_array_clear(pZip, &file_data_array); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data); + local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64)); /* may be 0 if there's a descriptor */ + + found_zip64_ext_data_in_ldir = MZ_TRUE; + break; + } + + pExtra_data += field_total_size; + extra_size_remaining -= field_total_size; + } while (extra_size_remaining); + + mz_zip_array_clear(pZip, &file_data_array); + } + + if (!pState->m_zip64) + { + /* Try to detect if the new archive will most likely wind up too big and bail early (+(sizeof(mz_uint32) * 4) is for the optional descriptor which could be present, +64 is a fudge factor). */ + /* We also check when the archive is finalized so this doesn't need to be perfect. */ + mz_uint64 approx_new_archive_size = cur_dst_file_ofs + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + src_archive_bytes_remaining + (sizeof(mz_uint32) * 4) + + pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 64; + + if (approx_new_archive_size >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + } + + /* Write dest archive padding */ + if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, num_alignment_padding_bytes)) + return MZ_FALSE; + + cur_dst_file_ofs += num_alignment_padding_bytes; + + local_dir_header_ofs = cur_dst_file_ofs; + if (pZip->m_file_offset_alignment) + { + MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); + } + + /* The original zip's local header+ext block doesn't change, even with zip64, so we can just copy it over to the dest zip */ + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + /* Copy over the source archive bytes to the dest archive, also ensure we have enough buf space to handle optional data descriptor */ + if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)MZ_MAX(32U, MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining))))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + while (src_archive_bytes_remaining) + { + n = (mz_uint)MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining); + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + cur_src_file_ofs += n; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + cur_dst_file_ofs += n; + + src_archive_bytes_remaining -= n; + } + + /* Now deal with the optional data descriptor */ + bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); + if (bit_flags & 8) + { + /* Copy data descriptor */ + if ((pSource_zip->m_pState->m_zip64) || (found_zip64_ext_data_in_ldir)) + { + /* src is zip64, dest must be zip64 */ + + /* name uint32_t's */ + /* id 1 (optional in zip64?) */ + /* crc 1 */ + /* comp_size 2 */ + /* uncomp_size 2 */ + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, (sizeof(mz_uint32) * 6)) != (sizeof(mz_uint32) * 6)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID) ? 6 : 5); + } + else + { + /* src is NOT zip64 */ + mz_bool has_id; + + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + has_id = (MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID); + + if (pZip->m_pState->m_zip64) + { + /* dest is zip64, so upgrade the data descriptor */ + const mz_uint32 *pSrc_descriptor = (const mz_uint32 *)((const mz_uint8 *)pBuf + (has_id ? sizeof(mz_uint32) : 0)); + const mz_uint32 src_crc32 = pSrc_descriptor[0]; + const mz_uint64 src_comp_size = pSrc_descriptor[1]; + const mz_uint64 src_uncomp_size = pSrc_descriptor[2]; + + mz_write_le32((mz_uint8 *)pBuf, MZ_ZIP_DATA_DESCRIPTOR_ID); + mz_write_le32((mz_uint8 *)pBuf + sizeof(mz_uint32) * 1, src_crc32); + mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 2, src_comp_size); + mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 4, src_uncomp_size); + + n = sizeof(mz_uint32) * 6; + } + else + { + /* dest is NOT zip64, just copy it as-is */ + n = sizeof(mz_uint32) * (has_id ? 4 : 3); + } + } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_src_file_ofs += n; + cur_dst_file_ofs += n; + } + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + + /* Finally, add the new central dir header */ + orig_central_dir_size = pState->m_central_dir.m_size; + + memcpy(new_central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + + if (pState->m_zip64) + { + /* This is the painful part: We need to write a new central dir header + ext block with updated zip64 fields, and ensure the old fields (if any) are not included. */ + const mz_uint8 *pSrc_ext = pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len; + mz_zip_array new_ext_block; + + mz_zip_array_init(&new_ext_block, sizeof(mz_uint8)); + + MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_UINT32_MAX); + MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_UINT32_MAX); + MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_UINT32_MAX); + + if (!mz_zip_writer_update_zip64_extension_block(&new_ext_block, pZip, pSrc_ext, src_ext_len, &src_file_stat.m_comp_size, &src_file_stat.m_uncomp_size, &local_dir_header_ofs, NULL)) + { + mz_zip_array_clear(pZip, &new_ext_block); + return MZ_FALSE; + } + + MZ_WRITE_LE16(new_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS, new_ext_block.m_size); + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) + { + mz_zip_array_clear(pZip, &new_ext_block); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_filename_len)) + { + mz_zip_array_clear(pZip, &new_ext_block); + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_ext_block.m_p, new_ext_block.m_size)) + { + mz_zip_array_clear(pZip, &new_ext_block); + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len + src_ext_len, src_comment_len)) + { + mz_zip_array_clear(pZip, &new_ext_block); + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + mz_zip_array_clear(pZip, &new_ext_block); + } + else + { + /* sanity checks */ + if (cur_dst_file_ofs > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + if (local_dir_header_ofs >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_dir_header_ofs); + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_central_dir_following_data_size)) + { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + } + + /* This shouldn't trigger unless we screwed up during the initial sanity checks */ + if (pState->m_central_dir.m_size >= MZ_UINT32_MAX) + { + /* TODO: Support central dirs >= 32-bits in size */ + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + } + + n = (mz_uint32)orig_central_dir_size; + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1)) + { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + pZip->m_total_files++; + pZip->m_archive_size = cur_dst_file_ofs; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState; + mz_uint64 central_dir_ofs, central_dir_size; + mz_uint8 hdr[256]; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + if (pState->m_zip64) + { + if ((pZip->m_total_files > MZ_UINT32_MAX) || (pState->m_central_dir.m_size >= MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + if ((pZip->m_total_files > MZ_UINT16_MAX) || ((pZip->m_archive_size + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + + central_dir_ofs = 0; + central_dir_size = 0; + if (pZip->m_total_files) + { + /* Write central directory */ + central_dir_ofs = pZip->m_archive_size; + central_dir_size = pState->m_central_dir.m_size; + pZip->m_central_directory_file_ofs = central_dir_ofs; + if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, pState->m_central_dir.m_p, (size_t)central_dir_size) != central_dir_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + pZip->m_archive_size += central_dir_size; + } + + if (pState->m_zip64) + { + /* Write zip64 end of central directory header */ + mz_uint64 rel_ofs_to_zip64_ecdr = pZip->m_archive_size; + + MZ_CLEAR_OBJ(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDH_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - sizeof(mz_uint32) - sizeof(mz_uint64)); + MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS, 0x031E); /* TODO: always Unix */ + MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS, 0x002D); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, pZip->m_total_files); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_SIZE_OFS, central_dir_size); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_OFS_OFS, central_dir_ofs); + if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE; + + /* Write zip64 end of central directory locator */ + MZ_CLEAR_OBJ(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS, rel_ofs_to_zip64_ecdr); + MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS, 1); + if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) != MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE; + } + + /* Write end of central directory record */ + MZ_CLEAR_OBJ(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files)); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files)); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_size)); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_ofs)); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + +#ifndef MINIZ_NO_STDIO + if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); +#endif /* #ifndef MINIZ_NO_STDIO */ + + pZip->m_archive_size += MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE; + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED; + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize) +{ + if ((!ppBuf) || (!pSize)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + *ppBuf = NULL; + *pSize = 0; + + if ((!pZip) || (!pZip->m_pState)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (pZip->m_pWrite != mz_zip_heap_write_func) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_writer_finalize_archive(pZip)) + return MZ_FALSE; + + *ppBuf = pZip->m_pState->m_pMem; + *pSize = pZip->m_pState->m_mem_size; + pZip->m_pState->m_pMem = NULL; + pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_end(mz_zip_archive *pZip) +{ + return mz_zip_writer_end_internal(pZip, MZ_TRUE); +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) +{ + return mz_zip_add_mem_to_archive_file_in_place_v2(pZip_filename, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, NULL); +} + +mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr) +{ + mz_bool status, created_new_archive = MZ_FALSE; + mz_zip_archive zip_archive; + struct MZ_FILE_STAT_STRUCT file_stat; + mz_zip_error actual_err = MZ_ZIP_NO_ERROR; + + mz_zip_zero_struct(&zip_archive); + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + + if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION)) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_PARAMETER; + return MZ_FALSE; + } + + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_FILENAME; + return MZ_FALSE; + } + + /* Important: The regular non-64 bit version of stat() can fail here if the file is very large, which could cause the archive to be overwritten. */ + /* So be sure to compile with _LARGEFILE64_SOURCE 1 */ + if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0) + { + /* Create a new archive. */ + if (!mz_zip_writer_init_file_v2(&zip_archive, pZip_filename, 0, level_and_flags)) + { + if (pErr) + *pErr = zip_archive.m_last_error; + return MZ_FALSE; + } + + created_new_archive = MZ_TRUE; + } + else + { + /* Append to an existing archive. */ + if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0)) + { + if (pErr) + *pErr = zip_archive.m_last_error; + return MZ_FALSE; + } + + if (!mz_zip_writer_init_from_reader_v2(&zip_archive, pZip_filename, level_and_flags)) + { + if (pErr) + *pErr = zip_archive.m_last_error; + + mz_zip_reader_end_internal(&zip_archive, MZ_FALSE); + + return MZ_FALSE; + } + } + + status = mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, 0, 0); + actual_err = zip_archive.m_last_error; + + /* Always finalize, even if adding failed for some reason, so we have a valid central directory. (This may not always succeed, but we can try.) */ + if (!mz_zip_writer_finalize_archive(&zip_archive)) + { + if (!actual_err) + actual_err = zip_archive.m_last_error; + + status = MZ_FALSE; + } + + if (!mz_zip_writer_end_internal(&zip_archive, status)) + { + if (!actual_err) + actual_err = zip_archive.m_last_error; + + status = MZ_FALSE; + } + + if ((!status) && (created_new_archive)) + { + /* It's a new archive and something went wrong, so just delete it. */ + int ignoredStatus = MZ_DELETE_FILE(pZip_filename); + (void)ignoredStatus; + } + + if (pErr) + *pErr = actual_err; + + return status; +} + +void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr) +{ + mz_uint32 file_index; + mz_zip_archive zip_archive; + void *p = NULL; + + if (pSize) + *pSize = 0; + + if ((!pZip_filename) || (!pArchive_name)) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_PARAMETER; + + return NULL; + } + + mz_zip_zero_struct(&zip_archive); + if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0)) + { + if (pErr) + *pErr = zip_archive.m_last_error; + + return NULL; + } + + if (mz_zip_reader_locate_file_v2(&zip_archive, pArchive_name, pComment, flags, &file_index)) + { + p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags); + } + + mz_zip_reader_end_internal(&zip_archive, p != NULL); + + if (pErr) + *pErr = zip_archive.m_last_error; + + return p; +} + +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags) +{ + return mz_zip_extract_archive_file_to_heap_v2(pZip_filename, pArchive_name, NULL, pSize, flags, NULL); +} + +#endif /* #ifndef MINIZ_NO_STDIO */ + +#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */ + +/* ------------------- Misc utils */ + +mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_zip_mode : MZ_ZIP_MODE_INVALID; +} + +mz_zip_type mz_zip_get_type(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_zip_type : MZ_ZIP_TYPE_INVALID; +} + +mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num) +{ + mz_zip_error prev_err; + + if (!pZip) + return MZ_ZIP_INVALID_PARAMETER; + + prev_err = pZip->m_last_error; + + pZip->m_last_error = err_num; + return prev_err; +} + +mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip) +{ + if (!pZip) + return MZ_ZIP_INVALID_PARAMETER; + + return pZip->m_last_error; +} + +mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip) +{ + return mz_zip_set_last_error(pZip, MZ_ZIP_NO_ERROR); +} + +mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip) +{ + mz_zip_error prev_err; + + if (!pZip) + return MZ_ZIP_INVALID_PARAMETER; + + prev_err = pZip->m_last_error; + + pZip->m_last_error = MZ_ZIP_NO_ERROR; + return prev_err; +} + +const char *mz_zip_get_error_string(mz_zip_error mz_err) +{ + switch (mz_err) + { + case MZ_ZIP_NO_ERROR: + return "no error"; + case MZ_ZIP_UNDEFINED_ERROR: + return "undefined error"; + case MZ_ZIP_TOO_MANY_FILES: + return "too many files"; + case MZ_ZIP_FILE_TOO_LARGE: + return "file too large"; + case MZ_ZIP_UNSUPPORTED_METHOD: + return "unsupported method"; + case MZ_ZIP_UNSUPPORTED_ENCRYPTION: + return "unsupported encryption"; + case MZ_ZIP_UNSUPPORTED_FEATURE: + return "unsupported feature"; + case MZ_ZIP_FAILED_FINDING_CENTRAL_DIR: + return "failed finding central directory"; + case MZ_ZIP_NOT_AN_ARCHIVE: + return "not a ZIP archive"; + case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED: + return "invalid header or archive is corrupted"; + case MZ_ZIP_UNSUPPORTED_MULTIDISK: + return "unsupported multidisk archive"; + case MZ_ZIP_DECOMPRESSION_FAILED: + return "decompression failed or archive is corrupted"; + case MZ_ZIP_COMPRESSION_FAILED: + return "compression failed"; + case MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE: + return "unexpected decompressed size"; + case MZ_ZIP_CRC_CHECK_FAILED: + return "CRC-32 check failed"; + case MZ_ZIP_UNSUPPORTED_CDIR_SIZE: + return "unsupported central directory size"; + case MZ_ZIP_ALLOC_FAILED: + return "allocation failed"; + case MZ_ZIP_FILE_OPEN_FAILED: + return "file open failed"; + case MZ_ZIP_FILE_CREATE_FAILED: + return "file create failed"; + case MZ_ZIP_FILE_WRITE_FAILED: + return "file write failed"; + case MZ_ZIP_FILE_READ_FAILED: + return "file read failed"; + case MZ_ZIP_FILE_CLOSE_FAILED: + return "file close failed"; + case MZ_ZIP_FILE_SEEK_FAILED: + return "file seek failed"; + case MZ_ZIP_FILE_STAT_FAILED: + return "file stat failed"; + case MZ_ZIP_INVALID_PARAMETER: + return "invalid parameter"; + case MZ_ZIP_INVALID_FILENAME: + return "invalid filename"; + case MZ_ZIP_BUF_TOO_SMALL: + return "buffer too small"; + case MZ_ZIP_INTERNAL_ERROR: + return "internal error"; + case MZ_ZIP_FILE_NOT_FOUND: + return "file not found"; + case MZ_ZIP_ARCHIVE_TOO_LARGE: + return "archive is too large"; + case MZ_ZIP_VALIDATION_FAILED: + return "validation failed"; + case MZ_ZIP_WRITE_CALLBACK_FAILED: + return "write calledback failed"; + default: + break; + } + + return "unknown error"; +} + +/* Note: Just because the archive is not zip64 doesn't necessarily mean it doesn't have Zip64 extended information extra field, argh. */ +mz_bool mz_zip_is_zip64(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return MZ_FALSE; + + return pZip->m_pState->m_zip64; +} + +size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return 0; + + return pZip->m_pState->m_central_dir.m_size; +} + +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_total_files : 0; +} + +mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip) +{ + if (!pZip) + return 0; + return pZip->m_archive_size; +} + +mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return 0; + return pZip->m_pState->m_file_archive_start_ofs; +} + +MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return 0; + return pZip->m_pState->m_pFile; +} + +size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + return pZip->m_pRead(pZip->m_pIO_opaque, file_ofs, pBuf, n); +} + +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size) +{ + mz_uint n; + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + if (filename_buf_size) + pFilename[0] = '\0'; + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return 0; + } + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_buf_size) + { + n = MZ_MIN(n, filename_buf_size - 1); + memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pFilename[n] = '\0'; + } + return n + 1; +} + +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat) +{ + return mz_zip_file_stat_internal(pZip, file_index, mz_zip_get_cdh(pZip, file_index), pStat, NULL); +} + +mz_bool mz_zip_end(mz_zip_archive *pZip) +{ + if (!pZip) + return MZ_FALSE; + + if (pZip->m_zip_mode == MZ_ZIP_MODE_READING) + return mz_zip_reader_end(pZip); +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + else if ((pZip->m_zip_mode == MZ_ZIP_MODE_WRITING) || (pZip->m_zip_mode == MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED)) + return mz_zip_writer_end(pZip); +#endif + + return MZ_FALSE; +} + +#ifdef __cplusplus +} +#endif + +#endif /*#ifndef MINIZ_NO_ARCHIVE_APIS*/ diff --git a/gearboy/src/miniz/miniz.h b/gearboy/src/miniz/miniz.h new file mode 100644 index 00000000..6cc398c9 --- /dev/null +++ b/gearboy/src/miniz/miniz.h @@ -0,0 +1,1350 @@ +#define MINIZ_EXPORT +/* miniz.c 2.2.0 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing + See "unlicense" statement at the end of this file. + Rich Geldreich <richgel99@gmail.com>, last updated Oct. 13, 2013 + Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt + + Most API's defined in miniz.c are optional. For example, to disable the archive related functions just define + MINIZ_NO_ARCHIVE_APIS, or to get rid of all stdio usage define MINIZ_NO_STDIO (see the list below for more macros). + + * Low-level Deflate/Inflate implementation notes: + + Compression: Use the "tdefl" API's. The compressor supports raw, static, and dynamic blocks, lazy or + greedy parsing, match length filtering, RLE-only, and Huffman-only streams. It performs and compresses + approximately as well as zlib. + + Decompression: Use the "tinfl" API's. The entire decompressor is implemented as a single function + coroutine: see tinfl_decompress(). It supports decompression into a 32KB (or larger power of 2) wrapping buffer, or into a memory + block large enough to hold the entire file. + + The low-level tdefl/tinfl API's do not make any use of dynamic memory allocation. + + * zlib-style API notes: + + miniz.c implements a fairly large subset of zlib. There's enough functionality present for it to be a drop-in + zlib replacement in many apps: + The z_stream struct, optional memory allocation callbacks + deflateInit/deflateInit2/deflate/deflateReset/deflateEnd/deflateBound + inflateInit/inflateInit2/inflate/inflateReset/inflateEnd + compress, compress2, compressBound, uncompress + CRC-32, Adler-32 - Using modern, minimal code size, CPU cache friendly routines. + Supports raw deflate streams or standard zlib streams with adler-32 checking. + + Limitations: + The callback API's are not implemented yet. No support for gzip headers or zlib static dictionaries. + I've tried to closely emulate zlib's various flavors of stream flushing and return status codes, but + there are no guarantees that miniz.c pulls this off perfectly. + + * PNG writing: See the tdefl_write_image_to_png_file_in_memory() function, originally written by + Alex Evans. Supports 1-4 bytes/pixel images. + + * ZIP archive API notes: + + The ZIP archive API's where designed with simplicity and efficiency in mind, with just enough abstraction to + get the job done with minimal fuss. There are simple API's to retrieve file information, read files from + existing archives, create new archives, append new files to existing archives, or clone archive data from + one archive to another. It supports archives located in memory or the heap, on disk (using stdio.h), + or you can specify custom file read/write callbacks. + + - Archive reading: Just call this function to read a single file from a disk archive: + + void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, + size_t *pSize, mz_uint zip_flags); + + For more complex cases, use the "mz_zip_reader" functions. Upon opening an archive, the entire central + directory is located and read as-is into memory, and subsequent file access only occurs when reading individual files. + + - Archives file scanning: The simple way is to use this function to scan a loaded archive for a specific file: + + int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); + + The locate operation can optionally check file comments too, which (as one example) can be used to identify + multiple versions of the same file in an archive. This function uses a simple linear search through the central + directory, so it's not very fast. + + Alternately, you can iterate through all the files in an archive (using mz_zip_reader_get_num_files()) and + retrieve detailed info on each file by calling mz_zip_reader_file_stat(). + + - Archive creation: Use the "mz_zip_writer" functions. The ZIP writer immediately writes compressed file data + to disk and builds an exact image of the central directory in memory. The central directory image is written + all at once at the end of the archive file when the archive is finalized. + + The archive writer can optionally align each file's local header and file data to any power of 2 alignment, + which can be useful when the archive will be read from optical media. Also, the writer supports placing + arbitrary data blobs at the very beginning of ZIP archives. Archives written using either feature are still + readable by any ZIP tool. + + - Archive appending: The simple way to add a single file to an archive is to call this function: + + mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, + const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); + + The archive will be created if it doesn't already exist, otherwise it'll be appended to. + Note the appending is done in-place and is not an atomic operation, so if something goes wrong + during the operation it's possible the archive could be left without a central directory (although the local + file headers and file data will be fine, so the archive will be recoverable). + + For more complex archive modification scenarios: + 1. The safest way is to use a mz_zip_reader to read the existing archive, cloning only those bits you want to + preserve into a new archive using using the mz_zip_writer_add_from_zip_reader() function (which compiles the + compressed file data as-is). When you're done, delete the old archive and rename the newly written archive, and + you're done. This is safe but requires a bunch of temporary disk space or heap memory. + + 2. Or, you can convert an mz_zip_reader in-place to an mz_zip_writer using mz_zip_writer_init_from_reader(), + append new files as needed, then finalize the archive which will write an updated central directory to the + original archive. (This is basically what mz_zip_add_mem_to_archive_file_in_place() does.) There's a + possibility that the archive's central directory could be lost with this method if anything goes wrong, though. + + - ZIP archive support limitations: + No spanning support. Extraction functions can only handle unencrypted, stored or deflated files. + Requires streams capable of seeking. + + * This is a header file library, like stb_image.c. To get only a header file, either cut and paste the + below header, or create miniz.h, #define MINIZ_HEADER_FILE_ONLY, and then include miniz.c from it. + + * Important: For best perf. be sure to customize the below macros for your target platform: + #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 + #define MINIZ_LITTLE_ENDIAN 1 + #define MINIZ_HAS_64BIT_REGISTERS 1 + + * On platforms using glibc, Be sure to "#define _LARGEFILE64_SOURCE 1" before including miniz.c to ensure miniz + uses the 64-bit variants: fopen64(), stat64(), etc. Otherwise you won't be able to process large files + (i.e. 32-bit stat() fails for me on files > 0x7FFFFFFF bytes). +*/ +#pragma once + + + +/* Defines to completely disable specific portions of miniz.c: + If all macros here are defined the only functionality remaining will be CRC-32, adler-32, tinfl, and tdefl. */ + +/* Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O. */ +/*#define MINIZ_NO_STDIO */ + +/* If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or */ +/* get/set file times, and the C run-time funcs that get/set times won't be called. */ +/* The current downside is the times written to your archives will be from 1979. */ +/*#define MINIZ_NO_TIME */ + +/* Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. */ +/*#define MINIZ_NO_ARCHIVE_APIS */ + +/* Define MINIZ_NO_ARCHIVE_WRITING_APIS to disable all writing related ZIP archive API's. */ +/*#define MINIZ_NO_ARCHIVE_WRITING_APIS */ + +/* Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression API's. */ +/*#define MINIZ_NO_ZLIB_APIS */ + +/* Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent conflicts against stock zlib. */ +/*#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES */ + +/* Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. + Note if MINIZ_NO_MALLOC is defined then the user must always provide custom user alloc/free/realloc + callbacks to the zlib and archive API's, and a few stand-alone helper API's which don't provide custom user + functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. */ +/*#define MINIZ_NO_MALLOC */ + +#if defined(__TINYC__) && (defined(__linux) || defined(__linux__)) +/* TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux */ +#define MINIZ_NO_TIME +#endif + +#include <stddef.h> + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS) +#include <time.h> +#endif + +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__) +/* MINIZ_X86_OR_X64_CPU is only used to help set the below macros. */ +#define MINIZ_X86_OR_X64_CPU 1 +#else +#define MINIZ_X86_OR_X64_CPU 0 +#endif + +#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU +/* Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. */ +#define MINIZ_LITTLE_ENDIAN 1 +#else +#define MINIZ_LITTLE_ENDIAN 0 +#endif + +/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES only if not set */ +#if !defined(MINIZ_USE_UNALIGNED_LOADS_AND_STORES) +#if MINIZ_X86_OR_X64_CPU +/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. */ +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 +#define MINIZ_UNALIGNED_USE_MEMCPY +#else +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0 +#endif +#endif + +#if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__) +/* Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions). */ +#define MINIZ_HAS_64BIT_REGISTERS 1 +#else +#define MINIZ_HAS_64BIT_REGISTERS 0 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- zlib-style API Definitions. */ + +/* For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits! */ +typedef unsigned long mz_ulong; + +/* mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap. */ +MINIZ_EXPORT void mz_free(void *p); + +#define MZ_ADLER32_INIT (1) +/* mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. */ +MINIZ_EXPORT mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); + +#define MZ_CRC32_INIT (0) +/* mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. */ +MINIZ_EXPORT mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); + +/* Compression strategies. */ +enum +{ + MZ_DEFAULT_STRATEGY = 0, + MZ_FILTERED = 1, + MZ_HUFFMAN_ONLY = 2, + MZ_RLE = 3, + MZ_FIXED = 4 +}; + +/* Method */ +#define MZ_DEFLATED 8 + +/* Heap allocation callbacks. +Note that mz_alloc_func parameter types purposely differ from zlib's: items/size is size_t, not unsigned long. */ +typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size); +typedef void (*mz_free_func)(void *opaque, void *address); +typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size); + +/* Compression levels: 0-9 are the standard zlib-style levels, 10 is best possible compression (not zlib compatible, and may be very slow), MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL. */ +enum +{ + MZ_NO_COMPRESSION = 0, + MZ_BEST_SPEED = 1, + MZ_BEST_COMPRESSION = 9, + MZ_UBER_COMPRESSION = 10, + MZ_DEFAULT_LEVEL = 6, + MZ_DEFAULT_COMPRESSION = -1 +}; + +#define MZ_VERSION "10.2.0" +#define MZ_VERNUM 0xA100 +#define MZ_VER_MAJOR 10 +#define MZ_VER_MINOR 2 +#define MZ_VER_REVISION 0 +#define MZ_VER_SUBREVISION 0 + +#ifndef MINIZ_NO_ZLIB_APIS + +/* Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other values are for advanced use (refer to the zlib docs). */ +enum +{ + MZ_NO_FLUSH = 0, + MZ_PARTIAL_FLUSH = 1, + MZ_SYNC_FLUSH = 2, + MZ_FULL_FLUSH = 3, + MZ_FINISH = 4, + MZ_BLOCK = 5 +}; + +/* Return status codes. MZ_PARAM_ERROR is non-standard. */ +enum +{ + MZ_OK = 0, + MZ_STREAM_END = 1, + MZ_NEED_DICT = 2, + MZ_ERRNO = -1, + MZ_STREAM_ERROR = -2, + MZ_DATA_ERROR = -3, + MZ_MEM_ERROR = -4, + MZ_BUF_ERROR = -5, + MZ_VERSION_ERROR = -6, + MZ_PARAM_ERROR = -10000 +}; + +/* Window bits */ +#define MZ_DEFAULT_WINDOW_BITS 15 + +struct mz_internal_state; + +/* Compression/decompression stream struct. */ +typedef struct mz_stream_s +{ + const unsigned char *next_in; /* pointer to next byte to read */ + unsigned int avail_in; /* number of bytes available at next_in */ + mz_ulong total_in; /* total number of bytes consumed so far */ + + unsigned char *next_out; /* pointer to next byte to write */ + unsigned int avail_out; /* number of bytes that can be written to next_out */ + mz_ulong total_out; /* total number of bytes produced so far */ + + char *msg; /* error msg (unused) */ + struct mz_internal_state *state; /* internal state, allocated by zalloc/zfree */ + + mz_alloc_func zalloc; /* optional heap allocation function (defaults to malloc) */ + mz_free_func zfree; /* optional heap free function (defaults to free) */ + void *opaque; /* heap alloc function user pointer */ + + int data_type; /* data_type (unused) */ + mz_ulong adler; /* adler32 of the source or uncompressed data */ + mz_ulong reserved; /* not used */ +} mz_stream; + +typedef mz_stream *mz_streamp; + +/* Returns the version string of miniz.c. */ +MINIZ_EXPORT const char *mz_version(void); + +/* mz_deflateInit() initializes a compressor with default options: */ +/* Parameters: */ +/* pStream must point to an initialized mz_stream struct. */ +/* level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION]. */ +/* level 1 enables a specially optimized compression function that's been optimized purely for performance, not ratio. */ +/* (This special func. is currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and MINIZ_LITTLE_ENDIAN are defined.) */ +/* Return values: */ +/* MZ_OK on success. */ +/* MZ_STREAM_ERROR if the stream is bogus. */ +/* MZ_PARAM_ERROR if the input parameters are bogus. */ +/* MZ_MEM_ERROR on out of memory. */ +MINIZ_EXPORT int mz_deflateInit(mz_streamp pStream, int level); + +/* mz_deflateInit2() is like mz_deflate(), except with more control: */ +/* Additional parameters: */ +/* method must be MZ_DEFLATED */ +/* window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer) */ +/* mem_level must be between [1, 9] (it's checked but ignored by miniz.c) */ +MINIZ_EXPORT int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy); + +/* Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). */ +MINIZ_EXPORT int mz_deflateReset(mz_streamp pStream); + +/* mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible. */ +/* Parameters: */ +/* pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. */ +/* flush may be MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH. */ +/* Return values: */ +/* MZ_OK on success (when flushing, or if more input is needed but not available, and/or there's more output to be written but the output buffer is full). */ +/* MZ_STREAM_END if all input has been consumed and all output bytes have been written. Don't call mz_deflate() on the stream anymore. */ +/* MZ_STREAM_ERROR if the stream is bogus. */ +/* MZ_PARAM_ERROR if one of the parameters is invalid. */ +/* MZ_BUF_ERROR if no forward progress is possible because the input and/or output buffers are empty. (Fill up the input buffer or free up some output space and try again.) */ +MINIZ_EXPORT int mz_deflate(mz_streamp pStream, int flush); + +/* mz_deflateEnd() deinitializes a compressor: */ +/* Return values: */ +/* MZ_OK on success. */ +/* MZ_STREAM_ERROR if the stream is bogus. */ +MINIZ_EXPORT int mz_deflateEnd(mz_streamp pStream); + +/* mz_deflateBound() returns a (very) conservative upper bound on the amount of data that could be generated by deflate(), assuming flush is set to only MZ_NO_FLUSH or MZ_FINISH. */ +MINIZ_EXPORT mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); + +/* Single-call compression functions mz_compress() and mz_compress2(): */ +/* Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure. */ +MINIZ_EXPORT int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); +MINIZ_EXPORT int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level); + +/* mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress(). */ +MINIZ_EXPORT mz_ulong mz_compressBound(mz_ulong source_len); + +/* Initializes a decompressor. */ +MINIZ_EXPORT int mz_inflateInit(mz_streamp pStream); + +/* mz_inflateInit2() is like mz_inflateInit() with an additional option that controls the window size and whether or not the stream has been wrapped with a zlib header/footer: */ +/* window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). */ +MINIZ_EXPORT int mz_inflateInit2(mz_streamp pStream, int window_bits); + +/* Quickly resets a compressor without having to reallocate anything. Same as calling mz_inflateEnd() followed by mz_inflateInit()/mz_inflateInit2(). */ +MINIZ_EXPORT int mz_inflateReset(mz_streamp pStream); + +/* Decompresses the input stream to the output, consuming only as much of the input as needed, and writing as much to the output as possible. */ +/* Parameters: */ +/* pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. */ +/* flush may be MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH. */ +/* On the first call, if flush is MZ_FINISH it's assumed the input and output buffers are both sized large enough to decompress the entire stream in a single call (this is slightly faster). */ +/* MZ_FINISH implies that there are no more source bytes available beside what's already in the input buffer, and that the output buffer is large enough to hold the rest of the decompressed data. */ +/* Return values: */ +/* MZ_OK on success. Either more input is needed but not available, and/or there's more output to be written but the output buffer is full. */ +/* MZ_STREAM_END if all needed input has been consumed and all output bytes have been written. For zlib streams, the adler-32 of the decompressed data has also been verified. */ +/* MZ_STREAM_ERROR if the stream is bogus. */ +/* MZ_DATA_ERROR if the deflate stream is invalid. */ +/* MZ_PARAM_ERROR if one of the parameters is invalid. */ +/* MZ_BUF_ERROR if no forward progress is possible because the input buffer is empty but the inflater needs more input to continue, or if the output buffer is not large enough. Call mz_inflate() again */ +/* with more input data, or with more room in the output buffer (except when using single call decompression, described above). */ +MINIZ_EXPORT int mz_inflate(mz_streamp pStream, int flush); + +/* Deinitializes a decompressor. */ +MINIZ_EXPORT int mz_inflateEnd(mz_streamp pStream); + +/* Single-call decompression. */ +/* Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure. */ +MINIZ_EXPORT int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); +MINIZ_EXPORT int mz_uncompress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong *pSource_len); + +/* Returns a string description of the specified error code, or NULL if the error code is invalid. */ +MINIZ_EXPORT const char *mz_error(int err); + +/* Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports. */ +/* Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project. */ +#ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES +typedef unsigned char Byte; +typedef unsigned int uInt; +typedef mz_ulong uLong; +typedef Byte Bytef; +typedef uInt uIntf; +typedef char charf; +typedef int intf; +typedef void *voidpf; +typedef uLong uLongf; +typedef void *voidp; +typedef void *const voidpc; +#define Z_NULL 0 +#define Z_NO_FLUSH MZ_NO_FLUSH +#define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH +#define Z_SYNC_FLUSH MZ_SYNC_FLUSH +#define Z_FULL_FLUSH MZ_FULL_FLUSH +#define Z_FINISH MZ_FINISH +#define Z_BLOCK MZ_BLOCK +#define Z_OK MZ_OK +#define Z_STREAM_END MZ_STREAM_END +#define Z_NEED_DICT MZ_NEED_DICT +#define Z_ERRNO MZ_ERRNO +#define Z_STREAM_ERROR MZ_STREAM_ERROR +#define Z_DATA_ERROR MZ_DATA_ERROR +#define Z_MEM_ERROR MZ_MEM_ERROR +#define Z_BUF_ERROR MZ_BUF_ERROR +#define Z_VERSION_ERROR MZ_VERSION_ERROR +#define Z_PARAM_ERROR MZ_PARAM_ERROR +#define Z_NO_COMPRESSION MZ_NO_COMPRESSION +#define Z_BEST_SPEED MZ_BEST_SPEED +#define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION +#define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION +#define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY +#define Z_FILTERED MZ_FILTERED +#define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY +#define Z_RLE MZ_RLE +#define Z_FIXED MZ_FIXED +#define Z_DEFLATED MZ_DEFLATED +#define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS +#define alloc_func mz_alloc_func +#define free_func mz_free_func +#define internal_state mz_internal_state +#define z_stream mz_stream +#define deflateInit mz_deflateInit +#define deflateInit2 mz_deflateInit2 +#define deflateReset mz_deflateReset +#define deflate mz_deflate +#define deflateEnd mz_deflateEnd +#define deflateBound mz_deflateBound +#define compress mz_compress +#define compress2 mz_compress2 +#define compressBound mz_compressBound +#define inflateInit mz_inflateInit +#define inflateInit2 mz_inflateInit2 +#define inflateReset mz_inflateReset +#define inflate mz_inflate +#define inflateEnd mz_inflateEnd +#define uncompress mz_uncompress +#define uncompress2 mz_uncompress2 +#define crc32 mz_crc32 +#define adler32 mz_adler32 +#define MAX_WBITS 15 +#define MAX_MEM_LEVEL 9 +#define zError mz_error +#define ZLIB_VERSION MZ_VERSION +#define ZLIB_VERNUM MZ_VERNUM +#define ZLIB_VER_MAJOR MZ_VER_MAJOR +#define ZLIB_VER_MINOR MZ_VER_MINOR +#define ZLIB_VER_REVISION MZ_VER_REVISION +#define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION +#define zlibVersion mz_version +#define zlib_version mz_version() +#endif /* #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES */ + +#endif /* MINIZ_NO_ZLIB_APIS */ + +#ifdef __cplusplus +} +#endif + + + + + +#pragma once +#include <assert.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + + + +/* ------------------- Types and macros */ +typedef unsigned char mz_uint8; +typedef signed short mz_int16; +typedef unsigned short mz_uint16; +typedef unsigned int mz_uint32; +typedef unsigned int mz_uint; +typedef int64_t mz_int64; +typedef uint64_t mz_uint64; +typedef int mz_bool; + +#define MZ_FALSE (0) +#define MZ_TRUE (1) + +/* Works around MSVC's spammy "warning C4127: conditional expression is constant" message. */ +#ifdef _MSC_VER +#define MZ_MACRO_END while (0, 0) +#else +#define MZ_MACRO_END while (0) +#endif + +#ifdef MINIZ_NO_STDIO +#define MZ_FILE void * +#else +#include <stdio.h> +#define MZ_FILE FILE +#endif /* #ifdef MINIZ_NO_STDIO */ + +#ifdef MINIZ_NO_TIME +typedef struct mz_dummy_time_t_tag +{ + int m_dummy; +} mz_dummy_time_t; +#define MZ_TIME_T mz_dummy_time_t +#else +#define MZ_TIME_T time_t +#endif + +#define MZ_ASSERT(x) assert(x) + +#ifdef MINIZ_NO_MALLOC +#define MZ_MALLOC(x) NULL +#define MZ_FREE(x) (void)x, ((void)0) +#define MZ_REALLOC(p, x) NULL +#else +#define MZ_MALLOC(x) malloc(x) +#define MZ_FREE(x) free(x) +#define MZ_REALLOC(p, x) realloc(p, x) +#endif + +#define MZ_MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define MZ_MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN +#define MZ_READ_LE16(p) *((const mz_uint16 *)(p)) +#define MZ_READ_LE32(p) *((const mz_uint32 *)(p)) +#else +#define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U)) +#define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U)) +#endif + +#define MZ_READ_LE64(p) (((mz_uint64)MZ_READ_LE32(p)) | (((mz_uint64)MZ_READ_LE32((const mz_uint8 *)(p) + sizeof(mz_uint32))) << 32U)) + +#ifdef _MSC_VER +#define MZ_FORCEINLINE __forceinline +#elif defined(__GNUC__) +#define MZ_FORCEINLINE __inline__ __attribute__((__always_inline__)) +#else +#define MZ_FORCEINLINE inline +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +extern MINIZ_EXPORT void *miniz_def_alloc_func(void *opaque, size_t items, size_t size); +extern MINIZ_EXPORT void miniz_def_free_func(void *opaque, void *address); +extern MINIZ_EXPORT void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size); + +#define MZ_UINT16_MAX (0xFFFFU) +#define MZ_UINT32_MAX (0xFFFFFFFFU) + +#ifdef __cplusplus +} +#endif + #pragma once + + +#ifdef __cplusplus +extern "C" { +#endif +/* ------------------- Low-level Compression API Definitions */ + +/* Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly slower, and raw/dynamic blocks will be output more frequently). */ +#define TDEFL_LESS_MEMORY 0 + +/* tdefl_init() compression flags logically OR'd together (low 12 bits contain the max. number of probes per dictionary search): */ +/* TDEFL_DEFAULT_MAX_PROBES: The compressor defaults to 128 dictionary probes per dictionary search. 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ (slowest/best compression). */ +enum +{ + TDEFL_HUFFMAN_ONLY = 0, + TDEFL_DEFAULT_MAX_PROBES = 128, + TDEFL_MAX_PROBES_MASK = 0xFFF +}; + +/* TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before the deflate data, and the Adler-32 of the source data at the end. Otherwise, you'll get raw deflate data. */ +/* TDEFL_COMPUTE_ADLER32: Always compute the adler-32 of the input data (even when not writing zlib headers). */ +/* TDEFL_GREEDY_PARSING_FLAG: Set to use faster greedy parsing, instead of more efficient lazy parsing. */ +/* TDEFL_NONDETERMINISTIC_PARSING_FLAG: Enable to decrease the compressor's initialization time to the minimum, but the output may vary from run to run given the same input (depending on the contents of memory). */ +/* TDEFL_RLE_MATCHES: Only look for RLE matches (matches with a distance of 1) */ +/* TDEFL_FILTER_MATCHES: Discards matches <= 5 chars if enabled. */ +/* TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables. */ +/* TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks. */ +/* The low 12 bits are reserved to control the max # of hash probes per dictionary lookup (see TDEFL_MAX_PROBES_MASK). */ +enum +{ + TDEFL_WRITE_ZLIB_HEADER = 0x01000, + TDEFL_COMPUTE_ADLER32 = 0x02000, + TDEFL_GREEDY_PARSING_FLAG = 0x04000, + TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000, + TDEFL_RLE_MATCHES = 0x10000, + TDEFL_FILTER_MATCHES = 0x20000, + TDEFL_FORCE_ALL_STATIC_BLOCKS = 0x40000, + TDEFL_FORCE_ALL_RAW_BLOCKS = 0x80000 +}; + +/* High level compression functions: */ +/* tdefl_compress_mem_to_heap() compresses a block in memory to a heap block allocated via malloc(). */ +/* On entry: */ +/* pSrc_buf, src_buf_len: Pointer and size of source block to compress. */ +/* flags: The max match finder probes (default is 128) logically OR'd against the above flags. Higher probes are slower but improve compression. */ +/* On return: */ +/* Function returns a pointer to the compressed data, or NULL on failure. */ +/* *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data. */ +/* The caller must free() the returned block when it's no longer needed. */ +MINIZ_EXPORT void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); + +/* tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory. */ +/* Returns 0 on failure. */ +MINIZ_EXPORT size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); + +/* Compresses an image to a compressed PNG file in memory. */ +/* On entry: */ +/* pImage, w, h, and num_chans describe the image to compress. num_chans may be 1, 2, 3, or 4. */ +/* The image pitch in bytes per scanline will be w*num_chans. The leftmost pixel on the top scanline is stored first in memory. */ +/* level may range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL */ +/* If flip is true, the image will be flipped on the Y axis (useful for OpenGL apps). */ +/* On return: */ +/* Function returns a pointer to the compressed data, or NULL on failure. */ +/* *pLen_out will be set to the size of the PNG image file. */ +/* The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed. */ +MINIZ_EXPORT void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip); +MINIZ_EXPORT void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out); + +/* Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. */ +typedef mz_bool (*tdefl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); + +/* tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally. */ +MINIZ_EXPORT mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +enum +{ + TDEFL_MAX_HUFF_TABLES = 3, + TDEFL_MAX_HUFF_SYMBOLS_0 = 288, + TDEFL_MAX_HUFF_SYMBOLS_1 = 32, + TDEFL_MAX_HUFF_SYMBOLS_2 = 19, + TDEFL_LZ_DICT_SIZE = 32768, + TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1, + TDEFL_MIN_MATCH_LEN = 3, + TDEFL_MAX_MATCH_LEN = 258 +}; + +/* TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed output block (using static/fixed Huffman codes). */ +#if TDEFL_LESS_MEMORY +enum +{ + TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024, + TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10, + TDEFL_MAX_HUFF_SYMBOLS = 288, + TDEFL_LZ_HASH_BITS = 12, + TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, + TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, + TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS +}; +#else +enum +{ + TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024, + TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10, + TDEFL_MAX_HUFF_SYMBOLS = 288, + TDEFL_LZ_HASH_BITS = 15, + TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, + TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, + TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS +}; +#endif + +/* The low-level tdefl functions below may be used directly if the above helper functions aren't flexible enough. The low-level functions don't make any heap allocations, unlike the above helper functions. */ +typedef enum { + TDEFL_STATUS_BAD_PARAM = -2, + TDEFL_STATUS_PUT_BUF_FAILED = -1, + TDEFL_STATUS_OKAY = 0, + TDEFL_STATUS_DONE = 1 +} tdefl_status; + +/* Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums */ +typedef enum { + TDEFL_NO_FLUSH = 0, + TDEFL_SYNC_FLUSH = 2, + TDEFL_FULL_FLUSH = 3, + TDEFL_FINISH = 4 +} tdefl_flush; + +/* tdefl's compression state structure. */ +typedef struct +{ + tdefl_put_buf_func_ptr m_pPut_buf_func; + void *m_pPut_buf_user; + mz_uint m_flags, m_max_probes[2]; + int m_greedy_parsing; + mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size; + mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end; + mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, m_bit_buffer; + mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, m_wants_to_finish; + tdefl_status m_prev_return_status; + const void *m_pIn_buf; + void *m_pOut_buf; + size_t *m_pIn_buf_size, *m_pOut_buf_size; + tdefl_flush m_flush; + const mz_uint8 *m_pSrc; + size_t m_src_buf_left, m_out_buf_ofs; + mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1]; + mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE]; + mz_uint16 m_next[TDEFL_LZ_DICT_SIZE]; + mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE]; + mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE]; +} tdefl_compressor; + +/* Initializes the compressor. */ +/* There is no corresponding deinit() function because the tdefl API's do not dynamically allocate memory. */ +/* pBut_buf_func: If NULL, output data will be supplied to the specified callback. In this case, the user should call the tdefl_compress_buffer() API for compression. */ +/* If pBut_buf_func is NULL the user should always call the tdefl_compress() API. */ +/* flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) */ +MINIZ_EXPORT tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +/* Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible. */ +MINIZ_EXPORT tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush); + +/* tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr. */ +/* tdefl_compress_buffer() always consumes the entire input buffer. */ +MINIZ_EXPORT tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush); + +MINIZ_EXPORT tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); +MINIZ_EXPORT mz_uint32 tdefl_get_adler32(tdefl_compressor *d); + +/* Create tdefl_compress() flags given zlib-style compression parameters. */ +/* level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files) */ +/* window_bits may be -15 (raw deflate) or 15 (zlib) */ +/* strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED */ +MINIZ_EXPORT mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy); + +#ifndef MINIZ_NO_MALLOC +/* Allocate the tdefl_compressor structure in C so that */ +/* non-C language bindings to tdefl_ API don't need to worry about */ +/* structure size and allocation mechanism. */ +MINIZ_EXPORT tdefl_compressor *tdefl_compressor_alloc(void); +MINIZ_EXPORT void tdefl_compressor_free(tdefl_compressor *pComp); +#endif + +#ifdef __cplusplus +} +#endif + #pragma once + +/* ------------------- Low-level Decompression API Definitions */ + +#ifdef __cplusplus +extern "C" { +#endif +/* Decompression flags used by tinfl_decompress(). */ +/* TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream. */ +/* TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. */ +/* TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). */ +/* TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. */ +enum +{ + TINFL_FLAG_PARSE_ZLIB_HEADER = 1, + TINFL_FLAG_HAS_MORE_INPUT = 2, + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, + TINFL_FLAG_COMPUTE_ADLER32 = 8 +}; + +/* High level decompression functions: */ +/* tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc(). */ +/* On entry: */ +/* pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress. */ +/* On return: */ +/* Function returns a pointer to the decompressed data, or NULL on failure. */ +/* *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. */ +/* The caller must call mz_free() on the returned block when it's no longer needed. */ +MINIZ_EXPORT void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); + +/* tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. */ +/* Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. */ +#define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) +MINIZ_EXPORT size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); + +/* tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. */ +/* Returns 1 on success or 0 on failure. */ +typedef int (*tinfl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); +MINIZ_EXPORT int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +struct tinfl_decompressor_tag; +typedef struct tinfl_decompressor_tag tinfl_decompressor; + +#ifndef MINIZ_NO_MALLOC +/* Allocate the tinfl_decompressor structure in C so that */ +/* non-C language bindings to tinfl_ API don't need to worry about */ +/* structure size and allocation mechanism. */ +MINIZ_EXPORT tinfl_decompressor *tinfl_decompressor_alloc(void); +MINIZ_EXPORT void tinfl_decompressor_free(tinfl_decompressor *pDecomp); +#endif + +/* Max size of LZ dictionary. */ +#define TINFL_LZ_DICT_SIZE 32768 + +/* Return status. */ +typedef enum { + /* This flags indicates the inflator needs 1 or more input bytes to make forward progress, but the caller is indicating that no more are available. The compressed data */ + /* is probably corrupted. If you call the inflator again with more bytes it'll try to continue processing the input but this is a BAD sign (either the data is corrupted or you called it incorrectly). */ + /* If you call it again with no input you'll just get TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS again. */ + TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS = -4, + + /* This flag indicates that one or more of the input parameters was obviously bogus. (You can try calling it again, but if you get this error the calling code is wrong.) */ + TINFL_STATUS_BAD_PARAM = -3, + + /* This flags indicate the inflator is finished but the adler32 check of the uncompressed data didn't match. If you call it again it'll return TINFL_STATUS_DONE. */ + TINFL_STATUS_ADLER32_MISMATCH = -2, + + /* This flags indicate the inflator has somehow failed (bad code, corrupted input, etc.). If you call it again without resetting via tinfl_init() it it'll just keep on returning the same status failure code. */ + TINFL_STATUS_FAILED = -1, + + /* Any status code less than TINFL_STATUS_DONE must indicate a failure. */ + + /* This flag indicates the inflator has returned every byte of uncompressed data that it can, has consumed every byte that it needed, has successfully reached the end of the deflate stream, and */ + /* if zlib headers and adler32 checking enabled that it has successfully checked the uncompressed data's adler32. If you call it again you'll just get TINFL_STATUS_DONE over and over again. */ + TINFL_STATUS_DONE = 0, + + /* This flag indicates the inflator MUST have more input data (even 1 byte) before it can make any more forward progress, or you need to clear the TINFL_FLAG_HAS_MORE_INPUT */ + /* flag on the next call if you don't have any more source data. If the source data was somehow corrupted it's also possible (but unlikely) for the inflator to keep on demanding input to */ + /* proceed, so be sure to properly set the TINFL_FLAG_HAS_MORE_INPUT flag. */ + TINFL_STATUS_NEEDS_MORE_INPUT = 1, + + /* This flag indicates the inflator definitely has 1 or more bytes of uncompressed data available, but it cannot write this data into the output buffer. */ + /* Note if the source compressed data was corrupted it's possible for the inflator to return a lot of uncompressed data to the caller. I've been assuming you know how much uncompressed data to expect */ + /* (either exact or worst case) and will stop calling the inflator and fail after receiving too much. In pure streaming scenarios where you have no idea how many bytes to expect this may not be possible */ + /* so I may need to add some code to address this. */ + TINFL_STATUS_HAS_MORE_OUTPUT = 2 +} tinfl_status; + +/* Initializes the decompressor to its initial state. */ +#define tinfl_init(r) \ + do \ + { \ + (r)->m_state = 0; \ + } \ + MZ_MACRO_END +#define tinfl_get_adler32(r) (r)->m_check_adler32 + +/* Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. */ +/* This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. */ +MINIZ_EXPORT tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags); + +/* Internal/private bits follow. */ +enum +{ + TINFL_MAX_HUFF_TABLES = 3, + TINFL_MAX_HUFF_SYMBOLS_0 = 288, + TINFL_MAX_HUFF_SYMBOLS_1 = 32, + TINFL_MAX_HUFF_SYMBOLS_2 = 19, + TINFL_FAST_LOOKUP_BITS = 10, + TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS +}; + +typedef struct +{ + mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; + mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; +} tinfl_huff_table; + +#if MINIZ_HAS_64BIT_REGISTERS +#define TINFL_USE_64BIT_BITBUF 1 +#else +#define TINFL_USE_64BIT_BITBUF 0 +#endif + +#if TINFL_USE_64BIT_BITBUF +typedef mz_uint64 tinfl_bit_buf_t; +#define TINFL_BITBUF_SIZE (64) +#else +typedef mz_uint32 tinfl_bit_buf_t; +#define TINFL_BITBUF_SIZE (32) +#endif + +struct tinfl_decompressor_tag +{ + mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES]; + tinfl_bit_buf_t m_bit_buf; + size_t m_dist_from_out_buf_start; + tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; + mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; +}; + +#ifdef __cplusplus +} +#endif + +#pragma once + + +/* ------------------- ZIP archive reading/writing */ + +#ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef __cplusplus +extern "C" { +#endif + +enum +{ + /* Note: These enums can be reduced as needed to save memory or stack space - they are pretty conservative. */ + MZ_ZIP_MAX_IO_BUF_SIZE = 64 * 1024, + MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 512, + MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 512 +}; + +typedef struct +{ + /* Central directory file index. */ + mz_uint32 m_file_index; + + /* Byte offset of this entry in the archive's central directory. Note we currently only support up to UINT_MAX or less bytes in the central dir. */ + mz_uint64 m_central_dir_ofs; + + /* These fields are copied directly from the zip's central dir. */ + mz_uint16 m_version_made_by; + mz_uint16 m_version_needed; + mz_uint16 m_bit_flag; + mz_uint16 m_method; + +#ifndef MINIZ_NO_TIME + MZ_TIME_T m_time; +#endif + + /* CRC-32 of uncompressed data. */ + mz_uint32 m_crc32; + + /* File's compressed size. */ + mz_uint64 m_comp_size; + + /* File's uncompressed size. Note, I've seen some old archives where directory entries had 512 bytes for their uncompressed sizes, but when you try to unpack them you actually get 0 bytes. */ + mz_uint64 m_uncomp_size; + + /* Zip internal and external file attributes. */ + mz_uint16 m_internal_attr; + mz_uint32 m_external_attr; + + /* Entry's local header file offset in bytes. */ + mz_uint64 m_local_header_ofs; + + /* Size of comment in bytes. */ + mz_uint32 m_comment_size; + + /* MZ_TRUE if the entry appears to be a directory. */ + mz_bool m_is_directory; + + /* MZ_TRUE if the entry uses encryption/strong encryption (which miniz_zip doesn't support) */ + mz_bool m_is_encrypted; + + /* MZ_TRUE if the file is not encrypted, a patch file, and if it uses a compression method we support. */ + mz_bool m_is_supported; + + /* Filename. If string ends in '/' it's a subdirectory entry. */ + /* Guaranteed to be zero terminated, may be truncated to fit. */ + char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; + + /* Comment field. */ + /* Guaranteed to be zero terminated, may be truncated to fit. */ + char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; + +} mz_zip_archive_file_stat; + +typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n); +typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n); +typedef mz_bool (*mz_file_needs_keepalive)(void *pOpaque); + +struct mz_zip_internal_state_tag; +typedef struct mz_zip_internal_state_tag mz_zip_internal_state; + +typedef enum { + MZ_ZIP_MODE_INVALID = 0, + MZ_ZIP_MODE_READING = 1, + MZ_ZIP_MODE_WRITING = 2, + MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 +} mz_zip_mode; + +typedef enum { + MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100, + MZ_ZIP_FLAG_IGNORE_PATH = 0x0200, + MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400, + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800, + MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG = 0x1000, /* if enabled, mz_zip_reader_locate_file() will be called on each file as its validated to ensure the func finds the file in the central dir (intended for testing) */ + MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY = 0x2000, /* validate the local headers, but don't decompress the entire file and check the crc32 */ + MZ_ZIP_FLAG_WRITE_ZIP64 = 0x4000, /* always use the zip64 file format, instead of the original zip file format with automatic switch to zip64. Use as flags parameter with mz_zip_writer_init*_v2 */ + MZ_ZIP_FLAG_WRITE_ALLOW_READING = 0x8000, + MZ_ZIP_FLAG_ASCII_FILENAME = 0x10000, + /*After adding a compressed file, seek back + to local file header and set the correct sizes*/ + MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE = 0x20000 +} mz_zip_flags; + +typedef enum { + MZ_ZIP_TYPE_INVALID = 0, + MZ_ZIP_TYPE_USER, + MZ_ZIP_TYPE_MEMORY, + MZ_ZIP_TYPE_HEAP, + MZ_ZIP_TYPE_FILE, + MZ_ZIP_TYPE_CFILE, + MZ_ZIP_TOTAL_TYPES +} mz_zip_type; + +/* miniz error codes. Be sure to update mz_zip_get_error_string() if you add or modify this enum. */ +typedef enum { + MZ_ZIP_NO_ERROR = 0, + MZ_ZIP_UNDEFINED_ERROR, + MZ_ZIP_TOO_MANY_FILES, + MZ_ZIP_FILE_TOO_LARGE, + MZ_ZIP_UNSUPPORTED_METHOD, + MZ_ZIP_UNSUPPORTED_ENCRYPTION, + MZ_ZIP_UNSUPPORTED_FEATURE, + MZ_ZIP_FAILED_FINDING_CENTRAL_DIR, + MZ_ZIP_NOT_AN_ARCHIVE, + MZ_ZIP_INVALID_HEADER_OR_CORRUPTED, + MZ_ZIP_UNSUPPORTED_MULTIDISK, + MZ_ZIP_DECOMPRESSION_FAILED, + MZ_ZIP_COMPRESSION_FAILED, + MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE, + MZ_ZIP_CRC_CHECK_FAILED, + MZ_ZIP_UNSUPPORTED_CDIR_SIZE, + MZ_ZIP_ALLOC_FAILED, + MZ_ZIP_FILE_OPEN_FAILED, + MZ_ZIP_FILE_CREATE_FAILED, + MZ_ZIP_FILE_WRITE_FAILED, + MZ_ZIP_FILE_READ_FAILED, + MZ_ZIP_FILE_CLOSE_FAILED, + MZ_ZIP_FILE_SEEK_FAILED, + MZ_ZIP_FILE_STAT_FAILED, + MZ_ZIP_INVALID_PARAMETER, + MZ_ZIP_INVALID_FILENAME, + MZ_ZIP_BUF_TOO_SMALL, + MZ_ZIP_INTERNAL_ERROR, + MZ_ZIP_FILE_NOT_FOUND, + MZ_ZIP_ARCHIVE_TOO_LARGE, + MZ_ZIP_VALIDATION_FAILED, + MZ_ZIP_WRITE_CALLBACK_FAILED, + MZ_ZIP_TOTAL_ERRORS +} mz_zip_error; + +typedef struct +{ + mz_uint64 m_archive_size; + mz_uint64 m_central_directory_file_ofs; + + /* We only support up to UINT32_MAX files in zip64 mode. */ + mz_uint32 m_total_files; + mz_zip_mode m_zip_mode; + mz_zip_type m_zip_type; + mz_zip_error m_last_error; + + mz_uint64 m_file_offset_alignment; + + mz_alloc_func m_pAlloc; + mz_free_func m_pFree; + mz_realloc_func m_pRealloc; + void *m_pAlloc_opaque; + + mz_file_read_func m_pRead; + mz_file_write_func m_pWrite; + mz_file_needs_keepalive m_pNeeds_keepalive; + void *m_pIO_opaque; + + mz_zip_internal_state *m_pState; + +} mz_zip_archive; + +typedef struct +{ + mz_zip_archive *pZip; + mz_uint flags; + + int status; +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + mz_uint file_crc32; +#endif + mz_uint64 read_buf_size, read_buf_ofs, read_buf_avail, comp_remaining, out_buf_ofs, cur_file_ofs; + mz_zip_archive_file_stat file_stat; + void *pRead_buf; + void *pWrite_buf; + + size_t out_blk_remain; + + tinfl_decompressor inflator; + +} mz_zip_reader_extract_iter_state; + +/* -------- ZIP reading */ + +/* Inits a ZIP archive reader. */ +/* These functions read and validate the archive's central directory. */ +MINIZ_EXPORT mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags); + +MINIZ_EXPORT mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags); + +#ifndef MINIZ_NO_STDIO +/* Read a archive from a disk file. */ +/* file_start_ofs is the file offset where the archive actually begins, or 0. */ +/* actual_archive_size is the true total size of the archive, which may be smaller than the file's actual size on disk. If zero the entire file is treated as the archive. */ +MINIZ_EXPORT mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags); +MINIZ_EXPORT mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size); + +/* Read an archive from an already opened FILE, beginning at the current file position. */ +/* The archive is assumed to be archive_size bytes long. If archive_size is 0, then the entire rest of the file is assumed to contain the archive. */ +/* The FILE will NOT be closed when mz_zip_reader_end() is called. */ +MINIZ_EXPORT mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags); +#endif + +/* Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. */ +MINIZ_EXPORT mz_bool mz_zip_reader_end(mz_zip_archive *pZip); + +/* -------- ZIP reading or writing */ + +/* Clears a mz_zip_archive struct to all zeros. */ +/* Important: This must be done before passing the struct to any mz_zip functions. */ +MINIZ_EXPORT void mz_zip_zero_struct(mz_zip_archive *pZip); + +MINIZ_EXPORT mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip); +MINIZ_EXPORT mz_zip_type mz_zip_get_type(mz_zip_archive *pZip); + +/* Returns the total number of files in the archive. */ +MINIZ_EXPORT mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip); + +MINIZ_EXPORT mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip); +MINIZ_EXPORT mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip); +MINIZ_EXPORT MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip); + +/* Reads n bytes of raw archive data, starting at file offset file_ofs, to pBuf. */ +MINIZ_EXPORT size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n); + +/* All mz_zip funcs set the m_last_error field in the mz_zip_archive struct. These functions retrieve/manipulate this field. */ +/* Note that the m_last_error functionality is not thread safe. */ +MINIZ_EXPORT mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num); +MINIZ_EXPORT mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip); +MINIZ_EXPORT mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip); +MINIZ_EXPORT mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip); +MINIZ_EXPORT const char *mz_zip_get_error_string(mz_zip_error mz_err); + +/* MZ_TRUE if the archive file entry is a directory entry. */ +MINIZ_EXPORT mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index); + +/* MZ_TRUE if the file is encrypted/strong encrypted. */ +MINIZ_EXPORT mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index); + +/* MZ_TRUE if the compression method is supported, and the file is not encrypted, and the file is not a compressed patch file. */ +MINIZ_EXPORT mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index); + +/* Retrieves the filename of an archive file entry. */ +/* Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename. */ +MINIZ_EXPORT mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size); + +/* Attempts to locates a file in the archive's central directory. */ +/* Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH */ +/* Returns -1 if the file cannot be found. */ +MINIZ_EXPORT int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *file_index); + +/* Returns detailed information about an archive file entry. */ +MINIZ_EXPORT mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat); + +/* MZ_TRUE if the file is in zip64 format. */ +/* A file is considered zip64 if it contained a zip64 end of central directory marker, or if it contained any zip64 extended file information fields in the central directory. */ +MINIZ_EXPORT mz_bool mz_zip_is_zip64(mz_zip_archive *pZip); + +/* Returns the total central directory size in bytes. */ +/* The current max supported size is <= MZ_UINT32_MAX. */ +MINIZ_EXPORT size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip); + +/* Extracts a archive file to a memory buffer using no memory allocation. */ +/* There must be at least enough room on the stack to store the inflator's state (~34KB or so). */ +MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); +MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); + +/* Extracts a archive file to a memory buffer. */ +MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags); + +/* Extracts a archive file to a dynamically allocated heap buffer. */ +/* The memory will be allocated via the mz_zip_archive's alloc/realloc functions. */ +/* Returns NULL and sets the last error on failure. */ +MINIZ_EXPORT void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags); +MINIZ_EXPORT void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags); + +/* Extracts a archive file using a callback function to output the file's data. */ +MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); + +/* Extract a file iteratively */ +MINIZ_EXPORT mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); +MINIZ_EXPORT mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags); +MINIZ_EXPORT size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size); +MINIZ_EXPORT mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState); + +#ifndef MINIZ_NO_STDIO +/* Extracts a archive file to a disk file and sets its last accessed and modified times. */ +/* This function only extracts files, not archive directory records. */ +MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags); + +/* Extracts a archive file starting at the current position in the destination FILE stream. */ +MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *File, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags); +#endif + +#if 0 +/* TODO */ + typedef void *mz_zip_streaming_extract_state_ptr; + mz_zip_streaming_extract_state_ptr mz_zip_streaming_extract_begin(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); + uint64_t mz_zip_streaming_extract_get_size(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); + uint64_t mz_zip_streaming_extract_get_cur_ofs(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); + mz_bool mz_zip_streaming_extract_seek(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, uint64_t new_ofs); + size_t mz_zip_streaming_extract_read(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, void *pBuf, size_t buf_size); + mz_bool mz_zip_streaming_extract_end(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); +#endif + +/* This function compares the archive's local headers, the optional local zip64 extended information block, and the optional descriptor following the compressed data vs. the data in the central directory. */ +/* It also validates that each file can be successfully uncompressed unless the MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY is specified. */ +MINIZ_EXPORT mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); + +/* Validates an entire archive by calling mz_zip_validate_file() on each file. */ +MINIZ_EXPORT mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags); + +/* Misc utils/helpers, valid for ZIP reading or writing */ +MINIZ_EXPORT mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr); +MINIZ_EXPORT mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr); + +/* Universal end function - calls either mz_zip_reader_end() or mz_zip_writer_end(). */ +MINIZ_EXPORT mz_bool mz_zip_end(mz_zip_archive *pZip); + +/* -------- ZIP writing */ + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +/* Inits a ZIP archive writer. */ +/*Set pZip->m_pWrite (and pZip->m_pIO_opaque) before calling mz_zip_writer_init or mz_zip_writer_init_v2*/ +/*The output is streamable, i.e. file_ofs in mz_file_write_func always increases only by n*/ +MINIZ_EXPORT mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size); +MINIZ_EXPORT mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags); + +MINIZ_EXPORT mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size); +MINIZ_EXPORT mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags); + +#ifndef MINIZ_NO_STDIO +MINIZ_EXPORT mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning); +MINIZ_EXPORT mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags); +#endif + +/* Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive. */ +/* For archives opened using mz_zip_reader_init_file, pFilename must be the archive's filename so it can be reopened for writing. If the file can't be reopened, mz_zip_reader_end() will be called. */ +/* For archives opened using mz_zip_reader_init_mem, the memory block must be growable using the realloc callback (which defaults to realloc unless you've overridden it). */ +/* Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL. */ +/* Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before */ +/* the archive is finalized the file's central directory will be hosed. */ +MINIZ_EXPORT mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename); +MINIZ_EXPORT mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags); + +/* Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive. */ +/* To add a directory entry, call this method with an archive name ending in a forwardslash with an empty buffer. */ +/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ +MINIZ_EXPORT mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags); + +/* Like mz_zip_writer_add_mem(), except you can specify a file comment field, and optionally supply the function with already compressed data. */ +/* uncomp_size/uncomp_crc32 are only used if the MZ_ZIP_FLAG_COMPRESSED_DATA flag is specified. */ +MINIZ_EXPORT mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + mz_uint64 uncomp_size, mz_uint32 uncomp_crc32); + +MINIZ_EXPORT mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified, const char *user_extra_data_local, mz_uint user_extra_data_local_len, + const char *user_extra_data_central, mz_uint user_extra_data_central_len); + +/* Adds the contents of a file to an archive. This function also records the disk file's modified time into the archive. */ +/* File data is supplied via a read callback function. User mz_zip_writer_add_(c)file to add a file directly.*/ +MINIZ_EXPORT mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pArchive_name, mz_file_read_func read_callback, void* callback_opaque, mz_uint64 max_size, + const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data_local, mz_uint user_extra_data_local_len, + const char *user_extra_data_central, mz_uint user_extra_data_central_len); + + +#ifndef MINIZ_NO_STDIO +/* Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive. */ +/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ +MINIZ_EXPORT mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); + +/* Like mz_zip_writer_add_file(), except the file data is read from the specified FILE stream. */ +MINIZ_EXPORT mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 max_size, + const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data_local, mz_uint user_extra_data_local_len, + const char *user_extra_data_central, mz_uint user_extra_data_central_len); +#endif + +/* Adds a file to an archive by fully cloning the data from another archive. */ +/* This function fully clones the source file's compressed data (no recompression), along with its full filename, extra data (it may add or modify the zip64 local header extra data field), and the optional descriptor following the compressed data. */ +MINIZ_EXPORT mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index); + +/* Finalizes the archive by writing the central directory records followed by the end of central directory record. */ +/* After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end(). */ +/* An archive must be manually finalized by calling this function for it to be valid. */ +MINIZ_EXPORT mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip); + +/* Finalizes a heap archive, returning a poiner to the heap block and its size. */ +/* The heap block will be allocated using the mz_zip_archive's alloc/realloc callbacks. */ +MINIZ_EXPORT mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize); + +/* Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used. */ +/* Note for the archive to be valid, it *must* have been finalized before ending (this function will not do it for you). */ +MINIZ_EXPORT mz_bool mz_zip_writer_end(mz_zip_archive *pZip); + +/* -------- Misc. high-level helper functions: */ + +/* mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) appends a memory blob to a ZIP archive. */ +/* Note this is NOT a fully safe operation. If it crashes or dies in some way your archive can be left in a screwed up state (without a central directory). */ +/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ +/* TODO: Perhaps add an option to leave the existing central dir in place in case the add dies? We could then truncate the file (so the old central dir would be at the end) if something goes wrong. */ +MINIZ_EXPORT mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); +MINIZ_EXPORT mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr); + +/* Reads a single file from an archive into a heap block. */ +/* If pComment is not NULL, only the file with the specified comment will be extracted. */ +/* Returns NULL on failure. */ +MINIZ_EXPORT void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags); +MINIZ_EXPORT void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr); + +#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */ + +#ifdef __cplusplus +} +#endif + +#endif /* MINIZ_NO_ARCHIVE_APIS */ diff --git a/gearboy/src/opcode_names.h b/gearboy/src/opcode_names.h new file mode 100644 index 00000000..379db969 --- /dev/null +++ b/gearboy/src/opcode_names.h @@ -0,0 +1,598 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#ifndef OPCODE_NAMES_H +#define OPCODE_NAMES_H + +struct stOPCodeInfo +{ + const char* name; + int size; + int type; +}; + +static const stOPCodeInfo kOPCodeNames[256] = { + { "NOP", 1, 0 }, + { "LD BC,$%04X", 3, 2 }, + { "LD (BC),A", 1, 0 }, + { "INC BC", 1, 0 }, + { "INC B", 1, 0 }, + { "DEC B", 1, 0 }, + { "LD B,$%02X", 2, 1 }, + { "RLCA", 1, 0 }, + { "LD ($%04X),SP", 3, 2 }, + { "ADD HL,BC", 1, 0 }, + { "LD A,(BC)", 1, 0 }, + { "DEC BC", 1, 0 }, + { "INC C", 1, 0 }, + { "DEC C", 1, 0 }, + { "LD C,$%02X", 2, 1 }, + { "RRCA", 1, 0 }, + + { "STOP", 2, 0 }, + { "LD DE,$%04X", 3, 2 }, + { "LD (DE),A", 1, 0 }, + { "INC DE", 1, 0 }, + { "INC D", 1, 0 }, + { "DEC D", 1, 0 }, + { "LD D,$%02X", 2, 1 }, + { "RLA", 1, 0 }, + { "JR $%04X [%+d]", 2, 4 }, + { "ADD HL,DE", 1, 0 }, + { "LD A,(DE)", 1, 0 }, + { "DEC DE", 1, 0 }, + { "INC E", 1, 0 }, + { "DEC E", 1, 0 }, + { "LD E,$%02X", 2, 1 }, + { "RRA", 1, 0 }, + + { "JR NZ,$%04X [%+d]", 2, 4 }, + { "LD HL,$%04X", 3, 2 }, + { "LD (HL+),A", 1, 0 }, + { "INC HL", 1, 0 }, + { "INC H", 1, 0 }, + { "DEC H", 1, 0 }, + { "LD H,$%02X", 2, 1 }, + { "DAA", 1, 0 }, + { "JR Z,$%04X [%+d]", 2, 4 }, + { "ADD HL,HL", 1, 0 }, + { "LD A,(HL+)", 1, 0 }, + { "DEC HL", 1, 0 }, + { "INC L", 1, 0 }, + { "DEC L", 1, 0 }, + { "LD L,$%02X", 2, 1 }, + { "CPL", 1, 0 }, + + { "JR NC,$%04X [%+d]", 2, 4 }, + { "LD SP,$%04X", 3, 2 }, + { "LD (HL-),A", 1, 0 }, + { "INC SP", 1, 0 }, + { "INC (HL)", 1, 0 }, + { "DEC (HL)", 1, 0 }, + { "LD (HL),$%02X", 2, 1 }, + { "SCF", 1, 0 }, + { "JR C,$%04X [%+d]", 2, 4 }, + { "ADD HL,SP", 1, 0 }, + { "LD A,(HL-)", 1, 0 }, + { "DEC SP", 1, 0 }, + { "INC A", 1, 0 }, + { "DEC A", 1, 0 }, + { "LD A,$%02X", 2, 1 }, + { "CCF", 1, 0 }, + + { "LD B,B", 1, 0 }, + { "LD B,C", 1, 0 }, + { "LD B,D", 1, 0 }, + { "LD B,E", 1, 0 }, + { "LD B,H", 1, 0 }, + { "LD B,L", 1, 0 }, + { "LD B,(HL)", 1, 0 }, + { "LD B,A", 1, 0 }, + { "LD C,B", 1, 0 }, + { "LD C,C", 1, 0 }, + { "LD C,D", 1, 0 }, + { "LD C,E", 1, 0 }, + { "LD C,H", 1, 0 }, + { "LD C,L", 1, 0 }, + { "LD C,(HL)", 1, 0 }, + { "LD C,A", 1, 0 }, + + { "LD D,B", 1, 0 }, + { "LD D,C", 1, 0 }, + { "LD D,D", 1, 0 }, + { "LD D,E", 1, 0 }, + { "LD D,H", 1, 0 }, + { "LD D,L", 1, 0 }, + { "LD D,(HL)", 1, 0 }, + { "LD D,A", 1, 0 }, + { "LD E,B", 1, 0 }, + { "LD E,C", 1, 0 }, + { "LD E,D", 1, 0 }, + { "LD E,E", 1, 0 }, + { "LD E,H", 1, 0 }, + { "LD E,L", 1, 0 }, + { "LD E,(HL)", 1, 0 }, + { "LD E,A", 1, 0 }, + + { "LD H,B", 1, 0 }, + { "LD H,C", 1, 0 }, + { "LD H,D", 1, 0 }, + { "LD H,E", 1, 0 }, + { "LD H,H", 1, 0 }, + { "LD H,L", 1, 0 }, + { "LD H,(HL)", 1, 0 }, + { "LD H,A", 1, 0 }, + { "LD L,B", 1, 0 }, + { "LD L,C", 1, 0 }, + { "LD L,D", 1, 0 }, + { "LD L,E", 1, 0 }, + { "LD L,H", 1, 0 }, + { "LD L,L", 1, 0 }, + { "LD L,(HL)", 1, 0 }, + { "LD L,A", 1, 0 }, + + { "LD (HL),B", 1, 0 }, + { "LD (HL),C", 1, 0 }, + { "LD (HL),D", 1, 0 }, + { "LD (HL),E", 1, 0 }, + { "LD (HL),H", 1, 0 }, + { "LD (HL),L", 1, 0 }, + { "HALT", 1, 0 }, + { "LD (HL),A", 1, 0 }, + { "LD A,B", 1, 0 }, + { "LD A,C", 1, 0 }, + { "LD A,D", 1, 0 }, + { "LD A,E", 1, 0 }, + { "LD A,H", 1, 0 }, + { "LD A,L", 1, 0 }, + { "LD A,(HL)", 1, 0 }, + { "LD A,A", 1, 0 }, + + { "ADD A,B", 1, 0 }, + { "ADD A,C", 1, 0 }, + { "ADD A,D", 1, 0 }, + { "ADD A,E", 1, 0 }, + { "ADD A,H", 1, 0 }, + { "ADD A,L", 1, 0 }, + { "ADD A,(HL)", 1, 0 }, + { "ADD A,A", 1, 0 }, + { "ADC A,B", 1, 0 }, + { "ADC A,C", 1, 0 }, + { "ADC A,D", 1, 0 }, + { "ADC A,E", 1, 0 }, + { "ADC A,H", 1, 0 }, + { "ADC A,L", 1, 0 }, + { "ADC A,(HL)", 1, 0 }, + { "ADC A,A", 1, 0 }, + + { "SUB B", 1, 0 }, + { "SUB C", 1, 0 }, + { "SUB D", 1, 0 }, + { "SUB E", 1, 0 }, + { "SUB H", 1, 0 }, + { "SUB L", 1, 0 }, + { "SUB (HL)", 1, 0 }, + { "SUB A", 1, 0 }, + { "SBC A,B", 1, 0 }, + { "SBC A,C", 1, 0 }, + { "SBC A,D", 1, 0 }, + { "SBC A,E", 1, 0 }, + { "SBC A,H", 1, 0 }, + { "SBC A,L", 1, 0 }, + { "SBC A,(HL)", 1, 0 }, + { "SBC A,A", 1, 0 }, + + { "AND B", 1, 0 }, + { "AND C", 1, 0 }, + { "AND D", 1, 0 }, + { "AND E", 1, 0 }, + { "AND H", 1, 0 }, + { "AND L", 1, 0 }, + { "AND (HL)", 1, 0 }, + { "AND A", 1, 0 }, + { "XOR B", 1, 0 }, + { "XOR C", 1, 0 }, + { "XOR D", 1, 0 }, + { "XOR E", 1, 0 }, + { "XOR H", 1, 0 }, + { "XOR L", 1, 0 }, + { "XOR (HL)", 1, 0 }, + { "XOR A", 1, 0 }, + + { "OR B", 1, 0 }, + { "OR C", 1, 0 }, + { "OR D", 1, 0 }, + { "OR E", 1, 0 }, + { "OR H", 1, 0 }, + { "OR L", 1, 0 }, + { "OR (HL)", 1, 0 }, + { "OR A", 1, 0 }, + { "CP B", 1, 0 }, + { "CP C", 1, 0 }, + { "CP D", 1, 0 }, + { "CP E", 1, 0 }, + { "CP H", 1, 0 }, + { "CP L", 1, 0 }, + { "CP (HL)", 1, 0 }, + { "CP A", 1, 0 }, + + { "RET NZ", 1, 0 }, + { "POP BC", 1, 0 }, + { "JP NZ,$%04X", 3, 2 }, + { "JP $%04X", 3, 2 }, + { "CALL NZ,$%04X", 3, 2 }, + { "PUSH BC", 1, 0 }, + { "ADD A,$%02X", 2, 1 }, + { "RST ", 1, 0 }, + { "RET Z", 1, 0 }, + { "RET", 1, 0 }, + { "JP Z,$%04X", 3, 2 }, + { "cb opcode", 1, 0 }, + { "CALL Z,$%04X", 3, 2 }, + { "CALL $%04X", 3, 2 }, + { "ADC A,$%02X", 2, 1 }, + { "RST $08", 1, 0 }, + + { "RET NC", 1, 0 }, + { "POP DE", 1, 0 }, + { "JP NC,$%04X", 3, 2 }, + { "[UNUSED]", 1, 0 }, + { "CALL NC,$%04X", 3, 2 }, + { "PUSH DE", 1, 0 }, + { "SUB $%02X", 2, 1 }, + { "RST $10", 1, 0 }, + { "RET C", 1, 0 }, + { "RETI", 1, 0 }, + { "JP C,$%04X", 3, 2 }, + { "[UNUSED]", 1, 0 }, + { "CALL C,$%04X", 3, 2 }, + { "[UNUSED]", 1, 0 }, + { "SBC A,$%02X", 2, 1 }, + { "RST $18", 1, 0 }, + + { "LD ($FF00+$%02X),A [%s]", 2, 5 }, + { "POP HL", 1, 0 }, + { "LD ($FF00+C),A", 1, 0 }, + { "[UNUSED]", 1, 0 }, + { "[UNUSED]", 1, 0 }, + { "PUSH HL", 1, 0 }, + { "AND $%02X", 2, 1 }, + { "RST $20", 1, 0 }, + { "ADD SP,%+d", 2, 3 }, + { "JP (HL)", 1, 0 }, + { "LD ($%04X),A", 3, 2 }, + { "[UNUSED]", 1, 0 }, + { "[UNUSED]", 1, 0 }, + { "[UNUSED]", 1, 0 }, + { "XOR $%02X", 2, 1 }, + { "RST $28", 1, 0 }, + + { "LD A,($FF00+$%02X) [%s]", 2, 5 }, + { "POP AF", 1, 0 }, + { "LD A,($FF00+C)", 1, 0 }, + { "DI", 1, 0 }, + { "[UNUSED]", 1, 0 }, + { "PUSH AF", 1, 0 }, + { "OR $%02X", 2, 1 }, + { "RST $30", 1, 0 }, + { "LD HL,(SP%+d)", 2, 3 }, + { "LD SP,HL", 1, 0 }, + { "LD A,($%04X)", 3, 2 }, + { "EI", 1, 0 }, + { "[UNUSED]", 1, 0 }, + { "[UNUSED]", 1, 0 }, + { "CP $%02X", 2, 1 }, + { "RST $38", 1, 0 } +}; + +static const stOPCodeInfo kOPCodeCBNames[256] = { + { "RLC B", 2, 0 }, + { "RLC C", 2, 0 }, + { "RLC D", 2, 0 }, + { "RLC E", 2, 0 }, + { "RLC H", 2, 0 }, + { "RLC L", 2, 0 }, + { "RLC (HL)", 2, 0 }, + { "RLC A", 2, 0 }, + { "RRC B", 2, 0 }, + { "RRC C", 2, 0 }, + { "RRC D", 2, 0 }, + { "RRC E", 2, 0 }, + { "RRC H", 2, 0 }, + { "RRC L", 2, 0 }, + { "RRC (HL)", 2, 0 }, + { "RRC A", 2, 0 }, + + { "RL B", 2, 0 }, + { "RL C", 2, 0 }, + { "RL D", 2, 0 }, + { "RL E", 2, 0 }, + { "RL H", 2, 0 }, + { "RL L ", 2, 0 }, + { "RL (HL)", 2, 0 }, + { "RL A", 2, 0 }, + { "RR B", 2, 0 }, + { "RR C", 2, 0 }, + { "RR D", 2, 0 }, + { "RR E", 2, 0 }, + { "RR H", 2, 0 }, + { "RR L", 2, 0 }, + { "RR (HL)", 2, 0 }, + { "RR A", 2, 0 }, + + { "SLA B", 2, 0 }, + { "SLA C", 2, 0 }, + { "SLA D", 2, 0 }, + { "SLA E", 2, 0 }, + { "SLA H", 2, 0 }, + { "SLA L", 2, 0 }, + { "SLA (HL)", 2, 0 }, + { "SLA A", 2, 0 }, + { "SRA B", 2, 0 }, + { "SRA C", 2, 0 }, + { "SRA D", 2, 0 }, + { "SRA E", 2, 0 }, + { "SRA H", 2, 0 }, + { "SRA L", 2, 0 }, + { "SRA (HL)", 2, 0 }, + { "SRA A", 2, 0 }, + + { "SWAP B", 2, 0 }, + { "SWAP C", 2, 0 }, + { "SWAP D", 2, 0 }, + { "SWAP E", 2, 0 }, + { "SWAP H", 2, 0 }, + { "SWAP L", 2, 0 }, + { "SWAP (HL)", 2, 0 }, + { "SWAP A", 2, 0 }, + { "SRL B", 2, 0 }, + { "SRL C", 2, 0 }, + { "SRL D", 2, 0 }, + { "SRL E", 2, 0 }, + { "SRL H", 2, 0 }, + { "SRL L", 2, 0 }, + { "SRL (HL)", 2, 0 }, + { "SRL A", 2, 0 }, + + { "BIT 0 B", 2, 0 }, + { "BIT 0 C", 2, 0 }, + { "BIT 0 D", 2, 0 }, + { "BIT 0 E", 2, 0 }, + { "BIT 0 H", 2, 0 }, + { "BIT 0 L", 2, 0 }, + { "BIT 0 (HL)", 2, 0 }, + { "BIT 0 A", 2, 0 }, + { "BIT 1 B", 2, 0 }, + { "BIT 1 C", 2, 0 }, + { "BIT 1 D", 2, 0 }, + { "BIT 1 E", 2, 0 }, + { "BIT 1 H", 2, 0 }, + { "BIT 1 L", 2, 0 }, + { "BIT 1 (HL)", 2, 0 }, + { "BIT 1 A", 2, 0 }, + + { "BIT 2 B", 2, 0 }, + { "BIT 2 C", 2, 0 }, + { "BIT 2 D", 2, 0 }, + { "BIT 2 E", 2, 0 }, + { "BIT 2 H", 2, 0 }, + { "BIT 2 L", 2, 0 }, + { "BIT 2 (HL)", 2, 0 }, + { "BIT 2 A", 2, 0 }, + { "BIT 3 B", 2, 0 }, + { "BIT 3 C", 2, 0 }, + { "BIT 3 D", 2, 0 }, + { "BIT 3 E", 2, 0 }, + { "BIT 3 H", 2, 0 }, + { "BIT 3 L", 2, 0 }, + { "BIT 3 (HL)", 2, 0 }, + { "BIT 3 A", 2, 0 }, + + { "BIT 4 B", 2, 0 }, + { "BIT 4 C", 2, 0 }, + { "BIT 4 D", 2, 0 }, + { "BIT 4 E", 2, 0 }, + { "BIT 4 H", 2, 0 }, + { "BIT 4 L", 2, 0 }, + { "BIT 4 (HL)", 2, 0 }, + { "BIT 4 A", 2, 0 }, + { "BIT 5 B", 2, 0 }, + { "BIT 5 C", 2, 0 }, + { "BIT 5 D", 2, 0 }, + { "BIT 5 E", 2, 0 }, + { "BIT 5 H", 2, 0 }, + { "BIT 5 L", 2, 0 }, + { "BIT 5 (HL)", 2, 0 }, + { "BIT 5 A", 2, 0 }, + + { "BIT 6 B", 2, 0 }, + { "BIT 6 C", 2, 0 }, + { "BIT 6 D", 2, 0 }, + { "BIT 6 E", 2, 0 }, + { "BIT 6 H", 2, 0 }, + { "BIT 6 L", 2, 0 }, + { "BIT 6 (HL)", 2, 0 }, + { "BIT 6 A", 2, 0 }, + { "BIT 7 B", 2, 0 }, + { "BIT 7 C", 2, 0 }, + { "BIT 7 D", 2, 0 }, + { "BIT 7 E", 2, 0 }, + { "BIT 7 H", 2, 0 }, + { "BIT 7 L", 2, 0 }, + { "BIT 7 (HL)", 2, 0 }, + { "BIT 7 A", 2, 0 }, + + { "RES 0 B", 2, 0 }, + { "RES 0 C", 2, 0 }, + { "RES 0 D", 2, 0 }, + { "RES 0 E", 2, 0 }, + { "RES 0 H", 2, 0 }, + { "RES 0 L", 2, 0 }, + { "RES 0 (HL)", 2, 0 }, + { "RES 0 A", 2, 0 }, + { "RES 1 B", 2, 0 }, + { "RES 1 C", 2, 0 }, + { "RES 1 D", 2, 0 }, + { "RES 1 E", 2, 0 }, + { "RES 1 H", 2, 0 }, + { "RES 1 L", 2, 0 }, + { "RES 1 (HL)", 2, 0 }, + { "RES 1 A", 2, 0 }, + + { "RES 2 B", 2, 0 }, + { "RES 2 C", 2, 0 }, + { "RES 2 D", 2, 0 }, + { "RES 2 E", 2, 0 }, + { "RES 2 H", 2, 0 }, + { "RES 2 L", 2, 0 }, + { "RES 2 (HL)", 2, 0 }, + { "RES 2 A", 2, 0 }, + { "RES 3 B", 2, 0 }, + { "RES 3 C", 2, 0 }, + { "RES 3 D", 2, 0 }, + { "RES 3 E", 2, 0 }, + { "RES 3 H", 2, 0 }, + { "RES 3 L", 2, 0 }, + { "RES 3 (HL)", 2, 0 }, + { "RES 3 A", 2, 0 }, + + { "RES 4 B", 2, 0 }, + { "RES 4 C", 2, 0 }, + { "RES 4 D", 2, 0 }, + { "RES 4 E", 2, 0 }, + { "RES 4 H", 2, 0 }, + { "RES 4 L", 2, 0 }, + { "RES 4 (HL)", 2, 0 }, + { "RES 4 A", 2, 0 }, + { "RES 5 B", 2, 0 }, + { "RES 5 C", 2, 0 }, + { "RES 5 D", 2, 0 }, + { "RES 5 E", 2, 0 }, + { "RES 5 H", 2, 0 }, + { "RES 5 L", 2, 0 }, + { "RES 5 (HL)", 2, 0 }, + { "RES 5 A", 2, 0 }, + + { "RES 6 B", 2, 0 }, + { "RES 6 C", 2, 0 }, + { "RES 6 D", 2, 0 }, + { "RES 6 E", 2, 0 }, + { "RES 6 H", 2, 0 }, + { "RES 6 L", 2, 0 }, + { "RES 6 (HL)", 2, 0 }, + { "RES 6 A", 2, 0 }, + { "RES 7 B", 2, 0 }, + { "RES 7 C", 2, 0 }, + { "RES 7 D", 2, 0 }, + { "RES 7 E", 2, 0 }, + { "RES 7 H", 2, 0 }, + { "RES 7 L", 2, 0 }, + { "RES 7 (HL)", 2, 0 }, + { "RES 7 A", 2, 0 }, + + { "SET 0 B", 2, 0 }, + { "SET 0 C", 2, 0 }, + { "SET 0 D", 2, 0 }, + { "SET 0 E", 2, 0 }, + { "SET 0 H", 2, 0 }, + { "SET 0 L", 2, 0 }, + { "SET 0 (HL)", 2, 0 }, + { "SET 0 A", 2, 0 }, + { "SET 1 B", 2, 0 }, + { "SET 1 C", 2, 0 }, + { "SET 1 D", 2, 0 }, + { "SET 1 E", 2, 0 }, + { "SET 1 H", 2, 0 }, + { "SET 1 L", 2, 0 }, + { "SET 1 (HL)", 2, 0 }, + { "SET 1 A", 2, 0 }, + + { "SET 2 B", 2, 0 }, + { "SET 2 C", 2, 0 }, + { "SET 2 D", 2, 0 }, + { "SET 2 E", 2, 0 }, + { "SET 2 H", 2, 0 }, + { "SET 2 L", 2, 0 }, + { "SET 2 (HL)", 2, 0 }, + { "SET 2 A", 2, 0 }, + { "SET 3 B", 2, 0 }, + { "SET 3 C", 2, 0 }, + { "SET 3 D", 2, 0 }, + { "SET 3 E", 2, 0 }, + { "SET 3 H", 2, 0 }, + { "SET 3 L", 2, 0 }, + { "SET 3 (HL)", 2, 0 }, + { "SET 3 A", 2, 0 }, + + { "SET 4 B", 2, 0 }, + { "SET 4 C", 2, 0 }, + { "SET 4 D", 2, 0 }, + { "SET 4 E", 2, 0 }, + { "SET 4 H", 2, 0 }, + { "SET 4 L", 2, 0 }, + { "SET 4 (HL)", 2, 0 }, + { "SET 4 A", 2, 0 }, + { "SET 5 B", 2, 0 }, + { "SET 5 C", 2, 0 }, + { "SET 5 D", 2, 0 }, + { "SET 5 E", 2, 0 }, + { "SET 5 H", 2, 0 }, + { "SET 5 L", 2, 0 }, + { "SET 5 (HL)", 2, 0 }, + { "SET 5 A", 2, 0 }, + + { "SET 6 B", 2, 0 }, + { "SET 6 C", 2, 0 }, + { "SET 6 D", 2, 0 }, + { "SET 6 E", 2, 0 }, + { "SET 6 H", 2, 0 }, + { "SET 6 L", 2, 0 }, + { "SET 6 (HL)", 2, 0 }, + { "SET 6 A", 2, 0 }, + { "SET 7 B", 2, 0 }, + { "SET 7 C", 2, 0 }, + { "SET 7 D", 2, 0 }, + { "SET 7 E", 2, 0 }, + { "SET 7 H", 2, 0 }, + { "SET 7 L", 2, 0 }, + { "SET 7 (HL)", 2, 0 }, + { "SET 7 A", 2, 0 } +}; + +static const char* kRegisterNames[256] = { + "P1", "SB", "SC", "UNKNOWN", "DIV", "TIMA", "TMA", "TAC", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "IF", + "NR10", "NR11", "NR12", "NR13", "NR14", "UNKNOWN", "NR21", "NR22", "NR23", "NR24", "NR30", "NR31", "NR32", "NR33", "NR34", "UNKNOWN", + "NR41", "NR42", "NR43", "NR44", "NR50", "NR51", "NR52", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", + "WAVE 0", "WAVE 1", "WAVE 2", "WAVE 3", "WAVE 4", "WAVE 5", "WAVE 6", "WAVE 7", "WAVE 8", "WAVE 9", "WAVE A", "WAVE B", "WAVE C", "WAVE D", "WAVE E", "WAVE F", + "LCDC", "STAT", "SCY", "SCX", "LY", "LYC", "DMA", "BGP", "OBP0", "OBP1", "WY", "WX", "UNKNOWN", "KEY1", "UNKNOWN", "VBK", + "BOOT", "HDMA SOURCE HI", "HDMA SOURCE LOW", "HDMA DEST HI", "HDMA DEST LOW", "HDMA LEN", "RP", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", + "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "BCPS", "BCPD", "OCPS", "OCPD", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", + "SVBK", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "PCM12", "PCM34", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", + "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", + "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", + "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", + "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", + "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", + "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", + "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", + "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "HIRAM", "IE" +}; + +#endif /* OPCODE_NAMES_H */ + diff --git a/gearboy/src/opcode_timing.h b/gearboy/src/opcode_timing.h new file mode 100644 index 00000000..f8ca75e9 --- /dev/null +++ b/gearboy/src/opcode_timing.h @@ -0,0 +1,122 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#ifndef OPCODE_CYCLES_H +#define OPCODE_CYCLES_H + +// This values are from the tests written by Shay Green +// (http://blargg.parodius.com/gb-tests/) + +const u8 kOPCodeMachineCycles[256] = { + 1, 3, 2, 2, 1, 1, 2, 1, 5, 2, 2, 2, 1, 1, 2, 1, + 1, 3, 2, 2, 1, 1, 2, 1, 3, 2, 2, 2, 1, 1, 2, 1, + 2, 3, 2, 2, 1, 1, 2, 1, 2, 2, 2, 2, 1, 1, 2, 1, + 2, 3, 2, 2, 3, 3, 3, 1, 2, 2, 2, 2, 1, 1, 2, 1, + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, + 2, 2, 2, 2, 2, 2, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, + 2, 3, 3, 4, 3, 4, 2, 4, 2, 4, 3, 0, 3, 6, 2, 4, + 2, 3, 3, 0, 3, 4, 2, 4, 2, 4, 3, 0, 3, 0, 2, 4, + 3, 3, 2, 0, 0, 4, 2, 4, 4, 1, 4, 0, 0, 0, 2, 4, + 3, 3, 2, 1, 0, 4, 2, 4, 3, 2, 4, 1, 0, 0, 2, 4 +}; + +const u8 kOPCodeBranchMachineCycles[256] = { + 1, 3, 2, 2, 1, 1, 2, 1, 5, 2, 2, 2, 1, 1, 2, 1, + 1, 3, 2, 2, 1, 1, 2, 1, 3, 2, 2, 2, 1, 1, 2, 1, + 3, 3, 2, 2, 1, 1, 2, 1, 3, 2, 2, 2, 1, 1, 2, 1, + 3, 3, 2, 2, 3, 3, 3, 1, 3, 2, 2, 2, 1, 1, 2, 1, + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, + 2, 2, 2, 2, 2, 2, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, + 5, 3, 4, 4, 6, 4, 2, 4, 5, 4, 4, 0, 6, 6, 2, 4, + 5, 3, 4, 0, 6, 4, 2, 4, 5, 4, 4, 0, 6, 0, 2, 4, + 3, 3, 2, 0, 0, 4, 2, 4, 4, 1, 4, 0, 0, 0, 2, 4, + 3, 3, 2, 1, 0, 4, 2, 4, 3, 2, 4, 1, 0, 0, 2, 4 +}; + +const u8 kOPCodeCBMachineCycles[256] = { + 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, + 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, + 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, + 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, + 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, + 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, + 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, + 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, + 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, + 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, + 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, + 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, + 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, + 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, + 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, + 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2 +}; + +const u8 kOPCodeAccurate[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 +}; + +const u8 kOPCodeCBAccurate[256] = { + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0 +}; + +#endif /* OPCODE_CYCLES_H */ + diff --git a/gearboy/src/opcodes.cpp b/gearboy/src/opcodes.cpp new file mode 100644 index 00000000..1ed04d42 --- /dev/null +++ b/gearboy/src/opcodes.cpp @@ -0,0 +1,1865 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#include "Processor.h" +#include "Memory.h" +#include "opcode_timing.h" + +void Processor::OPCode0x00() +{ + // NOP +} + +void Processor::OPCode0x01() +{ + // LD BC,nn + OPCodes_LD(BC.GetLowRegister(), PC.GetValue()); + PC.Increment(); + OPCodes_LD(BC.GetHighRegister(), PC.GetValue()); + PC.Increment(); +} + +void Processor::OPCode0x02() +{ + // LD (BC),A + OPCodes_LD(BC.GetValue(), AF.GetHigh()); +} + +void Processor::OPCode0x03() +{ + // INC BC + BC.Increment(); +} + +void Processor::OPCode0x04() +{ + // INC B + OPCodes_INC(BC.GetHighRegister()); +} + +void Processor::OPCode0x05() +{ + // DEC B + OPCodes_DEC(BC.GetHighRegister()); +} + +void Processor::OPCode0x06() +{ + // LD B,n + OPCodes_LD(BC.GetHighRegister(), PC.GetValue()); + PC.Increment(); +} + +void Processor::OPCode0x07() +{ + // RLCA + OPCodes_RLC(AF.GetHighRegister(), true); +} + +void Processor::OPCode0x08() +{ + // LD (nn),SP + u8 l = m_pMemory->Read(PC.GetValue()); + PC.Increment(); + u8 h = m_pMemory->Read(PC.GetValue()); + PC.Increment(); + u16 address = ((h << 8) + l); + m_pMemory->Write(address, SP.GetLow()); + m_pMemory->Write(address + 1, SP.GetHigh()); +} + +void Processor::OPCode0x09() +{ + // ADD HL,BC + OPCodes_ADD_HL(BC.GetValue()); +} + +void Processor::OPCode0x0A() +{ + // LD A,(BC) + OPCodes_LD(AF.GetHighRegister(), BC.GetValue()); +} + +void Processor::OPCode0x0B() +{ + // DEC BC + BC.Decrement(); +} + +void Processor::OPCode0x0C() +{ + // INC C + OPCodes_INC(BC.GetLowRegister()); +} + +void Processor::OPCode0x0D() +{ + // DEC C + OPCodes_DEC(BC.GetLowRegister()); +} + +void Processor::OPCode0x0E() +{ + // LD C,n + OPCodes_LD(BC.GetLowRegister(), PC.GetValue()); + PC.Increment(); +} + +void Processor::OPCode0x0F() +{ + // RRCA + OPCodes_RRC(AF.GetHighRegister(), true); +} + +void Processor::OPCode0x10() +{ + // STOP + PC.Increment(); + + if (m_bCGB) + { + u8 current_key1 = m_pMemory->Retrieve(0xFF4D); + + if (IsSetBit(current_key1, 0)) + { + m_bCGBSpeed = !m_bCGBSpeed; + + if (m_bCGBSpeed) + { + m_iSpeedMultiplier = 1; + m_pMemory->Load(0xFF4D, 0x80); + } + else + { + m_iSpeedMultiplier = 0; + m_pMemory->Load(0xFF4D, 0x00); + } + } + } +} + +void Processor::OPCode0x11() +{ + // LD DE,nn + OPCodes_LD(DE.GetLowRegister(), PC.GetValue()); + PC.Increment(); + OPCodes_LD(DE.GetHighRegister(), PC.GetValue()); + PC.Increment(); +} + +void Processor::OPCode0x12() +{ + // LD (DE),A + OPCodes_LD(DE.GetValue(), AF.GetHigh()); +} + +void Processor::OPCode0x13() +{ + // INC DE + DE.Increment(); +} + +void Processor::OPCode0x14() +{ + // INC D + OPCodes_INC(DE.GetHighRegister()); +} + +void Processor::OPCode0x15() +{ + // DEC D + OPCodes_DEC(DE.GetHighRegister()); +} + +void Processor::OPCode0x16() +{ + // LD D,n + OPCodes_LD(DE.GetHighRegister(), PC.GetValue()); + PC.Increment(); +} + +void Processor::OPCode0x17() +{ + // RLA + OPCodes_RL(AF.GetHighRegister(), true); +} + +void Processor::OPCode0x18() +{ + // JR n + PC.SetValue(PC.GetValue() + 1 + (static_cast<s8> (m_pMemory->Read(PC.GetValue())))); +} + +void Processor::OPCode0x19() +{ + // ADD HL,DE + OPCodes_ADD_HL(DE.GetValue()); +} + +void Processor::OPCode0x1A() +{ + // LD A,(DE) + OPCodes_LD(AF.GetHighRegister(), DE.GetValue()); +} + +void Processor::OPCode0x1B() +{ + // DEC DE + DE.Decrement(); +} + +void Processor::OPCode0x1C() +{ + // INC E + OPCodes_INC(DE.GetLowRegister()); +} + +void Processor::OPCode0x1D() +{ + // DEC E + OPCodes_DEC(DE.GetLowRegister()); +} + +void Processor::OPCode0x1E() +{ + // LD E,n + OPCodes_LD(DE.GetLowRegister(), PC.GetValue()); + PC.Increment(); +} + +void Processor::OPCode0x1F() +{ + // RRA + OPCodes_RR(AF.GetHighRegister(), true); +} + +void Processor::OPCode0x20() +{ + // JR NZ,n + if (!IsSetFlag(FLAG_ZERO)) + { + PC.SetValue(PC.GetValue() + 1 + (static_cast<s8> (m_pMemory->Read(PC.GetValue())))); + m_bBranchTaken = true; + } + else + { + PC.Increment(); + } +} + +void Processor::OPCode0x21() +{ + // LD HL,nn + OPCodes_LD(HL.GetLowRegister(), PC.GetValue()); + PC.Increment(); + OPCodes_LD(HL.GetHighRegister(), PC.GetValue()); + PC.Increment(); +} + +void Processor::OPCode0x22() +{ + // LD (HLI),A + OPCodes_LD(HL.GetValue(), AF.GetHigh()); + HL.Increment(); +} + +void Processor::OPCode0x23() +{ + // INC HL + HL.Increment(); +} + +void Processor::OPCode0x24() +{ + // INC H + OPCodes_INC(HL.GetHighRegister()); +} + +void Processor::OPCode0x25() +{ + // DEC H + OPCodes_DEC(HL.GetHighRegister()); +} + +void Processor::OPCode0x26() +{ + // LD H,n + OPCodes_LD(HL.GetHighRegister(), PC.GetValue()); + PC.Increment(); +} + +void Processor::OPCode0x27() +{ + // DAA + int a = AF.GetHigh(); + + if (!IsSetFlag(FLAG_SUB)) + { + if (IsSetFlag(FLAG_HALF) || ((a & 0xF) > 9)) + a += 0x06; + + if (IsSetFlag(FLAG_CARRY) || (a > 0x9F)) + a += 0x60; + } + else + { + if (IsSetFlag(FLAG_HALF)) + a = (a - 6) & 0xFF; + + if (IsSetFlag(FLAG_CARRY)) + a -= 0x60; + } + + UntoggleFlag(FLAG_HALF); + UntoggleFlag(FLAG_ZERO); + + if ((a & 0x100) == 0x100) + ToggleFlag(FLAG_CARRY); + + a &= 0xFF; + + ToggleZeroFlagFromResult(a); + + AF.SetHigh(a); +} + +void Processor::OPCode0x28() +{ + // JR Z,n + if (IsSetFlag(FLAG_ZERO)) + { + PC.SetValue(PC.GetValue() + 1 + (static_cast<s8> (m_pMemory->Read(PC.GetValue())))); + m_bBranchTaken = true; + } + else + { + PC.Increment(); + } +} + +void Processor::OPCode0x29() +{ + // ADD HL,HL + OPCodes_ADD_HL(HL.GetValue()); +} + +void Processor::OPCode0x2A() +{ + // LD A,(HLI) + OPCodes_LD(AF.GetHighRegister(), HL.GetValue()); + HL.Increment(); +} + +void Processor::OPCode0x2B() +{ + // DEC HL + HL.Decrement(); +} + +void Processor::OPCode0x2C() +{ + // INC L + OPCodes_INC(HL.GetLowRegister()); +} + +void Processor::OPCode0x2D() +{ + // DEC L + OPCodes_DEC(HL.GetLowRegister()); +} + +void Processor::OPCode0x2E() +{ + // LD L,n + OPCodes_LD(HL.GetLowRegister(), PC.GetValue()); + PC.Increment(); + +} + +void Processor::OPCode0x2F() +{ + // CPL + AF.SetHigh(~AF.GetHigh()); + ToggleFlag(FLAG_HALF); + ToggleFlag(FLAG_SUB); +} + +void Processor::OPCode0x30() +{ + // JR NC,n + if (!IsSetFlag(FLAG_CARRY)) + { + PC.SetValue(PC.GetValue() + 1 + (static_cast<s8> (m_pMemory->Read(PC.GetValue())))); + m_bBranchTaken = true; + } + else + { + PC.Increment(); + } +} + +void Processor::OPCode0x31() +{ + // LD SP,nn + SP.SetLow(m_pMemory->Read(PC.GetValue())); + PC.Increment(); + SP.SetHigh(m_pMemory->Read(PC.GetValue())); + PC.Increment(); +} + +void Processor::OPCode0x32() +{ + // LD (HLD), A + OPCodes_LD(HL.GetValue(), AF.GetHigh()); + HL.Decrement(); +} + +void Processor::OPCode0x33() +{ + // INC SP + SP.Increment(); +} + +void Processor::OPCode0x34() +{ + // INC (HL) + OPCodes_INC_HL(); +} + +void Processor::OPCode0x35() +{ + // DEC (HL) + OPCodes_DEC_HL(); +} + +void Processor::OPCode0x36() +{ + // LD (HL),n + m_pMemory->Write(HL.GetValue(), m_pMemory->Read(PC.GetValue())); + PC.Increment(); +} + +void Processor::OPCode0x37() +{ + // SCF + ToggleFlag(FLAG_CARRY); + UntoggleFlag(FLAG_HALF); + UntoggleFlag(FLAG_SUB); +} + +void Processor::OPCode0x38() +{ + // JR C,n + if (IsSetFlag(FLAG_CARRY)) + { + PC.SetValue(PC.GetValue() + 1 + (static_cast<s8> (m_pMemory->Read(PC.GetValue())))); + m_bBranchTaken = true; + } + else + { + PC.Increment(); + } +} + +void Processor::OPCode0x39() +{ + // ADD HL,SP + OPCodes_ADD_HL(SP.GetValue()); +} + +void Processor::OPCode0x3A() +{ + // LD A,(HLD) + OPCodes_LD(AF.GetHighRegister(), HL.GetValue()); + HL.Decrement(); +} + +void Processor::OPCode0x3B() +{ + // DEC SP + SP.Decrement(); +} + +void Processor::OPCode0x3C() +{ + // INC A + OPCodes_INC(AF.GetHighRegister()); +} + +void Processor::OPCode0x3D() +{ + // DEC A + OPCodes_DEC(AF.GetHighRegister()); + +} + +void Processor::OPCode0x3E() +{ + // LD A,n + OPCodes_LD(AF.GetHighRegister(), PC.GetValue()); + PC.Increment(); +} + +void Processor::OPCode0x3F() +{ + // CCF + FlipFlag(FLAG_CARRY); + UntoggleFlag(FLAG_HALF); + UntoggleFlag(FLAG_SUB); +} + +void Processor::OPCode0x40() +{ + // LD B,B + OPCodes_LD(BC.GetHighRegister(), BC.GetHigh()); +} + +void Processor::OPCode0x41() +{ + // LD B,C + OPCodes_LD(BC.GetHighRegister(), BC.GetLow()); +} + +void Processor::OPCode0x42() +{ + // LD B,D + OPCodes_LD(BC.GetHighRegister(), DE.GetHigh()); +} + +void Processor::OPCode0x43() +{ + // LD B,E + OPCodes_LD(BC.GetHighRegister(), DE.GetLow()); +} + +void Processor::OPCode0x44() +{ + // LD B,H + OPCodes_LD(BC.GetHighRegister(), HL.GetHigh()); +} + +void Processor::OPCode0x45() +{ + // LD B,L + OPCodes_LD(BC.GetHighRegister(), HL.GetLow()); +} + +void Processor::OPCode0x46() +{ + // LD B,(HL) + OPCodes_LD(BC.GetHighRegister(), HL.GetValue()); +} + +void Processor::OPCode0x47() +{ + // LD B,A + OPCodes_LD(BC.GetHighRegister(), AF.GetHigh()); +} + +void Processor::OPCode0x48() +{ + // LD C,B + OPCodes_LD(BC.GetLowRegister(), BC.GetHigh()); +} + +void Processor::OPCode0x49() +{ + // LD C,C + OPCodes_LD(BC.GetLowRegister(), BC.GetLow()); +} + +void Processor::OPCode0x4A() +{ + // LD C,D + OPCodes_LD(BC.GetLowRegister(), DE.GetHigh()); +} + +void Processor::OPCode0x4B() +{ + // LD C,E + OPCodes_LD(BC.GetLowRegister(), DE.GetLow()); +} + +void Processor::OPCode0x4C() +{ + // LD C,H + OPCodes_LD(BC.GetLowRegister(), HL.GetHigh()); +} + +void Processor::OPCode0x4D() +{ + // LD C,L + OPCodes_LD(BC.GetLowRegister(), HL.GetLow()); +} + +void Processor::OPCode0x4E() +{ + // LD C,(HL) + OPCodes_LD(BC.GetLowRegister(), HL.GetValue()); +} + +void Processor::OPCode0x4F() +{ + // LD C,A + OPCodes_LD(BC.GetLowRegister(), AF.GetHigh()); +} + +void Processor::OPCode0x50() +{ + // LD D,B + OPCodes_LD(DE.GetHighRegister(), BC.GetHigh()); +} + +void Processor::OPCode0x51() +{ + // LD D,C + OPCodes_LD(DE.GetHighRegister(), BC.GetLow()); +} + +void Processor::OPCode0x52() +{ + // LD D,D + OPCodes_LD(DE.GetHighRegister(), DE.GetHigh()); +} + +void Processor::OPCode0x53() +{ + // LD D,E + OPCodes_LD(DE.GetHighRegister(), DE.GetLow()); +} + +void Processor::OPCode0x54() +{ + // LD D,H + OPCodes_LD(DE.GetHighRegister(), HL.GetHigh()); +} + +void Processor::OPCode0x55() +{ + // LD D,L + OPCodes_LD(DE.GetHighRegister(), HL.GetLow()); +} + +void Processor::OPCode0x56() +{ + // LD D,(HL) + OPCodes_LD(DE.GetHighRegister(), HL.GetValue()); +} + +void Processor::OPCode0x57() +{ + // LD D,A + OPCodes_LD(DE.GetHighRegister(), AF.GetHigh()); +} + +void Processor::OPCode0x58() +{ + // LD E,B + OPCodes_LD(DE.GetLowRegister(), BC.GetHigh()); +} + +void Processor::OPCode0x59() +{ + // LD E,C + OPCodes_LD(DE.GetLowRegister(), BC.GetLow()); +} + +void Processor::OPCode0x5A() +{ + // LD E,D + OPCodes_LD(DE.GetLowRegister(), DE.GetHigh()); +} + +void Processor::OPCode0x5B() +{ + // LD E,E + OPCodes_LD(DE.GetLowRegister(), DE.GetLow()); +} + +void Processor::OPCode0x5C() +{ + // LD E,H + OPCodes_LD(DE.GetLowRegister(), HL.GetHigh()); +} + +void Processor::OPCode0x5D() +{ + // LD E,L + OPCodes_LD(DE.GetLowRegister(), HL.GetLow()); +} + +void Processor::OPCode0x5E() +{ + // LD E,(HL) + OPCodes_LD(DE.GetLowRegister(), HL.GetValue()); +} + +void Processor::OPCode0x5F() +{ + // LD E,A + OPCodes_LD(DE.GetLowRegister(), AF.GetHigh()); +} + +void Processor::OPCode0x60() +{ + // LD H,B + OPCodes_LD(HL.GetHighRegister(), BC.GetHigh()); +} + +void Processor::OPCode0x61() +{ + // LD H,C + OPCodes_LD(HL.GetHighRegister(), BC.GetLow()); +} + +void Processor::OPCode0x62() +{ + // LD H,D + OPCodes_LD(HL.GetHighRegister(), DE.GetHigh()); +} + +void Processor::OPCode0x63() +{ + // LD H,E + OPCodes_LD(HL.GetHighRegister(), DE.GetLow()); +} + +void Processor::OPCode0x64() +{ + // LD H,H + OPCodes_LD(HL.GetHighRegister(), HL.GetHigh()); +} + +void Processor::OPCode0x65() +{ + // LD H,L + OPCodes_LD(HL.GetHighRegister(), HL.GetLow()); +} + +void Processor::OPCode0x66() +{ + // LD H,(HL) + OPCodes_LD(HL.GetHighRegister(), HL.GetValue()); +} + +void Processor::OPCode0x67() +{ + // LD H,A + OPCodes_LD(HL.GetHighRegister(), AF.GetHigh()); +} + +void Processor::OPCode0x68() +{ + // LD L,B + OPCodes_LD(HL.GetLowRegister(), BC.GetHigh()); +} + +void Processor::OPCode0x69() +{ + // LD L,C + OPCodes_LD(HL.GetLowRegister(), BC.GetLow()); +} + +void Processor::OPCode0x6A() +{ + // LD L,D + OPCodes_LD(HL.GetLowRegister(), DE.GetHigh()); +} + +void Processor::OPCode0x6B() +{ + // LD L,E + OPCodes_LD(HL.GetLowRegister(), DE.GetLow()); +} + +void Processor::OPCode0x6C() +{ + // LD L,H + OPCodes_LD(HL.GetLowRegister(), HL.GetHigh()); +} + +void Processor::OPCode0x6D() +{ + // LD L,L + OPCodes_LD(HL.GetLowRegister(), HL.GetLow()); +} + +void Processor::OPCode0x6E() +{ + // LD L,(HL) + OPCodes_LD(HL.GetLowRegister(), HL.GetValue()); +} + +void Processor::OPCode0x6F() +{ + // LD L,A + OPCodes_LD(HL.GetLowRegister(), AF.GetHigh()); +} + +void Processor::OPCode0x70() +{ + // LD (HL),B + OPCodes_LD(HL.GetValue(), BC.GetHigh()); +} + +void Processor::OPCode0x71() +{ + // LD (HL),C + OPCodes_LD(HL.GetValue(), BC.GetLow()); +} + +void Processor::OPCode0x72() +{ + // LD (HL),D + OPCodes_LD(HL.GetValue(), DE.GetHigh()); +} + +void Processor::OPCode0x73() +{ + // LD (HL),E + OPCodes_LD(HL.GetValue(), DE.GetLow()); +} + +void Processor::OPCode0x74() +{ + // LD (HL),H + OPCodes_LD(HL.GetValue(), HL.GetHigh()); +} + +void Processor::OPCode0x75() +{ + // LD (HL),L + OPCodes_LD(HL.GetValue(), HL.GetLow()); +} + +void Processor::OPCode0x76() +{ + // HALT + if (m_iIMECycles > 0) + { + // If EI is pending interrupts are triggered before Halt + m_iIMECycles = 0; + m_bIME = true; + PC.Decrement(); + } + else + { + u8 if_reg = m_pMemory->Retrieve(0xFF0F); + u8 ie_reg = m_pMemory->Retrieve(0xFFFF); + + m_bHalt = true; + + if (!m_bCGB && !m_bIME && (if_reg & ie_reg & 0x1F)) + { + m_bSkipPCBug = true; + } + } +} + +void Processor::OPCode0x77() +{ + // LD (HL),A + OPCodes_LD(HL.GetValue(), AF.GetHigh()); +} + +void Processor::OPCode0x78() +{ + // LD A,B + OPCodes_LD(AF.GetHighRegister(), BC.GetHigh()); +} + +void Processor::OPCode0x79() +{ + // LD A,C + OPCodes_LD(AF.GetHighRegister(), BC.GetLow()); +} + +void Processor::OPCode0x7A() +{ + // LD A,D + OPCodes_LD(AF.GetHighRegister(), DE.GetHigh()); +} + +void Processor::OPCode0x7B() +{ + // LD A,E + OPCodes_LD(AF.GetHighRegister(), DE.GetLow()); + +} + +void Processor::OPCode0x7C() +{ + // LD A,H + OPCodes_LD(AF.GetHighRegister(), HL.GetHigh()); +} + +void Processor::OPCode0x7D() +{ + // LD A,L + OPCodes_LD(AF.GetHighRegister(), HL.GetLow()); +} + +void Processor::OPCode0x7E() +{ + // LD A,(HL) + OPCodes_LD(AF.GetHighRegister(), HL.GetValue()); +} + +void Processor::OPCode0x7F() +{ + // LD A,A + OPCodes_LD(AF.GetHighRegister(), AF.GetHigh()); +} + +void Processor::OPCode0x80() +{ + // ADD A,B + OPCodes_ADD(BC.GetHigh()); +} + +void Processor::OPCode0x81() +{ + // ADD A,C + OPCodes_ADD(BC.GetLow()); +} + +void Processor::OPCode0x82() +{ + // ADD A,D + OPCodes_ADD(DE.GetHigh()); +} + +void Processor::OPCode0x83() +{ + // ADD A,E + OPCodes_ADD(DE.GetLow()); +} + +void Processor::OPCode0x84() +{ + // ADD A,H + OPCodes_ADD(HL.GetHigh()); +} + +void Processor::OPCode0x85() +{ + // ADD A,L + OPCodes_ADD(HL.GetLow()); +} + +void Processor::OPCode0x86() +{ + // ADD A,(HL) + OPCodes_ADD(m_pMemory->Read(HL.GetValue())); +} + +void Processor::OPCode0x87() +{ + // ADD A,A + OPCodes_ADD(AF.GetHigh()); +} + +void Processor::OPCode0x88() +{ + // ADC A,B + OPCodes_ADC(BC.GetHigh()); +} + +void Processor::OPCode0x89() +{ + // ADC A,C + OPCodes_ADC(BC.GetLow()); +} + +void Processor::OPCode0x8A() +{ + // ADC A,D + OPCodes_ADC(DE.GetHigh()); +} + +void Processor::OPCode0x8B() +{ + // ADC A,E + OPCodes_ADC(DE.GetLow()); +} + +void Processor::OPCode0x8C() +{ + // ADC A,H + OPCodes_ADC(HL.GetHigh()); +} + +void Processor::OPCode0x8D() +{ + // ADC A,L + OPCodes_ADC(HL.GetLow()); +} + +void Processor::OPCode0x8E() +{ + // ADC A,(HL) + OPCodes_ADC(m_pMemory->Read(HL.GetValue())); +} + +void Processor::OPCode0x8F() +{ + // ADC A,A + OPCodes_ADC(AF.GetHigh()); +} + +void Processor::OPCode0x90() +{ + // SUB B + OPCodes_SUB(BC.GetHigh()); +} + +void Processor::OPCode0x91() +{ + // SUB C + OPCodes_SUB(BC.GetLow()); +} + +void Processor::OPCode0x92() +{ + // SUB D + OPCodes_SUB(DE.GetHigh()); +} + +void Processor::OPCode0x93() +{ + // SUB E + OPCodes_SUB(DE.GetLow()); +} + +void Processor::OPCode0x94() +{ + // SUB H + OPCodes_SUB(HL.GetHigh()); +} + +void Processor::OPCode0x95() +{ + // SUB L + OPCodes_SUB(HL.GetLow()); +} + +void Processor::OPCode0x96() +{ + // SUB (HL) + OPCodes_SUB(m_pMemory->Read(HL.GetValue())); +} + +void Processor::OPCode0x97() +{ + // SUB A + OPCodes_SUB(AF.GetHigh()); +} + +void Processor::OPCode0x98() +{ + // SBC B + OPCodes_SBC(BC.GetHigh()); +} + +void Processor::OPCode0x99() +{ + // SBC C + OPCodes_SBC(BC.GetLow()); +} + +void Processor::OPCode0x9A() +{ + // SBC D + OPCodes_SBC(DE.GetHigh()); +} + +void Processor::OPCode0x9B() +{ + // SBC E + OPCodes_SBC(DE.GetLow()); +} + +void Processor::OPCode0x9C() +{ + // SBC H + OPCodes_SBC(HL.GetHigh()); +} + +void Processor::OPCode0x9D() +{ + // SBC L + OPCodes_SBC(HL.GetLow()); +} + +void Processor::OPCode0x9E() +{ + // SBC (HL) + OPCodes_SBC(m_pMemory->Read(HL.GetValue())); +} + +void Processor::OPCode0x9F() +{ + // SBC A + OPCodes_SBC(AF.GetHigh()); +} + +void Processor::OPCode0xA0() +{ + // AND B + OPCodes_AND(BC.GetHigh()); +} + +void Processor::OPCode0xA1() +{ + // AND C + OPCodes_AND(BC.GetLow()); +} + +void Processor::OPCode0xA2() +{ + // AND D + OPCodes_AND(DE.GetHigh()); +} + +void Processor::OPCode0xA3() +{ + // AND E + OPCodes_AND(DE.GetLow()); +} + +void Processor::OPCode0xA4() +{ + // AND H + OPCodes_AND(HL.GetHigh()); +} + +void Processor::OPCode0xA5() +{ + // AND L + OPCodes_AND(HL.GetLow()); +} + +void Processor::OPCode0xA6() +{ + // AND (HL) + OPCodes_AND(m_pMemory->Read(HL.GetValue())); +} + +void Processor::OPCode0xA7() +{ + // AND A + OPCodes_AND(AF.GetHigh()); +} + +void Processor::OPCode0xA8() +{ + // XOR B + OPCodes_XOR(BC.GetHigh()); +} + +void Processor::OPCode0xA9() +{ + // XOR C + OPCodes_XOR(BC.GetLow()); +} + +void Processor::OPCode0xAA() +{ + // XOR D + OPCodes_XOR(DE.GetHigh()); +} + +void Processor::OPCode0xAB() +{ + // XOR E + OPCodes_XOR(DE.GetLow()); +} + +void Processor::OPCode0xAC() +{ + // XOR H + OPCodes_XOR(HL.GetHigh()); +} + +void Processor::OPCode0xAD() +{ + // XOR L + OPCodes_XOR(HL.GetLow()); +} + +void Processor::OPCode0xAE() +{ + // XOR (HL) + OPCodes_XOR(m_pMemory->Read(HL.GetValue())); +} + +void Processor::OPCode0xAF() +{ + // XOR A + OPCodes_XOR(AF.GetHigh()); +} + +void Processor::OPCode0xB0() +{ + // OR B + OPCodes_OR(BC.GetHigh()); +} + +void Processor::OPCode0xB1() +{ + // OR C + OPCodes_OR(BC.GetLow()); +} + +void Processor::OPCode0xB2() +{ + // OR D + OPCodes_OR(DE.GetHigh()); +} + +void Processor::OPCode0xB3() +{ + // OR E + OPCodes_OR(DE.GetLow()); + +} + +void Processor::OPCode0xB4() +{ + // OR H + OPCodes_OR(HL.GetHigh()); +} + +void Processor::OPCode0xB5() +{ + // OR L + OPCodes_OR(HL.GetLow()); +} + +void Processor::OPCode0xB6() +{ + // OR (HL) + OPCodes_OR(m_pMemory->Read(HL.GetValue())); +} + +void Processor::OPCode0xB7() +{ + // OR A + OPCodes_OR(AF.GetHigh()); +} + +void Processor::OPCode0xB8() +{ + // CP B + OPCodes_CP(BC.GetHigh()); +} + +void Processor::OPCode0xB9() +{ + // CP C + OPCodes_CP(BC.GetLow()); +} + +void Processor::OPCode0xBA() +{ + // CP D + OPCodes_CP(DE.GetHigh()); +} + +void Processor::OPCode0xBB() +{ + // CP E + OPCodes_CP(DE.GetLow()); +} + +void Processor::OPCode0xBC() +{ + // CP H + OPCodes_CP(HL.GetHigh()); +} + +void Processor::OPCode0xBD() +{ + // CP L + OPCodes_CP(HL.GetLow()); +} + +void Processor::OPCode0xBE() +{ + // CP (HL) + OPCodes_CP(m_pMemory->Read(HL.GetValue())); +} + +void Processor::OPCode0xBF() +{ + // CP A + OPCodes_CP(AF.GetHigh()); +} + +void Processor::OPCode0xC0() +{ + // RET NZ + if (!IsSetFlag(FLAG_ZERO)) + { + StackPop(&PC); + m_bBranchTaken = true; + } +} + +void Processor::OPCode0xC1() +{ + // POP BC + StackPop(&BC); +} + +void Processor::OPCode0xC2() +{ + // JP NZ,nn + if (!IsSetFlag(FLAG_ZERO)) + { + u8 l = m_pMemory->Read(PC.GetValue()); + PC.Increment(); + u8 h = m_pMemory->Read(PC.GetValue()); + PC.SetHigh(h); + PC.SetLow(l); + m_bBranchTaken = true; + } + else + { + PC.Increment(); + PC.Increment(); + } +} + +void Processor::OPCode0xC3() +{ + // JP nn + u8 l = m_pMemory->Read(PC.GetValue()); + PC.Increment(); + u8 h = m_pMemory->Read(PC.GetValue()); + PC.SetHigh(h); + PC.SetLow(l); +} + +void Processor::OPCode0xC4() +{ + // CALL NZ,nn + if (!IsSetFlag(FLAG_ZERO)) + { + u8 l = m_pMemory->Read(PC.GetValue()); + PC.Increment(); + u8 h = m_pMemory->Read(PC.GetValue()); + PC.Increment(); + StackPush(&PC); + PC.SetHigh(h); + PC.SetLow(l); + m_bBranchTaken = true; + } + else + { + PC.Increment(); + PC.Increment(); + } +} + +void Processor::OPCode0xC5() +{ + // PUSH BC + StackPush(&BC); +} + +void Processor::OPCode0xC6() +{ + // ADD A,n + OPCodes_ADD(m_pMemory->Read(PC.GetValue())); + PC.Increment(); +} + +void Processor::OPCode0xC7() +{ + // RST 00H + StackPush(&PC); + PC.SetValue(0x0000); +} + +void Processor::OPCode0xC8() +{ + // RET Z + if (IsSetFlag(FLAG_ZERO)) + { + StackPop(&PC); + m_bBranchTaken = true; + } +} + +void Processor::OPCode0xC9() +{ + // RET + StackPop(&PC); +} + +void Processor::OPCode0xCA() +{ + // JP Z,nn + if (IsSetFlag(FLAG_ZERO)) + { + u8 l = m_pMemory->Read(PC.GetValue()); + PC.Increment(); + u8 h = m_pMemory->Read(PC.GetValue()); + PC.SetHigh(h); + PC.SetLow(l); + m_bBranchTaken = true; + } + else + { + PC.Increment(); + PC.Increment(); + } +} + +void Processor::OPCode0xCB() +{ + // CB prefixed instruction +} + +void Processor::OPCode0xCC() +{ + // CALL Z,nn + if (IsSetFlag(FLAG_ZERO)) + { + u8 l = m_pMemory->Read(PC.GetValue()); + PC.Increment(); + u8 h = m_pMemory->Read(PC.GetValue()); + PC.Increment(); + StackPush(&PC); + PC.SetHigh(h); + PC.SetLow(l); + m_bBranchTaken = true; + } + else + { + PC.Increment(); + PC.Increment(); + } +} + +void Processor::OPCode0xCD() +{ + // CALL nn + u8 l = m_pMemory->Read(PC.GetValue()); + PC.Increment(); + u8 h = m_pMemory->Read(PC.GetValue()); + PC.Increment(); + StackPush(&PC); + PC.SetHigh(h); + PC.SetLow(l); +} + +void Processor::OPCode0xCE() +{ + // ADC A,n + OPCodes_ADC(m_pMemory->Read(PC.GetValue())); + PC.Increment(); +} + +void Processor::OPCode0xCF() +{ + // RST 08H + StackPush(&PC); + PC.SetValue(0x0008); +} + +void Processor::OPCode0xD0() +{ + // RET NC + if (!IsSetFlag(FLAG_CARRY)) + { + StackPop(&PC); + m_bBranchTaken = true; + } +} + +void Processor::OPCode0xD1() +{ + // POP DE + StackPop(&DE); +} + +void Processor::OPCode0xD2() +{ + // JP NC,nn + if (!IsSetFlag(FLAG_CARRY)) + { + u8 l = m_pMemory->Read(PC.GetValue()); + PC.Increment(); + u8 h = m_pMemory->Read(PC.GetValue()); + PC.SetHigh(h); + PC.SetLow(l); + m_bBranchTaken = true; + } + else + { + PC.Increment(); + PC.Increment(); + } +} + +void Processor::OPCode0xD3() +{ + InvalidOPCode(); +} + +void Processor::OPCode0xD4() +{ + // CALL NC,nn + if (!IsSetFlag(FLAG_CARRY)) + { + u8 l = m_pMemory->Read(PC.GetValue()); + PC.Increment(); + u8 h = m_pMemory->Read(PC.GetValue()); + PC.Increment(); + StackPush(&PC); + PC.SetHigh(h); + PC.SetLow(l); + m_bBranchTaken = true; + } + else + { + PC.Increment(); + PC.Increment(); + } +} + +void Processor::OPCode0xD5() +{ + // PUSH DE + StackPush(&DE); +} + +void Processor::OPCode0xD6() +{ + // SUB n + OPCodes_SUB(m_pMemory->Read(PC.GetValue())); + PC.Increment(); +} + +void Processor::OPCode0xD7() +{ + // RST 10H + StackPush(&PC); + PC.SetValue(0x0010); +} + +void Processor::OPCode0xD8() +{ + // RET C + if (IsSetFlag(FLAG_CARRY)) + { + StackPop(&PC); + m_bBranchTaken = true; + } +} + +void Processor::OPCode0xD9() +{ + // RETI + StackPop(&PC); + m_bIME = true; +} + +void Processor::OPCode0xDA() +{ + // JP C,nn + if (IsSetFlag(FLAG_CARRY)) + { + u8 l = m_pMemory->Read(PC.GetValue()); + PC.Increment(); + u8 h = m_pMemory->Read(PC.GetValue()); + PC.SetHigh(h); + PC.SetLow(l); + m_bBranchTaken = true; + } + else + { + PC.Increment(); + PC.Increment(); + } +} + +void Processor::OPCode0xDB() +{ + InvalidOPCode(); +} + +void Processor::OPCode0xDC() +{ + // CALL C,nn + if (IsSetFlag(FLAG_CARRY)) + { + u8 l = m_pMemory->Read(PC.GetValue()); + PC.Increment(); + u8 h = m_pMemory->Read(PC.GetValue()); + PC.Increment(); + StackPush(&PC); + PC.SetHigh(h); + PC.SetLow(l); + m_bBranchTaken = true; + } + else + { + PC.Increment(); + PC.Increment(); + } +} + +void Processor::OPCode0xDD() +{ + InvalidOPCode(); +} + +void Processor::OPCode0xDE() +{ + // SBC n + OPCodes_SBC(m_pMemory->Read(PC.GetValue())); + PC.Increment(); +} + +void Processor::OPCode0xDF() +{ + // RST 18H + StackPush(&PC); + PC.SetValue(0x0018); +} + +void Processor::OPCode0xE0() +{ + // LD (0xFF00+n),A + OPCodes_LD(static_cast<u16> (0xFF00 + m_pMemory->Read(PC.GetValue())), AF.GetHigh()); + PC.Increment(); +} + +void Processor::OPCode0xE1() +{ + // POP HL + StackPop(&HL); +} + +void Processor::OPCode0xE2() +{ + // LD (0xFF00+C),A + OPCodes_LD(static_cast<u16> (0xFF00 + BC.GetLow()), AF.GetHigh()); +} + +void Processor::OPCode0xE3() +{ + InvalidOPCode(); +} + +void Processor::OPCode0xE4() +{ + InvalidOPCode(); +} + +void Processor::OPCode0xE5() +{ + // PUSH HL + StackPush(&HL); +} + +void Processor::OPCode0xE6() +{ + // AND n + OPCodes_AND(m_pMemory->Read(PC.GetValue())); + PC.Increment(); +} + +void Processor::OPCode0xE7() +{ + // RST 20H + StackPush(&PC); + PC.SetValue(0x0020); +} + +void Processor::OPCode0xE8() +{ + // ADD SP,n + OPCodes_ADD_SP(static_cast<u8> (m_pMemory->Read(PC.GetValue()))); + PC.Increment(); +} + +void Processor::OPCode0xE9() +{ + // JP (HL) + PC.SetValue(HL.GetValue()); +} + +void Processor::OPCode0xEA() +{ + // LD (nn),A + SixteenBitRegister tmp; + tmp.SetLow(m_pMemory->Read(PC.GetValue())); + PC.Increment(); + tmp.SetHigh(m_pMemory->Read(PC.GetValue())); + PC.Increment(); + OPCodes_LD(tmp.GetValue(), AF.GetHigh()); +} + +void Processor::OPCode0xEB() +{ + InvalidOPCode(); +} + +void Processor::OPCode0xEC() +{ + InvalidOPCode(); +} + +void Processor::OPCode0xED() +{ + InvalidOPCode(); +} + +void Processor::OPCode0xEE() +{ + // XOR n + OPCodes_XOR(m_pMemory->Read(PC.GetValue())); + PC.Increment(); +} + +void Processor::OPCode0xEF() +{ + // RST 28H + StackPush(&PC); + PC.SetValue(0x28); +} + +void Processor::OPCode0xF0() +{ + // LD A,(0xFF00+n) + OPCodes_LD(AF.GetHighRegister(), + static_cast<u16> (0xFF00 + m_pMemory->Read(PC.GetValue()))); + PC.Increment(); +} + +void Processor::OPCode0xF1() +{ + // POP AF + StackPop(&AF); + AF.SetLow(AF.GetLow() & 0xF0); +} + +void Processor::OPCode0xF2() +{ + // LD A,(C) + OPCodes_LD(AF.GetHighRegister(), static_cast<u16> (0xFF00 + BC.GetLow())); +} + +void Processor::OPCode0xF3() +{ + // DI + m_bIME = false; + m_iIMECycles = 0; +} + +void Processor::OPCode0xF4() +{ + InvalidOPCode(); +} + +void Processor::OPCode0xF5() +{ + // PUSH AF + StackPush(&AF); +} + +void Processor::OPCode0xF6() +{ + // OR n + OPCodes_OR(m_pMemory->Read(PC.GetValue())); + PC.Increment(); +} + +void Processor::OPCode0xF7() +{ + // RST 30H + StackPush(&PC); + PC.SetValue(0x0030); +} + +void Processor::OPCode0xF8() +{ + // LD HL,SP+n + s8 n = m_pMemory->Read(PC.GetValue()); + u16 result = SP.GetValue() + n; + ClearAllFlags(); + if (((SP.GetValue() ^ n ^ result) & 0x100) == 0x100) + ToggleFlag(FLAG_CARRY); + if (((SP.GetValue() ^ n ^ result) & 0x10) == 0x10) + ToggleFlag(FLAG_HALF); + HL.SetValue(result); + PC.Increment(); +} + +void Processor::OPCode0xF9() +{ + // LD SP,HL + SP.SetValue(HL.GetValue()); +} + +void Processor::OPCode0xFA() +{ + // LD A,(nn) + SixteenBitRegister tmp; + tmp.SetLow(m_pMemory->Read(PC.GetValue())); + PC.Increment(); + tmp.SetHigh(m_pMemory->Read(PC.GetValue())); + PC.Increment(); + OPCodes_LD(AF.GetHighRegister(), tmp.GetValue()); +} + +void Processor::OPCode0xFB() +{ + // EI + int ei_cycles = kOPCodeMachineCycles[0xFB] * AdjustedCycles(4); + m_iIMECycles = ei_cycles + 1; +} + +void Processor::OPCode0xFC() +{ + InvalidOPCode(); +} + +void Processor::OPCode0xFD() +{ + InvalidOPCode(); +} + +void Processor::OPCode0xFE() +{ + // CP n + OPCodes_CP(m_pMemory->Read(PC.GetValue())); + PC.Increment(); +} + +void Processor::OPCode0xFF() +{ + // RST 38H + StackPush(&PC); + PC.SetValue(0x0038); +} diff --git a/gearboy/src/opcodes_cb.cpp b/gearboy/src/opcodes_cb.cpp new file mode 100644 index 00000000..89319894 --- /dev/null +++ b/gearboy/src/opcodes_cb.cpp @@ -0,0 +1,1557 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * 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 + * 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 http://www.gnu.org/licenses/ + * + */ + +#include "Processor.h" + +void Processor::OPCodeCB0x00() +{ + // RLC B + OPCodes_RLC(BC.GetHighRegister()); +} + +void Processor::OPCodeCB0x01() +{ + // RLC C + OPCodes_RLC(BC.GetLowRegister()); +} + +void Processor::OPCodeCB0x02() +{ + // RLC D + OPCodes_RLC(DE.GetHighRegister()); +} + +void Processor::OPCodeCB0x03() +{ + // RLC E + OPCodes_RLC(DE.GetLowRegister()); +} + +void Processor::OPCodeCB0x04() +{ + // RLC H + OPCodes_RLC(HL.GetHighRegister()); +} + +void Processor::OPCodeCB0x05() +{ + // RLC L + OPCodes_RLC(HL.GetLowRegister()); +} + +void Processor::OPCodeCB0x06() +{ + // RLC (HL) + OPCodes_RLC_HL(); +} + +void Processor::OPCodeCB0x07() +{ + // RLC A + OPCodes_RLC(AF.GetHighRegister()); +} + +void Processor::OPCodeCB0x08() +{ + // RRC B + OPCodes_RRC(BC.GetHighRegister()); +} + +void Processor::OPCodeCB0x09() +{ + // RRC C + OPCodes_RRC(BC.GetLowRegister()); +} + +void Processor::OPCodeCB0x0A() +{ + // RRC D + OPCodes_RRC(DE.GetHighRegister()); +} + +void Processor::OPCodeCB0x0B() +{ + // RRC E + OPCodes_RRC(DE.GetLowRegister()); +} + +void Processor::OPCodeCB0x0C() +{ + // RRC H + OPCodes_RRC(HL.GetHighRegister()); +} + +void Processor::OPCodeCB0x0D() +{ + // RRC L + OPCodes_RRC(HL.GetLowRegister()); +} + +void Processor::OPCodeCB0x0E() +{ + // RRC (HL) + OPCodes_RRC_HL(); +} + +void Processor::OPCodeCB0x0F() +{ + // RRC A + OPCodes_RRC(AF.GetHighRegister()); +} + +void Processor::OPCodeCB0x10() +{ + // RL B + OPCodes_RL(BC.GetHighRegister()); +} + +void Processor::OPCodeCB0x11() +{ + // RL C + OPCodes_RL(BC.GetLowRegister()); +} + +void Processor::OPCodeCB0x12() +{ + // RL D + OPCodes_RL(DE.GetHighRegister()); +} + +void Processor::OPCodeCB0x13() +{ + // RL E + OPCodes_RL(DE.GetLowRegister()); +} + +void Processor::OPCodeCB0x14() +{ + // RL H + OPCodes_RL(HL.GetHighRegister()); +} + +void Processor::OPCodeCB0x15() +{ + // RL L + OPCodes_RL(HL.GetLowRegister()); +} + +void Processor::OPCodeCB0x16() +{ + // RL (HL) + OPCodes_RL_HL(); +} + +void Processor::OPCodeCB0x17() +{ + // RL A + OPCodes_RL(AF.GetHighRegister()); +} + +void Processor::OPCodeCB0x18() +{ + // RR B + OPCodes_RR(BC.GetHighRegister()); +} + +void Processor::OPCodeCB0x19() +{ + // RR C + OPCodes_RR(BC.GetLowRegister()); +} + +void Processor::OPCodeCB0x1A() +{ + // RR D + OPCodes_RR(DE.GetHighRegister()); +} + +void Processor::OPCodeCB0x1B() +{ + // RR E + OPCodes_RR(DE.GetLowRegister()); +} + +void Processor::OPCodeCB0x1C() +{ + // RR H + OPCodes_RR(HL.GetHighRegister()); +} + +void Processor::OPCodeCB0x1D() +{ + // RR L + OPCodes_RR(HL.GetLowRegister()); +} + +void Processor::OPCodeCB0x1E() +{ + // RR (HL) + OPCodes_RR_HL(); +} + +void Processor::OPCodeCB0x1F() +{ + // RR A + OPCodes_RR(AF.GetHighRegister()); +} + +void Processor::OPCodeCB0x20() +{ + // SLA B + OPCodes_SLA(BC.GetHighRegister()); +} + +void Processor::OPCodeCB0x21() +{ + // SLA C + OPCodes_SLA(BC.GetLowRegister()); +} + +void Processor::OPCodeCB0x22() +{ + // SLA D + OPCodes_SLA(DE.GetHighRegister()); +} + +void Processor::OPCodeCB0x23() +{ + // SLA E + OPCodes_SLA(DE.GetLowRegister()); +} + +void Processor::OPCodeCB0x24() +{ + // SLA H + OPCodes_SLA(HL.GetHighRegister()); +} + +void Processor::OPCodeCB0x25() +{ + // SLA L + OPCodes_SLA(HL.GetLowRegister()); +} + +void Processor::OPCodeCB0x26() +{ + // SLA (HL) + OPCodes_SLA_HL(); +} + +void Processor::OPCodeCB0x27() +{ + // SLA A + OPCodes_SLA(AF.GetHighRegister()); +} + +void Processor::OPCodeCB0x28() +{ + // SRA B + OPCodes_SRA(BC.GetHighRegister()); +} + +void Processor::OPCodeCB0x29() +{ + // SRA C + OPCodes_SRA(BC.GetLowRegister()); +} + +void Processor::OPCodeCB0x2A() +{ + // SRA D + OPCodes_SRA(DE.GetHighRegister()); +} + +void Processor::OPCodeCB0x2B() +{ + // SRA E + OPCodes_SRA(DE.GetLowRegister()); +} + +void Processor::OPCodeCB0x2C() +{ + // SRA H + OPCodes_SRA(HL.GetHighRegister()); +} + +void Processor::OPCodeCB0x2D() +{ + // SRA L + OPCodes_SRA(HL.GetLowRegister()); +} + +void Processor::OPCodeCB0x2E() +{ + // SRA (HL) + OPCodes_SRA_HL(); +} + +void Processor::OPCodeCB0x2F() +{ + // SRA A + OPCodes_SRA(AF.GetHighRegister()); +} + +void Processor::OPCodeCB0x30() +{ + // SWAP B + OPCodes_SWAP_Register(BC.GetHighRegister()); +} + +void Processor::OPCodeCB0x31() +{ + // SWAP C + OPCodes_SWAP_Register(BC.GetLowRegister()); +} + +void Processor::OPCodeCB0x32() +{ + // SWAP D + OPCodes_SWAP_Register(DE.GetHighRegister()); +} + +void Processor::OPCodeCB0x33() +{ + // SWAP E + OPCodes_SWAP_Register(DE.GetLowRegister()); +} + +void Processor::OPCodeCB0x34() +{ + // SWAP H + OPCodes_SWAP_Register(HL.GetHighRegister()); +} + +void Processor::OPCodeCB0x35() +{ + // SWAP L + OPCodes_SWAP_Register(HL.GetLowRegister()); +} + +void Processor::OPCodeCB0x36() +{ + // SWAP (HL) + OPCodes_SWAP_HL(); +} + +void Processor::OPCodeCB0x37() +{ + // SWAP A + OPCodes_SWAP_Register(AF.GetHighRegister()); +} + +void Processor::OPCodeCB0x38() +{ + // SRL B + OPCodes_SRL(BC.GetHighRegister()); +} + +void Processor::OPCodeCB0x39() +{ + // SRL C + OPCodes_SRL(BC.GetLowRegister()); +} + +void Processor::OPCodeCB0x3A() +{ + // SRL D + OPCodes_SRL(DE.GetHighRegister()); +} + +void Processor::OPCodeCB0x3B() +{ + // SRL E + OPCodes_SRL(DE.GetLowRegister()); +} + +void Processor::OPCodeCB0x3C() +{ + // SRL H + OPCodes_SRL(HL.GetHighRegister()); +} + +void Processor::OPCodeCB0x3D() +{ + // SRL L + OPCodes_SRL(HL.GetLowRegister()); +} + +void Processor::OPCodeCB0x3E() +{ + // SRL (HL) + OPCodes_SRL_HL(); +} + +void Processor::OPCodeCB0x3F() +{ + // SRL A + OPCodes_SRL(AF.GetHighRegister()); +} + +void Processor::OPCodeCB0x40() +{ + // BIT 0 B + OPCodes_BIT(BC.GetHighRegister(), 0); +} + +void Processor::OPCodeCB0x41() +{ + // BIT 0 C + OPCodes_BIT(BC.GetLowRegister(), 0); +} + +void Processor::OPCodeCB0x42() +{ + // BIT 0 D + OPCodes_BIT(DE.GetHighRegister(), 0); +} + +void Processor::OPCodeCB0x43() +{ + // BIT 0 E + OPCodes_BIT(DE.GetLowRegister(), 0); +} + +void Processor::OPCodeCB0x44() +{ + // BIT 0 H + OPCodes_BIT(HL.GetHighRegister(), 0); +} + +void Processor::OPCodeCB0x45() +{ + // BIT 0 L + OPCodes_BIT(HL.GetLowRegister(), 0); +} + +void Processor::OPCodeCB0x46() +{ + // BIT 0 (HL) + OPCodes_BIT_HL(0); +} + +void Processor::OPCodeCB0x47() +{ + // BIT 0 A + OPCodes_BIT(AF.GetHighRegister(), 0); +} + +void Processor::OPCodeCB0x48() +{ + // BIT 1 B + OPCodes_BIT(BC.GetHighRegister(), 1); +} + +void Processor::OPCodeCB0x49() +{ + // BIT 1 C + OPCodes_BIT(BC.GetLowRegister(), 1); +} + +void Processor::OPCodeCB0x4A() +{ + // BIT 1 D + OPCodes_BIT(DE.GetHighRegister(), 1); +} + +void Processor::OPCodeCB0x4B() +{ + // BIT 1 E + OPCodes_BIT(DE.GetLowRegister(), 1); +} + +void Processor::OPCodeCB0x4C() +{ + // BIT 1 H + OPCodes_BIT(HL.GetHighRegister(), 1); +} + +void Processor::OPCodeCB0x4D() +{ + // BIT 1 L + OPCodes_BIT(HL.GetLowRegister(), 1); +} + +void Processor::OPCodeCB0x4E() +{ + // BIT 1 (HL) + OPCodes_BIT_HL(1); +} + +void Processor::OPCodeCB0x4F() +{ + // BIT 1 A + OPCodes_BIT(AF.GetHighRegister(), 1); +} + +void Processor::OPCodeCB0x50() +{ + // BIT 2 B + OPCodes_BIT(BC.GetHighRegister(), 2); +} + +void Processor::OPCodeCB0x51() +{ + // BIT 2 C + OPCodes_BIT(BC.GetLowRegister(), 2); +} + +void Processor::OPCodeCB0x52() +{ + // BIT 2 D + OPCodes_BIT(DE.GetHighRegister(), 2); +} + +void Processor::OPCodeCB0x53() +{ + // BIT 2 E + OPCodes_BIT(DE.GetLowRegister(), 2); +} + +void Processor::OPCodeCB0x54() +{ + // BIT 2 H + OPCodes_BIT(HL.GetHighRegister(), 2); +} + +void Processor::OPCodeCB0x55() +{ + // BIT 2 L + OPCodes_BIT(HL.GetLowRegister(), 2); +} + +void Processor::OPCodeCB0x56() +{ + // BIT 2 (HL) + OPCodes_BIT_HL(2); +} + +void Processor::OPCodeCB0x57() +{ + // BIT 2 A + OPCodes_BIT(AF.GetHighRegister(), 2); +} + +void Processor::OPCodeCB0x58() +{ + // BIT 3 B + OPCodes_BIT(BC.GetHighRegister(), 3); +} + +void Processor::OPCodeCB0x59() +{ + // BIT 3 C + OPCodes_BIT(BC.GetLowRegister(), 3); +} + +void Processor::OPCodeCB0x5A() +{ + // BIT 3 D + OPCodes_BIT(DE.GetHighRegister(), 3); +} + +void Processor::OPCodeCB0x5B() +{ + // BIT 3 E + OPCodes_BIT(DE.GetLowRegister(), 3); +} + +void Processor::OPCodeCB0x5C() +{ + // BIT 3 H + OPCodes_BIT(HL.GetHighRegister(), 3); +} + +void Processor::OPCodeCB0x5D() +{ + // BIT 3 L + OPCodes_BIT(HL.GetLowRegister(), 3); +} + +void Processor::OPCodeCB0x5E() +{ + // BIT 3 (HL) + OPCodes_BIT_HL(3); +} + +void Processor::OPCodeCB0x5F() +{ + // BIT 3 A + OPCodes_BIT(AF.GetHighRegister(), 3); +} + +void Processor::OPCodeCB0x60() +{ + // BIT 4 B + OPCodes_BIT(BC.GetHighRegister(), 4); +} + +void Processor::OPCodeCB0x61() +{ + // BIT 4 C + OPCodes_BIT(BC.GetLowRegister(), 4); +} + +void Processor::OPCodeCB0x62() +{ + // BIT 4 D + OPCodes_BIT(DE.GetHighRegister(), 4); +} + +void Processor::OPCodeCB0x63() +{ + // BIT 4 E + OPCodes_BIT(DE.GetLowRegister(), 4); +} + +void Processor::OPCodeCB0x64() +{ + // BIT 4 H + OPCodes_BIT(HL.GetHighRegister(), 4); +} + +void Processor::OPCodeCB0x65() +{ + // BIT 4 L + OPCodes_BIT(HL.GetLowRegister(), 4); +} + +void Processor::OPCodeCB0x66() +{ + // BIT 4 (HL) + OPCodes_BIT_HL(4); +} + +void Processor::OPCodeCB0x67() +{ + // BIT 4 A + OPCodes_BIT(AF.GetHighRegister(), 4); +} + +void Processor::OPCodeCB0x68() +{ + // BIT 5 B + OPCodes_BIT(BC.GetHighRegister(), 5); +} + +void Processor::OPCodeCB0x69() +{ + // BIT 5 C + OPCodes_BIT(BC.GetLowRegister(), 5); +} + +void Processor::OPCodeCB0x6A() +{ + // BIT 5 D + OPCodes_BIT(DE.GetHighRegister(), 5); +} + +void Processor::OPCodeCB0x6B() +{ + // BIT 5 E + OPCodes_BIT(DE.GetLowRegister(), 5); +} + +void Processor::OPCodeCB0x6C() +{ + // BIT 5 H + OPCodes_BIT(HL.GetHighRegister(), 5); +} + +void Processor::OPCodeCB0x6D() +{ + // BIT 5 L + OPCodes_BIT(HL.GetLowRegister(), 5); +} + +void Processor::OPCodeCB0x6E() +{ + // BIT 5 (HL) + OPCodes_BIT_HL(5); +} + +void Processor::OPCodeCB0x6F() +{ + // BIT 5 A + OPCodes_BIT(AF.GetHighRegister(), 5); +} + +void Processor::OPCodeCB0x70() +{ + // BIT 6 B + OPCodes_BIT(BC.GetHighRegister(), 6); +} + +void Processor::OPCodeCB0x71() +{ + // BIT 6 C + OPCodes_BIT(BC.GetLowRegister(), 6); +} + +void Processor::OPCodeCB0x72() +{ + // BIT 6 D + OPCodes_BIT(DE.GetHighRegister(), 6); +} + +void Processor::OPCodeCB0x73() +{ + // BIT 6 E + OPCodes_BIT(DE.GetLowRegister(), 6); +} + +void Processor::OPCodeCB0x74() +{ + // BIT 6 H + OPCodes_BIT(HL.GetHighRegister(), 6); +} + +void Processor::OPCodeCB0x75() +{ + // BIT 6 L + OPCodes_BIT(HL.GetLowRegister(), 6); +} + +void Processor::OPCodeCB0x76() +{ + // BIT 6 (HL) + OPCodes_BIT_HL(6); +} + +void Processor::OPCodeCB0x77() +{ + // BIT 6 A + OPCodes_BIT(AF.GetHighRegister(), 6); +} + +void Processor::OPCodeCB0x78() +{ + // BIT 7 B + OPCodes_BIT(BC.GetHighRegister(), 7); +} + +void Processor::OPCodeCB0x79() +{ + // BIT 7 C + OPCodes_BIT(BC.GetLowRegister(), 7); +} + +void Processor::OPCodeCB0x7A() +{ + // BIT 7 D + OPCodes_BIT(DE.GetHighRegister(), 7); +} + +void Processor::OPCodeCB0x7B() +{ + // BIT 7 E + OPCodes_BIT(DE.GetLowRegister(), 7); +} + +void Processor::OPCodeCB0x7C() +{ + // BIT 7 H + OPCodes_BIT(HL.GetHighRegister(), 7); +} + +void Processor::OPCodeCB0x7D() +{ + // BIT 7 L + OPCodes_BIT(HL.GetLowRegister(), 7); +} + +void Processor::OPCodeCB0x7E() +{ + // BIT 7 (HL) + OPCodes_BIT_HL(7); +} + +void Processor::OPCodeCB0x7F() +{ + // BIT 7 A + OPCodes_BIT(AF.GetHighRegister(), 7); +} + +void Processor::OPCodeCB0x80() +{ + // RES 0 B + OPCodes_RES(BC.GetHighRegister(), 0); +} + +void Processor::OPCodeCB0x81() +{ + // RES 0 C + OPCodes_RES(BC.GetLowRegister(), 0); +} + +void Processor::OPCodeCB0x82() +{ + // RES 0 D + OPCodes_RES(DE.GetHighRegister(), 0); +} + +void Processor::OPCodeCB0x83() +{ + // RES 0 E + OPCodes_RES(DE.GetLowRegister(), 0); +} + +void Processor::OPCodeCB0x84() +{ + // RES 0 H + OPCodes_RES(HL.GetHighRegister(), 0); +} + +void Processor::OPCodeCB0x85() +{ + // RES 0 L + OPCodes_RES(HL.GetLowRegister(), 0); +} + +void Processor::OPCodeCB0x86() +{ + // RES 0 (HL) + OPCodes_RES_HL(0); +} + +void Processor::OPCodeCB0x87() +{ + // RES 0 A + OPCodes_RES(AF.GetHighRegister(), 0); +} + +void Processor::OPCodeCB0x88() +{ + // RES 1 B + OPCodes_RES(BC.GetHighRegister(), 1); +} + +void Processor::OPCodeCB0x89() +{ + // RES 1 C + OPCodes_RES(BC.GetLowRegister(), 1); +} + +void Processor::OPCodeCB0x8A() +{ + // RES 1 D + OPCodes_RES(DE.GetHighRegister(), 1); +} + +void Processor::OPCodeCB0x8B() +{ + // RES 1 E + OPCodes_RES(DE.GetLowRegister(), 1); +} + +void Processor::OPCodeCB0x8C() +{ + // RES 1 H + OPCodes_RES(HL.GetHighRegister(), 1); +} + +void Processor::OPCodeCB0x8D() +{ + // RES 1 L + OPCodes_RES(HL.GetLowRegister(), 1); +} + +void Processor::OPCodeCB0x8E() +{ + // RES 1 (HL) + OPCodes_RES_HL(1); +} + +void Processor::OPCodeCB0x8F() +{ + // RES 1 A + OPCodes_RES(AF.GetHighRegister(), 1); +} + +void Processor::OPCodeCB0x90() +{ + // RES 2 B + OPCodes_RES(BC.GetHighRegister(), 2); +} + +void Processor::OPCodeCB0x91() +{ + // RES 2 C + OPCodes_RES(BC.GetLowRegister(), 2); +} + +void Processor::OPCodeCB0x92() +{ + // RES 2 D + OPCodes_RES(DE.GetHighRegister(), 2); +} + +void Processor::OPCodeCB0x93() +{ + // RES 2 E + OPCodes_RES(DE.GetLowRegister(), 2); +} + +void Processor::OPCodeCB0x94() +{ + // RES 2 H + OPCodes_RES(HL.GetHighRegister(), 2); +} + +void Processor::OPCodeCB0x95() +{ + // RES 2 L + OPCodes_RES(HL.GetLowRegister(), 2); +} + +void Processor::OPCodeCB0x96() +{ + // RES 2 (HL) + OPCodes_RES_HL(2); +} + +void Processor::OPCodeCB0x97() +{ + // RES 2 A + OPCodes_RES(AF.GetHighRegister(), 2); +} + +void Processor::OPCodeCB0x98() +{ + // RES 3 B + OPCodes_RES(BC.GetHighRegister(), 3); +} + +void Processor::OPCodeCB0x99() +{ + // RES 3 C + OPCodes_RES(BC.GetLowRegister(), 3); +} + +void Processor::OPCodeCB0x9A() +{ + // RES 3 D + OPCodes_RES(DE.GetHighRegister(), 3); +} + +void Processor::OPCodeCB0x9B() +{ + // RES 3 E + OPCodes_RES(DE.GetLowRegister(), 3); +} + +void Processor::OPCodeCB0x9C() +{ + // RES 3 H + OPCodes_RES(HL.GetHighRegister(), 3); +} + +void Processor::OPCodeCB0x9D() +{ + // RES 3 L + OPCodes_RES(HL.GetLowRegister(), 3); +} + +void Processor::OPCodeCB0x9E() +{ + // RES 3 (HL) + OPCodes_RES_HL(3); +} + +void Processor::OPCodeCB0x9F() +{ + // RES 3 A + OPCodes_RES(AF.GetHighRegister(), 3); +} + +void Processor::OPCodeCB0xA0() +{ + // RES 4 B + OPCodes_RES(BC.GetHighRegister(), 4); +} + +void Processor::OPCodeCB0xA1() +{ + // RES 4 C + OPCodes_RES(BC.GetLowRegister(), 4); +} + +void Processor::OPCodeCB0xA2() +{ + // RES 4 D + OPCodes_RES(DE.GetHighRegister(), 4); +} + +void Processor::OPCodeCB0xA3() +{ + // RES 4 E + OPCodes_RES(DE.GetLowRegister(), 4); +} + +void Processor::OPCodeCB0xA4() +{ + // RES 4 H + OPCodes_RES(HL.GetHighRegister(), 4); +} + +void Processor::OPCodeCB0xA5() +{ + // RES 4 L + OPCodes_RES(HL.GetLowRegister(), 4); +} + +void Processor::OPCodeCB0xA6() +{ + // RES 4 (HL) + OPCodes_RES_HL(4); +} + +void Processor::OPCodeCB0xA7() +{ + // RES 4 A + OPCodes_RES(AF.GetHighRegister(), 4); +} + +void Processor::OPCodeCB0xA8() +{ + // RES 5 B + OPCodes_RES(BC.GetHighRegister(), 5); +} + +void Processor::OPCodeCB0xA9() +{ + // RES 5 C + OPCodes_RES(BC.GetLowRegister(), 5); +} + +void Processor::OPCodeCB0xAA() +{ + // RES 5 D + OPCodes_RES(DE.GetHighRegister(), 5); +} + +void Processor::OPCodeCB0xAB() +{ + // RES 5 E + OPCodes_RES(DE.GetLowRegister(), 5); +} + +void Processor::OPCodeCB0xAC() +{ + // RES 5 H + OPCodes_RES(HL.GetHighRegister(), 5); +} + +void Processor::OPCodeCB0xAD() +{ + // RES 5 L + OPCodes_RES(HL.GetLowRegister(), 5); +} + +void Processor::OPCodeCB0xAE() +{ + // RES 5 (HL) + OPCodes_RES_HL(5); +} + +void Processor::OPCodeCB0xAF() +{ + // RES 5 A + OPCodes_RES(AF.GetHighRegister(), 5); +} + +void Processor::OPCodeCB0xB0() +{ + // RES 6 B + OPCodes_RES(BC.GetHighRegister(), 6); +} + +void Processor::OPCodeCB0xB1() +{ + // RES 6 C + OPCodes_RES(BC.GetLowRegister(), 6); +} + +void Processor::OPCodeCB0xB2() +{ + // RES 6 D + OPCodes_RES(DE.GetHighRegister(), 6); +} + +void Processor::OPCodeCB0xB3() +{ + // RES 6 E + OPCodes_RES(DE.GetLowRegister(), 6); +} + +void Processor::OPCodeCB0xB4() +{ + // RES 6 H + OPCodes_RES(HL.GetHighRegister(), 6); +} + +void Processor::OPCodeCB0xB5() +{ + // RES 6 L + OPCodes_RES(HL.GetLowRegister(), 6); +} + +void Processor::OPCodeCB0xB6() +{ + // RES 6 (HL) + OPCodes_RES_HL(6); +} + +void Processor::OPCodeCB0xB7() +{ + // RES 6 A + OPCodes_RES(AF.GetHighRegister(), 6); +} + +void Processor::OPCodeCB0xB8() +{ + // RES 7 B + OPCodes_RES(BC.GetHighRegister(), 7); +} + +void Processor::OPCodeCB0xB9() +{ + // RES 7 C + OPCodes_RES(BC.GetLowRegister(), 7); +} + +void Processor::OPCodeCB0xBA() +{ + // RES 7 D + OPCodes_RES(DE.GetHighRegister(), 7); +} + +void Processor::OPCodeCB0xBB() +{ + // RES 7 E + OPCodes_RES(DE.GetLowRegister(), 7); +} + +void Processor::OPCodeCB0xBC() +{ + // RES 7 H + OPCodes_RES(HL.GetHighRegister(), 7); +} + +void Processor::OPCodeCB0xBD() +{ + // RES 7 L + OPCodes_RES(HL.GetLowRegister(), 7); +} + +void Processor::OPCodeCB0xBE() +{ + // RES 7 (HL) + OPCodes_RES_HL(7); +} + +void Processor::OPCodeCB0xBF() +{ + // RES 7 A + OPCodes_RES(AF.GetHighRegister(), 7); +} + +void Processor::OPCodeCB0xC0() +{ + // SET 0 B + OPCodes_SET(BC.GetHighRegister(), 0); +} + +void Processor::OPCodeCB0xC1() +{ + // SET 0 C + OPCodes_SET(BC.GetLowRegister(), 0); +} + +void Processor::OPCodeCB0xC2() +{ + // SET 0 D + OPCodes_SET(DE.GetHighRegister(), 0); +} + +void Processor::OPCodeCB0xC3() +{ + // SET 0 E + OPCodes_SET(DE.GetLowRegister(), 0); +} + +void Processor::OPCodeCB0xC4() +{ + // SET 0 H + OPCodes_SET(HL.GetHighRegister(), 0); +} + +void Processor::OPCodeCB0xC5() +{ + // SET 0 L + OPCodes_SET(HL.GetLowRegister(), 0); +} + +void Processor::OPCodeCB0xC6() +{ + // SET 0 (HL) + OPCodes_SET_HL(0); +} + +void Processor::OPCodeCB0xC7() +{ + // SET 0 A + OPCodes_SET(AF.GetHighRegister(), 0); +} + +void Processor::OPCodeCB0xC8() +{ + // SET 1 B + OPCodes_SET(BC.GetHighRegister(), 1); +} + +void Processor::OPCodeCB0xC9() +{ + // SET 1 C + OPCodes_SET(BC.GetLowRegister(), 1); +} + +void Processor::OPCodeCB0xCA() +{ + // SET 1 D + OPCodes_SET(DE.GetHighRegister(), 1); +} + +void Processor::OPCodeCB0xCB() +{ + // SET 1 E + OPCodes_SET(DE.GetLowRegister(), 1); +} + +void Processor::OPCodeCB0xCC() +{ + // SET 1 H + OPCodes_SET(HL.GetHighRegister(), 1); +} + +void Processor::OPCodeCB0xCD() +{ + // SET 1 L + OPCodes_SET(HL.GetLowRegister(), 1); +} + +void Processor::OPCodeCB0xCE() +{ + // SET 1 (HL) + OPCodes_SET_HL(1); +} + +void Processor::OPCodeCB0xCF() +{ + // SET 1 A + OPCodes_SET(AF.GetHighRegister(), 1); +} + +void Processor::OPCodeCB0xD0() +{ + // SET 2 B + OPCodes_SET(BC.GetHighRegister(), 2); +} + +void Processor::OPCodeCB0xD1() +{ + // SET 2 C + OPCodes_SET(BC.GetLowRegister(), 2); +} + +void Processor::OPCodeCB0xD2() +{ + // SET 2 D + OPCodes_SET(DE.GetHighRegister(), 2); +} + +void Processor::OPCodeCB0xD3() +{ + // SET 2 E + OPCodes_SET(DE.GetLowRegister(), 2); +} + +void Processor::OPCodeCB0xD4() +{ + // SET 2 H + OPCodes_SET(HL.GetHighRegister(), 2); +} + +void Processor::OPCodeCB0xD5() +{ + // SET 2 L + OPCodes_SET(HL.GetLowRegister(), 2); +} + +void Processor::OPCodeCB0xD6() +{ + // SET 2 (HL) + OPCodes_SET_HL(2); +} + +void Processor::OPCodeCB0xD7() +{ + // SET 2 A + OPCodes_SET(AF.GetHighRegister(), 2); +} + +void Processor::OPCodeCB0xD8() +{ + // SET 3 B + OPCodes_SET(BC.GetHighRegister(), 3); +} + +void Processor::OPCodeCB0xD9() +{ + // SET 3 C + OPCodes_SET(BC.GetLowRegister(), 3); +} + +void Processor::OPCodeCB0xDA() +{ + // SET 3 D + OPCodes_SET(DE.GetHighRegister(), 3); +} + +void Processor::OPCodeCB0xDB() +{ + // SET 3 E + OPCodes_SET(DE.GetLowRegister(), 3); +} + +void Processor::OPCodeCB0xDC() +{ + // SET 3 H + OPCodes_SET(HL.GetHighRegister(), 3); +} + +void Processor::OPCodeCB0xDD() +{ + // SET 3 L + OPCodes_SET(HL.GetLowRegister(), 3); +} + +void Processor::OPCodeCB0xDE() +{ + // SET 3 (HL) + OPCodes_SET_HL(3); +} + +void Processor::OPCodeCB0xDF() +{ + // SET 3 A + OPCodes_SET(AF.GetHighRegister(), 3); +} + +void Processor::OPCodeCB0xE0() +{ + // SET 4 B + OPCodes_SET(BC.GetHighRegister(), 4); +} + +void Processor::OPCodeCB0xE1() +{ + // SET 4 C + OPCodes_SET(BC.GetLowRegister(), 4); +} + +void Processor::OPCodeCB0xE2() +{ + // SET 4 D + OPCodes_SET(DE.GetHighRegister(), 4); +} + +void Processor::OPCodeCB0xE3() +{ + // SET 4 E + OPCodes_SET(DE.GetLowRegister(), 4); +} + +void Processor::OPCodeCB0xE4() +{ + // SET 4 H + OPCodes_SET(HL.GetHighRegister(), 4); +} + +void Processor::OPCodeCB0xE5() +{ + // SET 4 L + OPCodes_SET(HL.GetLowRegister(), 4); +} + +void Processor::OPCodeCB0xE6() +{ + // SET 4 (HL) + OPCodes_SET_HL(4); +} + +void Processor::OPCodeCB0xE7() +{ + // SET 4 A + OPCodes_SET(AF.GetHighRegister(), 4); + +} + +void Processor::OPCodeCB0xE8() +{ + // SET 5 B + OPCodes_SET(BC.GetHighRegister(), 5); +} + +void Processor::OPCodeCB0xE9() +{ + // SET 5 C + OPCodes_SET(BC.GetLowRegister(), 5); +} + +void Processor::OPCodeCB0xEA() +{ + // SET 5 D + OPCodes_SET(DE.GetHighRegister(), 5); +} + +void Processor::OPCodeCB0xEB() +{ + // SET 5 E + OPCodes_SET(DE.GetLowRegister(), 5); +} + +void Processor::OPCodeCB0xEC() +{ + // SET 5 H + OPCodes_SET(HL.GetHighRegister(), 5); +} + +void Processor::OPCodeCB0xED() +{ + // SET 5 L + OPCodes_SET(HL.GetLowRegister(), 5); +} + +void Processor::OPCodeCB0xEE() +{ + // SET 5 (HL) + OPCodes_SET_HL(5); +} + +void Processor::OPCodeCB0xEF() +{ + // SET 5 A + OPCodes_SET(AF.GetHighRegister(), 5); +} + +void Processor::OPCodeCB0xF0() +{ + // SET 6 B + OPCodes_SET(BC.GetHighRegister(), 6); +} + +void Processor::OPCodeCB0xF1() +{ + // SET 6 C + OPCodes_SET(BC.GetLowRegister(), 6); +} + +void Processor::OPCodeCB0xF2() +{ + // SET 6 D + OPCodes_SET(DE.GetHighRegister(), 6); +} + +void Processor::OPCodeCB0xF3() +{ + // SET 6 E + OPCodes_SET(DE.GetLowRegister(), 6); +} + +void Processor::OPCodeCB0xF4() +{ + // SET 6 H + OPCodes_SET(HL.GetHighRegister(), 6); +} + +void Processor::OPCodeCB0xF5() +{ + // SET 6 L + OPCodes_SET(HL.GetLowRegister(), 6); +} + +void Processor::OPCodeCB0xF6() +{ + // SET 6 (HL) + OPCodes_SET_HL(6); +} + +void Processor::OPCodeCB0xF7() +{ + // SET 6 A + OPCodes_SET(AF.GetHighRegister(), 6); +} + +void Processor::OPCodeCB0xF8() +{ + // SET 7 B + OPCodes_SET(BC.GetHighRegister(), 7); +} + +void Processor::OPCodeCB0xF9() +{ + // SET 7 C + OPCodes_SET(BC.GetLowRegister(), 7); +} + +void Processor::OPCodeCB0xFA() +{ + // SET 7 D + OPCodes_SET(DE.GetHighRegister(), 7); +} + +void Processor::OPCodeCB0xFB() +{ + // SET 7 E + OPCodes_SET(DE.GetLowRegister(), 7); +} + +void Processor::OPCodeCB0xFC() +{ + // SET 7 H + OPCodes_SET(HL.GetHighRegister(), 7); +} + +void Processor::OPCodeCB0xFD() +{ + // SET 7 L + OPCodes_SET(HL.GetLowRegister(), 7); +} + +void Processor::OPCodeCB0xFE() +{ + // SET 7 (HL) + OPCodes_SET_HL(7); +} + +void Processor::OPCodeCB0xFF() +{ + // SET 7 A + OPCodes_SET(AF.GetHighRegister(), 7); +} |
