Skip to content

Commit

Permalink
Add LuaConnector for autotracking N64 games
Browse files Browse the repository at this point in the history
This commit adds a LuaConnector class which is capable of connecting to
emulator Lua scripts for autotracking. Proof of concept was done using
BizHawk 2.8 and the WarpWorld ConnectorLib lua script to power the
autotracking in my fork of Hamsda's ZOOTR pack.

This commit also adds an IAutotrackProvider interface which the
autotracker uses to abstract away the implementation of the
LuaConnector. Maybe the other trackers (snes, uat, ap) can be
migrated over to this interface to help clean up the autotracker code.
  • Loading branch information
coavins committed Jul 26, 2023
1 parent 63951fb commit 8e8ae69
Show file tree
Hide file tree
Showing 17 changed files with 1,553 additions and 7 deletions.
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ SRC = $(wildcard $(SRC_DIR)/*.cpp) \
$(wildcard $(SRC_DIR)/usb2snes/*.cpp) \
$(wildcard $(SRC_DIR)/uat/*.cpp) \
$(wildcard $(SRC_DIR)/ap/*.cpp) \
$(wildcard $(SRC_DIR)/luaconnector/*.cpp) \
$(wildcard $(SRC_DIR)/http/*.cpp) \
$(wildcard $(SRC_DIR)/packmanager/*.cpp)
#lib/gifdec/gifdec.c
Expand All @@ -23,6 +24,7 @@ HDR = $(wildcard $(SRC_DIR)/*.h) \
$(wildcard $(SRC_DIR)/usb2snes/*.h) \
$(wildcard $(SRC_DIR)/uat/*.h) \
$(wildcard $(SRC_DIR)/ap/*.h) \
$(wildcard $(SRC_DIR)/luaconnector/*.h) \
$(wildcard $(SRC_DIR)/http/*.h) \
$(wildcard $(SRC_DIR)/packmanager/*.h)
INCLUDE_DIRS = -Ilib -Ilib/lua -Ilib/asio/include -DASIO_STANDALONE -Ilib/miniz -Ilib/json/include -Ilib/valijson/include -Ilib/tinyfiledialogs -Ilib/wswrap/include #-Ilib/gifdec
Expand Down
4 changes: 4 additions & 0 deletions schema/packs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
"description": "Platform the pack is used for. If platform is \"snes\", the snes autotracker is to be enabled.",
"type": "string"
},
"platform_poptracker": {
"description": "Platform the pack is used for. If specified, PopTracker will use this instead of the value indicated in the 'platform' field.",
"type": "string"
},
"versions_url": {
"description": "URL to versions.json of the pack. Can be used for automatic updates. Information from global packs.json takes precedence. See https://github.com/black-sliver/PopTracker/tree/packlist for more information.",
"type": "string",
Expand Down
151 changes: 148 additions & 3 deletions src/core/autotracker.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include "../usb2snes/usb2snes.h"
#include "../uat/uatclient.h"
#include "../ap/aptracker.h"
#include "../luaconnector/luaconnector.h"
#include "autotrackprovider.h"
#include "signal.h"
#include <string>
#include <string.h>
Expand Down Expand Up @@ -68,6 +70,13 @@ class AutoTracker final : public LuaInterface<AutoTracker>{
_snes->setMapping(USB2SNES::Mapping::EXHIROM);
}
}
if( strcasecmp(platform.c_str(), "n64") == 0 ) {
_provider = new LuaConnector::LuaConnector(_name);
_lastBackendIndex++;
_backendIndex[_provider] = _lastBackendIndex;
_state.push_back(State::Disabled);
_provider->setMapping(flags);
}
if (flags.find("uat") != flags.end()) {
_uat = new UATClient();
_lastBackendIndex++;
Expand Down Expand Up @@ -123,6 +132,12 @@ class AutoTracker final : public LuaInterface<AutoTracker>{
if (_ap) delete _ap;
_ap = nullptr;

if( _provider )
{
delete _provider;
_provider = nullptr;
}

if (spawnedWorkers) {
// wait a bit if we started a thread to increase readability of logs
std::this_thread::sleep_for(std::chrono::milliseconds(21));
Expand All @@ -146,6 +161,7 @@ class AutoTracker final : public LuaInterface<AutoTracker>{
if (name == BACKEND_AP_NAME) return _ap ? getState(_backendIndex[_ap]) : State::Unavailable;
if (name == BACKEND_UAT_NAME) return _uat ? getState(_backendIndex[_uat]) : State::Unavailable;
if (name == BACKEND_SNES_NAME) return _snes ? getState(_backendIndex[_snes]) : State::Unavailable;
if (_provider && name == _provider->getName()) return _provider ? getState(_backendIndex[_provider]) : State::Unavailable;
return State::Unavailable;
}

Expand All @@ -154,6 +170,7 @@ class AutoTracker final : public LuaInterface<AutoTracker>{
if (_ap && _backendIndex[_ap] == index) return BACKEND_AP_NAME;
if (_uat && _backendIndex[_uat] == index) return BACKEND_UAT_NAME;
if (_snes && _backendIndex[_snes] == index) return BACKEND_SNES_NAME;
if( _provider && _backendIndex[_provider] == index ) return _provider->getName();
return BACKEND_NONE_NAME;
}

Expand Down Expand Up @@ -243,6 +260,36 @@ class AutoTracker final : public LuaInterface<AutoTracker>{
res = true;
}

if( _provider && backendEnabled(_provider) )
{
int index = _backendIndex[_provider];
State oldState = _state[index];
bool isReady = _provider->isReady();
bool gameConnected = isReady ? _provider->isConnected() : false;

if( gameConnected ) {
_state[index] = State::ConsoleConnected;
}
else if( isReady ) {
_state[index] = State::BridgeConnected;
}
else {
_state[index] = State::Disconnected;
}

if( _state[index] != oldState ) {
onStateChange.emit(this, index, _state[index]);
}

if( _provider->update() ) {
if( _state[index] == State::ConsoleConnected ) {
onDataChange.emit(this);
}

res = true;
}
}

return res;
}

Expand All @@ -253,6 +300,11 @@ class AutoTracker final : public LuaInterface<AutoTracker>{
_snes->addWatch((uint32_t)addr, len);
return true;
}
else if( _provider )
{
_provider->addWatch((uint32_t)addr, len);
return true;
}
return false;
}

Expand All @@ -263,19 +315,27 @@ class AutoTracker final : public LuaInterface<AutoTracker>{
_snes->removeWatch((uint32_t)addr, len);
return true;
}
else if( _provider ) {
_provider->removeWatch((uint32_t)addr, len);
return true;
}
return false;
}

void setInterval(unsigned ms) {
if (_snes)
_snes->setUpdateInterval(ms);
if( _provider )
_provider->setWatchUpdateInterval(ms);
}

void clearCache() {
if (_snes)
_snes->clearCache();
if (_uat)
_uat->sync(_slot);
if( _provider )
_provider->clearCache();
}

// TODO: canRead(addr,len) to detect incomplete segment
Expand All @@ -288,18 +348,65 @@ class AutoTracker final : public LuaInterface<AutoTracker>{
for (size_t i=0; i<len; i++)
res.push_back(buf[i]);
}
if( _provider ) {
uint8_t buf[len];
_provider->readFromCache((uint32_t)addr, len, buf);
for( size_t i = 0; i < len; i++ )
res.push_back(buf[i]);
}
return res;
}

int ReadU8(int segment, int offset=0)
{
if( _provider )
{
// this is a live blocking call to read memory from the game
uint32_t address = segment;
uint32_t o = offset;
return _provider->readU8Live(address, o);
}
else
// NOTE: this is AutoTracker:Read8. we only have 1 segment, that is AutoTracker
return ReadUInt8(segment+offset);
}

int ReadU16(int segment, int offset=0) { return ReadUInt16(segment+offset); }
int ReadU24(int segment, int offset=0) { return ReadUInt24(segment+offset); }
int ReadU32(int segment, int offset=0) { return ReadUInt32(segment+offset); }
int ReadU16(int segment, int offset=0)
{
if( _provider )
{
// this is a live blocking call to read memory from the game
uint32_t address = segment;
uint32_t o = offset;
return _provider->readU16Live(address, o);
}
else
return ReadUInt16(segment+offset);
}
int ReadU24(int segment, int offset=0)
{
if( _provider )
{
// this is a live blocking call to read memory from the game
uint32_t address = segment;
uint32_t o = offset;
return _provider->readU32Live(address, o) & 0xffffff;
}
else
return ReadUInt24(segment+offset);
}
int ReadU32(int segment, int offset=0)
{
if( _provider )
{
// this is a live blocking call to read memory from the game
uint32_t address = segment;
uint32_t o = offset;
return _provider->readU32Live(address, o);
}
else
return ReadUInt32(segment+offset);
}

int ReadUInt8(int addr)
{
Expand All @@ -311,6 +418,11 @@ class AutoTracker final : public LuaInterface<AutoTracker>{
//printf("$%06x = %02x\n", a, res);
return res;
}
else if( _provider ) {
auto res = _provider->readUInt8FromCache(addr);
//if( res == 0 ) _provider->addWatch(addr, 1);
return res;
}
return 0;
}

Expand All @@ -321,6 +433,11 @@ class AutoTracker final : public LuaInterface<AutoTracker>{
if (res == 0) _snes->addWatch(addr,2);
return res;
}
else if( _provider ) {
auto res = _provider->readUInt16FromCache(addr);
//if( res == 0 ) _provider->addWatch(addr, 2);
return res;
}
return 0;
}

Expand All @@ -331,6 +448,11 @@ class AutoTracker final : public LuaInterface<AutoTracker>{
if (res == 0) _snes->addWatch(addr,3);
return res;
}
else if( _provider ) {
auto res = _provider->readUInt32FromCache(addr) & 0xffffff;
//if( res == 0 ) _provider->addWatch(addr, 3);
return res;
}
return 0;
}

Expand All @@ -341,6 +463,11 @@ class AutoTracker final : public LuaInterface<AutoTracker>{
if (res == 0) _snes->addWatch(addr,4);
return res;
}
else if( _provider ) {
auto res = _provider->readUInt32FromCache(addr);
//if( res == 0 ) _provider->addWatch(addr, 4);
return res;
}
return 0;
}

Expand Down Expand Up @@ -377,6 +504,12 @@ class AutoTracker final : public LuaInterface<AutoTracker>{
return true;
}
}
else if ( _provider && _backendIndex[_provider] == index) {
_state[index] = State::Disconnected;
onStateChange.emit(this, index, _state[index]);
_provider->start();
return true;
}
return false;
}

Expand Down Expand Up @@ -411,6 +544,12 @@ class AutoTracker final : public LuaInterface<AutoTracker>{
}
if (_ap && _backendIndex[_ap] == index)
_ap->disconnect();
if( _provider && _backendIndex[_provider] == index )
{
_provider->stop();
_provider->clearCache();
}

onStateChange.emit(this, index, _state[index]);
}
}
Expand All @@ -420,6 +559,11 @@ class AutoTracker final : public LuaInterface<AutoTracker>{
return _ap;
}

IAutotrackProvider* getAutotrackProvider() const
{
return _provider;
}

void setSnesAddresses(const std::vector<std::string>& addresses)
{
_snesAddresses = addresses;
Expand Down Expand Up @@ -457,6 +601,7 @@ class AutoTracker final : public LuaInterface<AutoTracker>{
USB2SNES *_snes = nullptr;
UATClient *_uat = nullptr;
APTracker *_ap = nullptr;
IAutotrackProvider* _provider = nullptr;
std::string _slot; // selected slot for UAT
std::map<std::string, nlohmann::json> _vars; // variable store for UAT
std::string _name;
Expand Down
44 changes: 44 additions & 0 deletions src/core/autotrackprovider.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#ifndef _CORE_AUTOTRACK_PROVIDER_H
#define _CORE_AUTOTRACK_PROVIDER_H

#include <stdint.h>
#include <vector>
#include <string>
#include <set>

class IAutotrackProvider {
public:
virtual ~IAutotrackProvider() = default;

virtual const std::string& getName() = 0;

virtual bool start() = 0;
virtual bool stop() = 0;

// Returns true if cache was changed
virtual bool update() = 0;

virtual bool isReady() = 0;
virtual bool isConnected() = 0;

virtual void clearCache() = 0;

virtual void addWatch(uint32_t address, unsigned int length) = 0;
virtual void removeWatch(uint32_t address, unsigned int length) = 0;
virtual void setWatchUpdateInterval(size_t interval) = 0;

virtual void setMapping(const std::set<std::string>& flags) = 0;
virtual uint32_t mapAddress(uint32_t address) = 0;

virtual bool readFromCache(uint32_t address, unsigned int length, void* out) = 0;
virtual uint8_t readUInt8FromCache(uint32_t address, uint32_t offset = 0) { return 0; }
virtual uint16_t readUInt16FromCache(uint32_t address, uint32_t offset = 0) { return 0; }
virtual uint32_t readUInt32FromCache(uint32_t address, uint32_t offset = 0) { return 0; }

virtual uint8_t readU8Live(uint32_t address, uint32_t offset = 0) { return 0; }
virtual uint16_t readU16Live(uint32_t address, uint32_t offset = 0) { return 0; }
virtual uint32_t readU32Live(uint32_t address, uint32_t offset = 0) { return 0; }
};

#endif /* _CORE_AUTOTRACK_PROVIDER_H */

15 changes: 14 additions & 1 deletion src/core/pack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ Pack::Info Pack::getInfo() const
_minPopTrackerVersion,
variants
};

// Use the PopTracker platform override field
std::string platform_override = to_string(_manifest, "platform_poptracker", "");
if( platform_override != "" )
info.platform = platform_override;

return info;
}

Expand Down Expand Up @@ -186,7 +192,14 @@ void Pack::setVariant(const std::string& variant)

std::string Pack::getPlatform() const
{
return to_string(_manifest,"platform","");
std::string platform = to_string(_manifest, "platform", "");

// Use the PopTracker platform override field
std::string platform_override = to_string(_manifest, "platform_poptracker", "");
if( platform_override != "" )
platform = platform_override;

return platform;
}

std::string Pack::getVersion() const
Expand Down
Loading

0 comments on commit 8e8ae69

Please sign in to comment.