#!/usr/bin/php Use shuffle_ld65.php as you would use ld65. shuffle_ld65 reads the .cfg file (-C option) for ld65, finds and detects sequences beginning with #<>SHUFFLE and shuffles the lines between those two lines, and reruns ld65, as many times as needed, until no pagewrap problems are found. There can be multiple shuffle regions, so you can e.g. shuffle functions separately and tables separately without mixing them up. Use this script to solve pagewrap problems without the need to add .align clauses in the source code or in the linker file, inflating the binary size. The final configuration is saved back to the place of the original cfg file, so that the next time you run shuffle_ld65.php you perhaps don't need to wait for reshuffling. Pagewrap problems are detected as assertion warnings from your code. The following strings are accepted as signs of pagewrap problems: "branch_check" "spans two pages" "causes page wrap" "cross page" "pagewrap" You might generate these assertions in a ca65 source file like this: .macro branch_check opc, dest opc dest .assert >* = >dest, warning, "branch_check: failed, crosses page" .endmacro .macro Jcc dest branch_check bcc, dest .endmacro ; Use Jcc instead of bcc to add link-time pagewrap checking to the branch Jcc target .macro TableWrapCheck table, last_index, message .assert >(table) = >(table+(last_index)), warning, message .endmacro ; Add TableWrapCheck to verify that the bounds of your data ; don't cross page boundaries. TableWrapCheck SomeData, 15, "SomeData spans two pages" SomeData: .res 16 Tip: Put all functions and tables in separate segments! The smaller the segments are, the better. Written in 2013 by Joel Yliluoma - http://iki.fi/bisqwit/ */ $command = Array(); $cfg_index = 0; foreach($argv as $c=>$s) { if($c==0)continue; if($s == '-C') $cfg_index = $c+1; $command[$c] = $s; } $orig_cfg_fn = $command[$cfg_index]; $temp_cfg_fn = tempnam('/tmp', 'sramsave_ld65'); $command[$cfg_index] = $temp_cfg_fn; $fp = fopen($orig_cfg_fn, 'r+'); if(!flock($fp, LOCK_EX)) { print "Couldn't lock $orig_cfg_fn\n"; return -1; } $orig_cfg = fread($fp, 65536); for($round=0; ; ++$round) { $shuffled = shuffle_config($orig_cfg, $round); file_put_contents($temp_cfg_fn, $shuffled); print "shuffle, round $round..."; $lines = Array(); exec('ld65 '.join(' ', $command).' 2>&1', $lines); $errors = 0; foreach($lines as $line) if(preg_match('/branch_check|spans two pages|causes page wrap|cross page|pagewrap/', $line)) ++$errors; if($errors) { print " $errors \r"; continue; } print "\rld65 ".join(' ', $command)."\n"; if($round) print "Found a non-pagewrapping solution after $round rounds\n"; print join("\n", $lines)."\n"; break; } if($round > 0) { // Write back the configuration that didn't cause problems fseek($fp, 0); fwrite($fp, $shuffled); ftruncate($fp, strlen($shuffled)); fclose($fp); } unlink($temp_cfg_fn); function shuffle_config($orig_cfg, $round) { if(!$round) return $orig_cfg; $result = ''; $shuffle_buf = Array(); $shuffling = false; foreach(explode("\n", trim($orig_cfg)) as $line) { if($line == '#<>SHUFFLE') { shuffle($shuffle_buf); $result .= join('', $shuffle_buf); $result .= "$line\n"; $shuffling = false; $shuffle_buf = Array(); } elseif($shuffling) { $shuffle_buf[] = "$line\n"; } else { $result .= "$line\n"; } } return $result; }