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
|
add_executable(swsf_bruteforce
|
||||||
src/asio_impl.cpp
|
src/asio_impl.cpp
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
|
src/worker.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_compile_definitions(swsf_bruteforce PRIVATE
|
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 <format>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
|
#include "ansi.hpp"
|
||||||
|
#include "fprint.hpp"
|
||||||
|
#include "worker.hpp"
|
||||||
|
|
||||||
namespace asio = boost::asio;
|
namespace asio = boost::asio;
|
||||||
|
|
||||||
constexpr static auto MAX_CODE_LENGTH = 8;
|
void BruteMain() {
|
||||||
constexpr static auto THREAD_COUNT = 26; // this is a-z
|
asio::thread_pool pool(swbf::THREAD_COUNT);
|
||||||
|
|
||||||
/// This is a constexpr-safe zero-allocation version of the algorithm
|
swbf::BruteforceWorker::Options options { .targetHash = 0x2c73af55, .exact = true, .startLength = 8 };
|
||||||
/// 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);
|
|
||||||
|
|
||||||
auto threadIndex = 0u;
|
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
|
// 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++]() {
|
asio::post(pool, [&, tid = threadIndex++]() {
|
||||||
BruteforceWorker state(tid, options);
|
swbf::BruteforceWorker state(tid, options);
|
||||||
infoData[tid].threadIndex = tid;
|
|
||||||
state.Bruteforce('a' + tid);
|
state.Bruteforce('a' + tid);
|
||||||
});
|
});
|
||||||
|
|
||||||
bool done = false;
|
bool done = false;
|
||||||
|
|
||||||
while(!done) {
|
while(!done) {
|
||||||
int doneIndex = 0;
|
done = swbf::DisplayThreadInfo();
|
||||||
for(auto& d : infoData) {
|
|
||||||
if(d.done)
|
|
||||||
doneIndex++;
|
|
||||||
d.DisplayProgress();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(doneIndex == THREAD_COUNT)
|
|
||||||
done = true;
|
|
||||||
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(66));
|
std::this_thread::sleep_for(std::chrono::milliseconds(66));
|
||||||
}
|
}
|
||||||
|
|
||||||
pool.join(); // just in case!
|
pool.join(); // just in case!
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
BruteMain();
|
||||||
return 0;
|
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