move worker code into new source file
This commit is contained in:
parent
a0704ef240
commit
8e657dbdc6
|
@ -26,6 +26,7 @@ project_set_alternate_linker()
|
|||
add_executable(swsf_bruteforce
|
||||
src/asio_impl.cpp
|
||||
src/main.cpp
|
||||
src/worker.cpp
|
||||
)
|
||||
|
||||
target_compile_definitions(swsf_bruteforce PRIVATE
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
# swsf_bruteforce
|
||||
|
||||
A performant, multithreaded, brute-forcer for scrambled cheat codes in Star Wars: Starfighter (JP) and Star Wars: Jedi Starfighter, written in modern C++20.
|
||||
|
||||
# Building
|
||||
|
||||
You need:
|
||||
|
||||
- A C++20 compiler (MSVC 2022, Clang, GCC)
|
||||
- Boost (I tested with 1.81, should work fine with 1.82 onwards)
|
||||
- CMake
|
||||
|
||||
## Windows
|
||||
|
||||
```
|
||||
cmake -Bbuild -GNinja -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build
|
||||
```
|
||||
|
||||
## Linux
|
||||
|
||||
```
|
||||
$ cmake -Bbuild -GNinja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_FLAGS="-march=native -mtune=native"
|
||||
$ cmake --build build
|
||||
`
|
||||
|
||||
# Usage
|
||||
|
||||
`./swsf_bruteforce -s [START_LENGTH] -l [LENGTH] -e 0xABCDABCD`
|
||||
|
||||
where:
|
||||
|
||||
`-s [START_LENGTH]` is the guessed start bound (greater than 2, less than end bound)
|
||||
`-l [LENGTH]` is the guessed end bound (less than or equal to 8)
|
||||
`0xABCDABCD` is the hash to use
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
#include <cstdint>
|
||||
#include <format>
|
||||
#include <string_view>
|
||||
|
||||
// see https://bisqwit.iki.fi/jutut/kuvat/programming_examples/cpp_thread_tutorial/ver05.cc
|
||||
// this version of the code doesn't even need to worry about synchronization
|
||||
namespace swbf::ansi {
|
||||
inline std::uint32_t ln = 1;
|
||||
|
||||
inline auto Color(int n) {
|
||||
return std::format("\33[38;5;{}m", n);
|
||||
}
|
||||
|
||||
inline std::string_view Reset() {
|
||||
return "\33[m";
|
||||
}
|
||||
|
||||
inline auto Line(int l) {
|
||||
int m = l - ln;
|
||||
ln = l;
|
||||
return "\r" + (m < 0 ? "\33[" + std::to_string(-m) + 'A' : std::string(m, '\n'));
|
||||
}
|
||||
|
||||
inline std::string_view ResetLine() {
|
||||
return "\33[2K";
|
||||
}
|
||||
|
||||
} // namespace swbf::ansi
|
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include <iterator>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <format>
|
||||
|
||||
namespace swbf {
|
||||
struct FputcIterator {
|
||||
using iterator_category = std::output_iterator_tag;
|
||||
using value_type = void;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = void;
|
||||
using reference = void;
|
||||
|
||||
FputcIterator(std::FILE* file) : file(file) {}
|
||||
FputcIterator& operator*() { return *this; }
|
||||
FputcIterator& operator++() { return *this; }
|
||||
FputcIterator& operator++(int) { return *this; }
|
||||
|
||||
FputcIterator& operator=(const char& val) {
|
||||
std::fputc(val, file);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
std::FILE* file;
|
||||
};
|
||||
|
||||
/// Poor Man's C++23
|
||||
template<class ...Args>
|
||||
inline void fprint(std::FILE* file, std::string_view format, Args&&... args) {
|
||||
std::vformat_to(FputcIterator(file), format, std::make_format_args(args...));
|
||||
}
|
||||
} // namespace swbf
|
203
src/main.cpp
203
src/main.cpp
|
@ -4,212 +4,37 @@
|
|||
#include <format>
|
||||
#include <thread>
|
||||
|
||||
#include "ansi.hpp"
|
||||
#include "fprint.hpp"
|
||||
#include "worker.hpp"
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
constexpr static auto MAX_CODE_LENGTH = 8;
|
||||
constexpr static auto THREAD_COUNT = 26; // this is a-z
|
||||
void BruteMain() {
|
||||
asio::thread_pool pool(swbf::THREAD_COUNT);
|
||||
|
||||
/// This is a constexpr-safe zero-allocation version of the algorithm
|
||||
/// the "Scramble" ConsoleScript hash algorithm.
|
||||
constexpr std::uint32_t SwsfScramble(std::string_view code) {
|
||||
std::uint32_t tally {};
|
||||
|
||||
auto cycle = [&tally](char c) { tally = c + (tally * 5); };
|
||||
|
||||
cycle('c');
|
||||
cycle('o');
|
||||
cycle('d');
|
||||
cycle('e');
|
||||
cycle('_');
|
||||
|
||||
for(auto* p = code.data(); *p; ++p)
|
||||
cycle(tolower(*p));
|
||||
return tally;
|
||||
}
|
||||
|
||||
struct FputcIterator {
|
||||
using iterator_category = std::output_iterator_tag;
|
||||
using value_type = void;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = void;
|
||||
using reference = void;
|
||||
|
||||
FputcIterator(std::FILE* file) : file(file) {}
|
||||
FputcIterator& operator*() { return *this; }
|
||||
FputcIterator& operator++() { return *this; }
|
||||
FputcIterator& operator++(int) { return *this; }
|
||||
|
||||
FputcIterator& operator=(const char& val) {
|
||||
fputc(val, file);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
std::FILE* file;
|
||||
};
|
||||
|
||||
bool NextCode(std::string& buffer, size_t start) {
|
||||
size_t len = buffer.length();
|
||||
for(size_t i = len - 1; i >= start; --i) {
|
||||
char c = buffer[i];
|
||||
if(c < 'z') {
|
||||
++buffer[i];
|
||||
return true;
|
||||
}
|
||||
buffer[i] = 'a';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// see https://bisqwit.iki.fi/jutut/kuvat/programming_examples/cpp_thread_tutorial/ver05.cc
|
||||
// this version of the code doesn't even need to worry about synchronization
|
||||
namespace {
|
||||
unsigned ln = 1;
|
||||
|
||||
auto Color(int n) {
|
||||
return std::format("\33[38;5;{}m", n);
|
||||
}
|
||||
|
||||
std::string_view Reset() {
|
||||
return "\33[m";
|
||||
}
|
||||
|
||||
auto Line(int l) {
|
||||
int m = l - ln;
|
||||
ln = l;
|
||||
return "\r" + (m < 0 ? "\33[" + std::to_string(-m) + 'A' : std::string(m, '\n'));
|
||||
}
|
||||
|
||||
std::string_view ResetLine() {
|
||||
return "\33[2K";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/// This class implements the display of progress
|
||||
struct ThreadInfoData {
|
||||
ThreadInfoData() { codeString.resize(MAX_CODE_LENGTH); }
|
||||
|
||||
std::uint32_t threadIndex;
|
||||
std::string codeString;
|
||||
std::uint32_t codeHash;
|
||||
bool done = false;
|
||||
|
||||
void DisplayProgress() {
|
||||
if(!done)
|
||||
std::format_to(FputcIterator(stdout), "{}Thread {:2}: Trying code {}{} {}({:08x}){}", Line(threadIndex), threadIndex, Color(172),
|
||||
codeString, Color(166), codeHash, Reset());
|
||||
else {
|
||||
std::format_to(FputcIterator(stdout), "{}{}Thread {:2}: {}Finished!{}", Line(threadIndex), ResetLine(), threadIndex, Color(172), Reset());
|
||||
}
|
||||
std::fflush(stdout);
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<ThreadInfoData> infoData(THREAD_COUNT);
|
||||
|
||||
/// This class actually implements the brunt of the logic
|
||||
struct BruteforceWorker {
|
||||
|
||||
/// Options for the worker.
|
||||
struct Options {
|
||||
/// The target hash to find matches for.
|
||||
std::uint32_t targetHash;
|
||||
|
||||
/// If `startLength` should be treated as the code length.
|
||||
bool exact;
|
||||
|
||||
std::uint32_t startLength;
|
||||
std::uint32_t endLength;
|
||||
};
|
||||
|
||||
constexpr BruteforceWorker(unsigned tid, const Options& options) : options(options), threadIndex(tid) {}
|
||||
|
||||
/// Public-facing driver function - the thread pool runs this.
|
||||
void Bruteforce(char prefix) {
|
||||
if(options.exact) {
|
||||
BruteforceForLength(prefix, options.startLength);
|
||||
} else {
|
||||
for(std::uint32_t i = options.startLength; i <= options.endLength; ++i)
|
||||
BruteforceForLength(prefix, i);
|
||||
}
|
||||
|
||||
DisplayData().done = true;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void BruteforceForLength(char prefix, std::uint32_t length) {
|
||||
DisplayData().codeString.resize(length);
|
||||
test_buffer.resize(length, 'a');
|
||||
test_buffer[0] = prefix;
|
||||
|
||||
// test all possible combinations
|
||||
while(true) {
|
||||
hash = SwsfScramble(test_buffer);
|
||||
|
||||
// There was a match!
|
||||
if(hash == options.targetHash)
|
||||
std::format_to(FputcIterator(stderr), "match: {} ({:08x})\n", test_buffer, hash);
|
||||
|
||||
CopyDisplayData();
|
||||
|
||||
if(!NextCode(test_buffer, 1))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CopyDisplayData() {
|
||||
memcpy(DisplayData().codeString.data(), test_buffer.data(), test_buffer.length());
|
||||
DisplayData().codeHash = hash;
|
||||
}
|
||||
|
||||
constexpr ThreadInfoData& DisplayData() { return infoData[threadIndex]; }
|
||||
|
||||
Options options;
|
||||
|
||||
unsigned threadIndex;
|
||||
std::string test_buffer;
|
||||
std::uint32_t hash;
|
||||
};
|
||||
|
||||
int main() {
|
||||
asio::io_context ioc;
|
||||
|
||||
asio::thread_pool pool(THREAD_COUNT);
|
||||
swbf::BruteforceWorker::Options options { .targetHash = 0x2c73af55, .exact = true, .startLength = 8 };
|
||||
|
||||
auto threadIndex = 0u;
|
||||
|
||||
BruteforceWorker::Options options {
|
||||
.targetHash = 0x2c73af55,
|
||||
.exact = true,
|
||||
.startLength = 8
|
||||
};
|
||||
|
||||
// post worker threads to run onto the thread pool & wait for them to complete
|
||||
for(int i = 0; i < THREAD_COUNT; ++i)
|
||||
for(int i = 0; i < swbf::THREAD_COUNT; ++i)
|
||||
asio::post(pool, [&, tid = threadIndex++]() {
|
||||
BruteforceWorker state(tid, options);
|
||||
infoData[tid].threadIndex = tid;
|
||||
swbf::BruteforceWorker state(tid, options);
|
||||
state.Bruteforce('a' + tid);
|
||||
});
|
||||
|
||||
bool done = false;
|
||||
|
||||
while(!done) {
|
||||
int doneIndex = 0;
|
||||
for(auto& d : infoData) {
|
||||
if(d.done)
|
||||
doneIndex++;
|
||||
d.DisplayProgress();
|
||||
}
|
||||
|
||||
if(doneIndex == THREAD_COUNT)
|
||||
done = true;
|
||||
|
||||
done = swbf::DisplayThreadInfo();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(66));
|
||||
}
|
||||
|
||||
pool.join(); // just in case!
|
||||
}
|
||||
|
||||
int main() {
|
||||
BruteMain();
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
#include <cctype>
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
namespace swbf {
|
||||
|
||||
/// This is a constexpr-safe zero-allocation version of the algorithm
|
||||
/// the "Scramble" ConsoleScript hash uses.
|
||||
constexpr std::uint32_t SwsfScramble(std::string_view code) {
|
||||
std::uint32_t tally {};
|
||||
auto cycle = [&tally](char c) { tally = c + (tally * 5); };
|
||||
|
||||
// "code_" is prepended and cycled to the input string
|
||||
cycle('c');
|
||||
cycle('o');
|
||||
cycle('d');
|
||||
cycle('e');
|
||||
cycle('_');
|
||||
|
||||
// the input string is also made lowercase
|
||||
for(auto c : code)
|
||||
cycle(std::tolower(c));
|
||||
return tally;
|
||||
}
|
||||
|
||||
} // namespace swbf
|
|
@ -0,0 +1,109 @@
|
|||
#include "worker.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "ansi.hpp"
|
||||
#include "fprint.hpp"
|
||||
#include "scramble.hpp"
|
||||
|
||||
namespace swbf {
|
||||
/// This class implements the display of progress
|
||||
struct ThreadInfoData {
|
||||
ThreadInfoData() { codeString.resize(MAX_CODE_LENGTH); }
|
||||
|
||||
std::uint32_t threadIndex;
|
||||
std::string codeString;
|
||||
std::uint32_t codeHash;
|
||||
bool done = false;
|
||||
|
||||
void DisplayProgress() {
|
||||
if(!done)
|
||||
swbf::fprint(stdout, "{}Thread {:2}: Trying code {}{} {}({:08x}){}", swbf::ansi::Line(threadIndex), threadIndex,
|
||||
swbf::ansi::Color(172), codeString, swbf::ansi::Color(166), codeHash, swbf::ansi::Reset());
|
||||
else {
|
||||
swbf::fprint(stdout, "{}{}Thread {:2}: {}Finished!{}", swbf::ansi::Line(threadIndex), swbf::ansi::ResetLine(), threadIndex,
|
||||
swbf::ansi::Color(172), swbf::ansi::Reset());
|
||||
}
|
||||
std::fflush(stdout);
|
||||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
std::vector<ThreadInfoData> infoData(THREAD_COUNT);
|
||||
|
||||
bool NextCode(std::string& buffer, size_t start) {
|
||||
size_t len = buffer.length();
|
||||
for(size_t i = len - 1; i >= start; --i) {
|
||||
char c = buffer[i];
|
||||
if(c < 'z') {
|
||||
++buffer[i];
|
||||
return true;
|
||||
}
|
||||
buffer[i] = 'a';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool DisplayThreadInfo() {
|
||||
int doneIndex = 0;
|
||||
for(auto& d : infoData) {
|
||||
if(d.done)
|
||||
doneIndex++;
|
||||
d.DisplayProgress();
|
||||
}
|
||||
|
||||
if(doneIndex == THREAD_COUNT)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
BruteforceWorker::BruteforceWorker(unsigned tid, const Options& options) : options(options), threadIndex(tid) {
|
||||
DisplayData().threadIndex = tid;
|
||||
}
|
||||
|
||||
/// Public-facing driver function - the thread pool runs this.
|
||||
void BruteforceWorker::Bruteforce(char prefix) {
|
||||
if(options.exact) {
|
||||
BruteforceForLength(prefix, options.startLength);
|
||||
} else {
|
||||
for(std::uint32_t i = options.startLength; i <= options.endLength; ++i)
|
||||
BruteforceForLength(prefix, i);
|
||||
}
|
||||
|
||||
DisplayData().done = true;
|
||||
}
|
||||
|
||||
void BruteforceWorker::BruteforceForLength(char prefix, std::uint32_t length) {
|
||||
DisplayData().codeString.resize(length);
|
||||
test_buffer.resize(length, 'a');
|
||||
test_buffer[0] = prefix;
|
||||
|
||||
// test all possible combinations
|
||||
while(true) {
|
||||
hash = swbf::SwsfScramble(test_buffer);
|
||||
|
||||
// There was a match!
|
||||
if(hash == options.targetHash)
|
||||
swbf::fprint(stderr, "match: {} ({:08x})\n", test_buffer, hash);
|
||||
|
||||
CopyDisplayData();
|
||||
|
||||
if(!NextCode(test_buffer, 1))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void BruteforceWorker::CopyDisplayData() {
|
||||
std::memcpy(DisplayData().codeString.data(), test_buffer.data(), test_buffer.length());
|
||||
DisplayData().codeHash = hash;
|
||||
}
|
||||
|
||||
ThreadInfoData& BruteforceWorker::DisplayData() {
|
||||
return infoData[threadIndex];
|
||||
}
|
||||
|
||||
} // namespace swbf
|
|
@ -0,0 +1,47 @@
|
|||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace swbf {
|
||||
constexpr static auto MAX_CODE_LENGTH = 8;
|
||||
constexpr static auto THREAD_COUNT = 26; // this is a-z
|
||||
|
||||
/// private structure
|
||||
struct ThreadInfoData;
|
||||
|
||||
/// returns true when all threads finished
|
||||
bool DisplayThreadInfo();
|
||||
|
||||
/// Per-thread worker for brute-forcing
|
||||
struct BruteforceWorker {
|
||||
/// Options for the worker.
|
||||
struct Options {
|
||||
/// The target hash to find matches for.
|
||||
std::uint32_t targetHash;
|
||||
|
||||
/// If `startLength` should be treated as the code length.
|
||||
bool exact;
|
||||
|
||||
std::uint32_t startLength;
|
||||
std::uint32_t endLength;
|
||||
};
|
||||
|
||||
BruteforceWorker(unsigned tid, const Options& options);
|
||||
|
||||
/// Public-facing driver function - the thread pool runs this.
|
||||
void Bruteforce(char prefix);
|
||||
|
||||
private:
|
||||
void BruteforceForLength(char prefix, std::uint32_t length);
|
||||
|
||||
void CopyDisplayData();
|
||||
ThreadInfoData& DisplayData();
|
||||
|
||||
Options options;
|
||||
|
||||
unsigned threadIndex;
|
||||
std::string test_buffer;
|
||||
std::uint32_t hash;
|
||||
};
|
||||
|
||||
} // namespace swbf
|
Loading…
Reference in New Issue