From e9413f6d941adce6c92ff910b4b70119e27f53e8 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Mon, 15 Feb 2021 02:36:15 +1000 Subject: [PATCH] WIP --- src/common/string_util.cpp | 60 +++++++- src/common/string_util.h | 7 + src/core/cdrom.cpp | 2 + src/core/core.vcxproj | 2 + src/core/core.vcxproj.filters | 2 + src/core/digital_controller.cpp | 10 ++ src/core/movie.cpp | 244 ++++++++++++++++++++++++++++++++ src/core/movie.h | 56 ++++++++ src/core/system.cpp | 19 +++ src/core/system.h | 1 + 10 files changed, 402 insertions(+), 1 deletion(-) create mode 100644 src/core/movie.cpp create mode 100644 src/core/movie.h diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index c5b61d2cd..ff90accff 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -186,10 +186,68 @@ std::string EncodeHex(const u8* data, int length) std::stringstream ss; for (int i = 0; i < length; i++) ss << std::hex << std::setfill('0') << std::setw(2) << static_cast(data[i]); - + return ss.str(); } +void TrimWhitespace(std::string& str) +{ + size_t pos = 0; + while (pos < str.size() && std::isspace(str[pos])) + pos++; + + if (pos > 0) + str.erase(0, pos); + + while (!str.empty() && std::isspace(str.back())) + str.pop_back(); +} + +std::string_view TrimWhitespace(const std::string_view& str) +{ + if (str.empty()) + return {}; + + size_t start_pos = 0; + while (start_pos < str.size() && std::isspace(str[start_pos])) + start_pos++; + + if (start_pos == str.size()) + return {}; + + size_t end_pos = str.size() - 1; + while (end_pos > start_pos && std::isspace(str[end_pos])) + end_pos--; + + return str.substr(start_pos, end_pos - start_pos + 1); +} + +void SplitString(const std::string_view& str, char delim, std::vector* tokens) +{ + tokens->clear(); + + std::string::size_type start_pos = 0; + std::string::size_type current_pos = 0; + while (current_pos < str.size()) + { + if (str[current_pos] != delim) + { + current_pos++; + continue; + } + + if (current_pos != start_pos) + tokens->push_back(str.substr(start_pos, current_pos - start_pos)); + else + tokens->emplace_back(); + + start_pos = ++current_pos; + } + + if (start_pos != current_pos) + tokens->push_back(str.substr(start_pos, current_pos - start_pos)); +} + #ifdef WIN32 std::wstring UTF8StringToWideString(const std::string_view& str) diff --git a/src/common/string_util.h b/src/common/string_util.h index 54cad5872..3c3f5a543 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -118,6 +118,13 @@ inline std::optional FromChars(const std::string_view& str, int base) std::optional> DecodeHex(const std::string_view& str); std::string EncodeHex(const u8* data, int length); +/// Trims whitespace characters +void TrimWhitespace(std::string& str); +std::string_view TrimWhitespace(const std::string_view& str); + +/// Splits a string into tokens. +void SplitString(const std::string_view& str, char delim, std::vector* tokens); + /// starts_with from C++20 ALWAYS_INLINE static bool StartsWith(const std::string_view& str, const char* prefix) { diff --git a/src/core/cdrom.cpp b/src/core/cdrom.cpp index 2a79dcfb9..c74ed9141 100644 --- a/src/core/cdrom.cpp +++ b/src/core/cdrom.cpp @@ -697,6 +697,8 @@ TickCount CDROM::GetTicksForSeek(CDImage::LBA new_lba) ticks += static_cast(static_cast(tps) * 0.1); } + ticks = 20000; + Log_DevPrintf("Seek time for %u LBAs: %d", lba_diff, ticks); return ticks; } diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj index 6142f7c8c..736ef9125 100644 --- a/src/core/core.vcxproj +++ b/src/core/core.vcxproj @@ -136,6 +136,7 @@ + @@ -213,6 +214,7 @@ + diff --git a/src/core/core.vcxproj.filters b/src/core/core.vcxproj.filters index 91d6d1d25..70c83f93b 100644 --- a/src/core/core.vcxproj.filters +++ b/src/core/core.vcxproj.filters @@ -57,6 +57,7 @@ + @@ -116,5 +117,6 @@ + \ No newline at end of file diff --git a/src/core/digital_controller.cpp b/src/core/digital_controller.cpp index 389c5f91b..a45f25906 100644 --- a/src/core/digital_controller.cpp +++ b/src/core/digital_controller.cpp @@ -112,6 +112,16 @@ bool DigitalController::Transfer(const u8 data_in, u8* data_out) case TransferState::ButtonsLSB: { + System::InputPolled(); + { + for (u32 i = 0; i < 16; i++) + { + if (m_button_state & (1 << i)) + continue; + + printf("Button %u was down on poll\n", i); + } + } *data_out = Truncate8(m_button_state) & GetButtonsLSBMask(); m_transfer_state = TransferState::ButtonsMSB; return true; diff --git a/src/core/movie.cpp b/src/core/movie.cpp new file mode 100644 index 000000000..49ba612c3 --- /dev/null +++ b/src/core/movie.cpp @@ -0,0 +1,244 @@ +#include "movie.h" +#include "common/file_system.h" +#include "common/log.h" +#include "common/string_util.h" +#include "controller.h" +#include "pad.h" +#include "settings.h" +#include "system.h" +#include +Log_SetChannel(Movie); + +Movie::Movie() = default; + +Movie::~Movie() = default; + +void Movie::Rewind() +{ + m_current_frame = START_OF_MOVIE; +} + +void Movie::NextFrame() +{ + if (m_current_frame == m_num_frames) + return; + + m_current_frame++; + ApplyInputsForFrame(m_current_frame); +} + +void Movie::ApplyInputsForFrame(u32 frame_number) +{ + if (frame_number >= m_num_frames) + return; + + const std::size_t start_index = frame_number * m_input_mappings.size(); + Assert((start_index + m_input_mappings.size()) <= m_input_values.size()); + + std::size_t index = start_index; + for (const InputMapping& im : m_input_mappings) + { + const s8 value = m_input_values[index++]; + switch (im.type) + { + case InputMapping::Type::Button: + { + Controller* controller = g_pad.GetController(im.controller_index); + if (controller) + { + if (value != 0) + Log_InfoPrintf("Frame %u button %d down", frame_number + 1, im.axis_or_button_code); + + controller->SetButtonState(im.axis_or_button_code, (value != 0)); + } + } + break; + } + } +} + +std::unique_ptr Movie::Load(const char* filename) +{ + std::unique_ptr movie = std::unique_ptr(new Movie()); + if (!movie->LoadBk2(filename)) + return {}; + + return movie; +} + +bool Movie::LoadBk2(const char* filename) +{ + std::optional data = FileSystem::ReadFileToString(filename); + if (!data.has_value() || data->empty()) + return false; + + return LoadBk2Txt(data.value()); +} + +static void SplitBk2InputString(const std::string_view& str, char delim, std::vector* tokens) +{ + tokens->clear(); + + std::string::size_type start_pos = 0; + std::string::size_type current_pos = 0; + while (current_pos < str.size()) + { + if (str[current_pos] == ' ' || str[current_pos] == ',' || str[current_pos] == '|') + { + if (current_pos != start_pos) + tokens->push_back(str.substr(start_pos, current_pos - start_pos)); + + current_pos++; + start_pos = current_pos; + continue; + } + + if (str[current_pos] >= '0' && str[current_pos] <= '9') + { + current_pos++; + continue; + } + + current_pos++; + tokens->push_back(str.substr(start_pos, current_pos - start_pos)); + start_pos = current_pos; + } + + if (start_pos != current_pos) + tokens->push_back(str.substr(start_pos, current_pos - start_pos)); +} + +bool Movie::LoadBk2Txt(const std::string& data) +{ + std::istringstream iss(data); + + std::string line; + std::vector tokens; + bool in_input_section = false; + while (std::getline(iss, line)) + { + StringUtil::TrimWhitespace(line); + if (line.empty()) + continue; + + if (line == "[Input]") + { + in_input_section = true; + continue; + } + else if (line == "[/Input]") + { + in_input_section = false; + break; + } + else if (!in_input_section) + { + continue; + } + else if (StringUtil::StartsWith(line, "LogKey:") && line.size() >= 8) + { + // parse key - extra character here for the terminating | + const std::string_view sv(line.c_str() + 7, line.size() - 8); + StringUtil::SplitString(sv, '|', &tokens); + for (const std::string_view& token : tokens) + { + InputMapping mapping = {}; + mapping.type = InputMapping::Type::None; + + if (token.size() > 2 && token[0] == 'P' && token[1] >= '1' && token[1] <= '2' && token[2] == ' ') + { + mapping.controller_index = token[1] - '1'; + + const ControllerType ctype = g_settings.controller_types[mapping.controller_index]; + if (ctype != ControllerType::None) + { + const std::string_view& bsv(token.substr(3)); + const Controller::ButtonList blist(Controller::GetButtonNames(ctype)); + for (const auto& it : blist) + { + if (bsv == it.first) + { + mapping.axis_or_button_code = it.second; + mapping.type = InputMapping::Type::Button; + break; + } + } + + if (mapping.type == InputMapping::Type::None) + Log_WarningPrintf("Input '%s' was not mapped", std::string(token).c_str()); + } + } + else + { + Log_WarningPrintf("Unhandled key token '%s'", std::string(token).c_str()); + } + + m_input_mappings.push_back(mapping); + } + } + else if (line.size() >= 2 && line.front() == '|' && line.back() == '|') + { + if (m_input_mappings.empty()) + { + Log_ErrorPrintf("Mappings not set"); + return false; + } + + // parse line + const std::string_view sv(line.c_str() + 1, line.size() - 2); + SplitBk2InputString(sv, ',', &tokens); + if (tokens.size() != m_input_mappings.size()) + { + Log_ErrorPrintf("Incorrect number of mappings: got %zu expected %zu", tokens.size(), m_input_mappings.size()); + return false; + } + + for (const std::string_view& token : tokens) + { + const std::string_view& trimmed_token(StringUtil::TrimWhitespace(token)); + if (trimmed_token.empty()) + { + // ??? + m_input_values.push_back(0); + } + else if (trimmed_token[0] == '.') + { + // button off + m_input_values.push_back(0); + } + else if (trimmed_token[0] < '0' || trimmed_token[0] > '1') + { + // button on + m_input_values.push_back(1); + } + else + { + const s32 value = StringUtil::FromChars(trimmed_token).value_or(0); + m_input_values.push_back(static_cast(std::clamp(value, -127, 128))); + } + } + } + else + { + Log_ErrorPrintf("Malformed line: '%s'", line.c_str()); + return false; + } + } + + if (m_input_mappings.empty()) + { + Log_ErrorPrintf("Missing input mapping"); + return false; + } + + m_num_frames = static_cast(m_input_values.size() / m_input_mappings.size()); + if (m_num_frames == 0) + { + Log_ErrorPrintf("Missing input data"); + return false; + } + + Log_InfoPrintf("Mapped %zu inputs", m_input_mappings.size()); + Log_InfoPrintf("Loaded inputs for %u frames", m_num_frames); + return true; +} diff --git a/src/core/movie.h b/src/core/movie.h new file mode 100644 index 000000000..73db60247 --- /dev/null +++ b/src/core/movie.h @@ -0,0 +1,56 @@ +#pragma once +#include "types.h" +#include +#include +#include + +class Movie +{ +public: + ~Movie(); + + static std::unique_ptr Load(const char* filename); + + ALWAYS_INLINE u32 GetNumFrames() const { return m_num_frames; } + ALWAYS_INLINE bool AtEnd() const { return (m_current_frame == m_num_frames); } + + void Rewind(); + void NextFrame(); + +private: + enum : u32 + { + START_OF_MOVIE = 0xFFFFFFFFu + }; + + using InputValue = s8; + + struct InputMapping + { + enum class Type + { + None, + DiscSelect, + DiscOpen, + DiscClose, + DiscReset, + Button, + Axis, + }; + + Type type; + u32 controller_index; + s32 axis_or_button_code; + }; + + Movie(); + + bool LoadBk2(const char* filename); + bool LoadBk2Txt(const std::string& data); + void ApplyInputsForFrame(u32 frame_number); + + std::vector m_input_mappings; + std::vector m_input_values; + u32 m_num_frames = 0; + u32 m_current_frame = START_OF_MOVIE; +}; \ No newline at end of file diff --git a/src/core/system.cpp b/src/core/system.cpp index d8e62f811..0e0b5bedb 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -30,6 +30,7 @@ #include "spu.h" #include "texture_replacements.h" #include "timers.h" +#include "movie.h" #include #include #include @@ -119,6 +120,7 @@ static std::vector s_media_playlist; static std::string s_media_playlist_filename; static std::unique_ptr s_cheat_list; +static std::unique_ptr s_movie; static bool s_memory_saves_enabled = false; @@ -220,6 +222,15 @@ void IncrementInternalFrameNumber() s_internal_frame_number++; } +void InputPolled() +{ +#if 1 + if (s_movie) + s_movie->NextFrame(); +#endif + Log_InfoPrintf("Input polled at frame %u", s_frame_number); +} + const std::string& GetRunningPath() { return s_running_game_path; @@ -773,6 +784,9 @@ bool Boot(const SystemBootParameters& params) // Good to go. s_state = (g_settings.start_paused || params.override_start_paused.value_or(false)) ? State::Paused : State::Running; + + s_movie = Movie::Load("D:\\test.txt"); + return true; } @@ -1264,6 +1278,11 @@ void SingleStepCPU() void DoRunFrame() { +#if 0 + if (s_movie) + s_movie->NextFrame(); +#endif + g_gpu->RestoreGraphicsAPIState(); if (CPU::g_state.use_debug_dispatcher) diff --git a/src/core/system.h b/src/core/system.h index 43251da23..dfce9b3f0 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -129,6 +129,7 @@ u32 GetFrameNumber(); u32 GetInternalFrameNumber(); void FrameDone(); void IncrementInternalFrameNumber(); +void InputPolled(); const std::string& GetRunningPath(); const std::string& GetRunningCode();