SMF 2.0 - Enable PHP7.2+ Compatibility version 1.5

Started by Chen Zhen, February 24, 2020, 08:16:43 PM

Previous topic - Next topic

Chen Zhen

February 24, 2020, 08:16:43 PM Last Edit: February 24, 2020, 08:46:26 PM by Chen Zhen
SMF 2.0 - Enable PHP7.2+ Compatibility version 1.5
Version 1.5 released February 24/2020.

This update includes fixes & omissions for SMF 2.0.17+ to facilitate proper PHP 7.2+ compatibility.

Still pending:
Changes to ../Sources/Subs-Db-sqlite.php to replace sqlite_create_function with modern standard.
ie. PDO::sqliteCreateFunction or SQLite3::createFunction





Please uninstall and delete any previous versions prior to using this version.
Report any issues with this modification in this thread.

Enjoy.


Available from the download section.

DOWNLOAD MODIFICATION

ManuDevil

Hello !

Thank you for your plugin.
I am running SMF 2.0.15, and when trying to install your plugin, there is a test failure on ./Sources/Subs.php.
Here is my original source code for this file :

array(
 'tag' => 'code',
 'type' => 'unparsed_content',
 'content' => '<div class="codeheader">' . $txt['code'] . ': <a href="javascript:void(0);" onclick="return smfSelectText(this);" class="codeoperation">' . $txt['code_select'] . '</a></div>' . ($context['browser']['is_gecko'] || $context['browser']['is_opera'] ? '<pre style="margin: 0; padding: 0;">' : '') . '<code class="bbc_code">$1</code>' . ($context['browser']['is_gecko'] || $context['browser']['is_opera'] ? '</pre>' : ''),
 // !!! Maybe this can be simplified?
 'validate' => isset($disabled['code']) ? null : create_function('&$tag, &$data, $disabled', '
 global $context;

 if (!isset($disabled[\'code\']))
 {
 $php_parts = preg_split(\'~(&lt;\?php|\?&gt;)~\', $data, -1, PREG_SPLIT_DELIM_CAPTURE);

 for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++)
 {
 // Do PHP code coloring?
 if ($php_parts[$php_i] != \'&lt;?php\')
 continue;

 $php_string = \'\';
 while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != \'?&gt;\')
 {
 $php_string .= $php_parts[$php_i];
 $php_parts[$php_i++] = \'\';
 }
 $php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]);
 }

 // Fix the PHP code stuff...
 $data = str_replace("<pre style=\"display: inline;\">\t</pre>", "\t", implode(\'\', $php_parts));

 // Older browsers are annoying, aren\'t they?
 if ($context[\'browser\'][\'is_ie4\'] || $context[\'browser\'][\'is_ie5\'] || $context[\'browser\'][\'is_ie5.5\'])
 $data = str_replace("\t", "<pre style=\"display: inline;\">\t</pre>", $data);
 else
 $data = str_replace("\t", "<span style=\"white-space: pre;\">\t</span>", $data);

 // Recent Opera bug requiring temporary fix. &nsbp; is needed before </code> to avoid broken selection.
 if ($context[\'browser\'][\'is_opera\'])
 $data .= \'&nbsp;\';
 }'),
 'block_level' => true,
 ),
 array(
 'tag' => 'code',
 'type' => 'unparsed_equals_content',
 'content' => '<div class="codeheader">' . $txt['code'] . ': ($2) <a href="#" onclick="return smfSelectText(this);" class="codeoperation">' . $txt['code_select'] . '</a></div>' . ($context['browser']['is_gecko'] || $context['browser']['is_opera'] ? '<pre style="margin: 0; padding: 0;">' : '') . '<code class="bbc_code">$1</code>' . ($context['browser']['is_gecko'] || $context['browser']['is_opera'] ? '</pre>' : ''),
 // !!! Maybe this can be simplified?
 'validate' => isset($disabled['code']) ? null : create_function('&$tag, &$data, $disabled', '
 global $context;

 if (!isset($disabled[\'code\']))
 {
 $php_parts = preg_split(\'~(&lt;\?php|\?&gt;)~\', $data[0], -1, PREG_SPLIT_DELIM_CAPTURE);

 for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++)
 {
 // Do PHP code coloring?
 if ($php_parts[$php_i] != \'&lt;?php\')
 continue;

 $php_string = \'\';
 while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != \'?&gt;\')
 {
 $php_string .= $php_parts[$php_i];
 $php_parts[$php_i++] = \'\';
 }
 $php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]);
 }

 // Fix the PHP code stuff...
 $data[0] = str_replace("<pre style=\"display: inline;\">\t</pre>", "\t", implode(\'\', $php_parts));

 // Older browsers are annoying, aren\'t they?
 if ($context[\'browser\'][\'is_ie4\'] || $context[\'browser\'][\'is_ie5\'] || $context[\'browser\'][\'is_ie5.5\'])
 $data[0] = str_replace("\t", "<pre style=\"display: inline;\">\t</pre>", $data[0]);
 else
 $data[0] = str_replace("\t", "<span style=\"white-space: pre;\">\t</span>", $data[0]);

 // Recent Opera bug requiring temporary fix. &nsbp; is needed before </code> to avoid broken selection.
 if ($context[\'browser\'][\'is_opera\'])
 $data[0] .= \'&nbsp;\';
 }'),
 'block_level' => true,
 ),
 array(
 'tag' => 'color',
 'type' => 'unparsed_equals',
 'test' => '(#[\da-fA-F]{3}|#[\da-fA-F]{6}|[A-Za-z]{1,20}|rgb\(\d{1,3}, ?\d{1,3}, ?\d{1,3}\))\]',
 'before' => '<span style="color: $1;" class="bbc_color">',
 'after' => '</span>',
 ),
 array(
 'tag' => 'email',
 'type' => 'unparsed_content',
 'content' => '<a href="mailto:$1" class="bbc_email">$1</a>',
 // !!! Should this respect guest_hideContacts?
 'validate' => create_function('&$tag, &$data, $disabled', '$data = strtr($data, array(\'<br />\' => \'\'));'),
 ),
 array(
 'tag' => 'email',
 'type' => 'unparsed_equals',
 'before' => '<a href="mailto:$1" class="bbc_email">',
 'after' => '</a>',
 // !!! Should this respect guest_hideContacts?
 'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
 'disabled_after' => ' ($1)',
 ),
 array(
 'tag' => 'flash',
 'type' => 'unparsed_commas_content',
 'test' => '\d+,\d+\]',
 'content' => ($context['browser']['is_ie'] && !$context['browser']['is_mac_ie'] ? '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="$2" height="$3"><param name="movie" value="$1" /><param name="play" value="true" /><param name="loop" value="true" /><param name="quality" value="high" /><param name="AllowScriptAccess" value="never" /><embed src="$1" width="$2" height="$3" play="true" loop="true" quality="high" AllowScriptAccess="never" /><noembed><a href="$1" target="_blank" class="new_win">$1</a></noembed></object>' : '<embed type="application/x-shockwave-flash" src="$1" width="$2" height="$3" play="true" loop="true" quality="high" AllowScriptAccess="never" /><noembed><a href="$1" target="_blank" class="new_win">$1</a></noembed>'),
 'validate' => create_function('&$tag, &$data, $disabled', '
 if (isset($disabled[\'url\']))
 $tag[\'content\'] = \'$1\';
 elseif (strpos($data[0], \'http://\') !== 0 && strpos($data[0], \'https://\') !== 0)
 $data[0] = \'http://\' . $data[0];
 '),
 'disabled_content' => '<a href="$1" target="_blank" class="new_win">$1</a>',
 ),
 array(
 'tag' => 'font',
 'type' => 'unparsed_equals',
 'test' => '[A-Za-z0-9_,\-\s]+?\]',
 'before' => '<span style="font-family: $1;" class="bbc_font">',
 'after' => '</span>',
 ),
 array(
 'tag' => 'ftp',
 'type' => 'unparsed_content',
 'content' => '<a href="$1" class="bbc_ftp new_win" target="_blank">$1</a>',
 'validate' => create_function('&$tag, &$data, $disabled', '
 $data = strtr($data, array(\'<br />\' => \'\'));
 if (strpos($data, \'ftp://\') !== 0 && strpos($data, \'ftps://\') !== 0)
 $data = \'ftp://\' . $data;
 '),
 ),
 array(
 'tag' => 'ftp',
 'type' => 'unparsed_equals',
 'before' => '<a href="$1" class="bbc_ftp new_win" target="_blank">',
 'after' => '</a>',
 'validate' => create_function('&$tag, &$data, $disabled', '
 if (strpos($data, \'ftp://\') !== 0 && strpos($data, \'ftps://\') !== 0)
 $data = \'ftp://\' . $data;
 '),
 'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
 'disabled_after' => ' ($1)',
 ),
 array(
 'tag' => 'glow',
 'type' => 'unparsed_commas',
 'test' => '[#0-9a-zA-Z\-]{3,12},([012]\d{1,2}|\d{1,2})(,[^]]+)?\]',
 'before' => $context['browser']['is_ie'] ? '<table border="0" cellpadding="0" cellspacing="0" style="display: inline; vertical-align: middle; font: inherit;"><tr><td style="filter: Glow(color=$1, strength=$2); font: inherit;">' : '<span style="text-shadow: $1 1px 1px 1px">',
 'after' => $context['browser']['is_ie'] ? '</td></tr></table> ' : '</span>',
 ),
 array(
 'tag' => 'green',
 'before' => '<span style="color: green;" class="bbc_color">',
 'after' => '</span>',
 ),
 array(
 'tag' => 'html',
 'type' => 'unparsed_content',
 'content' => '$1',
 'block_level' => true,
 'disabled_content' => '$1',
 ),
 array(
 'tag' => 'hr',
 'type' => 'closed',
 'content' => '<hr />',
 'block_level' => true,
 ),
 array(
 'tag' => 'i',
 'before' => '<em>',
 'after' => '</em>',
 ),
 array(
 'tag' => 'img',
 'type' => 'unparsed_content',
 'parameters' => array(
 'alt' => array('optional' => true),
 'width' => array('optional' => true, 'value' => ' width="$1"', 'match' => '(\d+)'),
 'height' => array('optional' => true, 'value' => ' height="$1"', 'match' => '(\d+)'),
 ),
 'content' => '<img src="$1" alt="{alt}"{width}{height} class="bbc_img resized" />',
 'validate' => function (&$tag, &$data, $disabled)
 {
 global $image_proxy_enabled, $image_proxy_secret, $boardurl;

 $data = strtr($data, array('<br>' => ''));
 if (strpos($data, 'http://') !== 0 && strpos($data, 'https://') !== 0)
 $data = 'http://' . $data;

 if (substr($data, 0, 8) != 'https://' && $image_proxy_enabled)
 $data = $boardurl . '/proxy.php?request=' . urlencode($data) . '&hash=' . md5($data . $image_proxy_secret);
 },
 'disabled_content' => '($1)',
 ),
 array(
 'tag' => 'img',
 'type' => 'unparsed_content',
 'content' => '<img src="$1" alt="" class="bbc_img" />',
 'validate' => function (&$tag, &$data, $disabled)
 {
 global $image_proxy_enabled, $image_proxy_secret, $boardurl;

 $data = strtr($data, array('<br>' => ''));
 if (strpos($data, 'http://') !== 0 && strpos($data, 'https://') !== 0)
 $data = 'http://' . $data;

 if (substr($data, 0, 8) != 'https://' && $image_proxy_enabled)
 $data = $boardurl . '/proxy.php?request=' . urlencode($data) . '&hash=' . md5($data . $image_proxy_secret);
 },
 'disabled_content' => '($1)',
 ),
 array(
 'tag' => 'iurl',
 'type' => 'unparsed_content',
 'content' => '<a href="$1" class="bbc_link">$1</a>',
 'validate' => create_function('&$tag, &$data, $disabled', '
 $data = strtr($data, array(\'<br />\' => \'\'));
 if (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0)
 $data = \'http://\' . $data;
 '),
 ),
 array(
 'tag' => 'iurl',
 'type' => 'unparsed_equals',
 'before' => '<a href="$1" class="bbc_link">',
 'after' => '</a>',
 'validate' => create_function('&$tag, &$data, $disabled', '
 if (substr($data, 0, 1) == \'#\')
 $data = \'#post_\' . substr($data, 1);
 elseif (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0)
 $data = \'http://\' . $data;
 '),
 'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
 'disabled_after' => ' ($1)',
 ),
 array(
 'tag' => 'left',
 'before' => '<div style="text-align: left;">',
 'after' => '</div>',
 'block_level' => true,
 ),
 array(
 'tag' => 'li',
 'before' => '<li>',
 'after' => '</li>',
 'trim' => 'outside',
 'require_parents' => array('list'),
 'block_level' => true,
 'disabled_before' => '',
 'disabled_after' => '<br />',
 ),
 array(
 'tag' => 'list',
 'before' => '<ul class="bbc_list">',
 'after' => '</ul>',
 'trim' => 'inside',
 'require_children' => array('li', 'list'),
 'block_level' => true,
 ),
 array(
 'tag' => 'list',
 'parameters' => array(
 'type' => array('match' => '(none|disc|circle|square|decimal|decimal-leading-zero|lower-roman|upper-roman|lower-alpha|upper-alpha|lower-greek|lower-latin|upper-latin|hebrew|armenian|georgian|cjk-ideographic|hiragana|katakana|hiragana-iroha|katakana-iroha)'),
 ),
 'before' => '<ul class="bbc_list" style="list-style-type: {type};">',
 'after' => '</ul>',
 'trim' => 'inside',
 'require_children' => array('li'),
 'block_level' => true,
 ),
 array(
 'tag' => 'ltr',
 'before' => '<div dir="ltr">',
 'after' => '</div>',
 'block_level' => true,
 ),
 array(
 'tag' => 'me',
 'type' => 'unparsed_equals',
 'before' => '<div class="meaction">* $1 ',
 'after' => '</div>',
 'quoted' => 'optional',
 'block_level' => true,
 'disabled_before' => '/me ',
 'disabled_after' => '<br />',
 ),
 array(
 'tag' => 'move',
 'before' => '<marquee>',
 'after' => '</marquee>',
 'block_level' => true,
 'disallow_children' => array('move'),
 ),
 array(
 'tag' => 'nobbc',
 'type' => 'unparsed_content',
 'content' => '$1',
 ),
 array(
 'tag' => 'php',
 'type' => 'unparsed_content',
 'content' => '<span class="phpcode">$1</span>',
 'validate' => isset($disabled['php']) ? null : create_function('&$tag, &$data, $disabled', '
 if (!isset($disabled[\'php\']))
 {
 $add_begin = substr(trim($data), 0, 5) != \'&lt;?\';
 $data = highlight_php_code($add_begin ? \'&lt;?php \' . $data . \'?&gt;\' : $data);
 if ($add_begin)
 $data = preg_replace(array(\'~^(.+?)&lt;\?.{0,40}?php(?:&nbsp;|\s)~\', \'~\?&gt;((?:</(font|span)>)*)$~\'), \'$1\', $data, 2);
 }'),
 'block_level' => false,
 'disabled_content' => '$1',
 ),
 array(
 'tag' => 'pre',
 'before' => '<pre>',
 'after' => '</pre>',
 ),
 array(
 'tag' => 'quote',
 'before' => '<div class="quoteheader"><div class="topslice_quote">' . $txt['quote'] . '</div></div><blockquote>',
 'after' => '</blockquote><div class="quotefooter"><div class="botslice_quote"></div></div>',
 'block_level' => true,
 ),
 array(
 'tag' => 'quote',
 'parameters' => array(
 'author' => array('match' => '(.{1,192}?)', 'quoted' => true),
 ),
 'before' => '<div class="quoteheader"><div class="topslice_quote">' . $txt['quote_from'] . ': {author}</div></div><blockquote>',
 'after' => '</blockquote><div class="quotefooter"><div class="botslice_quote"></div></div>',
 'block_level' => true,
 ),
 array(
 'tag' => 'quote',
 'type' => 'parsed_equals',
 'before' => '<div class="quoteheader"><div class="topslice_quote">' . $txt['quote_from'] . ': $1</div></div><blockquote>',
 'after' => '</blockquote><div class="quotefooter"><div class="botslice_quote"></div></div>',
 'quoted' => 'optional',
 // Don't allow everything to be embedded with the author name.
 'parsed_tags_allowed' => array('url', 'iurl', 'ftp'),
 'block_level' => true,
 ),
 array(
 'tag' => 'quote',
 'parameters' => array(
 'author' => array('match' => '([^<>]{1,192}?)'),
 'link' => array('match' => '(?:board=\d+;)?((?:topic|threadid)=[\dmsg#\./]{1,40}(?:;start=[\dmsg#\./]{1,40})?|action=profile;u=\d+)'),
 'date' => array('match' => '(\d+)', 'validate' => 'timeformat'),
 ),
 'before' => '<div class="quoteheader"><div class="topslice_quote"><a href="' . $scripturl . '?{link}">' . $txt['quote_from'] . ': {author} ' . $txt['search_on'] . ' {date}</a></div></div><blockquote>',
 'after' => '</blockquote><div class="quotefooter"><div class="botslice_quote"></div></div>',
 'block_level' => true,
 ),
 array(
 'tag' => 'quote',
 'parameters' => array(
 'author' => array('match' => '(.{1,192}?)'),
 ),
 'before' => '<div class="quoteheader"><div class="topslice_quote">' . $txt['quote_from'] . ': {author}</div></div><blockquote>',
 'after' => '</blockquote><div class="quotefooter"><div class="botslice_quote"></div></div>',
 'block_level' => true,
 ),
 array(
 'tag' => 'red',
 'before' => '<span style="color: red;" class="bbc_color">',
 'after' => '</span>',
 ),
 array(
 'tag' => 'right',
 'before' => '<div style="text-align: right;">',
 'after' => '</div>',
 'block_level' => true,
 ),
 array(
 'tag' => 'rtl',
 'before' => '<div dir="rtl">',
 'after' => '</div>',
 'block_level' => true,
 ),
 array(
 'tag' => 's',
 'before' => '<del>',
 'after' => '</del>',
 ),
 array(
 'tag' => 'shadow',
 'type' => 'unparsed_commas',
 'test' => '[#0-9a-zA-Z\-]{3,12},(left|right|top|bottom|[0123]\d{0,2})\]',
 'before' => $context['browser']['is_ie'] ? '<span style="display: inline-block; filter: Shadow(color=$1, direction=$2); height: 1.2em;">' : '<span style="text-shadow: $1 $2">',
 'after' => '</span>',
 'validate' => $context['browser']['is_ie'] ? create_function('&$tag, &$data, $disabled', '
 if ($data[1] == \'left\')
 $data[1] = 270;
 elseif ($data[1] == \'right\')
 $data[1] = 90;
 elseif ($data[1] == \'top\')
 $data[1] = 0;
 elseif ($data[1] == \'bottom\')
 $data[1] = 180;
 else
 $data[1] = (int) $data[1];') : create_function('&$tag, &$data, $disabled', '
 if ($data[1] == \'top\' || (is_numeric($data[1]) && $data[1] < 50))
 $data[1] = \'0 -2px 1px\';
 elseif ($data[1] == \'right\' || (is_numeric($data[1]) && $data[1] < 100))
 $data[1] = \'2px 0 1px\';
 elseif ($data[1] == \'bottom\' || (is_numeric($data[1]) && $data[1] < 190))
 $data[1] = \'0 2px 1px\';
 elseif ($data[1] == \'left\' || (is_numeric($data[1]) && $data[1] < 280))
 $data[1] = \'-2px 0 1px\';
 else
 $data[1] = \'1px 1px 1px\';'),
 ),
 array(
 'tag' => 'size',
 'type' => 'unparsed_equals',
 'test' => '([1-9][\d]?p[xt]|small(?:er)?|large[r]?|x[x]?-(?:small|large)|medium|(0\.[1-9]|[1-9](\.[\d][\d]?)?)?em)\]',
 'before' => '<span style="font-size: $1;" class="bbc_size">',
 'after' => '</span>',
 ),
 array(
 'tag' => 'size',
 'type' => 'unparsed_equals',
 'test' => '[1-7]\]',
 'before' => '<span style="font-size: $1;" class="bbc_size">',
 'after' => '</span>',
 'validate' => create_function('&$tag, &$data, $disabled', '
 $sizes = array(1 => 0.7, 2 => 1.0, 3 => 1.35, 4 => 1.45, 5 => 2.0, 6 => 2.65, 7 => 3.95);
 $data = $sizes[$data] . \'em\';'
 ),
 ),
 array(
 'tag' => 'sub',
 'before' => '<sub>',
 'after' => '</sub>',
 ),
 array(
 'tag' => 'sup',
 'before' => '<sup>',
 'after' => '</sup>',
 ),
 array(
 'tag' => 'table',
 'before' => '<table class="bbc_table">',
 'after' => '</table>',
 'trim' => 'inside',
 'require_children' => array('tr'),
 'block_level' => true,
 ),
 array(
 'tag' => 'td',
 'before' => '<td>',
 'after' => '</td>',
 'require_parents' => array('tr'),
 'trim' => 'outside',
 'block_level' => true,
 'disabled_before' => '',
 'disabled_after' => '',
 ),
 array(
 'tag' => 'time',
 'type' => 'unparsed_content',
 'content' => '$1',
 'validate' => create_function('&$tag, &$data, $disabled', '
 if (is_numeric($data))
 $data = timeformat($data);
 else
 $tag[\'content\'] = \'[time]$1[/time]\';'),
 ),
 array(
 'tag' => 'tr',
 'before' => '<tr>',
 'after' => '</tr>',
 'require_parents' => array('table'),
 'require_children' => array('td'),
 'trim' => 'both',
 'block_level' => true,
 'disabled_before' => '',
 'disabled_after' => '',
 ),
 array(
 'tag' => 'tt',
 'before' => '<tt class="bbc_tt">',
 'after' => '</tt>',
 ),
 array(
 'tag' => 'u',
 'before' => '<span class="bbc_u">',
 'after' => '</span>',
 ),
 array(
 'tag' => 'url',
 'type' => 'unparsed_content',
 'content' => '<a href="$1" class="bbc_link" target="_blank">$1</a>',
 'validate' => create_function('&$tag, &$data, $disabled', '
 $data = strtr($data, array(\'<br />\' => \'\'));
 if (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0)
 $data = \'http://\' . $data;
 '),
 ),
 array(
 'tag' => 'url',
 'type' => 'unparsed_equals',
 'before' => '<a href="$1" class="bbc_link" target="_blank">',
 'after' => '</a>',
 'validate' => create_function('&$tag, &$data, $disabled', '
 if (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0)
 $data = \'http://\' . $data;
 '),
 'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
 'disabled_after' => ' ($1)',
 ),
 array(
 'tag' => 'white',
 'before' => '<span style="color: white;" class="bbc_color">',
 'after' => '</span>',
 ),
 );

As you see, there are a lot of "\", maybe this is the cause of the failure.
Do you know what could be done ? Beacause of the PHP incompatibility, I cannot correctly upgrade SMF for now, and this is annoying.

Thank you !

Chen Zhen

I will test it on SMF 2.0.15 later today. It should work but perhaps I made an error in the pkg info install file.

Why not update to SMF 2.0.17 ?

ManuDevil

I didn't update to 2.0.17 because all the errors displaying on every pages. No particular problem on 2.0.15.

ManuDevil

In fact, I want to install a compatibility plugin before updating.

Chen Zhen

You will not be able to install the SMF update patches with v1.4 of this mod without errors.

Make sure this mod is uninstalled, then update SMF with the patches.
After you are at SMF 2.0.17 then you can install v1.5 of this mod.

Chen Zhen


I tested the mod on SMF 2.0.15 and SMF 2.0.17 whereas there are no issues with the installer.
You must have some modification installed that altered code in that file.
I suggest you uninstall any bbcode modification prior to installing SMF patches and/or this modification.



This mod has been updated yet again.
Please refer to this thread for version 1.6:
https://web-develop.ca/index.php?topic=481.0

Thread has been locked.