Compare commits

...

4 Commits

10 changed files with 602 additions and 68 deletions

14
db/ddd_db.sql Normal file
View File

@ -0,0 +1,14 @@
-- Create the database
CREATE DATABASE IF NOT EXISTS ddd_db;
-- Switch to the database
USE ddd_db;
-- Create the scores table
CREATE TABLE IF NOT EXISTS scores (
id INT AUTO_INCREMENT PRIMARY KEY,
Name VARCHAR(255) NOT NULL,
Score BIGINT NOT NULL,
Mode VARCHAR(50) NOT NULL,
Bosses TEXT
);

BIN
db/ddd_db.sqlite Normal file

Binary file not shown.

View File

@ -1,34 +1,74 @@
<?php
echo file_get_contents("ddd_source.html");
// Load the configuration file
$configFilePath = 'config.cfg';
$config = parse_ini_file($configFilePath, true);
// Parse the config file
$config = parse_ini_file('config.cfg', true);
// Include the MySQL connection script from the config file
include($config['mysql']['mysqlconnect_path']);
// Database connection
$db = null;
$mode = $_REQUEST['mode'];
if (isset($config['options']) && isset($config['options']['database'])) {
$database = $config['options']['database'];
if ($mode == "DX") {
echo "<img src='ddddxhs_files/top100dx.png' alt='Top 100 DX Mode'>";
$query = "SELECT * FROM ddd_db.scores WHERE ddd_db.scores.Mode = 'DX' ORDER BY ddd_db.scores.Score DESC LIMIT 100";
} elseif ($mode == "EX") {
echo "<img src='ddddxhs_files/top100ex.png' alt='Top 100 EX Mode'>";
$query = "SELECT * FROM ddd_db.scores WHERE ddd_db.scores.Mode = 'EX' ORDER BY ddd_db.scores.Score DESC LIMIT 100";
if ($database === 'mysql') {
$mysql_host = $config['mysql']['host'];
$mysql_username = $config['mysql']['username'];
$mysql_password = $config['mysql']['password'];
$mysql_database = $config['mysql']['database'];
$mysqli = new mysqli($mysql_host, $mysql_username, $mysql_password, $mysql_database);
if ($mysqli->connect_error) {
die("Connection failed: " . $mysqli->connect_error);
} else {
$db = $mysqli;
}
} elseif ($database === 'sqlite') {
$db_path = 'db/ddd_db.sqlite';
if (file_exists($db_path)) {
try {
$db = new SQLite3($db_path);
} catch (Exception $e) {
die("Failed to open the SQLite database: " . $e->getMessage());
}
} else {
die("Database file does not exist: " . $db_path);
}
} else {
die("Unsupported database type specified in config.cfg");
}
} else {
die("Database configuration not found in config.cfg");
}
?>
<?php
$result = $conn->query($query);
// Get mode from request
$mode = $_REQUEST['mode'] ?? 'DX';
if ($result->num_rows > 0) {
// output data of each row
// Construct query based on mode
if ($mode == "DX") {
echo "<img src='/ddddxhs_files/top100dx.png' alt='Top 100 DX Mode'>";
$query = "SELECT * FROM scores WHERE Mode = 'DX' ORDER BY Score DESC LIMIT 100";
} elseif ($mode == "EX") {
echo "<img src='/ddddxhs_files/top100ex.png' alt='Top 100 EX Mode'>";
$query = "SELECT * FROM scores WHERE Mode = 'EX' ORDER BY Score DESC LIMIT 100";
} else {
die("Invalid mode specified");
}
// Execute query and fetch results
$result = null;
if ($db instanceof SQLite3) {
$result = $db->query($query);
} elseif ($db instanceof mysqli) {
$result = $db->query($query);
}
if ($result) {
$counter = 1;
echo "<div class='top3'><ol>";
echo "<div class='top3'>";
while ($row = $result->fetch_assoc()) {
while ($row = ($db instanceof SQLite3) ? $result->fetchArray(SQLITE3_ASSOC) : $result->fetch_assoc()) {
$output = "<span id='player'>" . $counter . " " . $row["Name"] . "</span>" . "&nbsp;&nbsp;&nbsp;" .
"<span id='score'>" . number_format($row["Score"]) . "</span>";
@ -46,13 +86,12 @@ if ($result->num_rows > 0) {
// Display an image for each boss name
foreach ($bosses as $boss) {
$bossImage = trim(strtolower($boss)) . ".png";
if($bossImage != ".png"){
if ($bossImage != ".png") {
echo "<div class='bossImageWrapper'>";
echo "<img id='boss_image' src='boss_images/" . $bossImage . "' alt='" . $boss . "' title='" . $boss . "'>";
echo "<img id='boss_image' src='/boss_images/" . $bossImage . "' alt='" . $boss . "' title='" . $boss . "'>";
echo "<div class='bossLabel'>" . $boss . "</div>";
echo "</div>";
}
}
echo "</div>";
@ -63,14 +102,18 @@ if ($result->num_rows > 0) {
$counter++;
}
echo "</ol>";
echo "</ol></div>";
} else {
echo "<br>0 results";
}
$conn->close();
// Close database connection
if ($db instanceof SQLite3) {
$db->close();
} elseif ($db instanceof mysqli) {
$db->close();
}
?>
<br>
<a class='ddd' href='ddd_index.php'>Back</a>

View File

@ -1,13 +1,30 @@
[options]
database="sqlite" #sqlite or mysql
# Connect to your existing mysql database, we will create a single "scores" table with /db/ddd_db.sql
[mysql]
mysqlconnect_path="/var/config/mysqlconnect.php"
host="localhost"
username="user"
database="database"
password="password"
username="ddd_db_admin"
database="ddd_db"
password="dbd_db_password"
#To use this file, run the following
[sqlite]
sqlite_password="CHANGE_ME!" #This is used to manage the sqlite database from a ui at /update_db.php
# To use this file, run setup.sh with bash
# or you can use
#
# cp example.config.cfg config.cfg
# mkdir /var/config
# cp example.mysqlconnect.php
# Change the password, do not remove the quotes
# Access the admin page at yourwebsite.com/update_db.php
#
# If using sqlite (default)
# you can use the existing empty ddd_db.sqlite file
# and manually enter the sqlite_password into the config.cfg file
# for managing the database from /update_db.php
#
# If using mysql
# cp example.mysqlconnect.php /var/config/mysqlconnect.php
# chmod 755 /var/config/mysqlconnect.php
#
# the mysqlconnect.php is moved to /var/config to be outside the scope of the end user,
# you can place it elsewhere if you desire, be sure to update the mysqlconnect_path variable

View File

@ -1,5 +1,5 @@
<!--Load Original Source Files-->
<?php echo file_get_contents("ddd_source.html"); ?>
<?php echo file_get_contents('ddd_source.html'); ?>
<center>
<br><br><br><br>
<ol class='scoreList'>
@ -7,6 +7,10 @@
<img src="ddddxhs_files/download.png" alt="DOWNLOAD DASH-DA-DASH DX" title="DOWNLOAD DASH-DA-DASH DX">
</a>
<br>
<li class='even ddd' id='player' style="font-size:12px">
SHA256 Checksum: 67ea139fa4721a05a26094d7bba9f3386c8473d7d7450ef762da7bf19ef921e5
</li>
<br>
<br>
<br>
<img class='center' src="ddddxhs_files/features.png" alt="FEATURES DASH-DA-DASH DX" title="FEATURES DASH-DA-DASH DX">

45
initialize_sqlite.php Executable file
View File

@ -0,0 +1,45 @@
<?php
// Path to the SQLite database file
$db_path = 'db/ddd_db.sqlite';
// Check if the 'db' directory exists, if not, create it
if (!is_dir('db')) {
if (!mkdir('db', 0777, true)) {
die('Failed to create directories...');
}
}
// Check if the database file exists
if (!file_exists($db_path)) {
try {
// Create a new SQLite3 database file
$db = new SQLite3($db_path);
// Create the 'scores' table
$createTableQuery = "
CREATE TABLE IF NOT EXISTS scores (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
Name TEXT NOT NULL,
Score INTEGER NOT NULL,
Mode TEXT NOT NULL,
Bosses TEXT NOT NULL
);
";
if ($db->exec($createTableQuery)) {
echo "Database and table created successfully.<br>";
} else {
echo "Error creating table: " . $db->lastErrorMsg() . "<br>";
}
// Close the database connection
$db->close();
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "<br>";
} catch (Error $e) {
echo 'Caught error: ', $e->getMessage(), "<br>";
}
} else {
echo "Database already exists.<br>";
}
?>

View File

@ -1,12 +1,26 @@
<?php
echo file_get_contents("ddd_source.html");
// Load the configuration file
$configFilePath = 'config.cfg';
$config = parse_ini_file($configFilePath, true);
// Parse the config file
$config = parse_ini_file('config.cfg', true);
// Include the MySQL connection script from the config file
include($config['mysql']['mysqlconnect_path']);
// Check if the [options] section and database entry exist
if (isset($config['options']) && isset($config['options']['database'])) {
$database = $config['options']['database'];
if ($database === 'mysql') {
// Include the MySQL connection script from the config file
include($config['mysql']['mysqlconnect_path']);
} elseif ($database === 'sqlite') {
// Code for SQLite
$db_path = 'db/ddd_db.sqlite';
$db = new SQLite3($db_path);
} else {
echo "Unsupported database type specified in config.cfg\n";
}
} else {
echo "Database configuration not found in config.cfg\n";
}
$b64 = $_POST["gamePass"];
$decode = base64_decode($b64, true);
@ -25,18 +39,21 @@ echo $name . "<br>" . $score . "<br>" . $mode . "<br>";
echo "Bosses: " . $bossNames . "<br>";
// Prepare an SQL statement
$stmt = $conn->prepare("INSERT INTO ddd_db.scores (Name, Score, Mode, Bosses) VALUES (?, ?, ?, ?)");
$stmt->bind_param("siss", $name, $score, $mode, $bossNames);
$stmt = $db->prepare("INSERT INTO scores (Name, Score, Mode, Bosses) VALUES (?, ?, ?, ?)");
$stmt->bindValue(1, $name, SQLITE3_TEXT);
$stmt->bindValue(2, $score, SQLITE3_INTEGER);
$stmt->bindValue(3, $mode, SQLITE3_TEXT);
$stmt->bindValue(4, $bossNames, SQLITE3_TEXT);
// Execute the statement
if ($stmt->execute()) {
echo "New record created successfully";
} else {
echo "Error: " . $stmt->error;
echo "Error: " . $db->lastErrorMsg();
}
$stmt->close();
$conn->close();
$db->close();
?>
<br>
<a class='ddd' href='ddd_index.php'>Back</a>

65
readme.md Executable file
View File

@ -0,0 +1,65 @@
# DASH-DA-DASH DX Fan Patch
This is a fan patch of DASH-DA-DASH DX that has been updated version of the source code available from [mulengine.itch.io/ddddx](https://mulengine.itch.io/ddddx). The original high score board is no longer online, so now it is hosted at [ddd.dylanbanta.com](https://ddd.dylanbanta.com/) with a functional scoreboard available [here](https://ddd.dylanbanta.com/ddd_index.php). The project can also be downloaded so that users can self-host their own copy of the site as a private scoreboard.
The EXE file can be found here: [ddddxhs_files/DASH-DA-DASH DX (2023 Fan Patch).exe](https://git.dylanbanta.com/Dylan/Dash-Da-Dash-DX-2023-Fan-Patch-High-Scores/raw/branch/master/ddddxhs_files/DASH-DA-DASH%20DX%20%282023%20Fan%20Patch%29.exe) (SHA256 Checksum: 67ea139fa4721a05a26094d7bba9f3386c8473d7d7450ef762da7bf19ef921e5)
The updated .mfa source file can be found here: [ddddxhs_files/DASH-DA-DASH DX (2023 Fan Patch).mfa](https://git.dylanbanta.com/Dylan/Dash-Da-Dash-DX-2023-Fan-Patch-High-Scores/raw/branch/master/ddddxhs_files/DASH-DA-DASH%20DX%20%282023%20Fan%20Patch%29.mfa)
## Project Overview
This project was made for fun, but it has been repurposed to showcase my usage of git, including creating branches from the original master branch to new sqlite and mysql branches, and then finally merging them back into the master branch. Plus it was a fun challenge to migrate the system to sqlite.
The php and html is not great, but when I created the project I was focused on recreating the website from way back machine with authenticity. I didn't take it too seriously since it was a project I was doing for myself. Perhaps I'll come back and clean it up one day.
## Setup Instructions
To set up the project, you can run the `setup.sh` file or follow the manual instructions in the `example.config.cfg` file.
## Example Configuration File
```ini
[options]
database="sqlite" #sqlite or mysql
# Connect to your existing mysql database, we will create a single "scores" table with /db/ddd_db.sql
[mysql]
mysqlconnect_path="/var/config/mysqlconnect.php"
host="localhost"
username="ddd_db_admin"
database="ddd_db"
password="dbd_db_password"
[sqlite]
sqlite_password="CHANGE_ME!" #This is used to manage the sqlite database from a ui at /update_db.php
# To use this file, run setup.sh with bash
# or you can use
#
# cp example.config.cfg config.cfg
#
# If using sqlite (default)
# you can use the existing empty ddd_db.sqlite file
# and manually enter the sqlite_password into the config.cfg file
# for managing the database from /update_db.php
#
# If using mysql
# cp example.mysqlconnect.php /var/config/mysqlconnect.php
# chmod 755 /var/config/mysqlconnect.php
#
# the mysqlconnect.php is moved to /var/config to be outside the scope of the end user,
# you can place it elsewhere if you desire, be sure to update the mysqlconnect_path variable
```
## Credits
- [Wiki](https://lapfoxtrax.fandom.com/wiki/DASH-DA-DASH_DX)
- [itch.io (Has version 1.14 without 2023 Fan Patch)](https://mulengine.itch.io/ddddx)
- [DDDDX Archive](https://web.archive.org/web/20130821151756/http://renard.teknolust.org/ddd/)
- [Scores Archive](https://web.archive.org/web/20130710170054/http://renard.teknolust.org/ddd/score/index.php)
- [Current Website](https://halleylabs.com/)
- [Current Website Main](https://heckscaper.com/main.html)
- [Version 1.12 from WayBackMachine Archive](https://web.archive.org/web/20120615011611if_/http://renard.teknolust.org/ddd/dddxv12install.exe)
- [2023 Fan Patch .mfa Source File for Clickteam Fusion 2.5](https://ddd.dylanbanta.com/ddddxhs_files/DASH-DA-DASH%20DX%20(2023%20Fan%20Patch).mfa)

66
setup.sh Executable file
View File

@ -0,0 +1,66 @@
#!/bin/bash
# Function to create config.cfg based on user input
create_config_file() {
cat <<EOF > config.cfg
[options]
database="$1"
[mysql]
mysqlconnect_path="/var/config/mysqlconnect.php"
host="$2"
username="$3"
database="$4"
password="$5"
[sqlite]
sqlite_password="$6"
EOF
}
# Prompt user for database type
echo "Do you want to use sqlite or mysql? (default: sqlite)"
read -t 5 -p "Enter your choice: " db_choice
# Set default value if no input is given
db_choice=${db_choice:-sqlite}
if [ "$db_choice" = "mysql" ]; then
# Prompt user for MySQL options
read -p "Enter MySQL host (default: localhost): " mysql_host
mysql_host=${mysql_host:-localhost}
read -p "Enter MySQL username (default: dbUser): " mysql_user
mysql_user=${mysql_user:-dbUser}
read -p "Enter MySQL database name (default: dbName): " mysql_db
mysql_db=${mysql_db:-dbName}
read -s -p "Enter MySQL password (default: dbPassword): " mysql_pass
mysql_pass=${mysql_pass:-dbPassword}
echo # Move to a new line after reading the password
# Create config.cfg for MySQL
create_config_file $db_choice $mysql_host $mysql_user $mysql_db $mysql_pass ""
# Create directory for mysql configuration
mkdir -p /var/config
cp example.mysqlconnect.php /var/config/mysqlconnect.php
chmod 755 /var/config/mysqlconnect.php
elif [ "$db_choice" = "sqlite" ]; then
# Prompt user for SQLite password
read -s -p "Enter SQLite password (default: CHANGE_ME!): " sqlite_pass
sqlite_pass=${sqlite_pass:-CHANGE_ME!}
echo # Move to a new line after reading the password
# Create config.cfg for SQLite
create_config_file $db_choice "" "" "" "" $sqlite_pass
# Create empty sqlite database
php initialize_sqlite.php
# Ensure the database directory and file have the correct permissions
chmod 777 db
chmod 777 config.cfg
chmod 666 db/ddd_db.sqlite
fi
echo "Setup complete. Configuration saved in config.cfg."

263
update_db.php Executable file
View File

@ -0,0 +1,263 @@
<?php
// Path to the SQLite database file
$db_path = 'db/ddd_db.sqlite';
$config_path = 'config.cfg';
// Load password from configuration file
$password = '';
if (file_exists($config_path)) {
$config = parse_ini_file($config_path);
if (isset($config['sqlite_password'])) {
$password = $config['sqlite_password'];
}
}
// Function to display an alert message
function displayAlert($message, $type) {
echo '<div class="alert alert-' . $type . '">' . $message . '</div>';
}
// Check if the 'db' directory exists, if not, create it
if (!is_dir('db')) {
if (!mkdir('db', 0777, true)) {
die('Failed to create directories...');
}
}
// Handle password submission
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['login_password'])) {
if ($_POST['login_password'] === $password) {
session_start();
$_SESSION['logged_in'] = true;
} else {
displayAlert('Invalid password.', 'danger');
}
}
// Start the session
session_start();
?>
<!DOCTYPE html>
<html>
<head>
<title>Add Score</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<?php if (!isset($_SESSION['logged_in']) || $_SESSION['logged_in'] !== true): ?>
<h1 class="mt-5">Enter Password to Access</h1>
<form action="" method="post" class="mt-4">
<div class="form-group">
<label for="login_password">Password:</label>
<input type="password" class="form-control" id="login_password" name="login_password" required>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<?php else: ?>
<h1 class="mt-5">Add a New Score</h1>
<?php
// Process form submission
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['action'])) {
if ($_POST['action'] == 'add' && isset($_POST['name']) && isset($_POST['score']) && isset($_POST['mode'])) {
if (file_exists($db_path)) {
try {
// Open the SQLite3 database file
$db = new SQLite3($db_path);
// Prepare the insert query
$insertQuery = $db->prepare('INSERT INTO scores (Name, Score, Mode, Bosses) VALUES (:name, :score, :mode, :bosses)');
$insertQuery->bindValue(':name', $_POST['name'], SQLITE3_TEXT);
$insertQuery->bindValue(':score', $_POST['score'], SQLITE3_INTEGER);
$insertQuery->bindValue(':mode', $_POST['mode'], SQLITE3_TEXT);
$insertQuery->bindValue(':bosses', $_POST['bosses'], SQLITE3_TEXT);
// Execute the query
if ($insertQuery->execute()) {
displayAlert('Score added successfully.', 'success');
} else {
displayAlert('Error adding score: ' . $db->lastErrorMsg(), 'danger');
}
// Close the database connection
$db->close();
} catch (Exception $e) {
displayAlert('Caught exception: ' . $e->getMessage(), 'danger');
} catch (Error $e) {
displayAlert('Caught error: ' . $e->getMessage(), 'danger');
}
} else {
displayAlert('Database does not exist.', 'warning');
}
}
if ($_POST['action'] == 'delete' && isset($_POST['delete_id'])) {
if (file_exists($db_path)) {
try {
// Open the SQLite3 database file
$db = new SQLite3($db_path);
// Prepare the delete query
$deleteQuery = $db->prepare('DELETE FROM scores WHERE ID = :id');
$deleteQuery->bindValue(':id', $_POST['delete_id'], SQLITE3_INTEGER);
// Execute the query
if ($deleteQuery->execute()) {
displayAlert('Score deleted successfully.', 'success');
} else {
displayAlert('Error deleting score: ' . $db->lastErrorMsg(), 'danger');
}
// Close the database connection
$db->close();
} catch (Exception $e) {
displayAlert('Caught exception: ' . $e->getMessage(), 'danger');
} catch (Error $e) {
displayAlert('Caught error: ' . $e->getMessage(), 'danger');
}
} else {
displayAlert('Database does not exist.', 'warning');
}
}
}
?>
<form action="" method="post" class="mt-4">
<div class="form-group">
<label for="name">Name:</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="form-group">
<label for="score">Score:</label>
<input type="number" class="form-control" id="score" name="score" required>
</div>
<div class="form-group">
<label for="mode">Mode:</label>
<input type="text" class="form-control" id="mode" name="mode" required>
</div>
<div class="form-group">
<label for="bosses">Bosses:</label>
<input type="text" class="form-control" id="bosses" name="bosses">
</div>
<input type="hidden" name="action" value="add">
<button type="submit" class="btn btn-primary">Add Score</button>
</form>
<h2 class="mt-5">Current Scores (DX)</h2>
<table class="table table-bordered mt-3">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Score</th>
<th>Mode</th>
<th>Bosses</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php
if (file_exists($db_path)) {
try {
// Open the SQLite3 database file
$db = new SQLite3($db_path);
// Query to fetch current scores for DX mode ordered by highest score
$result = $db->query("SELECT * FROM scores WHERE Mode = 'DX' ORDER BY Score DESC");
// Display the rows
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
echo "<tr>
<td>{$row['ID']}</td>
<td>{$row['Name']}</td>
<td>{$row['Score']}</td>
<td>{$row['Mode']}</td>
<td>{$row['Bosses']}</td>
<td>
<form action='' method='post' style='display:inline-block;'>
<input type='hidden' name='delete_id' value='{$row['ID']}'>
<input type='hidden' name='action' value='delete'>
<button type='submit' class='btn btn-danger btn-sm'>Delete</button>
</form>
</td>
</tr>";
}
// Close the database connection
$db->close();
} catch (Exception $e) {
displayAlert('Caught exception: ' . $e->getMessage(), 'danger');
} catch (Error $e) {
displayAlert('Caught error: ' . $e->getMessage(), 'danger');
}
} else {
echo '<tr><td colspan="6">No scores found. Database does not exist.</td></tr>';
}
?>
</tbody>
</table>
<h2 class="mt-5">Current Scores (EX)</h2>
<table class="table table-bordered mt-3">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Score</th>
<th>Mode</th>
<th>Bosses</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php
if (file_exists($db_path)) {
try {
// Open the SQLite3 database file
$db = new SQLite3($db_path);
// Query to fetch current scores for EX mode ordered by highest score
$result = $db->query("SELECT * FROM scores WHERE Mode = 'EX' ORDER BY Score DESC");
// Display the rows
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
echo "<tr>
<td>{$row['ID']}</td>
<td>{$row['Name']}</td>
<td>{$row['Score']}</td>
<td>{$row['Mode']}</td>
<td>{$row['Bosses']}</td>
<td>
<form action='' method='post' style='display:inline-block;'>
<input type='hidden' name='delete_id' value='{$row['ID']}'>
<input type='hidden' name='action' value='delete'>
<button type='submit' class='btn btn-danger btn-sm'>Delete</button>
</form>
</td>
</tr>";
}
// Close the database connection
$db->close();
} catch (Exception $e) {
displayAlert('Caught exception: ' . $e->getMessage(), 'danger');
} catch (Error $e) {
displayAlert('Caught error: ' . $e->getMessage(), 'danger');
}
} else {
echo '<tr><td colspan="6">No scores found. Database does not exist.</td></tr>';
}
?>
</tbody>
</table>
<?php endif; ?>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</body>
</html>