7 Commits

Author SHA1 Message Date
Muzychenko Andrey
16b527e3cf Added AddressSanitizer to Windows build config, disabled by default.
VS older that 2019 do not support it.
Game passes ASan checks at the moment of writing.
2021-11-09 16:50:09 +03:00
Muzychenko Andrey
683204519c Added UTF-8 path support on Windows.
Ref issue #82.
2021-11-06 19:22:56 +03:00
Muzychenko Andrey
ecdf802d68 Added game data loading from user folder (SDL_GetPrefPath).
Ref issue #80.
2021-11-05 10:16:27 +03:00
Muzychenko Andrey
dc00dbde0d Fixed bug with mission accept scores.
Ref issue #81.
2021-11-04 18:46:04 +03:00
Muzychenko Andrey
862fe13dcd Added game controller exit shortcut: back/select when paused.
Ref issue #79.
2021-11-01 09:09:19 +03:00
Muzychenko Andrey
6c299ed103 Updated plans in readme 2021-10-30 12:51:24 +03:00
Muzychenko Andrey
fc1975a607 Fixed bug: dialogs not shown when main menu is hidden.
Ref issue #76.
2021-10-30 12:34:17 +03:00
13 changed files with 94 additions and 29 deletions

View File

@@ -9,7 +9,8 @@
"installRoot": "${projectDir}\\out\\install\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeCommandArgs": "", "cmakeCommandArgs": "",
"buildCommandArgs": "", "buildCommandArgs": "",
"ctestCommandArgs": "" "ctestCommandArgs": "",
"addressSanitizerEnabled": false
}, },
{ {
"name": "x86-Debug", "name": "x86-Debug",
@@ -20,7 +21,8 @@
"cmakeCommandArgs": "", "cmakeCommandArgs": "",
"buildCommandArgs": "", "buildCommandArgs": "",
"ctestCommandArgs": "", "ctestCommandArgs": "",
"inheritEnvironments": [ "msvc_x86" ] "inheritEnvironments": [ "msvc_x86" ],
"addressSanitizerEnabled": false
}, },
{ {
"name": "x86-Release", "name": "x86-Release",

View File

@@ -77,10 +77,10 @@ Tested with: macOS Big Sur (Intel) with Xcode 13 & macOS Montery Beta (Apple Sil
* ~~Decompile original game~~ * ~~Decompile original game~~
* ~~Resizable window, scaled graphics~~ * ~~Resizable window, scaled graphics~~
* ~~Loader for high-res sprites from CADET.DAT~~ * ~~Loader for high-res sprites from CADET.DAT~~
* ~~Cross-platform port using SDL2, SDL2_mixer, ImGui~~
* Misc features of Full Tilt: 3 music tracks, multiball, centered textboxes, etc. * Misc features of Full Tilt: 3 music tracks, multiball, centered textboxes, etc.
* Cross-platform port * Maybe: Text translations
* Using SDL2, SDL2_mixer, ImGui * Maybe: Android port
* Maybe: Android port
* Maybe x2: support for other two tables * Maybe x2: support for other two tables
* Table specific BL (control interactions and missions) is hardcoded, othere parts might be also patched * Table specific BL (control interactions and missions) is hardcoded, othere parts might be also patched

View File

@@ -297,7 +297,7 @@ void DatFile::Finalize()
// PINBALL2.MID is an alternative font provided in 3DPB data // PINBALL2.MID is an alternative font provided in 3DPB data
// Scaled down because it is too large for top text box // Scaled down because it is too large for top text box
/*auto file = pinball::make_path_name("PINBALL2.MID"); /*auto file = pinball::make_path_name("PINBALL2.MID");
auto fileHandle = fopen(file.c_str(), "rb"); auto fileHandle = fopenu(file.c_str(), "rb");
fseek(fileHandle, 0, SEEK_END); fseek(fileHandle, 0, SEEK_END);
auto fileSize = static_cast<uint32_t>(ftell(fileHandle)); auto fileSize = static_cast<uint32_t>(ftell(fileHandle));
auto rcData = reinterpret_cast<MsgFont*>(new uint8_t[fileSize]); auto rcData = reinterpret_cast<MsgFont*>(new uint8_t[fileSize]);

View File

@@ -57,7 +57,7 @@ void Sound::PlaySound(Mix_Chunk* wavePtr, int time)
Mix_Chunk* Sound::LoadWaveFile(const std::string& lpName) Mix_Chunk* Sound::LoadWaveFile(const std::string& lpName)
{ {
auto wavFile = fopen(lpName.c_str(), "r"); auto wavFile = fopenu(lpName.c_str(), "r");
if (!wavFile) if (!wavFile)
return nullptr; return nullptr;
fclose(wavFile); fclose(wavFile);

View File

@@ -34,6 +34,25 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
{ {
return MainActual(lpCmdLine); return MainActual(lpCmdLine);
} }
// fopen to _wfopen adapter, for UTF-8 paths
FILE* fopenu(const char* path, const char* opt)
{
wchar_t* wideArgs[2]{};
for (auto& arg : wideArgs)
{
auto src = wideArgs[0] ? opt : path;
auto length = MultiByteToWideChar(CP_UTF8, 0, src, -1, nullptr, 0);
arg = new wchar_t[length];
MultiByteToWideChar(CP_UTF8, 0, src, -1, arg, length);
}
auto fileHandle = _wfopen(wideArgs[0], wideArgs[1]);
for (auto arg : wideArgs)
delete[] arg;
return fileHandle;
}
#endif #endif
// Run program: Ctrl + F5 or Debug > Start Without Debugging menu // Run program: Ctrl + F5 or Debug > Start Without Debugging menu

View File

@@ -3775,9 +3775,9 @@ void control::SelectMissionController(int code, TPinballComponent* caller)
if (light_on(&control_lite319_tag)) if (light_on(&control_lite319_tag))
control_lite319_tag.Component->Message(20, 0.0); control_lite319_tag.Component->Message(20, 0.0);
control_lite198_tag.Component->MessageField = control_lite56_tag.Component->MessageField; control_lite198_tag.Component->MessageField = control_lite56_tag.Component->MessageField;
auto scoreId = control_lite56_tag.Component->MessageField - 2;
MissionControl(66, nullptr); MissionControl(66, nullptr);
int addedScore = SpecialAddScore( int addedScore = SpecialAddScore(mission_select_scores[scoreId]);
mission_select_scores[control_lite56_tag.Component->MessageField - 2]);
snprintf(Buffer, sizeof Buffer, pinball::get_rc_string(77, 0), addedScore); snprintf(Buffer, sizeof Buffer, pinball::get_rc_string(77, 0), addedScore);
control_mission_text_box_tag.Component->Display(Buffer, 4.0); control_mission_text_box_tag.Component->Display(Buffer, 4.0);
} }

View File

@@ -158,7 +158,7 @@ int loader::get_sound_id(int groupIndex)
} }
auto filePath = pinball::make_path_name(fileName); auto filePath = pinball::make_path_name(fileName);
auto file = fopen(filePath.c_str(), "rb"); auto file = fopenu(filePath.c_str(), "rb");
if (file) if (file)
{ {
fread(&wavHeader, 1, sizeof wavHeader, file); fread(&wavHeader, 1, sizeof wavHeader, file);

View File

@@ -101,11 +101,12 @@ Mix_Music* midi::load_track(std::string fileName)
if (i == 0) if (i == 0)
{ {
auto filePath = basePath + ".MID"; auto filePath = basePath + ".MID";
auto fileHandle = fopen(filePath.c_str(), "rb"); auto fileHandle = fopenu(filePath.c_str(), "rb");
if (fileHandle) if (fileHandle)
{ {
fclose(fileHandle); fclose(fileHandle);
audio = Mix_LoadMUS(filePath.c_str()); auto rw = SDL_RWFromFile(filePath.c_str(), "rb");
audio = Mix_LoadMUS_RW(rw, 1);
} }
} }
else else
@@ -115,7 +116,7 @@ Mix_Music* midi::load_track(std::string fileName)
{ {
// Dump converted MIDI file // Dump converted MIDI file
/*auto filePath = basePath + ".midi"; /*auto filePath = basePath + ".midi";
FILE* fileHandle = fopen(filePath.c_str(), "wb"); FILE* fileHandle = fopenu(filePath.c_str(), "wb");
fwrite(midi->data(), 1, midi->size(), fileHandle); fwrite(midi->data(), 1, midi->size(), fileHandle);
fclose(fileHandle);*/ fclose(fileHandle);*/
@@ -164,7 +165,7 @@ bool midi::play_track(Mix_Music* midi)
/// <returns>Vector that contains MIDI file</returns> /// <returns>Vector that contains MIDI file</returns>
std::vector<uint8_t>* midi::MdsToMidi(std::string file) std::vector<uint8_t>* midi::MdsToMidi(std::string file)
{ {
auto fileHandle = fopen(file.c_str(), "rb"); auto fileHandle = fopenu(file.c_str(), "rb");
if (!fileHandle) if (!fileHandle)
return nullptr; return nullptr;

View File

@@ -16,7 +16,7 @@ DatFile* partman::load_records(LPCSTR lpFileName, bool fullTiltMode)
dat8BitBmpHeader bmpHeader{}; dat8BitBmpHeader bmpHeader{};
dat16BitBmpHeader zMapHeader{}; dat16BitBmpHeader zMapHeader{};
auto fileHandle = fopen(lpFileName, "rb"); auto fileHandle = fopenu(lpFileName, "rb");
if (fileHandle == nullptr) if (fileHandle == nullptr)
return nullptr; return nullptr;

View File

@@ -39,6 +39,8 @@ int pb::init()
{ {
float projMat[12], zMin = 0, zScaler = 0; float projMat[12], zMin = 0, zScaler = 0;
if (winmain::DatFileName.empty())
return 1;
auto dataFilePath = pinball::make_path_name(winmain::DatFileName); auto dataFilePath = pinball::make_path_name(winmain::DatFileName);
record_table = partman::load_records(dataFilePath.c_str(), FullTiltMode); record_table = partman::load_records(dataFilePath.c_str(), FullTiltMode);

View File

@@ -83,4 +83,14 @@ int Sign(T val)
return (T(0) < val) - (val < T(0)); return (T(0) < val) - (val < T(0));
} }
// UTF-8 path adapter for fopen on Windows, implemented in SpaceCadetPinball.cpp
#ifdef _WIN32
extern FILE* fopenu(const char* path, const char* opt);
#else
inline FILE* fopenu(const char* path, const char* opt)
{
return fopen(path, opt);
}
#endif
#endif //PCH_H #endif //PCH_H

View File

@@ -36,7 +36,7 @@ bool winmain::ShowSpriteViewer = false;
bool winmain::LaunchBallEnabled = true; bool winmain::LaunchBallEnabled = true;
bool winmain::HighScoresEnabled = true; bool winmain::HighScoresEnabled = true;
bool winmain::DemoActive = false; bool winmain::DemoActive = false;
char* winmain::BasePath; std::string winmain::BasePath;
int winmain::MainMenuHeight = 0; int winmain::MainMenuHeight = 0;
std::string winmain::FpsDetails; std::string winmain::FpsDetails;
double winmain::UpdateToFrameRatio; double winmain::UpdateToFrameRatio;
@@ -58,19 +58,44 @@ int winmain::WinMain(LPCSTR lpCmdLine)
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Could not initialize SDL2", SDL_GetError(), nullptr); SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Could not initialize SDL2", SDL_GetError(), nullptr);
return 1; return 1;
} }
BasePath = SDL_GetBasePath();
pinball::quickFlag = strstr(lpCmdLine, "-quick") != nullptr; pinball::quickFlag = strstr(lpCmdLine, "-quick") != nullptr;
DatFileName = options::get_string("Pinball Data", pinball::get_rc_string(168, 0));
/*Check for full tilt .dat file and switch to it automatically*/ // Search for game data in: game folder, user folder
auto cadetFilePath = pinball::make_path_name("CADET.DAT"); // Game data test order: CADET.DAT, PINBALL.DAT
auto cadetDat = fopen(cadetFilePath.c_str(), "r"); char* dataSearchPaths[2]
if (cadetDat)
{ {
fclose(cadetDat); SDL_GetBasePath(),
DatFileName = "CADET.DAT"; SDL_GetPrefPath(nullptr, "SpaceCadetPinball")
pb::FullTiltMode = true; };
std::string datFileNames[2]
{
"CADET.DAT",
options::get_string("Pinball Data", pinball::get_rc_string(168, 0))
};
for (auto path : dataSearchPaths)
{
if (DatFileName.empty() && path)
{
BasePath = path;
for (int i = 0; i < 2; i++)
{
auto datFileName = datFileNames[i];
auto datFilePath = pinball::make_path_name(datFileName);
auto datFile = fopenu(datFilePath.c_str(), "r");
if (datFile)
{
fclose(datFile);
DatFileName = datFileName;
if (i == 0)
pb::FullTiltMode = true;
printf("Loading game from: %s\n", datFilePath.c_str());
break;
}
}
}
SDL_free(path);
} }
// SDL window // SDL window
@@ -339,7 +364,6 @@ void winmain::RenderUi()
} }
ImGui::End(); ImGui::End();
ImGui::PopStyleVar(); ImGui::PopStyleVar();
return;
} }
// No demo window in release to save space // No demo window in release to save space
@@ -348,7 +372,7 @@ void winmain::RenderUi()
ImGui::ShowDemoWindow(&ShowImGuiDemo); ImGui::ShowDemoWindow(&ShowImGuiDemo);
#endif #endif
if (ImGui::BeginMainMenuBar()) if (Options.ShowMenu && ImGui::BeginMainMenuBar())
{ {
int currentMenuHeight = static_cast<int>(ImGui::GetWindowSize().y); int currentMenuHeight = static_cast<int>(ImGui::GetWindowSize().y);
if (MainMenuHeight != currentMenuHeight) if (MainMenuHeight != currentMenuHeight)
@@ -777,7 +801,14 @@ int winmain::event_handler(const SDL_Event* event)
case SDL_CONTROLLER_BUTTON_START: case SDL_CONTROLLER_BUTTON_START:
pause(); pause();
break; break;
default: ; case SDL_CONTROLLER_BUTTON_BACK:
if (single_step)
{
SDL_Event event{ SDL_QUIT };
SDL_PushEvent(&event);
}
break;
default:;
} }
break; break;
case SDL_CONTROLLERBUTTONUP: case SDL_CONTROLLERBUTTONUP:

View File

@@ -48,7 +48,7 @@ public:
static bool LaunchBallEnabled; static bool LaunchBallEnabled;
static bool HighScoresEnabled; static bool HighScoresEnabled;
static bool DemoActive; static bool DemoActive;
static char* BasePath; static std::string BasePath;
static int MainMenuHeight; static int MainMenuHeight;
static int WinMain(LPCSTR lpCmdLine); static int WinMain(LPCSTR lpCmdLine);