mirror of https://github.com/rusefi/msqur.git
commit
e615fcc490
51
README.md
51
README.md
|
@ -1,25 +1,48 @@
|
|||
# MSQur
|
||||
|
||||
'Masker' I guess? Supposed to be a play on imgur.
|
||||
MegaSquirt MSQ file sharing and viewing site.
|
||||
|
||||
Pronounced 'masker' I guess? Supposed to be a play on imgur.
|
||||
|
||||
Parses MSQ "XML" in tandem with an associated INI (config) file and displays it in a familiar format for viewing and comparing.
|
||||
|
||||
Try it now at: https://msqur.com/
|
||||
Try it now at: https://msqur.com
|
||||
|
||||
### Build Status:
|
||||
* msqur.com [![Build Status](https://travis-ci.org/nearwood/msqur.svg?branch=msqur.com)](https://travis-ci.org/nearwood/msqur)
|
||||
* [msqur.com](https://msqur.com) [![Build Status](https://travis-ci.org/nearwood/msqur.svg?branch=msqur.com)](https://travis-ci.org/nearwood/msqur)
|
||||
* master [![Build Status](https://travis-ci.org/nearwood/msqur.svg?branch=master)](https://travis-ci.org/nearwood/msqur)
|
||||
|
||||
## Development status
|
||||
|
||||
Firmware support:
|
||||
|
||||
- [ ] MS1
|
||||
- [ ] MSnS-extra (partial)
|
||||
- [x] MS2
|
||||
- [x] MS2Extra
|
||||
- [x] MS3
|
||||
- [ ] Speeduino
|
||||
|
||||
## Contributing
|
||||
|
||||
This is basically a one-man operation. I welcome any contributions: code, styles, text content, or simply spelling & grammar.
|
||||
If you're interesting in helping out, please first take a look at the existing [issues](issues) and see if you can offer any assistance with them.
|
||||
If you don't see your issue or new idea listed there you can [create a new issue](issues/new). Please be detailed.
|
||||
|
||||
If you'd like to run a copy to develop yourself, read the [Installation](#Installation) section below.
|
||||
|
||||
### Installation
|
||||
|
||||
#### Needed software
|
||||
|
||||
* MariaDB, PHP
|
||||
* PDO extension for PHP.
|
||||
- PHP 7.x with the following extensions:
|
||||
- PDO
|
||||
- OpenSSL
|
||||
- MySQL/MariaDB
|
||||
|
||||
#### Recommended software
|
||||
#### Optional software
|
||||
|
||||
* Web server (Apache/nginx/etc.)
|
||||
* phpMyAdmin - For managing the DB
|
||||
|
||||
#### Development Setup
|
||||
|
@ -33,6 +56,18 @@ Try it now at: https://msqur.com/
|
|||
1. Update DB with update scripts in sequential order (patse into phpMyAdmin or piped to `sqlcmd`, etc.)
|
||||
1. Hit webserver to start using it (eg. `php -S`, etc.)
|
||||
|
||||
### Source tree description
|
||||
|
||||
* `db` - Database scripts
|
||||
* `doxygen` - Doxygen configuration and generated code documentation
|
||||
* `src` - PHP source
|
||||
* `ini` - Megasquirt configuration files
|
||||
* `view` - PHP/JS frontend source
|
||||
* `lib` - JS 3rd party libraries
|
||||
* `img` - Static images
|
||||
* `tests` - PHP Unit Tests (TODO)
|
||||
|
||||
|
||||
### Update & Deployment Instructions
|
||||
|
||||
> These steps are outdated
|
||||
|
@ -48,13 +83,13 @@ msqur is licensed under the GPL v3.0. A copy of this license is included in the
|
|||
|
||||
### Who do I talk to?
|
||||
|
||||
* Nicholas Earwood
|
||||
* Nick
|
||||
* nearwood@gmail.com
|
||||
* https://nearwood.dev/
|
||||
|
||||
### Credits
|
||||
|
||||
[CamHenlin](https://github.com/CamHenlin)
|
||||
* [CamHenlin](https://github.com/CamHenlin)
|
||||
|
||||
> This section needs to be updated
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#. script.config
|
||||
|
||||
mkdir -p ~\/.ssh
|
||||
# mkdir -p ~/.ssh
|
||||
openssl aes-256-cbc -K $encrypted_571de2096706_key -iv $encrypted_571de2096706_iv -in .travis/travis_ci.enc -out .travis/id_rsa -d
|
||||
|
||||
eval "$(ssh-agent -s)"
|
||||
|
|
|
@ -22,23 +22,25 @@ $msqur->header();
|
|||
?>
|
||||
<div>
|
||||
<h2>About</h2>
|
||||
<p>Created out of a need to share .MSQ files.
|
||||
I was tired of downloading files and having to open them in <strike>MegaTune</strike> Tuner Studio.
|
||||
So, I created this site. It's open source, so <a href="https://github.com/nearwood/msqur">feel free to contribute</a>.
|
||||
Since going "live" I only add things here and there whenever I have time or the urge to add features/fixes.</p>
|
||||
<p>Created out of a need to share .MSQ files.</p>
|
||||
<p>I was tired of downloading files and having to open them in <strike>MegaTune</strike> Tuner Studio, so I created this site.</p>
|
||||
<p>It's open source, so <a href="https://github.com/nearwood/msqur">anyone can contribute</a>.</p>
|
||||
<p>Since going "live" I only add things here and there whenever I have time or the urge to add features/fixes.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h2>FAQ</h2>
|
||||
<ul id="faq">
|
||||
<li class="q">Why is this site so ugly?</li>
|
||||
<li class="a">My Ballmer peak doesn't last all day.</li>
|
||||
<li class="a">It's a side project and my <a href="https://www.xkcd.com/323/">Ballmer Peak</a> doesn't last all day.</li>
|
||||
<li class="q">Can you add X feature?</li>
|
||||
<li class="a">File a request for it <a href="https://github.com/nearwood/msqur/issues">here</a>.</li>
|
||||
<li class="q">What tech stack does this site run on?</li>
|
||||
<li class="a">The frontend is Javascript (jQuery and a little Angular.js).<br/>The backend that does most of the work was made with PHP and data is stored in a SQL database.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<?php if (@readfile("VERSION") === FALSE) echo "DEV"; ?> <a href="http://httpd.apache.org/">Apache</a> <a href="http://php.net/">PHP</a> <a href="https://angularjs.org/">AngularJS</a> <a href="http://jquery.com/">jQuery</a>
|
||||
</div>
|
||||
<h6 style="float: right;">
|
||||
Version: <?php if (@readfile("VERSION") === FALSE) echo "DEV"; ?>
|
||||
</h6>
|
||||
<?php
|
||||
$msqur->footer();
|
||||
?>
|
||||
|
|
|
@ -88,11 +88,11 @@ $numResults = count($results);
|
|||
|
||||
echo '<div id="content"><div class="info">' . $numResults . ' results.</div>';
|
||||
echo '<table ng-controller="BrowseController">';
|
||||
echo '<tr><th>ID</th><th>Download</th><th>Engine Make</th><th>Engine Code</th><th>Cylinders</th><th>Liters</th><th>Compression</th><th>Aspiration</th><th>Firmware/Version</th><th>Upload Date</th><th>Views</th></tr>';
|
||||
echo '<tr><th>Uploaded</th><th>Engine Make</th><th>Engine Code</th><th>Cylinders</th><th>Liters</th><th>Compression</th><th>Aspiration</th><th>Firmware/Version</th><th>Views</th><th>Options</th></tr>';
|
||||
for ($c = 0; $c < $numResults; $c++)
|
||||
{
|
||||
$aspiration = $results[$c]['induction'] == 1 ? "Turbo" : "NA";
|
||||
echo '<tr><td><a href="view.php?msq=' . $results[$c]['mid'] . '">' . $results[$c]['mid'] . '</a></td><td><a href="download.php?msq=' . $results[$c]['mid'] . '">💾</a></td><td>' . $results[$c]['make'] . '</td><td>' . $results[$c]['code'] . '</td><td>' . $results[$c]['numCylinders'] . '</td><td>' . $results[$c]['displacement'] . '</td><td>' . $results[$c]['compression'] . ':1</td><td>' . $aspiration . '</td><td>' . $results[$c]['firmware'] . '/' . $results[$c]['signature'] . '</td><td>' . $results[$c]['uploadDate'] . '</td><td>' . $results[$c]['views'] . '</td></tr>';
|
||||
echo '<tr><td><a href="view.php?msq=' . $results[$c]['mid'] . '">' . $results[$c]['uploadDate'] . '</a></td><td>' . $results[$c]['make'] . '</td><td>' . $results[$c]['code'] . '</td><td>' . $results[$c]['numCylinders'] . '</td><td>' . $results[$c]['displacement'] . '</td><td>' . $results[$c]['compression'] . ':1</td><td>' . $aspiration . '</td><td>' . $results[$c]['firmware'] . '/' . $results[$c]['signature'] . '</td><td>' . $results[$c]['views'] . '</td><td><a title="Download MSQ" href="download.php?msq=' . $results[$c]['mid'] . '">💾</a></td></tr>';
|
||||
}
|
||||
echo '</table></div>';
|
||||
|
||||
|
|
91
src/db.php
91
src/db.php
|
@ -27,26 +27,27 @@ class DB
|
|||
{
|
||||
if (isset($this->db) && $this->db instanceof PDO)
|
||||
{
|
||||
if (DEBUG) debug("DEBUG: Reusing DB connection.");
|
||||
//if (DEBUG) debug("Reusing DB connection.");
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
if (DEBUG) debug('<div class="debug">Connecting to DB: ' . "mysql:dbname=" . DB_NAME . ";host=" . DB_HOST . "," . DB_USERNAME . ", [****]" . '</div>');
|
||||
//if (DEBUG) debug('Connecting to DB: ' . "mysql:dbname=" . DB_NAME . ";host=" . DB_HOST . "," . DB_USERNAME . ", [****]");
|
||||
$this->db = new PDO("mysql:dbname=" . DB_NAME . ";host=" . DB_HOST, DB_USERNAME, DB_PASSWORD);
|
||||
//Persistent connection:
|
||||
//$this->db = new PDO("mysql:dbname=" . DB_NAME . ";host=" . DB_HOST, DB_USERNAME, DB_PASSWORD, array(PDO::ATTR_PERSISTENT => true);
|
||||
}
|
||||
catch (PDOException $e)
|
||||
{
|
||||
error("Could not connect to database");
|
||||
echo '<div class="error">Error connecting to database.</div>';
|
||||
$this->dbError($e);
|
||||
$this->db = null; //Redundant.
|
||||
}
|
||||
}
|
||||
|
||||
if (DEBUG) debug('<div class="debug">Connecting to DB: ' . (($this->db != null) ? 'Connected.' : 'Connection FAILED') . '</div>');
|
||||
//if (DEBUG) debug('Connecting to DB: ' . (($this->db != null) ? 'Connected.' : 'Connection FAILED'));
|
||||
return ($this->db != null);
|
||||
}
|
||||
|
||||
|
@ -68,6 +69,8 @@ class DB
|
|||
try
|
||||
{
|
||||
//TODO Compress?
|
||||
|
||||
//TODO transaction so we can rollback (`$db->beginTransaction()`)
|
||||
$st = $this->db->prepare("INSERT INTO msqs (xml) VALUES (:xml)");
|
||||
$xml = file_get_contents($file['tmp_name']);
|
||||
//Convert encoding to UTF-8
|
||||
|
@ -87,11 +90,23 @@ class DB
|
|||
$dt = new DateTime();
|
||||
$dt = $dt->format('Y-m-d H:i:s');
|
||||
DB::tryBind($st, ":uploaded", $dt);
|
||||
if ($st->execute()) $id = $this->db->lastInsertId();
|
||||
else $id = -1;
|
||||
$st->closeCursor();
|
||||
if ($st->execute()) {
|
||||
$id = $this->db->lastInsertId();
|
||||
} else {
|
||||
error("Error inserting metadata");
|
||||
if (DEBUG) {
|
||||
print_r($st->errorInfo());
|
||||
}
|
||||
$id = -1;
|
||||
}
|
||||
$st->closeCursor();
|
||||
} else {
|
||||
error("Error inserting XML data");
|
||||
if (DEBUG) {
|
||||
print_r($st->errorInfo());
|
||||
}
|
||||
$id = -1;
|
||||
}
|
||||
else $id = -1;
|
||||
}
|
||||
catch (PDOException $e)
|
||||
{
|
||||
|
@ -126,7 +141,7 @@ class DB
|
|||
|
||||
try
|
||||
{
|
||||
if (DEBUG) debug("<div class=\"debug\">Add engine: \"$make\", \"$code\", $displacement, $compression, $turbo</div>");
|
||||
if (DEBUG) debug("Add engine: \"$make\", \"$code\", $displacement, $compression, $turbo");
|
||||
//TODO use any existing one before creating
|
||||
$st = $this->db->prepare("INSERT INTO engines (make, code, displacement, compression, induction) VALUES (:make, :code, :displacement, :compression, :induction)");
|
||||
|
||||
|
@ -151,7 +166,7 @@ class DB
|
|||
}
|
||||
}
|
||||
|
||||
if (DEBUG) debug("<div class=\"debug\">Add engine returns: $id</div>");
|
||||
if (DEBUG) debug("Add engine returns: $id");
|
||||
return $id;
|
||||
}
|
||||
|
||||
|
@ -178,7 +193,7 @@ class DB
|
|||
}
|
||||
else
|
||||
{
|
||||
if (DEBUG) debug("<div class=\"debug\">No result for $id</div>");
|
||||
if (DEBUG) debug("No result for $id");
|
||||
echo '<div class="error">Invalid MSQ</div>';
|
||||
$st->closeCursor();
|
||||
}
|
||||
|
@ -202,17 +217,17 @@ class DB
|
|||
|
||||
try
|
||||
{
|
||||
if (DEBUG) debug('<div class="debug">Updating HTML cache...</div>');
|
||||
if (DEBUG) debug('Updating HTML cache...');
|
||||
$st = $this->db->prepare("UPDATE metadata m SET m.reingest=FALSE WHERE m.id = :id");
|
||||
DB::tryBind($st, ":id", $id);
|
||||
if ($st->execute())
|
||||
{
|
||||
if (DEBUG) debug('<div class="debug">Reingest reset.</div>');
|
||||
if (DEBUG) debug('Reingest reset.');
|
||||
$st->closeCursor();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
if (DEBUG) debug('<div class="warn">Unable to update cache.</div>');
|
||||
if (DEBUG) debug('Unable to update cache.');
|
||||
|
||||
$st->closeCursor();
|
||||
}
|
||||
|
@ -233,13 +248,13 @@ class DB
|
|||
{
|
||||
if (DISABLE_MSQ_CACHE)
|
||||
{
|
||||
if (DEBUG) debug('<div class="debug warn">Cache disabled.</div>');
|
||||
if (DEBUG) debug('Cache disabled.');
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ($this->needReingest($id))
|
||||
{
|
||||
if (DEBUG) debug('<div class="debug info">Flagged for reingest.</div>');
|
||||
if (DEBUG) debug('Flagged for reingest.');
|
||||
$this->resetReingest($id);
|
||||
return FALSE;
|
||||
}
|
||||
|
@ -260,14 +275,14 @@ class DB
|
|||
$html = $result['html'];
|
||||
if ($html === NULL)
|
||||
{
|
||||
if (DEBUG) debug('<div class="debug">No HTML cache found.</div>');
|
||||
if (DEBUG) debug('No HTML cache found.');
|
||||
return FALSE;
|
||||
}
|
||||
else if (DEBUG) debug('<div class="debug">Cached, returning HTML.</div>');
|
||||
else if (DEBUG) debug('Cached, returning HTML.');
|
||||
}
|
||||
else
|
||||
{
|
||||
if (DEBUG) debug("<div class=\"debug\">No result for $id</div>");
|
||||
if (DEBUG) debug("No result for $id");
|
||||
echo '<div class="error">Invalid MSQ</div>';
|
||||
return null;
|
||||
}
|
||||
|
@ -297,7 +312,7 @@ class DB
|
|||
$result = $st->fetch(PDO::FETCH_ASSOC);
|
||||
$st->closeCursor();
|
||||
$xml = $result['xml'];
|
||||
if (DEBUG) debug('<div class="debug">Cached, returning HTML.</div>');
|
||||
if (DEBUG) debug('Cached, returning HTML.');
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -409,7 +424,7 @@ class DB
|
|||
|
||||
try
|
||||
{
|
||||
if (DEBUG) debug("<div class=\"debug\">Getting firmware list...</div>");
|
||||
if (DEBUG) debug("Getting firmware list...");
|
||||
$st = $this->db->prepare("SELECT DISTINCT firmware FROM `metadata`");
|
||||
|
||||
if ($st->execute())
|
||||
|
@ -439,7 +454,7 @@ class DB
|
|||
|
||||
try
|
||||
{
|
||||
if (DEBUG) debug("<div class=\"debug\">Getting firmware version list...</div>");
|
||||
if (DEBUG) debug("Getting firmware version list...");
|
||||
if ($firmware == null)
|
||||
{
|
||||
$st = $this->db->prepare("SELECT DISTINCT signature FROM `metadata`");
|
||||
|
@ -472,7 +487,7 @@ class DB
|
|||
|
||||
try
|
||||
{
|
||||
if (DEBUG) debug("<div class=\"debug\">Getting engine make list...</div>");
|
||||
if (DEBUG) debug("Getting engine make list...");
|
||||
$st = $this->db->prepare("SELECT DISTINCT make FROM `engines`");
|
||||
|
||||
if ($st->execute())
|
||||
|
@ -497,7 +512,7 @@ class DB
|
|||
|
||||
try
|
||||
{
|
||||
if (DEBUG) debug("<div class=\"debug\">Getting engine code list...</div>");
|
||||
if (DEBUG) debug("Getting engine code list...");
|
||||
|
||||
if ($make !== null && gettype($make) == "string")
|
||||
{
|
||||
|
@ -537,19 +552,19 @@ class DB
|
|||
|
||||
try
|
||||
{
|
||||
if (DEBUG) debug('<div class="debug">Updating HTML cache...</div>');
|
||||
if (DEBUG) debug('Updating HTML cache...');
|
||||
$st = $this->db->prepare("UPDATE msqs ms, metadata m SET ms.html=:html WHERE m.msq = ms.id AND m.id = :id");
|
||||
//$xml = mb_convert_encoding($html, "UTF-8");
|
||||
DB::tryBind($st, ":id", $id);
|
||||
DB::tryBind($st, ":html", $html);
|
||||
if ($st->execute())
|
||||
{
|
||||
if (DEBUG) debug('<div class="debug">Cache updated.</div>');
|
||||
if (DEBUG) debug('Cache updated.');
|
||||
$st->closeCursor();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
if (DEBUG) debug('<div class="warn">Unable to update cache.</div>');
|
||||
if (DEBUG) debug('Unable to update cache.');
|
||||
|
||||
$st->closeCursor();
|
||||
}
|
||||
|
@ -581,7 +596,7 @@ class DB
|
|||
|
||||
try
|
||||
{
|
||||
if (DEBUG) debug('<div class="debug">Updating engine information...</div>');
|
||||
if (DEBUG) debug('Updating engine information...');
|
||||
$st = $this->db->prepare("UPDATE engines e, metadata m SET e.numCylinders = :nCylinders, twoStroke = :twoStroke, injType = :injType, nInjectors = :nInjectors, engineType = :engineType WHERE e.id = m.engine AND m.id = :id");
|
||||
DB::tryBind($st, ":id", $id);
|
||||
DB::tryBind($st, ":nCylinders", $engine['nCylinders']);
|
||||
|
@ -596,12 +611,12 @@ class DB
|
|||
DB::tryBind($st, ":engineType", $engine['engineType']);
|
||||
if ($st->execute())
|
||||
{
|
||||
if (DEBUG) debug('<div class="debug">Engine updated.</div>');
|
||||
if (DEBUG) debug('Engine updated.');
|
||||
$st->closeCursor();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
if (DEBUG) debug('<div class="warn">Unable to update engine metadata.</div>');
|
||||
if (DEBUG) debug('Unable to update engine metadata.');
|
||||
|
||||
$st->closeCursor();
|
||||
}
|
||||
|
@ -626,13 +641,14 @@ class DB
|
|||
|
||||
if (!array_keys_exist($metadata, 'fileFormat', 'signature', 'firmware', 'author'))
|
||||
{
|
||||
if (DEBUG) debug('Invalid MSQ metadata: ' . $metadata);
|
||||
echo '<div class="warn">Incomplete MSQ metadata.</div>';
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (DEBUG) debug('<div class="debug">Updating HTML cache...</div>');
|
||||
if (DEBUG) debug('Updating HTML cache...');
|
||||
$st = $this->db->prepare("UPDATE metadata md SET md.fileFormat = :fileFormat, md.signature = :signature, md.firmware = :firmware, md.author = :author WHERE md.id = :id");
|
||||
//$xml = mb_convert_encoding($html, "UTF-8");
|
||||
DB::tryBind($st, ":id", $id);
|
||||
|
@ -642,12 +658,12 @@ class DB
|
|||
DB::tryBind($st, ":author", $metadata['author']);
|
||||
if ($st->execute())
|
||||
{
|
||||
if (DEBUG) debug('<div class="debug">Metadata updated.</div>');
|
||||
if (DEBUG) debug('Metadata updated.');
|
||||
$st->closeCursor();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
if (DEBUG) debug('<div class="warn">Unable to update metadata.</div>');
|
||||
if (DEBUG) debug('Unable to update metadata.');
|
||||
|
||||
$st->closeCursor();
|
||||
}
|
||||
|
@ -708,6 +724,7 @@ class DB
|
|||
{
|
||||
if (DEBUG)
|
||||
{
|
||||
error("DB Error: " . $e->getMessage());
|
||||
echo '<div class="error">Error executing database query:<br/>';
|
||||
echo $e->getMessage();
|
||||
echo '</div>';
|
||||
|
@ -722,7 +739,7 @@ class DB
|
|||
*/
|
||||
public function getXML($id)
|
||||
{
|
||||
if (DEBUG) debug('<div class="debug">Getting XML for id: ' . $id . '</div>');
|
||||
if (DEBUG) debug('Getting XML for id: ' . $id);
|
||||
|
||||
if (!$this->connect()) return null;
|
||||
|
||||
|
@ -732,14 +749,16 @@ class DB
|
|||
{
|
||||
$st = $this->db->prepare("SELECT xml FROM msqs INNER JOIN metadata ON metadata.msq = msqs.id WHERE metadata.id = :id LIMIT 1");
|
||||
DB::tryBind($st, ":id", $id);
|
||||
if ($st->execute())
|
||||
if ($st->execute() && $st->rowCount() === 1)
|
||||
{
|
||||
if (DEBUG) debug('<div class="debug">XML Found...</div>');
|
||||
if (DEBUG) debug('XML Found.');
|
||||
$result = $st->fetch(PDO::FETCH_ASSOC);
|
||||
$st->closeCursor();
|
||||
$xml = $result['xml'];
|
||||
} else {
|
||||
//TODO Send real 404
|
||||
echo '<div class="error">404 MSQ not found.</div>';
|
||||
}
|
||||
else echo '<div class="error">XML not found.</div>';
|
||||
}
|
||||
catch (PDOException $e)
|
||||
{
|
||||
|
|
54
src/ini.php
54
src/ini.php
|
@ -21,14 +21,18 @@ class INI
|
|||
//"MS2Extra Serial321 "
|
||||
//"MS2Extra Serial310 "
|
||||
//"MSII Rev 3.83000 "
|
||||
//"MSnS-extra format 024s *********"
|
||||
//"MSnS-extra format 024y3 ********"
|
||||
|
||||
//Get the signature from the MSQ
|
||||
$sig = explode(' ', $signature); //, 3); limit 3 buckets
|
||||
$msVersion = $sig[0];
|
||||
|
||||
//Handle MS2 strings that don't have 'format' in them
|
||||
if ($msVersion == "MS2Extra") $fwVersion = $sig[1];
|
||||
else $fwVersion = $sig[2];
|
||||
|
||||
if (DEBUG) debug("<div class=\"debug\">$msVersion/$fwVersion</div>");
|
||||
debug("Firmware version: $msVersion/$fwVersion");
|
||||
|
||||
//Parse msVersion
|
||||
switch ($msVersion)
|
||||
|
@ -36,15 +40,25 @@ class INI
|
|||
case "MS1":
|
||||
$msDir = "ms1/";
|
||||
break;
|
||||
|
||||
case "MSnS-extra":
|
||||
$msDir = "msns-extra/";
|
||||
break;
|
||||
|
||||
case "MSII":
|
||||
$msDir = "ms2/";
|
||||
break;
|
||||
|
||||
case "MS2Extra":
|
||||
$msDir = "ms2extra/";
|
||||
break;
|
||||
|
||||
case "MS3":
|
||||
$msDir = "ms3/";
|
||||
break;
|
||||
|
||||
default:
|
||||
$msDir = "unknown";
|
||||
}
|
||||
|
||||
//Setup firmware version for matching.
|
||||
|
@ -57,6 +71,9 @@ class INI
|
|||
$signature = array($msVersion, $fwVersion);
|
||||
|
||||
$iniFile = "ini/" . $msDir . $fwVersion . ".ini";
|
||||
|
||||
debug("INI File: $iniFile");
|
||||
|
||||
$msqMap = INI::parse($iniFile, TRUE);
|
||||
|
||||
return $msqMap;
|
||||
|
@ -79,16 +96,20 @@ class INI
|
|||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
//TODO I'm not sure `file` throws
|
||||
echo "<div class=\"error\">Error opening file: $file</div>";
|
||||
error("Exception opening: $file");
|
||||
error($e->getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($ini == FALSE || count($ini) == 0)
|
||||
{
|
||||
echo "<div class=\"error\">Error opening file: $file</div>";
|
||||
error("Error or empty file: $file");
|
||||
return null;
|
||||
}
|
||||
else if (DEBUG) debug("<div class=\"debug\">Opened: $file</div>");
|
||||
else if (DEBUG) debug("Opened: $file");
|
||||
|
||||
$globals = array();
|
||||
$curve = array();
|
||||
|
@ -110,7 +131,7 @@ class INI
|
|||
{
|
||||
$currentSection = substr($line, 1, -1);
|
||||
$values[$currentSection] = array();
|
||||
if (DEBUG) debug("<div class=\"debug\">Reading section: $currentSection</div>");
|
||||
if (DEBUG) debug("Reading section: $currentSection");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -123,7 +144,7 @@ class INI
|
|||
//For the menu, this is whether the menu item is visible or enabled.
|
||||
INI::parseExpression($line);
|
||||
|
||||
//if (DEBUG) debug("<div class=\"debug\">Skipping expression in line: $line</div>");
|
||||
//if (DEBUG) debug("Skipping expression in line: $line");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -161,7 +182,7 @@ class INI
|
|||
case "curve": //start of new curve
|
||||
if (!empty($curve))
|
||||
{//save the last one, if any
|
||||
if (DEBUG) debug('<div class="debug">Parsed curve: ' . $curve['id'] . '</div>');
|
||||
if (DEBUG) debug('Parsed curve: ' . $curve['id']);
|
||||
//var_export($curve);
|
||||
$values[$currentSection][$curve['id']] = $curve;
|
||||
}
|
||||
|
@ -173,7 +194,7 @@ class INI
|
|||
$curve['id'] = $value[0];
|
||||
$curve['desc'] = trim($value[1], '"');
|
||||
}
|
||||
else if (DEBUG) debug("<div class=\"warn\">Invalid curve: $key</div>");
|
||||
else if (DEBUG) debug("Invalid curve: $key");
|
||||
break;
|
||||
case "topicHelp":
|
||||
if (is_array($curve))
|
||||
|
@ -188,7 +209,7 @@ class INI
|
|||
$curve['xLabel'] = $value[0];
|
||||
$curve['yLabel'] = $value[1];
|
||||
}
|
||||
else if (DEBUG) debug("<div class=\"warn\">Invalid curve column label: $key</div>");
|
||||
else if (DEBUG) debug("Invalid curve column label: $key");
|
||||
break;
|
||||
case "xAxis":
|
||||
$value = array_map('trim', explode(',', $value));
|
||||
|
@ -198,7 +219,7 @@ class INI
|
|||
$curve['xMax'] = $value[1];
|
||||
$curve['xSomething'] = $value[2];
|
||||
}
|
||||
else if (DEBUG) debug("<div class=\"warn\">Invalid curve X axis: $key</div>");
|
||||
else if (DEBUG) debug("Invalid curve X axis: $key");
|
||||
break;
|
||||
case "yAxis":
|
||||
$value = array_map('trim', explode(',', $value));
|
||||
|
@ -208,7 +229,7 @@ class INI
|
|||
$curve['yMax'] = $value[1];
|
||||
$curve['ySomething'] = $value[2];
|
||||
}
|
||||
else if (DEBUG) debug("<div class=\"warn\">Invalid curve Y axis: $key</div>");
|
||||
else if (DEBUG) debug("Invalid curve Y axis: $key");
|
||||
break;
|
||||
case "xBins":
|
||||
$value = array_map('trim', explode(',', $value));
|
||||
|
@ -218,7 +239,7 @@ class INI
|
|||
//$curve['xBinVar'] = $value[1]; //The value read from the ECU
|
||||
//Think they all have index 1 except bogus curves
|
||||
}
|
||||
else if (DEBUG) debug("<div class=\"warn\">Invalid curve X bins: $key</div>");
|
||||
else if (DEBUG) debug("Invalid curve X bins: $key");
|
||||
break;
|
||||
case "yBins":
|
||||
$value = array_map('trim', explode(',', $value));
|
||||
|
@ -226,7 +247,7 @@ class INI
|
|||
{
|
||||
$curve['yBinConstant'] = $value[0];
|
||||
}
|
||||
else if (DEBUG) debug("<div class=\"warn\">Invalid curve Y bins: $key</div>");
|
||||
else if (DEBUG) debug("Invalid curve Y bins: $key");
|
||||
break;
|
||||
case "gauge": //not all have this
|
||||
break;
|
||||
|
@ -239,7 +260,7 @@ class INI
|
|||
case "table": //start of new curve
|
||||
if (!empty($table))
|
||||
{//save the last one, if any
|
||||
if (DEBUG) debug('<div class="debug">Parsed table: ' . $table['id'] . '</div>');
|
||||
if (DEBUG) debug('Parsed table: ' . $table['id']);
|
||||
//var_export($curve);
|
||||
$values[$currentSection][$table['id']] = $table;
|
||||
}
|
||||
|
@ -253,7 +274,7 @@ class INI
|
|||
$table['desc'] = trim($value[2], '"');
|
||||
//$table['page'] = $value[3]; //Don't care for this one AFAIK.
|
||||
}
|
||||
else if (DEBUG) debug("<div class=\"warn\">Invalid table: $key</div>");
|
||||
else if (DEBUG) debug("Invalid table: $key");
|
||||
break;
|
||||
case "topicHelp":
|
||||
if (is_array($table))
|
||||
|
@ -269,7 +290,7 @@ class INI
|
|||
//$table['xBinVar'] = $value[1]; //The value read from the ECU
|
||||
//Think they all have index 1 except bogus tables
|
||||
}
|
||||
else if (DEBUG) debug("<div class=\"warn\">Invalid table X bins: $key</div>");
|
||||
else if (DEBUG) debug("Invalid table X bins: $key");
|
||||
break;
|
||||
case "yBins":
|
||||
$value = array_map('trim', explode(',', $value));
|
||||
|
@ -277,7 +298,7 @@ class INI
|
|||
{
|
||||
$table['yBinConstant'] = $value[0];
|
||||
}
|
||||
else if (DEBUG) debug("<div class=\"warn\">Invalid table Y bins: $key</div>");
|
||||
else if (DEBUG) debug("Invalid table Y bins: $key");
|
||||
break;
|
||||
case "zBins": //not all have this
|
||||
$value = array_map('trim', explode(',', $value));
|
||||
|
@ -285,7 +306,7 @@ class INI
|
|||
{
|
||||
$table['zBinConstant'] = $value[0];
|
||||
}
|
||||
else if (DEBUG) debug("<div class=\"warn\">Invalid table Z bins: $key</div>");
|
||||
else if (DEBUG) debug("Invalid table Z bins: $key");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
@ -310,7 +331,6 @@ class INI
|
|||
//Should be global values (don't think any ini's have them)
|
||||
assert($currentSection === NULL);
|
||||
$globals[$key] = INI::defaultSectionHandler($value);
|
||||
continue; //Skip the section values assignment below
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -32,8 +32,8 @@
|
|||
customWBAfrLow = scalar, U16, ":1", 0.01, 0, 0, 25, 2
|
||||
customWBAfrHigh = scalar, U16, ":1", 0.01, 0, 0, 25, 2
|
||||
|
||||
crankPrime_tempC = array, S16, [10], "Cー", 0.1, 0, -40, 240, 1, noMsqSave
|
||||
crankPrime_tempF = array, S16, [10], "Fー", 0.1, 0, -40, 240, 1, noMsqSave
|
||||
crankPrime_tempC = array, S16, [10], "C<EFBFBD>", 0.1, 0, -40, 240, 1, noMsqSave
|
||||
crankPrime_tempF = array, S16, [10], "F<EFBFBD>", 0.1, 0, -40, 240, 1, noMsqSave
|
||||
|
||||
outputPinMenuView = bits, U08, [0:1], "Output pins 1 - 2", "Output Pins 3 - 4", "OutPut Pins 1,2,3,4", "INVALID"
|
||||
|
43
src/msq.php
43
src/msq.php
|
@ -48,29 +48,38 @@ class MSQ
|
|||
public function parseMSQ($xml, &$engine, &$metadata)
|
||||
{
|
||||
$html = array();
|
||||
if (DEBUG) debug('<div class="debug">Parsing MSQ...</div>');
|
||||
if (DEBUG) debug('Parsing XML...');
|
||||
$errorCount = 0; //Keep track of how many things go wrong.
|
||||
|
||||
libxml_use_internal_errors(true);
|
||||
$msq = simplexml_load_string($xml);
|
||||
|
||||
if ($msq)
|
||||
{
|
||||
if ($msq === false) {
|
||||
error("Failed to parse XML.");
|
||||
foreach(libxml_get_errors() as $error) {
|
||||
error($error->message);
|
||||
}
|
||||
|
||||
$html['header'] = '<div class="error">Unable to parse MSQ.</div>';
|
||||
} else if ($msq) {
|
||||
$msqHeader = '<div class="info">';
|
||||
$msqHeader .= '<div style="float: right;"><a title="Download MSQ File" href="download.php?msq=' . $_GET['msq'] . '">💾</a></div>';
|
||||
$msqHeader .= "<div>Format Version: " . $msq->versionInfo['fileFormat'] . "</div>";
|
||||
$msqHeader .= "<div>MS Signature: " . $msq->versionInfo['signature'] . "</div>";
|
||||
$msqHeader .= "<div>Tuning SW: " . $msq->bibliography['author'] . "</div>";
|
||||
$msqHeader .= "<div>Date: " . $msq->bibliography['writeDate'] . "</div>";
|
||||
$msqHeader .= "<div><a href='download.php?msq=" . $_GET['msq'] . "'>💾</a></div>";
|
||||
$msqHeader .= '</div>';
|
||||
|
||||
$sig = $msq->versionInfo['signature'];
|
||||
$sigString = $sig;
|
||||
$msqMap = INI::getConfig($sig);
|
||||
|
||||
if ($msqMap == null)
|
||||
{
|
||||
$msqHeader .= "<div class=\"error\">Unable to load the corresponding configuration file for that MSQ. Please file a bug requesting: $sig[0]/$sig[1]</div>";
|
||||
$issueTitle = urlencode("INI Request: $sigString");
|
||||
$msqHeader .= '<div class="error">Unable to load the corresponding configuration file for that MSQ. Please <a href="https://github.com/nearwood/msqur/issues/new?title=' . $issueTitle . '">file a bug!</a></div>';
|
||||
$html['msqHeader'] = $msqHeader;
|
||||
return $html;
|
||||
return $html; //TODO Signal caller to skip engine/metadata updates
|
||||
}
|
||||
|
||||
$html['msqHeader'] = $msqHeader;
|
||||
|
@ -99,10 +108,10 @@ class MSQ
|
|||
{
|
||||
if (in_array($curve['id'], $this->msq_curve_blacklist))
|
||||
{
|
||||
if (DEBUG) debug('<div class="debug">Skipping curve: ' . $curve['id'] . '</div>');
|
||||
if (DEBUG) debug('Skipping curve: ' . $curve['id']);
|
||||
continue;
|
||||
}
|
||||
else if (DEBUG) debug('<div class="debug">Curve: ' . $curve['id'] . '</div>');
|
||||
else if (DEBUG) debug('Curve: ' . $curve['id']);
|
||||
|
||||
//id is just for menu (and our reference)
|
||||
//need to find xBin (index 0, 1 is the live meatball variable)
|
||||
|
@ -123,14 +132,14 @@ class MSQ
|
|||
$yAxis = preg_split("/\s+/", trim($yBins));
|
||||
$html["curves"] .= $this->msqTable2D($curve, $curve['xMin'], $curve['xMax'], $xAxis, $curve['yMin'], $curve['yMax'], $yAxis, $help);
|
||||
}
|
||||
else if (DEBUG) debug('<div class="debug">Missing/unsupported curve information: ' . $curve['id'] . '</div>');
|
||||
else if (DEBUG) debug('Missing/unsupported curve information: ' . $curve['id']);
|
||||
}
|
||||
|
||||
$html["pretables"] = '<div class="group-header">3D Tables</div>';;
|
||||
$html["tables"] = "";
|
||||
foreach ($tables as $table)
|
||||
{
|
||||
if (DEBUG) debug('<div class="debug">Table: ' . $table['id'] . '</div>');
|
||||
if (DEBUG) debug('Table: ' . $table['id']);
|
||||
|
||||
$help = NULL;
|
||||
if (array_key_exists('topicHelp', $table))
|
||||
|
@ -148,7 +157,7 @@ class MSQ
|
|||
$zData = preg_split("/\s+/", trim($zBins));//, PREG_SPLIT_NO_EMPTY); //, $limit);
|
||||
$html["tables"] .= $this->msqTable3D($table, $xAxis, $yAxis, $zData, $help);
|
||||
}
|
||||
else if (DEBUG) debug('<div class="debug">Missing/unsupported table information: ' . $table['id'] . '</div>');
|
||||
else if (DEBUG) debug('Missing/unsupported table information: ' . $table['id']);
|
||||
}
|
||||
|
||||
$html["preconstants"] = '<div class="group-header">Constants</div>';
|
||||
|
@ -159,13 +168,13 @@ class MSQ
|
|||
|
||||
$value = $this->findConstant($msq, $key);
|
||||
|
||||
//if (DEBUG) debug("<div class=\"debug\">Trying $key for engine data</div>");
|
||||
//if (DEBUG) debug("Trying $key for engine data");
|
||||
if ($value !== NULL)
|
||||
{
|
||||
$value = trim($value, '"');
|
||||
if (array_key_exists($key, $engineSchema))
|
||||
{
|
||||
if (DEBUG) debug("<div class=\"debug\">Found engine data: $key => $value</div>");
|
||||
if (DEBUG) debug(" $value");
|
||||
$engine[$key] = $value;
|
||||
}
|
||||
|
||||
|
@ -176,10 +185,6 @@ class MSQ
|
|||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$html['header'] = '<div class="error">Unable to parse tune.</div>';
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
@ -237,7 +242,7 @@ class MSQ
|
|||
|
||||
//var_export($curve);
|
||||
|
||||
//if (DEBUG) debug('<div class="debug">Formatting curve: ' . $curve['id'] . '</div>');
|
||||
//if (DEBUG) debug('Formatting curve: ' . $curve['id']);
|
||||
|
||||
$dataCount = count($xAxis);
|
||||
if ($dataCount !== count($yAxis))
|
||||
|
@ -279,7 +284,7 @@ class MSQ
|
|||
$rows = count($yAxis);
|
||||
$cols = count($xAxis);
|
||||
|
||||
//if (DEBUG) debug('<div class="debug">Formatting table: ' . $table['id'] . '</div>');
|
||||
//if (DEBUG) debug('Formatting table: ' . $table['id']);
|
||||
|
||||
$dataCount = count($zBins);
|
||||
if ($dataCount !== $rows * $cols)
|
||||
|
|
|
@ -141,7 +141,7 @@ class Msqur
|
|||
public function view($id)
|
||||
{
|
||||
$this->header();
|
||||
if (DEBUG) debug('<div class="debug">Load MSQ: ' . $id . '</div>');
|
||||
if (DEBUG) debug('Load MSQ: ' . $id);
|
||||
//Get cached HTML and display it, or reparse and display (in order)
|
||||
$html = $this->getMSQ($id);
|
||||
if ($html !== null)
|
||||
|
@ -154,6 +154,7 @@ class Msqur
|
|||
$engine = array();
|
||||
$metadata = array();
|
||||
$xml = $this->db->getXML($id);
|
||||
if ($xml !== null) {
|
||||
$groupedHtml = $msq->parseMSQ($xml, $engine, $metadata);
|
||||
$this->db->updateMetadata($id, $metadata);
|
||||
$this->db->updateEngine($id, $engine);
|
||||
|
@ -170,6 +171,7 @@ class Msqur
|
|||
$this->db->updateCache($id, $html);
|
||||
}
|
||||
}
|
||||
}
|
||||
//TODO else show 404
|
||||
|
||||
echo $html;
|
||||
|
|
|
@ -67,8 +67,10 @@ function checkUploads($files)
|
|||
|
||||
//Get and check mime types (ignoring provided ones)
|
||||
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||
if ($finfo->file($file['tmp_name']) != "application/xml")
|
||||
$mimeType = $finfo->file($file['tmp_name']);
|
||||
if ($mimeType != "application/xml" && $mimeType != "text/xml")
|
||||
{
|
||||
if (DEBUG) warn('File: ' . $file['tmp_name'] . ': Invalid MIME type ' . $mimeType);
|
||||
unset($files[$index]);
|
||||
continue;
|
||||
}
|
||||
|
@ -95,7 +97,7 @@ if (isset($_POST['upload']) && isset($_FILES))
|
|||
else
|
||||
echo '<div class="info">' . count($files) . ' files were uploaded.</div>';
|
||||
|
||||
if (DEBUG) debug('<div class="debug">Adding engine: ' . $_POST['make'] . ', ' . $_POST['code'] . ', ' . $_POST['displacement'] . ', ' . $_POST['compression'] . ', ' . $_POST['aspiration'] . '</div>');
|
||||
if (DEBUG) debug('Adding engine: ' . $_POST['make'] . ', ' . $_POST['code'] . ', ' . $_POST['displacement'] . ', ' . $_POST['compression'] . ', ' . $_POST['aspiration']);
|
||||
|
||||
$engineid = $msqur->addEngine($_POST['make'], $_POST['code'], $_POST['displacement'], $_POST['compression'], $_POST['aspiration']);
|
||||
$fileList = $msqur->addMSQs($files, $engineid);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
</div>
|
||||
<?php
|
||||
if (!LOCAL)
|
||||
if (!DEBUG)
|
||||
{
|
||||
?>
|
||||
<script>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="view/msqur.css" />
|
||||
<?php
|
||||
if (LOCAL) { ?>
|
||||
if (DEBUG) { ?>
|
||||
<script src="view/lib/jquery.min.js"></script>
|
||||
<link rel="stylesheet" href="view/lib/jquery-ui.css" />
|
||||
<script src="view/lib/jquery-ui.min.js"></script>
|
||||
|
|
|
@ -14,25 +14,25 @@ GNU General Public License for more details.
|
|||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
* {
|
||||
body, button {
|
||||
font-family: "Verdana", sans-serif;
|
||||
}
|
||||
|
||||
div.error {
|
||||
color: red;
|
||||
color: darkred;
|
||||
}
|
||||
|
||||
div.warn, div.warning {
|
||||
color: orange;
|
||||
color: darkorange;
|
||||
}
|
||||
|
||||
div.info {
|
||||
color: green;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
div.debug {
|
||||
font-family: monospace !important;
|
||||
color: dark-gray;
|
||||
color: black;
|
||||
}
|
||||
|
||||
div#navigation {
|
||||
|
|
|
@ -1,37 +1,23 @@
|
|||
<div class="info" id="splash">
|
||||
<h3>Overview</h3>
|
||||
<div>View MSQ files online. Upload your Tuner Studio .msq files to view and share them.</div>
|
||||
<p>View MSQ files online. Upload your Tuner Studio .msq files to view and share them.</p>
|
||||
<h3>How to use this site:</h3>
|
||||
<div>
|
||||
<section>
|
||||
<h4>To add your MSQ file to share and view online:</h4>
|
||||
<div>Click on the upload button:</div>
|
||||
<div><img class="tutorial" id="tutorial1" src="view/img/tutorial1.png"/></div>
|
||||
</div>
|
||||
<div>
|
||||
Click the "Browse" button (or area) and select the MSQ file you want to upload. Note that you can upload more than one at a time, using the same engine information for each:
|
||||
<div><img class="tutorial" id="tutorial2" src="view/img/tutorial2.png"/></div>
|
||||
</div>
|
||||
<div>
|
||||
Now enter in the engine information for the MSQ file(s):
|
||||
<div><img class="tutorial" id="tutorial3" src="view/img/tutorial3.png"/></div>
|
||||
</div>
|
||||
<div>
|
||||
Click upload, and you should see links to your MSQ file(s) if successful. You can copy (right-click and select "Copy Link Location" or something like that), or click on it to view the file.
|
||||
<div><img class="tutorial" id="tutorial4" src="view/img/tutorial4.png"/></div>
|
||||
</div>
|
||||
<div>
|
||||
Now you can browse the various sections of your MSQ file:
|
||||
<div><img class="tutorial" id="tutorial5" src="view/img/tutorial5.png"/></div>
|
||||
</div>
|
||||
<div>
|
||||
Since I don't have the configuration files for every single Megasquirt version, you might see an error like this:
|
||||
<div><img class="tutorial" id="tutorial6" src="view/img/tutorial6.png"/></div>
|
||||
</div>
|
||||
<div>
|
||||
If so, please email me (nearwood@gmail.com) with your "INI" file (msqur tries to give you a hint as to the version it needs).
|
||||
</div>
|
||||
<p>Click on the upload button:</p>
|
||||
<img class="tutorial" id="tutorial1" src="view/img/tutorial1.png"/>
|
||||
<p>Click the "Browse" button (or area) and select the MSQ file you want to upload. Note that you can upload more than one at a time, using the same engine information for each:</p>
|
||||
<img class="tutorial" id="tutorial2" src="view/img/tutorial2.png"/>
|
||||
<p>Now enter in the engine information for the MSQ file(s):</p>
|
||||
<img class="tutorial" id="tutorial3" src="view/img/tutorial3.png"/>
|
||||
<p>Click upload, and you should see links to your MSQ file(s) if successful. You can copy (right-click and select "Copy Link Location" or something like that), or click on it to view the file.</p>
|
||||
<img class="tutorial" id="tutorial4" src="view/img/tutorial4.png"/>
|
||||
<p>Now you can browse the various sections of your MSQ file:</p>
|
||||
<img class="tutorial" id="tutorial5" src="view/img/tutorial5.png"/>
|
||||
<p>Since I don't have the configuration files for every single Megasquirt version, you might see an error like this:</p>
|
||||
<img class="tutorial" id="tutorial6" src="view/img/tutorial6.png"/>
|
||||
</section>
|
||||
<p>If so, please email me (nearwood@gmail.com) with your "INI" file (msqur tries to give you a hint as to the version it needs).</p>
|
||||
<h3>Developers</h3>
|
||||
<div>
|
||||
Interested in contributing? Check out msqur's <a href="https://github.com/nearwood/msqur">repository</a>. Msqur's source is licensed under the GPL.
|
||||
</div>
|
||||
<p>Interested in contributing? Check out msqur's <a href="https://github.com/nearwood/msqur">repository</a>. Msqur's source is licensed under the GPL.</p>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue