mirror of
https://github.com/specht/champ.git
synced 2024-11-24 17:33:23 +00:00
the big update
This commit is contained in:
parent
50f4dad58c
commit
c6ff6ac331
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
report.html
|
||||||
|
report-files
|
||||||
|
p65c02
|
||||||
|
pgif
|
6
Makefile
6
Makefile
@ -1,6 +0,0 @@
|
|||||||
champ: champ.c labels.h watches.h
|
|
||||||
gcc -o champ champ.c -lX11
|
|
||||||
|
|
||||||
labels.h: parse.rb plot3d20_Output.txt
|
|
||||||
./parse.rb
|
|
||||||
|
|
481
champ.rb
Executable file
481
champ.rb
Executable file
@ -0,0 +1,481 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
require 'fileutils'
|
||||||
|
require 'open3'
|
||||||
|
require 'set'
|
||||||
|
require 'tmpdir'
|
||||||
|
require 'yaml'
|
||||||
|
require 'stringio'
|
||||||
|
|
||||||
|
KEYWORDS = <<EOS
|
||||||
|
ADC AND ASL BCC BCS BEQ BIT BMI BNE BPL BRA BRK BVC BVS CLC CLD CLI CLV
|
||||||
|
CMP CPX CPY DEA DEC DEX DEY DSK EOR EQU INA INC INX INY JMP JSR LDA LDX
|
||||||
|
LDY LSR MX NOP ORA ORG PHA PHP PHX PHY PLA PLP PLX PLY ROL ROR RTI RTS
|
||||||
|
SBC SEC SED SEI STA STX STY STZ TAX TAY TRB TSB TSX TXA TXS TYA
|
||||||
|
EOS
|
||||||
|
|
||||||
|
class Champ
|
||||||
|
def initialize
|
||||||
|
if ARGV.empty?
|
||||||
|
STDERR.puts 'Usage: ./champ.rb [options] <config.yaml>'
|
||||||
|
STDERR.puts 'Options:'
|
||||||
|
STDERR.puts ' --max-frames <n>'
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
@files_dir = 'report-files'
|
||||||
|
FileUtils.rm_rf(@files_dir)
|
||||||
|
FileUtils.mkpath(@files_dir)
|
||||||
|
@max_frames = nil
|
||||||
|
args = ARGV.dup
|
||||||
|
while args.size > 1
|
||||||
|
item = args.shift
|
||||||
|
if item == '--max-frames'
|
||||||
|
@max_frames = args.shift.to_i
|
||||||
|
else
|
||||||
|
STDERR.puts "Invalid argument: #{item}"
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@config = YAML.load(File.read(args.shift))
|
||||||
|
@source_files = []
|
||||||
|
@config['load'].each do |tuple|
|
||||||
|
@source_files << {:path => File.absolute_path(tuple[1]), :address => tuple[0]}
|
||||||
|
end
|
||||||
|
|
||||||
|
@keywords = Set.new(KEYWORDS.split(/\s/).map { |x| x.strip }.reject { |x| x.empty? })
|
||||||
|
@global_variables = {}
|
||||||
|
@watches = {}
|
||||||
|
@watches_for_index = []
|
||||||
|
@label_for_pc = {}
|
||||||
|
@pc_for_label = {}
|
||||||
|
|
||||||
|
# init disk image to zeroes
|
||||||
|
@disk_image = [0] * 0x10000
|
||||||
|
# load empty disk image
|
||||||
|
load_image('empty', 0)
|
||||||
|
@source_files.each do |source_file|
|
||||||
|
@source_path = File.absolute_path(source_file[:path])
|
||||||
|
@source_line = 0
|
||||||
|
unless File.exist?(@source_path)
|
||||||
|
STDERR.puts 'Input file not found.'
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
Dir::mktmpdir do |temp_dir|
|
||||||
|
FileUtils.cp(@source_path, temp_dir)
|
||||||
|
|
||||||
|
pwd = Dir.pwd
|
||||||
|
Dir.chdir(temp_dir)
|
||||||
|
if @source_path[-2, 2] == '.s'
|
||||||
|
# compile the source
|
||||||
|
merlin_output = `Merlin32 -V . #{File.basename(@source_path)}`
|
||||||
|
if $?.exitstatus != 0 || File.exist?('error_output.txt')
|
||||||
|
STDERR.puts merlin_output
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
# collect Merlin output files
|
||||||
|
@merlin_output_path = File.absolute_path(Dir['*_Output.txt'].first)
|
||||||
|
@merlin_binary_path = @merlin_output_path.sub('_Output.txt', '')
|
||||||
|
|
||||||
|
# parse Merlin output files
|
||||||
|
# TODO: Adjust addresses!!!
|
||||||
|
parse_merlin_output(@merlin_output_path)
|
||||||
|
load_image(@merlin_binary_path, source_file[:address])
|
||||||
|
else
|
||||||
|
load_image(@source_path, source_file[:address])
|
||||||
|
end
|
||||||
|
Dir.chdir(pwd)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_image(path, address)
|
||||||
|
# puts "[#{sprintf('0x%04x', address)} - #{sprintf('0x%04x', address + File.size(path) - 1)}] - loading #{File.basename(path)}"
|
||||||
|
File::binread(path).unpack('C*').each.with_index { |b, i| @disk_image[i + address] = b }
|
||||||
|
end
|
||||||
|
|
||||||
|
def run
|
||||||
|
Dir::mktmpdir do |temp_dir|
|
||||||
|
if @config.include?('instant_rts')
|
||||||
|
@config['instant_rts'].each do |label|
|
||||||
|
@disk_image[@pc_for_label[label]] = 0x60 # insert RTS
|
||||||
|
end
|
||||||
|
end
|
||||||
|
File.binwrite(File.join(temp_dir, 'disk_image'), @disk_image.pack('C*'))
|
||||||
|
# build watch input for C program
|
||||||
|
io = StringIO.new
|
||||||
|
watch_index = 0
|
||||||
|
@watches.keys.sort.each do |pc|
|
||||||
|
@watches[pc].each do |watch0|
|
||||||
|
watch0[:components].each do |watch|
|
||||||
|
which = watch.include?(:register) ?
|
||||||
|
sprintf('reg,%s', watch[:register]) :
|
||||||
|
sprintf('mem,0x%04x', watch[:address])
|
||||||
|
io.puts sprintf('%d,0x%04x,%d,%s,%s',
|
||||||
|
watch_index, pc,
|
||||||
|
watch0[:post] ? 1 : 0,
|
||||||
|
watch[:type],
|
||||||
|
which)
|
||||||
|
end
|
||||||
|
@watches_for_index << watch0
|
||||||
|
watch_index += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
watch_input = io.string
|
||||||
|
|
||||||
|
@watch_values = {}
|
||||||
|
start_pc = @pc_for_label[@config['entry']] || @config['entry']
|
||||||
|
Signal.trap('INT') do
|
||||||
|
puts 'Killing 65C02 profiler...'
|
||||||
|
throw :sigint
|
||||||
|
end
|
||||||
|
@frame_count = 0
|
||||||
|
cycle_count = 0
|
||||||
|
last_frame_time = 0
|
||||||
|
frame_cycles = []
|
||||||
|
@cycles_per_function = {}
|
||||||
|
@calls_per_function = {}
|
||||||
|
call_stack = []
|
||||||
|
last_call_stack_cycles = 0
|
||||||
|
Open3.popen2("./p65c02 --hide-log --start-pc #{start_pc} #{File.join(temp_dir, 'disk_image')}") do |stdin, stdout, thread|
|
||||||
|
stdin.puts watch_input.split("\n").size
|
||||||
|
stdin.puts watch_input
|
||||||
|
stdin.close
|
||||||
|
catch :sigint do
|
||||||
|
gi, go, gt = Open3.popen2("./pgif 280 192 4 > #{File.join(@files_dir, 'frames.gif')}")
|
||||||
|
gi.puts '000000'
|
||||||
|
gi.puts 'ffffff'
|
||||||
|
gi.puts '000000'
|
||||||
|
gi.puts 'ffffff'
|
||||||
|
stdout.each_line do |line|
|
||||||
|
# puts "> #{line}"
|
||||||
|
parts = line.split(' ')
|
||||||
|
if parts.first == 'jsr'
|
||||||
|
pc = parts[1].to_i(16)
|
||||||
|
cycles = parts[2].to_i
|
||||||
|
@calls_per_function[pc] ||= 0
|
||||||
|
@calls_per_function[pc] += 1
|
||||||
|
unless call_stack.empty?
|
||||||
|
@cycles_per_function[call_stack.last] ||= 0
|
||||||
|
@cycles_per_function[call_stack.last] += cycles - last_call_stack_cycles
|
||||||
|
end
|
||||||
|
last_call_stack_cycles = cycles
|
||||||
|
call_stack << pc
|
||||||
|
elsif parts.first == 'rts'
|
||||||
|
cycles = parts[1].to_i
|
||||||
|
unless call_stack.empty?
|
||||||
|
@cycles_per_function[call_stack.last] ||= 0
|
||||||
|
@cycles_per_function[call_stack.last] += cycles - last_call_stack_cycles
|
||||||
|
end
|
||||||
|
last_call_stack_cycles = cycles
|
||||||
|
call_stack.pop
|
||||||
|
elsif parts.first == 'watch'
|
||||||
|
watch_index = parts[1].to_i
|
||||||
|
@watch_values[watch_index] ||= []
|
||||||
|
@watch_values[watch_index] << parts[2, parts.size - 2].map { |x| x.to_i }
|
||||||
|
elsif parts.first == 'screen'
|
||||||
|
@frame_count += 1
|
||||||
|
print "\rFrames: #{@frame_count}, Cycles: #{cycle_count}"
|
||||||
|
this_frame_cycles = parts[1].to_i
|
||||||
|
frame_cycles << this_frame_cycles
|
||||||
|
data = parts[2, parts.size - 2].map { |x| x.to_i }
|
||||||
|
gi.puts 'l'
|
||||||
|
(0...192).each do |y|
|
||||||
|
(0...280).each do |x|
|
||||||
|
b = (data[y * 40 + (x / 7)] >> (x % 7)) & 1
|
||||||
|
gi.print b
|
||||||
|
end
|
||||||
|
gi.puts
|
||||||
|
end
|
||||||
|
|
||||||
|
gi.puts "d #{(this_frame_cycles - last_frame_time) / 10000}"
|
||||||
|
last_frame_time = this_frame_cycles
|
||||||
|
|
||||||
|
if @max_frames && @frame_count >= @max_frames
|
||||||
|
break
|
||||||
|
end
|
||||||
|
elsif parts.first == 'cycles'
|
||||||
|
cycle_count = parts[1].to_i
|
||||||
|
print "\rFrames: #{@frame_count}, Cycles: #{cycle_count}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
gi.close
|
||||||
|
gt.join
|
||||||
|
end
|
||||||
|
end
|
||||||
|
puts
|
||||||
|
|
||||||
|
@cycles_per_frame = []
|
||||||
|
(2...frame_cycles.size).each do |i|
|
||||||
|
@cycles_per_frame << frame_cycles[i] - frame_cycles[i - 1]
|
||||||
|
end
|
||||||
|
if @cycles_per_frame.size > 0
|
||||||
|
puts "Cycles per frame: #{@cycles_per_frame.inject(0) { |sum, x| sum + x } / @cycles_per_frame.size}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_report
|
||||||
|
html_name = 'report.html'
|
||||||
|
print "Writing report to file://#{File.absolute_path(html_name)} ..."
|
||||||
|
File::open(html_name, 'w') do |f|
|
||||||
|
report = DATA.read
|
||||||
|
report.sub!('#{source_name}', File.basename(@source_path))
|
||||||
|
|
||||||
|
# write frames
|
||||||
|
io = StringIO.new
|
||||||
|
io.puts "<img class='screenshot' src='#{File.join(@files_dir, 'frames.gif')}' /><br />"
|
||||||
|
if @cycles_per_frame.size > 0
|
||||||
|
io.puts '<p>'
|
||||||
|
io.puts "Frames recorded: #{@frame_count}<br />"
|
||||||
|
io.puts "Average cycles/frame: #{@cycles_per_frame.inject(0) { |sum, x| sum + x } / @cycles_per_frame.size}<br />"
|
||||||
|
io.puts '<p>'
|
||||||
|
end
|
||||||
|
report.sub!('#{screenshots}', io.string)
|
||||||
|
|
||||||
|
# write watches
|
||||||
|
io = StringIO.new
|
||||||
|
@watches_for_index.each.with_index do |watch, index|
|
||||||
|
io.puts "<div style='display: inline-block;'>"
|
||||||
|
io.puts "<h3>#{watch[:components].map { |x| x[:name] }.join('/')} (#{File.basename(@source_path)}:#{watch[:line_number]})</h3>"
|
||||||
|
if @watch_values.include?(index)
|
||||||
|
if watch[:components].size == 1
|
||||||
|
io.puts '<em>TODO</em>'
|
||||||
|
elsif watch[:components].size == 2
|
||||||
|
histogram = {}
|
||||||
|
@watch_values[index].each do |item|
|
||||||
|
histogram[item.join('/')] ||= 0
|
||||||
|
histogram[item.join('/')] += 1
|
||||||
|
end
|
||||||
|
histogram_max = histogram.values.max
|
||||||
|
width = 256
|
||||||
|
height = 256
|
||||||
|
pixels = [63] * width * height
|
||||||
|
histogram.each_pair do |key, value|
|
||||||
|
key_parts = key.split('/').map { |x| x.to_i }
|
||||||
|
(0..1).each do |i|
|
||||||
|
if watch[:components][i][:type] == 's8'
|
||||||
|
key_parts[i] = key_parts[i] + 128
|
||||||
|
elsif watch[:components][i][:type] == 'u16'
|
||||||
|
key_parts[i] = key_parts[i] >> 8
|
||||||
|
elsif watch[:components][i][:type] == 's16'
|
||||||
|
key_parts[i] = (key_parts[i] + 32768) >> 8
|
||||||
|
end
|
||||||
|
end
|
||||||
|
x = key_parts[0]
|
||||||
|
y = 255 - key_parts[1]
|
||||||
|
pixels[y * width + x] = (((value.to_f / histogram_max) ** 0.5) * 63).to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
gi, go, gt = Open3.popen2("./pgif #{width} #{height} 64")
|
||||||
|
(0...64).each do |i|
|
||||||
|
g = ((i + 1) << 2) - 1
|
||||||
|
gi.puts sprintf('%02x%02x%02x', g, g, g)
|
||||||
|
end
|
||||||
|
gi.puts 'f'
|
||||||
|
gi.puts pixels.map { |x| sprintf('%02x', x) }.join("\n")
|
||||||
|
gi.close
|
||||||
|
gt.join
|
||||||
|
watch_path = File.join(@files_dir, "watch_#{index}.gif")
|
||||||
|
File::open(watch_path, 'w') do |f|
|
||||||
|
f.write go.read
|
||||||
|
end
|
||||||
|
|
||||||
|
io.puts "<img src='#{watch_path}'></img>"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
io.puts "<em>No values recorded.</em>"
|
||||||
|
end
|
||||||
|
io.puts "</div>"
|
||||||
|
end
|
||||||
|
report.sub!('#{watches}', io.string)
|
||||||
|
|
||||||
|
# write cycles
|
||||||
|
io = StringIO.new
|
||||||
|
io.puts "<table>"
|
||||||
|
io.puts "<thead>"
|
||||||
|
io.puts "<tr>"
|
||||||
|
io.puts "<th>Addr</th>"
|
||||||
|
io.puts "<th>CC</th>"
|
||||||
|
io.puts "<th>CC %</th>"
|
||||||
|
io.puts "<th>Calls</th>"
|
||||||
|
io.puts "<th>CC/Call</th>"
|
||||||
|
io.puts "<th>Label</th>"
|
||||||
|
io.puts "</tr>"
|
||||||
|
io.puts "</thead>"
|
||||||
|
cycles_sum = @cycles_per_function.values.inject(0) { |a, b| a + b }
|
||||||
|
@cycles_per_function.keys.sort do |a, b|
|
||||||
|
@cycles_per_function[b] <=> @cycles_per_function[a]
|
||||||
|
end.each do |pc|
|
||||||
|
io.puts "<tr>"
|
||||||
|
io.puts "<td>#{sprintf('0x%04x', pc)}</td>"
|
||||||
|
io.puts "<td style='text-align: right;'>#{@cycles_per_function[pc]}</td>"
|
||||||
|
io.puts "<td style='text-align: right;'>#{sprintf('%1.2f%%', @cycles_per_function[pc].to_f * 100.0 / cycles_sum)}</td>"
|
||||||
|
io.puts "<td style='text-align: right;'>#{@calls_per_function[pc]}</td>"
|
||||||
|
io.puts "<td style='text-align: right;'>#{@cycles_per_function[pc] / @calls_per_function[pc]}</td>"
|
||||||
|
io.puts "<td>#{@label_for_pc[pc]}</td>"
|
||||||
|
io.puts "</tr>"
|
||||||
|
|
||||||
|
end
|
||||||
|
io.puts "</table>"
|
||||||
|
report.sub!('#{cycles}', io.string)
|
||||||
|
|
||||||
|
f.puts report
|
||||||
|
end
|
||||||
|
puts ' done.'
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_merlin_output(path)
|
||||||
|
@source_line = -3
|
||||||
|
File.open(path, 'r') do |f|
|
||||||
|
f.each_line do |line|
|
||||||
|
@source_line += 1
|
||||||
|
parts = line.split('|')
|
||||||
|
next unless parts.size > 2
|
||||||
|
line_type = parts[2].strip
|
||||||
|
if ['Code', 'Equivalence', 'Empty'].include?(line_type)
|
||||||
|
next if parts[6].nil?
|
||||||
|
next unless parts[6] =~ /[0-9A-F]{2}\/[0-9A-F]{4}/
|
||||||
|
pc = parts[6].split(' ').first.split('/').last.to_i(16)
|
||||||
|
code = parts[7].strip
|
||||||
|
code_parts = code.split(/\s+/)
|
||||||
|
next if code_parts.empty?
|
||||||
|
|
||||||
|
label = nil
|
||||||
|
unless @keywords.include?(code_parts.first)
|
||||||
|
label = code_parts.first
|
||||||
|
code = code.sub(label, '').strip
|
||||||
|
@label_for_pc[pc] = label
|
||||||
|
@pc_for_label[label] = pc
|
||||||
|
end
|
||||||
|
|
||||||
|
champ_directives = []
|
||||||
|
if code.include?(';')
|
||||||
|
comment = code[code.index(';') + 1, code.size].strip
|
||||||
|
comment.scan(/@[^\s]+/).each do |match|
|
||||||
|
champ_directives << match.to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
next if champ_directives.empty?
|
||||||
|
|
||||||
|
if line_type == 'Equivalence'
|
||||||
|
if champ_directives.size != 1
|
||||||
|
fail('No more than one champ directive allowed in equivalence declaration.')
|
||||||
|
end
|
||||||
|
item = {
|
||||||
|
:address => code_parts[2].sub('$', '').to_i(16),
|
||||||
|
:type => parse_champ_directive(champ_directives.first, true)[:type]
|
||||||
|
}
|
||||||
|
@global_variables[label] = item
|
||||||
|
elsif line_type == 'Code'
|
||||||
|
@watches[pc] ||= []
|
||||||
|
champ_directives.each do |directive|
|
||||||
|
watch = parse_champ_directive(directive, false)
|
||||||
|
watch[:line_number] = @source_line
|
||||||
|
@watches[pc] << watch
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if line.include?(';')
|
||||||
|
if line.split(';').last.include?('@')
|
||||||
|
fail('Champ directive not allowed here.')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def fail(message)
|
||||||
|
STDERR.puts sprintf('[%s:%d] %s', File::basename(@source_path), @source_line, message)
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_champ_directive(s, global_variable = false)
|
||||||
|
original_directive = s.dup
|
||||||
|
# Au Au(post) As Xs(post) RX u8 Au,Xu,Yu
|
||||||
|
result = {}
|
||||||
|
s = s[1, s.size - 1] if s[0] == '@'
|
||||||
|
if global_variable
|
||||||
|
if ['u8', 's8', 'u16', 's16'].include?(s)
|
||||||
|
result[:type] = s
|
||||||
|
else
|
||||||
|
fail("Error parsing champ directive: #{original_directive}")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if s.include?('(post)')
|
||||||
|
result[:post] = true
|
||||||
|
s.sub!('(post)', '')
|
||||||
|
end
|
||||||
|
result[:components] = s.split(',').map do |part|
|
||||||
|
part_result = {}
|
||||||
|
if ['Au', 'As', 'Xu', 'Xs', 'Yu', 'Ys'].include?(part[0, 2])
|
||||||
|
part_result[:register] = part[0]
|
||||||
|
part_result[:name] = part[0]
|
||||||
|
part_result[:type] = part[1] + '8'
|
||||||
|
elsif @global_variables.include?(part)
|
||||||
|
part_result[:address] = @global_variables[part][:address]
|
||||||
|
part_result[:type] = @global_variables[part][:type]
|
||||||
|
part_result[:name] = part
|
||||||
|
else
|
||||||
|
fail("Error parsing champ directive: #{original_directive}")
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
part_result
|
||||||
|
end
|
||||||
|
if result[:components].size > 2
|
||||||
|
fail("No more than two components allowed per watch in #{original_directive}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
['p65c02', 'pgif'].each do |file|
|
||||||
|
unless FileUtils.uptodate?(file, ["#{file}.c"])
|
||||||
|
system("gcc -o #{file} #{file}.c")
|
||||||
|
unless $?.exitstatus == 0
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
champ = Champ.new
|
||||||
|
champ.run
|
||||||
|
champ.write_report
|
||||||
|
|
||||||
|
__END__
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>champ report</title>
|
||||||
|
<style type='text/css'>
|
||||||
|
body {
|
||||||
|
background-color: #eee;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
.screenshot {
|
||||||
|
background-color: #222;
|
||||||
|
box-shadow: inset 0 0 10px rgba(0,0,0,1.0);
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
text-align: left;
|
||||||
|
padding: 0 0.5em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>champ report for #{source_name}</h1>
|
||||||
|
<div style='float: left; border-right: 1px solid #aaa;'>
|
||||||
|
<h2>Frames</h2>
|
||||||
|
#{screenshots}
|
||||||
|
<h2>Cycles</h2>
|
||||||
|
#{cycles}
|
||||||
|
</div>
|
||||||
|
<div style='margin-left: 440px; padding-top: 5px;'>
|
||||||
|
<h2>Watches</h2>
|
||||||
|
#{watches}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -1,17 +1,12 @@
|
|||||||
#include <X11/Xlib.h>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include "labels.h"
|
|
||||||
#include "watches.h"
|
|
||||||
|
|
||||||
#define SCALE 4
|
|
||||||
#define SCREEN_WIDTH 280
|
#define SCREEN_WIDTH 280
|
||||||
#define SCREEN_HEIGHT 192
|
#define SCREEN_HEIGHT 192
|
||||||
#define SCREEN_REFRESH_EVERY_CYCLES 10000
|
|
||||||
|
|
||||||
#define CARRY 0x01
|
#define CARRY 0x01
|
||||||
#define ZERO 0x02
|
#define ZERO 0x02
|
||||||
@ -21,15 +16,6 @@
|
|||||||
#define OVERFLOW 0x40
|
#define OVERFLOW 0x40
|
||||||
#define NEGATIVE 0x80
|
#define NEGATIVE 0x80
|
||||||
|
|
||||||
Display *dsp;
|
|
||||||
Window win;
|
|
||||||
GC gc;
|
|
||||||
unsigned int white, black;
|
|
||||||
Atom wmDelete;
|
|
||||||
XEvent evt;
|
|
||||||
KeyCode keyQ;
|
|
||||||
|
|
||||||
uint8_t show_screen = 0;
|
|
||||||
uint8_t show_log = 1;
|
uint8_t show_log = 1;
|
||||||
uint8_t show_call_stack = 0;
|
uint8_t show_call_stack = 0;
|
||||||
|
|
||||||
@ -44,41 +30,6 @@ uint64_t frame_count = 0;
|
|||||||
|
|
||||||
uint16_t start_pc = 0x6000;
|
uint16_t start_pc = 0x6000;
|
||||||
uint16_t start_frame_pc = 0xffff;
|
uint16_t start_frame_pc = 0xffff;
|
||||||
uint64_t max_frames = 0;
|
|
||||||
FILE *watches_file = 0;
|
|
||||||
|
|
||||||
int init_display()
|
|
||||||
{
|
|
||||||
dsp = XOpenDisplay(NULL);
|
|
||||||
if (!dsp)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
int screen = DefaultScreen(dsp);
|
|
||||||
white = WhitePixel(dsp, screen);
|
|
||||||
black = BlackPixel(dsp, screen);
|
|
||||||
|
|
||||||
win = XCreateSimpleWindow(dsp, DefaultRootWindow(dsp), 0, 0,
|
|
||||||
SCREEN_WIDTH * SCALE, SCREEN_HEIGHT * SCALE,
|
|
||||||
0, black, black );
|
|
||||||
|
|
||||||
wmDelete = XInternAtom(dsp, "WM_DELETE_WINDOW", True);
|
|
||||||
XSetWMProtocols(dsp, win, &wmDelete, 1);
|
|
||||||
|
|
||||||
gc = XCreateGC(dsp, win, 0, NULL);
|
|
||||||
|
|
||||||
// XSetForeground(dsp, gc, white);
|
|
||||||
|
|
||||||
long eventMask = StructureNotifyMask;
|
|
||||||
eventMask |= ButtonPressMask|ButtonReleaseMask|KeyPressMask|KeyReleaseMask;
|
|
||||||
XSelectInput(dsp, win, eventMask);
|
|
||||||
|
|
||||||
keyQ = XKeysymToKeycode(dsp, XStringToKeysym("Q"));
|
|
||||||
|
|
||||||
XMapWindow(dsp, win);
|
|
||||||
|
|
||||||
// wait until window appears
|
|
||||||
do { XNextEvent(dsp,&evt); } while (evt.type != MapNotify);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t yoffset[192] = {
|
uint16_t yoffset[192] = {
|
||||||
0x0000, 0x0400, 0x0800, 0x0c00, 0x1000, 0x1400, 0x1800, 0x1c00,
|
0x0000, 0x0400, 0x0800, 0x0c00, 0x1000, 0x1400, 0x1800, 0x1c00,
|
||||||
@ -108,7 +59,7 @@ uint16_t yoffset[192] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint16_t ip;
|
uint16_t pc;
|
||||||
uint8_t sp;
|
uint8_t sp;
|
||||||
uint64_t total_cycles;
|
uint64_t total_cycles;
|
||||||
uint8_t a, x, y, flags;
|
uint8_t a, x, y, flags;
|
||||||
@ -116,7 +67,7 @@ typedef struct {
|
|||||||
|
|
||||||
void init_cpu(r_cpu* cpu)
|
void init_cpu(r_cpu* cpu)
|
||||||
{
|
{
|
||||||
cpu->ip = 0;
|
cpu->pc = 0;
|
||||||
cpu->sp = 0xff;
|
cpu->sp = 0xff;
|
||||||
cpu->total_cycles = 0;
|
cpu->total_cycles = 0;
|
||||||
cpu->a = 0;
|
cpu->a = 0;
|
||||||
@ -128,6 +79,27 @@ void init_cpu(r_cpu* cpu)
|
|||||||
uint8_t ram[0x10000];
|
uint8_t ram[0x10000];
|
||||||
r_cpu cpu;
|
r_cpu cpu;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t index;
|
||||||
|
uint16_t pc;
|
||||||
|
uint8_t post;
|
||||||
|
enum {
|
||||||
|
u8, s8, u16, s16
|
||||||
|
} data_type;
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
MEMORY,
|
||||||
|
REGISTER_A,
|
||||||
|
REGISTER_X,
|
||||||
|
REGISTER_Y
|
||||||
|
} type;
|
||||||
|
uint16_t memory_address;
|
||||||
|
} r_watch;
|
||||||
|
|
||||||
|
r_watch *watches = 0;
|
||||||
|
size_t watch_count = 0;
|
||||||
|
int32_t watch_offset_for_pc_and_post[0x20000];
|
||||||
|
|
||||||
void load(char* path, uint16_t offset)
|
void load(char* path, uint16_t offset)
|
||||||
{
|
{
|
||||||
uint8_t buffer[0x10000];
|
uint8_t buffer[0x10000];
|
||||||
@ -140,20 +112,19 @@ void load(char* path, uint16_t offset)
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
size = fread(buffer, 1, 0x10000, f);
|
size = fread(buffer, 1, 0x10000, f);
|
||||||
fprintf(stderr, "Read 0x%04x bytes from %s, saved to 0x%04x.\n", (int)size, path, offset);
|
|
||||||
memcpy(ram + offset, buffer, size);
|
memcpy(ram + offset, buffer, size);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t rip8()
|
uint8_t rpc8()
|
||||||
{
|
{
|
||||||
return ram[cpu.ip++];
|
return ram[cpu.pc++];
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t rip16()
|
uint16_t rpc16()
|
||||||
{
|
{
|
||||||
uint16_t result = rip8();
|
uint16_t result = rpc8();
|
||||||
result |= ((uint16_t)rip8()) << 8;
|
result |= ((uint16_t)rpc8()) << 8;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,41 +140,9 @@ uint16_t read16(uint16_t address)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void refresh_watches(uint16_t address)
|
|
||||||
{
|
|
||||||
// refresh global variable watches after write at address
|
|
||||||
for (int i = 0; i < WATCH_COUNT; i++)
|
|
||||||
{
|
|
||||||
if ((WATCH_ADDRESSES[i] == address) ||
|
|
||||||
((WATCH_TYPES[i] == WATCH_U16 || WATCH_TYPES[i] == WATCH_S16) &&
|
|
||||||
(WATCH_ADDRESSES[i] == address - 1)))
|
|
||||||
{
|
|
||||||
int32_t value = 0;
|
|
||||||
switch (WATCH_TYPES[i])
|
|
||||||
{
|
|
||||||
case WATCH_U8:
|
|
||||||
value = (uint8_t)read8(WATCH_ADDRESSES[i]);
|
|
||||||
break;
|
|
||||||
case WATCH_S8:
|
|
||||||
value = (int8_t)read8(WATCH_ADDRESSES[i]);
|
|
||||||
break;
|
|
||||||
case WATCH_U16:
|
|
||||||
value = (uint16_t)read16(WATCH_ADDRESSES[i]);
|
|
||||||
break;
|
|
||||||
case WATCH_S16:
|
|
||||||
value = (int16_t)read16(WATCH_ADDRESSES[i]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
fprintf(watches_file, "~ 0x%04x %s %d\n", cpu.ip, WATCH_LABELS[i], value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void write8(uint16_t address, uint8_t value)
|
void write8(uint16_t address, uint8_t value)
|
||||||
{
|
{
|
||||||
ram[address] = value;
|
ram[address] = value;
|
||||||
if (watches_file)
|
|
||||||
refresh_watches(address);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void push(uint8_t value)
|
void push(uint8_t value)
|
||||||
@ -365,7 +304,7 @@ const char* const ADDRESSING_MODE_STRINGS[] = { ADDRESSING_MODES };
|
|||||||
|
|
||||||
#define TEST_OPCODE(opcode) test_opcode = opcode;
|
#define TEST_OPCODE(opcode) test_opcode = opcode;
|
||||||
#define OPCODE_VARIANT(opcode, cycles, addressing_mode) \
|
#define OPCODE_VARIANT(opcode, cycles, addressing_mode) \
|
||||||
if (opcode_from_ip == opcode) { \
|
if (opcode_from_pc == opcode) { \
|
||||||
*_opcode = test_opcode; \
|
*_opcode = test_opcode; \
|
||||||
*_addressing_mode = addressing_mode; \
|
*_addressing_mode = addressing_mode; \
|
||||||
*_cycles = cycles; \
|
*_cycles = cycles; \
|
||||||
@ -378,8 +317,8 @@ void fetch_next_opcode(uint8_t* _read_opcode, r_opcode* _opcode, r_addressing_mo
|
|||||||
* https://www.atarimax.com/jindroush.atari.org/aopc.html
|
* https://www.atarimax.com/jindroush.atari.org/aopc.html
|
||||||
*/
|
*/
|
||||||
r_opcode test_opcode = NO_OPCODE;
|
r_opcode test_opcode = NO_OPCODE;
|
||||||
uint8_t opcode_from_ip = rip8();
|
uint8_t opcode_from_pc = rpc8();
|
||||||
*_read_opcode = opcode_from_ip;
|
*_read_opcode = opcode_from_pc;
|
||||||
|
|
||||||
TEST_OPCODE(ADC)
|
TEST_OPCODE(ADC)
|
||||||
OPCODE_VARIANT(0x69, 2, immediate)
|
OPCODE_VARIANT(0x69, 2, immediate)
|
||||||
@ -477,7 +416,7 @@ void fetch_next_opcode(uint8_t* _read_opcode, r_opcode* _opcode, r_addressing_mo
|
|||||||
OPCODE_VARIANT(0xE6, 5, zero_page)
|
OPCODE_VARIANT(0xE6, 5, zero_page)
|
||||||
OPCODE_VARIANT(0xF6, 6, zero_page_x)
|
OPCODE_VARIANT(0xF6, 6, zero_page_x)
|
||||||
OPCODE_VARIANT(0xEE, 6, absolute)
|
OPCODE_VARIANT(0xEE, 6, absolute)
|
||||||
OPCODE_VARIANT(0xFe, 7, absolute_x)
|
OPCODE_VARIANT(0xFE, 7, absolute_x)
|
||||||
TEST_OPCODE(JMP)
|
TEST_OPCODE(JMP)
|
||||||
OPCODE_VARIANT(0x4C, 3, absolute)
|
OPCODE_VARIANT(0x4C, 3, absolute)
|
||||||
OPCODE_VARIANT(0x6C, 5, indirect)
|
OPCODE_VARIANT(0x6C, 5, indirect)
|
||||||
@ -616,15 +555,15 @@ void branch(uint8_t condition, int8_t offset, uint8_t* cycles)
|
|||||||
{
|
{
|
||||||
// branch succeeds
|
// branch succeeds
|
||||||
*cycles += 1;
|
*cycles += 1;
|
||||||
if ((cpu.ip & 0xfff0) != ((cpu.ip + offset) & 0xfff0))
|
if ((cpu.pc & 0xfff0) != ((cpu.pc + offset) & 0xfff0))
|
||||||
*cycles += 1;
|
*cycles += 1;
|
||||||
cpu.ip += offset;
|
cpu.pc += offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void handle_next_opcode()
|
void handle_next_opcode()
|
||||||
{
|
{
|
||||||
uint16_t old_ip = cpu.ip;
|
uint16_t old_pc = cpu.pc;
|
||||||
|
|
||||||
// fetch opcode, addressing mode and cycles for next instruction
|
// fetch opcode, addressing mode and cycles for next instruction
|
||||||
uint8_t read_opcode = 0;
|
uint8_t read_opcode = 0;
|
||||||
@ -635,7 +574,7 @@ void handle_next_opcode()
|
|||||||
|
|
||||||
if (opcode == NO_OPCODE || addressing_mode == NO_ADDRESSING_MODE)
|
if (opcode == NO_OPCODE || addressing_mode == NO_ADDRESSING_MODE)
|
||||||
{
|
{
|
||||||
fprintf(stderr, "Unhandled opcode at %04x: %02x\n", old_ip, read_opcode);
|
fprintf(stderr, "Unhandled opcode at %04x: %02x\n", old_pc, read_opcode);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -646,47 +585,47 @@ void handle_next_opcode()
|
|||||||
switch (addressing_mode)
|
switch (addressing_mode)
|
||||||
{
|
{
|
||||||
case immediate:
|
case immediate:
|
||||||
immediate_value = rip8();
|
immediate_value = rpc8();
|
||||||
break;
|
break;
|
||||||
case relative:
|
case relative:
|
||||||
relative_offset = (int8_t)rip8();
|
relative_offset = (int8_t)rpc8();
|
||||||
break;
|
break;
|
||||||
case absolute:
|
case absolute:
|
||||||
target_address = rip16();
|
target_address = rpc16();
|
||||||
break;
|
break;
|
||||||
case zero_page:
|
case zero_page:
|
||||||
target_address = rip8();
|
target_address = rpc8();
|
||||||
break;
|
break;
|
||||||
case indirect:
|
case indirect:
|
||||||
target_address = read16(rip16());
|
target_address = read16(rpc16());
|
||||||
break;
|
break;
|
||||||
case zero_page_indirect:
|
case zero_page_indirect:
|
||||||
target_address = read16(rip8());
|
target_address = read16(rpc8());
|
||||||
break;
|
break;
|
||||||
case zero_page_x:
|
case zero_page_x:
|
||||||
target_address = (rip8() + cpu.x) % 0xff;
|
target_address = (rpc8() + cpu.x) % 0xff;
|
||||||
break;
|
break;
|
||||||
case zero_page_y:
|
case zero_page_y:
|
||||||
target_address = (rip8() + cpu.y) % 0xff;
|
target_address = (rpc8() + cpu.y) % 0xff;
|
||||||
break;
|
break;
|
||||||
case absolute_x:
|
case absolute_x:
|
||||||
target_address = rip16();
|
target_address = rpc16();
|
||||||
if ((target_address >> 12) != ((target_address + cpu.x) >> 12))
|
if ((target_address >> 12) != ((target_address + cpu.x) >> 12))
|
||||||
cycles += 1;
|
cycles += 1;
|
||||||
target_address += cpu.x;
|
target_address += cpu.x;
|
||||||
break;
|
break;
|
||||||
case absolute_y:
|
case absolute_y:
|
||||||
target_address = rip16();
|
target_address = rpc16();
|
||||||
if ((target_address >> 12) != ((target_address + cpu.y) >> 12))
|
if ((target_address >> 12) != ((target_address + cpu.y) >> 12))
|
||||||
cycles += 1;
|
cycles += 1;
|
||||||
target_address += cpu.y;
|
target_address += cpu.y;
|
||||||
break;
|
break;
|
||||||
case indexed_indirect_x:
|
case indexed_indirect_x:
|
||||||
target_address = read16((rip8() + cpu.x) & 0xff);
|
target_address = read16((rpc8() + cpu.x) & 0xff);
|
||||||
break;
|
break;
|
||||||
case indirect_indexed_y:
|
case indirect_indexed_y:
|
||||||
target_address = cpu.y;
|
target_address = cpu.y;
|
||||||
uint16_t temp = read16(rip8());
|
uint16_t temp = read16(rpc8());
|
||||||
if ((target_address >> 12) != ((target_address + temp) >> 12))
|
if ((target_address >> 12) != ((target_address + temp) >> 12))
|
||||||
cycles += 1;
|
cycles += 1;
|
||||||
target_address += temp;
|
target_address += temp;
|
||||||
@ -695,7 +634,7 @@ void handle_next_opcode()
|
|||||||
|
|
||||||
if (show_log)
|
if (show_log)
|
||||||
{
|
{
|
||||||
fprintf(stderr, "%04x | %d | %02x | %s %-18s | ", old_ip, cycles, read_opcode, OPCODE_STRINGS[opcode],
|
fprintf(stderr, "# %04x | %d | %02x | %s %-18s | ", old_pc, cycles, read_opcode, OPCODE_STRINGS[opcode],
|
||||||
ADDRESSING_MODE_STRINGS[addressing_mode]
|
ADDRESSING_MODE_STRINGS[addressing_mode]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -836,19 +775,21 @@ void handle_next_opcode()
|
|||||||
update_zero_and_negative_flags(cpu.y);
|
update_zero_and_negative_flags(cpu.y);
|
||||||
break;
|
break;
|
||||||
case JMP:
|
case JMP:
|
||||||
cpu.ip = target_address;
|
cpu.pc = target_address;
|
||||||
// TODO handle page boundary behaviour?
|
// TODO handle page boundary behaviour?
|
||||||
break;
|
break;
|
||||||
case JSR:
|
case JSR:
|
||||||
// push IP - 1 because target address has already been read
|
// push PC - 1 because target address has already been read
|
||||||
trace_stack_function[trace_stack_pointer] = target_address;
|
trace_stack_function[trace_stack_pointer] = target_address;
|
||||||
trace_stack[trace_stack_pointer] = cpu.sp;
|
trace_stack[trace_stack_pointer] = cpu.sp;
|
||||||
trace_stack_pointer--;
|
trace_stack_pointer--;
|
||||||
calls_per_function[target_address]++;
|
calls_per_function[target_address]++;
|
||||||
|
printf("jsr 0x%04x %d\n", target_address, cpu.total_cycles);
|
||||||
|
fflush(stdout);
|
||||||
|
|
||||||
push(((cpu.ip - 1) >> 8) & 0xff);
|
push(((cpu.pc - 1) >> 8) & 0xff);
|
||||||
push((cpu.ip - 1) & 0xff);
|
push((cpu.pc - 1) & 0xff);
|
||||||
cpu.ip = target_address;
|
cpu.pc = target_address;
|
||||||
break;
|
break;
|
||||||
case LDA:
|
case LDA:
|
||||||
cpu.a = (addressing_mode == immediate) ? immediate_value : read8(target_address);
|
cpu.a = (addressing_mode == immediate) ? immediate_value : read8(target_address);
|
||||||
@ -939,17 +880,19 @@ void handle_next_opcode()
|
|||||||
cpu.flags = pop();
|
cpu.flags = pop();
|
||||||
t16 = pop();
|
t16 = pop();
|
||||||
t16 |= ((uint16_t)pop()) << 8;
|
t16 |= ((uint16_t)pop()) << 8;
|
||||||
cpu.ip = t16;
|
cpu.pc = t16;
|
||||||
break;
|
break;
|
||||||
case RTS:
|
case RTS:
|
||||||
if (trace_stack[trace_stack_pointer + 1] == cpu.sp + 2)
|
if (trace_stack[trace_stack_pointer + 1] == cpu.sp + 2)
|
||||||
{
|
{
|
||||||
|
printf("rts %d\n", cpu.total_cycles);
|
||||||
|
fflush(stdout);
|
||||||
trace_stack_pointer++;
|
trace_stack_pointer++;
|
||||||
}
|
}
|
||||||
|
|
||||||
t16 = pop();
|
t16 = pop();
|
||||||
t16 |= ((uint16_t)pop()) << 8;
|
t16 |= ((uint16_t)pop()) << 8;
|
||||||
cpu.ip = t16 + 1;
|
cpu.pc = t16 + 1;
|
||||||
break;
|
break;
|
||||||
case SBC:
|
case SBC:
|
||||||
sbc((addressing_mode == immediate) ? immediate_value : read8(target_address));
|
sbc((addressing_mode == immediate) ? immediate_value : read8(target_address));
|
||||||
@ -1004,7 +947,8 @@ void handle_next_opcode()
|
|||||||
};
|
};
|
||||||
if (unhandled_opcode)
|
if (unhandled_opcode)
|
||||||
{
|
{
|
||||||
fprintf(stderr, "Opcode not implemented yet!\n");
|
fprintf(stderr, "Opcode %s not implemented yet at PC 0x%04x\n",
|
||||||
|
OPCODE_STRINGS[opcode], cpu.pc);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
cpu.total_cycles += cycles;
|
cpu.total_cycles += cycles;
|
||||||
@ -1021,45 +965,88 @@ void handle_next_opcode()
|
|||||||
flags_str[5] = 0;
|
flags_str[5] = 0;
|
||||||
|
|
||||||
fprintf(stderr, "A: %02x, X: %02x, Y: %02x, PC: %04x, SP: %02x, FLAGS: %02x %s | %10ld |",
|
fprintf(stderr, "A: %02x, X: %02x, Y: %02x, PC: %04x, SP: %02x, FLAGS: %02x %s | %10ld |",
|
||||||
cpu.a, cpu.x, cpu.y, cpu.ip, cpu.sp, cpu.flags, flags_str, cpu.total_cycles);
|
cpu.a, cpu.x, cpu.y, cpu.pc, cpu.sp, cpu.flags, flags_str, cpu.total_cycles);
|
||||||
fprintf(stderr, "\n");
|
fprintf(stderr, "\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_pixel(int px, int py, unsigned int color)
|
int parse_int(const char* s, int base)
|
||||||
{
|
{
|
||||||
int x, y;
|
char *p = 0;
|
||||||
XSetForeground(dsp, gc, color);
|
int i = strtol(s, &p, base);
|
||||||
for (y = 0; y < SCALE; y++)
|
if (p == s)
|
||||||
for (x = 0; x < SCALE; x++)
|
{
|
||||||
XDrawPoint(dsp, win, gc, px * SCALE + x, py * SCALE + y);
|
fprintf(stderr, "Error parsing integer: %s", s);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
void render_hires_screen()
|
void handle_watch(uint16_t pc, uint8_t post)
|
||||||
{
|
{
|
||||||
uint8_t current_screen = ram[0x30b];
|
int32_t offset = watch_offset_for_pc_and_post[((int32_t)pc << 1) | post];
|
||||||
int x, y;
|
if (offset == -1)
|
||||||
for (y = 0; y < 192; y++)
|
return;
|
||||||
{
|
|
||||||
uint16_t line_offset = yoffset[y] | (current_screen == 1 ? 0x2000 : 0x4000);
|
|
||||||
for (x = 0; x < 40; x++)
|
|
||||||
{
|
|
||||||
uint8_t byte = ram[line_offset + x];
|
|
||||||
for (int px = 0; px < 7; px++)
|
|
||||||
set_pixel(x * 7 + px, y, ((byte >> px) & 1) ? white : black);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t find_address_for_label(const char* label)
|
int32_t old_index = -1;
|
||||||
|
while (watches[offset].pc == pc && watches[offset].post == post)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < LABEL_COUNT; i++)
|
r_watch* watch = &watches[offset];
|
||||||
if ((strlen(label) == strlen(LABELS[i])) &&
|
if (old_index == -1)
|
||||||
(strcmp(label, LABELS[i]) == 0))
|
{
|
||||||
for (int k = 0; k < 0x10000; k++)
|
printf("watch %d", watch->index);
|
||||||
if (label_for_address[k] == i)
|
}
|
||||||
return k;
|
else
|
||||||
return -1;
|
if (old_index != watch->index)
|
||||||
|
printf("\nwatch %d", watch->index);
|
||||||
|
|
||||||
|
old_index = watch->index;
|
||||||
|
int32_t value = 0;
|
||||||
|
if (watch->type == MEMORY)
|
||||||
|
{
|
||||||
|
switch (watch->data_type)
|
||||||
|
{
|
||||||
|
case u8:
|
||||||
|
value = (uint8_t)read8(watch->memory_address);
|
||||||
|
break;
|
||||||
|
case s8:
|
||||||
|
value = (int8_t)read8(watch->memory_address);
|
||||||
|
break;
|
||||||
|
case u16:
|
||||||
|
value = (uint16_t)read16(watch->memory_address);
|
||||||
|
break;
|
||||||
|
case s16:
|
||||||
|
value = (int16_t)read16(watch->memory_address);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "Invalid data type!\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
printf(" %d", value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (watch->type)
|
||||||
|
{
|
||||||
|
case REGISTER_A:
|
||||||
|
value = (watch->data_type == u8) ? (uint8_t)cpu.a : (int8_t)cpu.a;
|
||||||
|
break;
|
||||||
|
case REGISTER_X:
|
||||||
|
value = (watch->data_type == u8) ? (uint8_t)cpu.x : (int8_t)cpu.x;
|
||||||
|
break;
|
||||||
|
case REGISTER_Y:
|
||||||
|
value = (watch->data_type == u8) ? (uint8_t)cpu.y : (int8_t)cpu.y;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "Invalid type!\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
printf(" %d", value);
|
||||||
|
}
|
||||||
|
offset++;
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char** argv)
|
int main(int argc, char** argv)
|
||||||
@ -1069,28 +1056,89 @@ int main(int argc, char** argv)
|
|||||||
printf("Usage: ./champ [options] <memory dump>\n");
|
printf("Usage: ./champ [options] <memory dump>\n");
|
||||||
printf("\n");
|
printf("\n");
|
||||||
printf("Options:\n");
|
printf("Options:\n");
|
||||||
printf(" --show-screen\n");
|
|
||||||
printf(" --hide-log\n");
|
printf(" --hide-log\n");
|
||||||
printf(" --start-pc <address or label>\n");
|
printf(" --start-pc <address or label>\n");
|
||||||
printf(" --frame-start <address or label>\n");
|
printf(" --frame-start <address or label>\n");
|
||||||
printf(" --max-frames <n>\n");
|
printf(" --max-frames <n>\n");
|
||||||
printf(" --watches <output filename>\n");
|
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 0x20000; i++)
|
||||||
|
watch_offset_for_pc_and_post[i] = -1;
|
||||||
|
|
||||||
|
char s[1024];
|
||||||
|
int watch_index = 0;
|
||||||
|
while (fgets(s, 1024, stdin))
|
||||||
|
{
|
||||||
|
if (!watches)
|
||||||
|
{
|
||||||
|
watch_count = parse_int(s, 0);
|
||||||
|
watches = malloc(sizeof(r_watch) * watch_count);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
char *p = s;
|
||||||
|
r_watch watch;
|
||||||
|
memset(&watch, 0, sizeof(watch));
|
||||||
|
watch.index = parse_int(p, 0);
|
||||||
|
while (*(p++) != ',');
|
||||||
|
watch.pc = parse_int(p + 2, 16);
|
||||||
|
while (*(p++) != ',');
|
||||||
|
watch.post = parse_int(p, 0);
|
||||||
|
while (*(p++) != ',');
|
||||||
|
if (strncmp(p, "u8", 2) == 0)
|
||||||
|
watch.data_type = u8;
|
||||||
|
else if (strncmp(p, "s8", 2) == 0)
|
||||||
|
watch.data_type = s8;
|
||||||
|
else if (strncmp(p, "u16", 3) == 0)
|
||||||
|
watch.data_type = u16;
|
||||||
|
else if (strncmp(p, "s16", 3) == 0)
|
||||||
|
watch.data_type = s16;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Invalid data type!");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
while (*(p++) != ',');
|
||||||
|
if (strncmp(p, "mem", 3) == 0)
|
||||||
|
{
|
||||||
|
watch.type = MEMORY;
|
||||||
|
while (*(p++) != ',');
|
||||||
|
watch.memory_address = parse_int(p + 2, 16);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
while (*(p++) != ',');
|
||||||
|
if (strncmp(p, "A", 1) == 0)
|
||||||
|
watch.type = REGISTER_A;
|
||||||
|
else if (strncmp(p, "X", 1) == 0)
|
||||||
|
watch.type = REGISTER_X;
|
||||||
|
else if (strncmp(p, "Y", 1) == 0)
|
||||||
|
watch.type = REGISTER_Y;
|
||||||
|
}
|
||||||
|
int32_t offset = (((int32_t)watch.pc) << 1) | watch.post;
|
||||||
|
|
||||||
|
if (watch_offset_for_pc_and_post[offset] == -1)
|
||||||
|
watch_offset_for_pc_and_post[offset] = watch_index;
|
||||||
|
|
||||||
|
watches[watch_index++] = watch;
|
||||||
|
|
||||||
|
// printf("watch index %d pc 0x%04x post %d data_type %d type %d memory_address 0x%04x offset %d value %d\n",
|
||||||
|
// watch.index, watch.pc, watch.post, watch.data_type, watch.type,
|
||||||
|
// watch.memory_address, offset, watch_offset_for_pc_and_post[offset]
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 1; i < argc - 1; i++)
|
for (int i = 1; i < argc - 1; i++)
|
||||||
{
|
{
|
||||||
if (strcmp(argv[i], "--show-screen") == 0)
|
if (strcmp(argv[i], "--hide-log") == 0)
|
||||||
show_screen = 1;
|
|
||||||
else if (strcmp(argv[i], "--hide-log") == 0)
|
|
||||||
show_log = 0;
|
show_log = 0;
|
||||||
else if (strcmp(argv[i], "--start-pc") == 0)
|
else if (strcmp(argv[i], "--start-pc") == 0)
|
||||||
{
|
{
|
||||||
char *temp = argv[++i];
|
char *temp = argv[++i];
|
||||||
char *p = 0;
|
char *p = 0;
|
||||||
start_pc = strtol(temp, &p, 0);
|
start_pc = strtol(temp, &p, 0);
|
||||||
if (p == temp)
|
|
||||||
start_pc = find_address_for_label(temp);
|
|
||||||
fprintf(stderr, "Using start PC: 0x%04x\n", start_pc);
|
fprintf(stderr, "Using start PC: 0x%04x\n", start_pc);
|
||||||
}
|
}
|
||||||
else if (strcmp(argv[i], "--start-frame") == 0)
|
else if (strcmp(argv[i], "--start-frame") == 0)
|
||||||
@ -1098,30 +1146,8 @@ int main(int argc, char** argv)
|
|||||||
char *temp = argv[++i];
|
char *temp = argv[++i];
|
||||||
char *p = 0;
|
char *p = 0;
|
||||||
start_frame_pc = strtol(temp, &p, 0);
|
start_frame_pc = strtol(temp, &p, 0);
|
||||||
if (p == temp)
|
|
||||||
start_frame_pc = find_address_for_label(temp);
|
|
||||||
fprintf(stderr, "Using frame start: 0x%04x\n", start_frame_pc);
|
fprintf(stderr, "Using frame start: 0x%04x\n", start_frame_pc);
|
||||||
}
|
}
|
||||||
else if (strcmp(argv[i], "--max-frames") == 0)
|
|
||||||
{
|
|
||||||
char *temp = argv[++i];
|
|
||||||
char *p = 0;
|
|
||||||
max_frames = strtol(temp, &p, 0);
|
|
||||||
if (p == temp)
|
|
||||||
max_frames = find_address_for_label(temp);
|
|
||||||
fprintf(stderr, "Max frames: %d\n", max_frames);
|
|
||||||
}
|
|
||||||
else if (strcmp(argv[i], "--watches") == 0)
|
|
||||||
{
|
|
||||||
const char* filename = argv[++i];
|
|
||||||
if (access(filename, F_OK) != -1)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Watch output file exists: %s, exiting...\n", filename);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
watches_file = fopen(filename, "w");
|
|
||||||
fprintf(stderr, "Writing watches to %s.\n", filename);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
fprintf(stderr, "Unknown argument: %s\n", argv[i]);
|
fprintf(stderr, "Unknown argument: %s\n", argv[i]);
|
||||||
@ -1134,123 +1160,56 @@ int main(int argc, char** argv)
|
|||||||
|
|
||||||
load(argv[argc - 1], 0);
|
load(argv[argc - 1], 0);
|
||||||
|
|
||||||
if (show_screen)
|
|
||||||
init_display();
|
|
||||||
|
|
||||||
init_cpu(&cpu);
|
init_cpu(&cpu);
|
||||||
cpu.ip = start_pc;
|
cpu.pc = start_pc;
|
||||||
struct timespec tstart = {0, 0};
|
struct timespec tstart = {0, 0};
|
||||||
clock_gettime(CLOCK_MONOTONIC, &tstart);
|
clock_gettime(CLOCK_MONOTONIC, &tstart);
|
||||||
unsigned long start_time = tstart.tv_sec * 1000000000 + tstart.tv_nsec;
|
unsigned long start_time = tstart.tv_sec * 1000000000 + tstart.tv_nsec;
|
||||||
uint32_t next_display_refresh = 0;
|
uint32_t next_display_refresh = 0;
|
||||||
uint8_t old_screen_number = 0;
|
uint8_t old_screen_number = 0;
|
||||||
|
int last_cycles = -1;
|
||||||
while (1) {
|
while (1) {
|
||||||
if (watches_file)
|
handle_watch(cpu.pc, 0);
|
||||||
{
|
uint16_t old_pc = cpu.pc;
|
||||||
// handle register watches for certain IP locations
|
|
||||||
uint8_t printed_reg_watches = 0;
|
|
||||||
for (int i = 0; i < ARG_WATCH_COUNT; i++)
|
|
||||||
{
|
|
||||||
if (cpu.ip == ARG_WATCH_ADDRESSES[i])
|
|
||||||
{
|
|
||||||
int32_t value = 0;
|
|
||||||
uint8_t reg = 0;
|
|
||||||
if (ARG_WATCH_REGISTERS[i] == WATCH_A)
|
|
||||||
reg = cpu.a;
|
|
||||||
else if (ARG_WATCH_REGISTERS[i] == WATCH_X)
|
|
||||||
reg = cpu.x;
|
|
||||||
else if (ARG_WATCH_REGISTERS[i] == WATCH_Y)
|
|
||||||
reg = cpu.y;
|
|
||||||
if (ARG_WATCH_TYPES[i] == WATCH_S8)
|
|
||||||
value = (int8_t)reg;
|
|
||||||
else
|
|
||||||
value = (uint8_t)reg;
|
|
||||||
if (!printed_reg_watches)
|
|
||||||
fprintf(watches_file, "@ 0x%04x ", cpu.ip);
|
|
||||||
else
|
|
||||||
fprintf(watches_file, ", ");
|
|
||||||
fprintf(watches_file, "%s %d",
|
|
||||||
WATCH_REGISTER_LABELS[ARG_WATCH_REGISTERS[i]],
|
|
||||||
value);
|
|
||||||
printed_reg_watches = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (printed_reg_watches)
|
|
||||||
fprintf(watches_file, "\n");
|
|
||||||
}
|
|
||||||
handle_next_opcode();
|
handle_next_opcode();
|
||||||
if ((start_frame_pc != 0xffff) && (cpu.ip == start_frame_pc))
|
handle_watch(old_pc, 1);
|
||||||
|
if ((start_frame_pc != 0xffff) && (cpu.pc == start_frame_pc))
|
||||||
{
|
{
|
||||||
if (last_frame_cycle_count > 0)
|
if (last_frame_cycle_count > 0)
|
||||||
{
|
{
|
||||||
frame_cycle_count += (cpu.total_cycles - last_frame_cycle_count);
|
frame_cycle_count += (cpu.total_cycles - last_frame_cycle_count);
|
||||||
frame_count += 1;
|
frame_count += 1;
|
||||||
// printf("%ld\n", (cpu.total_cycles - last_frame_cycle_count));
|
|
||||||
// printf("%d\n", (uint64_t)((double)frame_cycle_count / frame_count));
|
|
||||||
if ((max_frames > 0) && (frame_count >= max_frames))
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
last_frame_cycle_count = cpu.total_cycles;
|
last_frame_cycle_count = cpu.total_cycles;
|
||||||
}
|
}
|
||||||
// if (cycles)
|
if (cpu.total_cycles / 100000 != last_cycles)
|
||||||
// {
|
|
||||||
// // struct timespec tlap = {0, 0};
|
|
||||||
// // clock_gettime(CLOCK_MONOTONIC, &tlap);
|
|
||||||
// // unsigned long lap_time = tlap.tv_sec * 1000000000 + tlap.tv_nsec;
|
|
||||||
// // unsigned long elapsed_time = lap_time - start_time;
|
|
||||||
// // start_time = lap_time;
|
|
||||||
// // printf("Time taken for %d cycles: %ld\n", cycles, elapsed_time);
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// break;
|
|
||||||
if (show_screen)
|
|
||||||
{
|
{
|
||||||
if (XEventsQueued(dsp, QueuedAfterFlush) > 0)
|
last_cycles = cpu.total_cycles / 100000;
|
||||||
{
|
printf("cycles %d\n", last_cycles * 100000);
|
||||||
int exit_program = 0;
|
|
||||||
XNextEvent(dsp, &evt);
|
|
||||||
|
|
||||||
switch (evt.type) {
|
|
||||||
|
|
||||||
case (KeyRelease) :
|
|
||||||
if (evt.xkey.keycode == keyQ)
|
|
||||||
exit_program = 1;
|
|
||||||
|
|
||||||
case (ClientMessage) :
|
|
||||||
if (evt.xclient.data.l[0] == wmDelete)
|
|
||||||
exit_program = 1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
if (exit_program)
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
if (ram[0x30b] != old_screen_number)
|
if (ram[0x30b] != old_screen_number)
|
||||||
{
|
{
|
||||||
old_screen_number = ram[0x30b];
|
old_screen_number = ram[0x30b];
|
||||||
render_hires_screen();
|
uint8_t current_screen = old_screen_number;
|
||||||
|
int x, y;
|
||||||
|
printf("screen %d", cpu.total_cycles);
|
||||||
|
for (y = 0; y < 192; y++)
|
||||||
|
{
|
||||||
|
uint16_t line_offset = yoffset[y] | (current_screen == 1 ? 0x2000 : 0x4000);
|
||||||
|
for (x = 0; x < 40; x++)
|
||||||
|
printf(" %d", ram[line_offset + x]);
|
||||||
}
|
}
|
||||||
|
printf("\n");
|
||||||
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fprintf(stderr, "Total cycles: %d\n", cpu.total_cycles);
|
fprintf(stderr, "Total cycles: %d\n", cpu.total_cycles);
|
||||||
fprintf(stderr, "Cycles per frame: %d\n", (uint64_t)((double)frame_cycle_count / frame_count));
|
|
||||||
printf("%12s %6s %8s %8s %4s %s\n", "Total CC", "% CC", "Calls", "CC/Call", "Addr", "Label");
|
if (watches)
|
||||||
for (uint32_t i = 0; i < 0x10000; i++)
|
|
||||||
{
|
{
|
||||||
if (cycles_per_function[i] > 0)
|
free(watches);
|
||||||
{
|
watches = 0;
|
||||||
printf("%12d %5.2f%% %8d %8d %04x",
|
|
||||||
cycles_per_function[i],
|
|
||||||
cycles_per_function[i] * 100.0 / cpu.total_cycles,
|
|
||||||
calls_per_function[i],
|
|
||||||
cycles_per_function[i] / calls_per_function[i],
|
|
||||||
i);
|
|
||||||
if (label_for_address[i] >= 0)
|
|
||||||
printf(" %s", LABELS[label_for_address[i]]);
|
|
||||||
printf("\n");
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (watches_file)
|
|
||||||
fclose(watches_file);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
124
parse.rb
124
parse.rb
@ -1,124 +0,0 @@
|
|||||||
#!/usr/bin/env ruby
|
|
||||||
|
|
||||||
require 'yaml'
|
|
||||||
|
|
||||||
label_for_address = {}
|
|
||||||
symbols = []
|
|
||||||
watches = []
|
|
||||||
arg_watches = []
|
|
||||||
|
|
||||||
File::open('plot3d20_Output.txt') do |f|
|
|
||||||
f.each_line do |line|
|
|
||||||
parts = line.split('|')
|
|
||||||
next unless parts.size == 8
|
|
||||||
address = parts[6].split(' ').first.split('/')[1]
|
|
||||||
symbol = parts[7]
|
|
||||||
symbol = symbol[1, symbol.size - 1]
|
|
||||||
next if symbol[0] == '*'
|
|
||||||
next if symbol[0] == ';'
|
|
||||||
next if symbol[0] == ' '
|
|
||||||
if symbol =~ /^\w+\s+EQU\s+\$([0-9A-F]{2,4})/
|
|
||||||
address = $1
|
|
||||||
end
|
|
||||||
watch_this = symbol.match(/~(u8|s8|u16|s16)/)
|
|
||||||
watch_args = symbol.scan(/(@(u8|s8|u16|s16)[AXY])/).map { |x| x.first }
|
|
||||||
symbol = symbol.strip.split(' ').first
|
|
||||||
next if address.nil?
|
|
||||||
label_for_address[address.to_i(16)] = symbol
|
|
||||||
symbols << symbol
|
|
||||||
if watch_this
|
|
||||||
watches << {
|
|
||||||
:address => address.to_i(16),
|
|
||||||
:label => symbol,
|
|
||||||
:type => watch_this[1]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
watch_args.each do |watch_arg|
|
|
||||||
arg_watches << {:register => watch_arg[watch_arg.size - 1],
|
|
||||||
:type => watch_arg.sub('@', '')[0, watch_arg.size - 2],
|
|
||||||
:address => address.to_i(16)}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
symbols.uniq!
|
|
||||||
symbols.sort! { |a, b| a.downcase <=> b.downcase }
|
|
||||||
|
|
||||||
File::open('labels.h', 'w') do |f|
|
|
||||||
f.puts "const uint16_t LABEL_COUNT = #{symbols.size};"
|
|
||||||
f.puts "const char* LABELS[] = {";
|
|
||||||
line_length = 0
|
|
||||||
f.print ' '
|
|
||||||
symbols.each.with_index do |symbol, _|
|
|
||||||
part = "\"#{symbol}\""
|
|
||||||
if _ < symbols.size - 1
|
|
||||||
part += ', '
|
|
||||||
end
|
|
||||||
f.print part
|
|
||||||
line_length += part.size
|
|
||||||
if line_length > 60
|
|
||||||
f.puts
|
|
||||||
f.print ' '
|
|
||||||
line_length = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
f.puts "};"
|
|
||||||
|
|
||||||
f.puts "const int16_t label_for_address[] = {"
|
|
||||||
f.print ' '
|
|
||||||
(0..0xffff).each do |address|
|
|
||||||
value = -1
|
|
||||||
if label_for_address.include?(address)
|
|
||||||
value = symbols.index(label_for_address[address]);
|
|
||||||
end
|
|
||||||
f.print "#{value}, "
|
|
||||||
if (address + 1) % 16 == 0
|
|
||||||
f.puts
|
|
||||||
f.print ' '
|
|
||||||
end
|
|
||||||
end
|
|
||||||
f.puts "};";
|
|
||||||
end
|
|
||||||
|
|
||||||
File::open('watches.h', 'w') do |f|
|
|
||||||
f.puts "const uint16_t WATCH_COUNT = #{watches.size};"
|
|
||||||
types = ['u8', 'u16', 's8', 's16'];
|
|
||||||
types.each.with_index do |x, _|
|
|
||||||
f.puts "#define WATCH_#{x.upcase} #{_ + 1}"
|
|
||||||
end
|
|
||||||
f.puts "const char* WATCH_LABELS[] = {";
|
|
||||||
f.puts watches.map { |x| " \"#{x[:label]}\"" }.join(",\n");
|
|
||||||
f.puts "};"
|
|
||||||
f.puts "const uint16_t WATCH_ADDRESSES[] = {";
|
|
||||||
f.puts watches.map { |x| " #{sprintf('0x%04x', x[:address])}" }.join(",\n");
|
|
||||||
f.puts "};"
|
|
||||||
f.puts "const uint8_t WATCH_TYPES[] = {";
|
|
||||||
f.puts watches.map { |x| " #{types.index(x[:type]) + 1}" }.join(",\n");
|
|
||||||
f.puts "};"
|
|
||||||
|
|
||||||
f.puts "const uint16_t ARG_WATCH_COUNT = #{arg_watches.size};"
|
|
||||||
types = ['u8', 'u16', 's8', 's16'];
|
|
||||||
f.puts "const uint16_t ARG_WATCH_ADDRESSES[] = {";
|
|
||||||
f.puts arg_watches.map { |x| " #{sprintf('0x%04x', x[:address])}" }.join(",\n");
|
|
||||||
f.puts "};"
|
|
||||||
f.puts "const uint8_t ARG_WATCH_TYPES[] = {";
|
|
||||||
f.puts arg_watches.map { |x| " #{types.index(x[:type]) + 1}" }.join(",\n");
|
|
||||||
f.puts "};"
|
|
||||||
registers = ['A', 'X', 'Y']
|
|
||||||
registers.each.with_index do |x, _|
|
|
||||||
f.puts "#define WATCH_#{x} #{_ + 1}"
|
|
||||||
end
|
|
||||||
f.puts "const uint8_t ARG_WATCH_REGISTERS[] = {";
|
|
||||||
f.puts arg_watches.map { |x| " #{registers.index(x[:register]) + 1}" }.join(",\n");
|
|
||||||
f.puts "};"
|
|
||||||
f.puts "const char* WATCH_REGISTER_LABELS[] = {";
|
|
||||||
f.puts " \"\","
|
|
||||||
f.puts registers.map { |x| " \"#{x}\"" }.join(",\n");
|
|
||||||
f.puts "};"
|
|
||||||
end
|
|
||||||
|
|
||||||
puts "Found #{symbols.size} labels and #{watches.size} watches."
|
|
||||||
|
|
||||||
# label_for_address.keys.sort.each do |address|
|
|
||||||
# puts sprintf("%04x %s", address, label_for_address[address])
|
|
||||||
# end
|
|
@ -1,30 +0,0 @@
|
|||||||
#!/usr/bin/env ruby
|
|
||||||
|
|
||||||
vars = {}
|
|
||||||
|
|
||||||
File::open(ARGV.first) do |f|
|
|
||||||
f.each_line do |line|
|
|
||||||
if line[0] == '~'
|
|
||||||
parts = line.split(' ')
|
|
||||||
name = parts[2]
|
|
||||||
value = parts[3].to_i
|
|
||||||
vars[name] ||= []
|
|
||||||
vars[name] << value
|
|
||||||
elsif line[0] == '@'
|
|
||||||
address = line.split(' ')[1].sub('0x', '').to_i(16)
|
|
||||||
rest = line[line.index(' ', 2), line.size].strip
|
|
||||||
rest.split(',').each do |part|
|
|
||||||
part.strip!
|
|
||||||
# puts part
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
vars.keys.sort.each do |name|
|
|
||||||
vars[name].sort!
|
|
||||||
format_str = "%-#{vars.keys.map { |x| x.size }.max}s assigned %8d times, min. %6d, max. %6d, median %6d"
|
|
||||||
puts sprintf(format_str,
|
|
||||||
name, vars[name].size, vars[name].first, vars[name].last,
|
|
||||||
vars[name][vars[name].size / 2])
|
|
||||||
end
|
|
470
pgif.c
Normal file
470
pgif.c
Normal file
@ -0,0 +1,470 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
|
||||||
|
#define ANIMATION_OPTIMIZATION_CROP
|
||||||
|
// don't use transparency, it's not saving space with 1 bit graphics
|
||||||
|
// #define ANIMATION_OPTIMIZATION_TRANSPARENCY
|
||||||
|
|
||||||
|
struct header_block {
|
||||||
|
char signature[3];
|
||||||
|
char version[3];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct logical_screen_descriptor {
|
||||||
|
uint16_t canvas_width;
|
||||||
|
uint16_t canvas_height;
|
||||||
|
uint8_t size_of_global_color_table: 3;
|
||||||
|
uint8_t sort_flag: 1;
|
||||||
|
uint8_t color_resolution: 3;
|
||||||
|
uint8_t global_color_table_flag: 1;
|
||||||
|
uint8_t background_color_index;
|
||||||
|
uint8_t pixel_aspect_ratio;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct graphics_control_extension {
|
||||||
|
uint8_t extension_introducer;
|
||||||
|
uint8_t graphics_control_label;
|
||||||
|
uint8_t byte_size;
|
||||||
|
uint8_t transparent_color_flag: 1;
|
||||||
|
uint8_t user_input_flag: 1;
|
||||||
|
uint8_t disposal_method: 3;
|
||||||
|
uint8_t reserved: 3;
|
||||||
|
uint16_t delay_time;
|
||||||
|
uint8_t transparent_color_index;
|
||||||
|
uint8_t block_terminator;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct image_descriptor {
|
||||||
|
uint8_t image_separator;
|
||||||
|
uint16_t image_left;
|
||||||
|
uint16_t image_top;
|
||||||
|
uint16_t image_width;
|
||||||
|
uint16_t image_height;
|
||||||
|
uint8_t size_of_local_color_table: 3;
|
||||||
|
uint8_t reserved: 2;
|
||||||
|
uint8_t sort_flag: 1;
|
||||||
|
uint8_t interlace_flag: 1;
|
||||||
|
uint8_t local_color_table_flag: 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct application_extension {
|
||||||
|
uint8_t gif_extension_code;
|
||||||
|
uint8_t application_extension_label;
|
||||||
|
uint8_t length_of_application_block;
|
||||||
|
char label[11];
|
||||||
|
uint8_t length_of_data_sub_block;
|
||||||
|
uint8_t one;
|
||||||
|
uint16_t loop_count;
|
||||||
|
uint8_t terminator;
|
||||||
|
};
|
||||||
|
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
void put8(uint8_t i)
|
||||||
|
{
|
||||||
|
fputc(i, stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void put_struct(void* p, size_t size)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < size; i++)
|
||||||
|
put8(*(unsigned char*)(p + i));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct lzw_emitter {
|
||||||
|
uint8_t byte_buffer[255];
|
||||||
|
uint8_t byte_buffer_size;
|
||||||
|
uint32_t buffer;
|
||||||
|
int8_t offset;
|
||||||
|
uint8_t code_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
void flush_bytes(struct lzw_emitter* emitter)
|
||||||
|
{
|
||||||
|
put8(emitter->byte_buffer_size);
|
||||||
|
put_struct(emitter->byte_buffer, emitter->byte_buffer_size);
|
||||||
|
emitter->byte_buffer_size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void emit_byte(struct lzw_emitter* emitter)
|
||||||
|
{
|
||||||
|
emitter->byte_buffer[emitter->byte_buffer_size++] = emitter->buffer & 0xff;
|
||||||
|
emitter->buffer >>= 8;
|
||||||
|
emitter->offset -= 8;
|
||||||
|
if (emitter->byte_buffer_size == 0xff)
|
||||||
|
flush_bytes(emitter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void emit_code(struct lzw_emitter* emitter, uint32_t code)
|
||||||
|
{
|
||||||
|
emitter->buffer |= code << emitter->offset;
|
||||||
|
emitter->offset += emitter->code_size;
|
||||||
|
while (emitter->offset > 7)
|
||||||
|
emit_byte(emitter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void flush_emitter(struct lzw_emitter* emitter)
|
||||||
|
{
|
||||||
|
while (emitter->offset > 0)
|
||||||
|
emit_byte(emitter);
|
||||||
|
if (emitter->byte_buffer_size > 0)
|
||||||
|
flush_bytes(emitter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void encode_image(uint8_t* pixels, uint8_t* previous_pixels,
|
||||||
|
uint16_t width, uint16_t height,
|
||||||
|
uint8_t colors_used, uint16_t frame_delay)
|
||||||
|
{
|
||||||
|
uint8_t color_depth = 1;
|
||||||
|
while (colors_used > (1 << color_depth))
|
||||||
|
color_depth++;
|
||||||
|
|
||||||
|
uint16_t cropped_left = 0;
|
||||||
|
uint16_t cropped_top = 0;
|
||||||
|
uint16_t cropped_width = width;
|
||||||
|
uint16_t cropped_height = height;
|
||||||
|
int16_t transparent_color = -1;
|
||||||
|
|
||||||
|
if (previous_pixels)
|
||||||
|
{
|
||||||
|
// if previous_pixels is not null, we can do some differential encoding!
|
||||||
|
|
||||||
|
#ifdef ANIMATION_OPTIMIZATION_CROP
|
||||||
|
// crop left and right
|
||||||
|
for (int dir = 0; dir < 2; dir++)
|
||||||
|
{
|
||||||
|
while (cropped_width > 0)
|
||||||
|
{
|
||||||
|
uint8_t same_pixels = 1;
|
||||||
|
int x, y;
|
||||||
|
x = (dir == 0) ? cropped_left : cropped_left + cropped_width - 1;
|
||||||
|
for (int y = cropped_top; same_pixels && (y < cropped_top + cropped_height); y++)
|
||||||
|
{
|
||||||
|
int offset = y * width + x;
|
||||||
|
if (pixels[offset] != previous_pixels[offset])
|
||||||
|
same_pixels = 0;
|
||||||
|
}
|
||||||
|
if (same_pixels)
|
||||||
|
{
|
||||||
|
if (dir == 0)
|
||||||
|
cropped_left++;
|
||||||
|
cropped_width--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// crop top and bottom
|
||||||
|
for (int dir = 0; dir < 2; dir++)
|
||||||
|
{
|
||||||
|
while (cropped_height > 0)
|
||||||
|
{
|
||||||
|
uint8_t same_pixels = 1;
|
||||||
|
int x, y;
|
||||||
|
y = (dir == 0) ? cropped_top : cropped_top + cropped_height - 1;
|
||||||
|
for (int x = cropped_left; same_pixels && (x < cropped_left + cropped_width); x++)
|
||||||
|
{
|
||||||
|
int offset = y * width + x;
|
||||||
|
if (pixels[offset] != previous_pixels[offset])
|
||||||
|
same_pixels = 0;
|
||||||
|
}
|
||||||
|
if (same_pixels)
|
||||||
|
{
|
||||||
|
if (dir == 0)
|
||||||
|
cropped_top++;
|
||||||
|
cropped_height--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ANIMATION_OPTIMIZATION_TRANSPARENCY
|
||||||
|
// replace unchanged pixels with tranparency
|
||||||
|
if (colors_used < 255)
|
||||||
|
{
|
||||||
|
transparent_color = colors_used;
|
||||||
|
colors_used++;
|
||||||
|
while (colors_used >= (1 << color_depth))
|
||||||
|
color_depth++;
|
||||||
|
for (int y = 0; y < cropped_height; y++)
|
||||||
|
{
|
||||||
|
uint32_t offset = (y + cropped_top) * width + cropped_left;
|
||||||
|
uint8_t* p = pixels + offset;
|
||||||
|
uint8_t* pp = previous_pixels + offset;
|
||||||
|
for (int x = 0; x < cropped_width; x++)
|
||||||
|
{
|
||||||
|
if (*p == *pp)
|
||||||
|
{
|
||||||
|
*p = (uint8_t)transparent_color;
|
||||||
|
}
|
||||||
|
p++;
|
||||||
|
pp++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
// write graphics control extension
|
||||||
|
struct graphics_control_extension gce;
|
||||||
|
memset(&gce, 0, sizeof(gce));
|
||||||
|
gce.extension_introducer = 0x21;
|
||||||
|
gce.graphics_control_label = 0xf9;
|
||||||
|
gce.byte_size = 4;
|
||||||
|
gce.disposal_method = 1; // draw on top
|
||||||
|
gce.delay_time = frame_delay; // in 1/100 seconds
|
||||||
|
|
||||||
|
#ifdef ANIMATION_OPTIMIZATION_TRANSPARENCY
|
||||||
|
if (transparent_color >= 0)
|
||||||
|
{
|
||||||
|
gce.transparent_color_flag = 1;
|
||||||
|
gce.transparent_color_index = transparent_color;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
put_struct(&gce, sizeof(gce));
|
||||||
|
|
||||||
|
// write image descriptor
|
||||||
|
struct image_descriptor id;
|
||||||
|
memset(&id, 0, sizeof(id));
|
||||||
|
id.image_separator = 0x2c;
|
||||||
|
id.image_left = cropped_left;
|
||||||
|
id.image_top = cropped_top;
|
||||||
|
id.image_width = cropped_width;
|
||||||
|
id.image_height = cropped_height;
|
||||||
|
put_struct(&id, sizeof(id));
|
||||||
|
|
||||||
|
uint8_t lzw_minimum_code_size = color_depth;
|
||||||
|
if (lzw_minimum_code_size < 2)
|
||||||
|
lzw_minimum_code_size = 2;
|
||||||
|
|
||||||
|
put8(lzw_minimum_code_size);
|
||||||
|
|
||||||
|
struct lzw_emitter emitter;
|
||||||
|
emitter.byte_buffer_size = 0;
|
||||||
|
emitter.buffer = 0;
|
||||||
|
emitter.offset = 0;
|
||||||
|
emitter.code_size = lzw_minimum_code_size + 1;
|
||||||
|
|
||||||
|
// set up LZW encoder
|
||||||
|
uint16_t clear_code = (1 << color_depth);
|
||||||
|
uint16_t end_of_information_code = clear_code + 1;
|
||||||
|
|
||||||
|
uint16_t* prefix_table = malloc(sizeof(uint16_t) * (2048 - 1 - end_of_information_code));
|
||||||
|
uint8_t* suffix_table = malloc(sizeof(uint8_t) * (2048 - 1 - end_of_information_code));
|
||||||
|
uint16_t table_length = 0;
|
||||||
|
|
||||||
|
emit_code(&emitter, clear_code);
|
||||||
|
|
||||||
|
uint8_t* p = pixels;
|
||||||
|
uint16_t index_buffer = 0;
|
||||||
|
for (int y = 0; y < cropped_height; y++)
|
||||||
|
{
|
||||||
|
uint8_t* p = pixels + (y + cropped_top) * width + cropped_left;
|
||||||
|
for (int x = 0; x < cropped_width; x++)
|
||||||
|
{
|
||||||
|
if (x == 0 && y == 0)
|
||||||
|
{
|
||||||
|
index_buffer = (uint16_t)(*(p++));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
uint8_t k = *(p++);
|
||||||
|
uint16_t found_table_entry = 0xffff;
|
||||||
|
for (int i = 0; i < table_length; i++)
|
||||||
|
if (index_buffer == prefix_table[i] && k == suffix_table[i])
|
||||||
|
found_table_entry = i + end_of_information_code + 1;
|
||||||
|
if (found_table_entry < 0xffff)
|
||||||
|
index_buffer = found_table_entry;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
prefix_table[table_length] = index_buffer;
|
||||||
|
suffix_table[table_length] = k;
|
||||||
|
table_length += 1;
|
||||||
|
if (table_length + end_of_information_code > (1 << emitter.code_size))
|
||||||
|
emitter.code_size++;
|
||||||
|
emit_code(&emitter, index_buffer);
|
||||||
|
index_buffer = k;
|
||||||
|
if (table_length >= 2048 - 1 - end_of_information_code)
|
||||||
|
{
|
||||||
|
emit_code(&emitter, clear_code);
|
||||||
|
table_length = 0;
|
||||||
|
emitter.code_size = lzw_minimum_code_size + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_code(&emitter, index_buffer);
|
||||||
|
emit_code(&emitter, end_of_information_code);
|
||||||
|
flush_emitter(&emitter);
|
||||||
|
|
||||||
|
free(suffix_table);
|
||||||
|
free(prefix_table);
|
||||||
|
|
||||||
|
put8(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
if (argc < 4)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "This program creates an animated GIF from a series of frames\n");
|
||||||
|
fprintf(stderr, "passed via stdin.\n");
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
fprintf(stderr, "Usage: ./pgif <width> <height> <number of colors>\n");
|
||||||
|
fprintf(stderr, " <width> 1 to 65535\n");
|
||||||
|
fprintf(stderr, " <height> 1 to 65535\n");
|
||||||
|
fprintf(stderr, " <number of colors> 1 to 255\n");
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
fprintf(stderr, "After the program has started, write to its stdin:\n");
|
||||||
|
fprintf(stderr, "- the palette (one color per line, HTML notation without #)\n");
|
||||||
|
fprintf(stderr, " example: '000000\\nffffff\\n' if you specified two colors\n");
|
||||||
|
fprintf(stderr, "- as many frames as you wish, one per line, formatted as\n");
|
||||||
|
fprintf(stderr, " one big hex string and starting with f\n");
|
||||||
|
fprintf(stderr, " example: 'f 000100000100\\n' if you specified a 3x2 image\n");
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
fprintf(stderr, "The default frame delay is 100 ms. You may change the frame delay\n");
|
||||||
|
fprintf(stderr, "for all following frames by specifying 'd <number>\\n' where\n");
|
||||||
|
fprintf(stderr, "<number> is a decimal number and specifies the delay in 1/100 seconds.\n");
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
fprintf(stderr, "To finalize the GIF, close the stdin stream. Output is written to stdout.\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
char* temp = 0;
|
||||||
|
uint16_t width = strtol(argv[1], &temp, 0);
|
||||||
|
uint16_t height = strtol(argv[2], &temp, 0);
|
||||||
|
uint8_t colors_used = strtol(argv[3], &temp, 0);
|
||||||
|
|
||||||
|
uint8_t color_depth = 1;
|
||||||
|
while (colors_used > (1 << color_depth))
|
||||||
|
color_depth++;
|
||||||
|
|
||||||
|
// write header
|
||||||
|
struct header_block header;
|
||||||
|
strncpy(header.signature, "GIF", 3);
|
||||||
|
strncpy(header.version, "89a", 3);
|
||||||
|
put_struct(&header, sizeof(header));
|
||||||
|
|
||||||
|
// write logical screen descriptor
|
||||||
|
struct logical_screen_descriptor lsd;
|
||||||
|
memset(&lsd, 0, sizeof(lsd));
|
||||||
|
lsd.canvas_width = width;
|
||||||
|
lsd.canvas_height = height;
|
||||||
|
lsd.size_of_global_color_table = color_depth - 1;
|
||||||
|
lsd.color_resolution = color_depth - 1;
|
||||||
|
lsd.global_color_table_flag = 1;
|
||||||
|
put_struct(&lsd, sizeof(lsd));
|
||||||
|
|
||||||
|
size_t max_line_size = width + 1024;
|
||||||
|
char* line = malloc(max_line_size);
|
||||||
|
if (!line)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Error allocating line!\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
char* line_p;
|
||||||
|
char hex[4];
|
||||||
|
memset(hex, 0, 4);
|
||||||
|
|
||||||
|
// write global color table, transfer palette from stdin to GIF
|
||||||
|
for (int i = 0; i < colors_used; i++)
|
||||||
|
{
|
||||||
|
fgets(line, max_line_size, stdin);
|
||||||
|
line_p = line;
|
||||||
|
for (int k = 0; k < 3; k++)
|
||||||
|
{
|
||||||
|
strncpy(hex, line_p, 2);
|
||||||
|
put8(strtol(hex, &temp, 16));
|
||||||
|
line_p += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill remaining colors, if any
|
||||||
|
for (int i = colors_used; i < (1 << color_depth); i++)
|
||||||
|
{
|
||||||
|
put8(0); put8(0); put8(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// write application extension NETSCAPE2.0 to loop the animation
|
||||||
|
// (otherwise it just plays once, duh...)
|
||||||
|
struct application_extension ae;
|
||||||
|
memset(&ae, 0, sizeof(ae));
|
||||||
|
ae.gif_extension_code = 0x21;
|
||||||
|
ae.application_extension_label = 0xff;
|
||||||
|
ae.length_of_application_block = 0x0b;
|
||||||
|
strncpy(ae.label, "NETSCAPE2.0", 0x0b);
|
||||||
|
ae.length_of_data_sub_block = 3;
|
||||||
|
ae.one = 1;
|
||||||
|
put_struct(&ae, sizeof(ae));
|
||||||
|
|
||||||
|
uint8_t *previous_pixels = 0;
|
||||||
|
|
||||||
|
uint8_t* pixels = malloc(width * height);
|
||||||
|
if (!pixels)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Error allocating buffer for image.\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t frame_delay = 10;
|
||||||
|
while (fgets(line, max_line_size, stdin))
|
||||||
|
{
|
||||||
|
if (line[0] == 'f' || line[0] == 'l')
|
||||||
|
{
|
||||||
|
uint8_t* p = pixels;
|
||||||
|
if (line[0] == 'f')
|
||||||
|
{
|
||||||
|
for (int y = 0; y < height; y++)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < width; x++)
|
||||||
|
{
|
||||||
|
fgets(hex, 4, stdin);
|
||||||
|
*(p++) = strtol(hex, &temp, 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (line[0] == 'l')
|
||||||
|
{
|
||||||
|
for (int y = 0; y < height; y++)
|
||||||
|
{
|
||||||
|
fgets(line, max_line_size, stdin);
|
||||||
|
line_p = line;
|
||||||
|
hex[1] = 0;
|
||||||
|
for (int x = 0; x < width; x++)
|
||||||
|
{
|
||||||
|
hex[0] = *(line_p++);
|
||||||
|
*(p++) = strtol(hex, &temp, 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encode_image(pixels, previous_pixels, width, height, colors_used, frame_delay);
|
||||||
|
if (!previous_pixels)
|
||||||
|
{
|
||||||
|
previous_pixels = malloc(width * height);
|
||||||
|
if (!previous_pixels)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Error allocating buffer for image.\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
memcpy(previous_pixels, pixels, width * height);
|
||||||
|
}
|
||||||
|
else if (line[0] == 'd')
|
||||||
|
frame_delay = strtol(line + 2, &temp, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previous_pixels)
|
||||||
|
free(previous_pixels);
|
||||||
|
free(pixels);
|
||||||
|
free(line);
|
||||||
|
|
||||||
|
// write trailer
|
||||||
|
put8(0x3b);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
12
plot3d.yaml
Normal file
12
plot3d.yaml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
load:
|
||||||
|
- [0x8900, 'plot3d/SINETABLE']
|
||||||
|
- [0x1200, 'plot3d/multtab.s']
|
||||||
|
- [0xb600, 'plot3d/projtab.s']
|
||||||
|
- [0x8400, 'plot3d/ldrwtab.s']
|
||||||
|
- [0x9500, 'plot3d/FONT']
|
||||||
|
- [0x9000, 'plot3d/object1.s']
|
||||||
|
- [0x8b00, 'plot3d/object2.s']
|
||||||
|
- [0x6000, 'plot3d/plot3d242.s']
|
||||||
|
entry: ENTRY
|
||||||
|
instant_rts:
|
||||||
|
- LOAD1
|
478
symbols.txt
478
symbols.txt
@ -1,478 +0,0 @@
|
|||||||
6000 ENTRY
|
|
||||||
6003 SETONCE
|
|
||||||
601a TABLESET
|
|
||||||
601d LOOP
|
|
||||||
6020 EVJOY
|
|
||||||
602e noROTmode
|
|
||||||
6038 RDPDLS
|
|
||||||
603d PREAD0
|
|
||||||
604c DECSPDY
|
|
||||||
606e INCSPDY
|
|
||||||
608d PREAD1
|
|
||||||
609c DECSPDX
|
|
||||||
60be INCSPDX
|
|
||||||
60dd READKBD
|
|
||||||
60e5 KEYPOINT
|
|
||||||
60e9 INCSPDZ
|
|
||||||
6108 NOINCZ
|
|
||||||
610b KEYCOMMA
|
|
||||||
610f DECSPDZ
|
|
||||||
612e NODECZ
|
|
||||||
6131 KEY1
|
|
||||||
6153 KEY2
|
|
||||||
6175 KEY3
|
|
||||||
6197 KEY4
|
|
||||||
61b9 KEY5
|
|
||||||
61db KEYO
|
|
||||||
61e5 KEYPLUS
|
|
||||||
620f keypend
|
|
||||||
6212 KEYMINUS
|
|
||||||
623c keymend
|
|
||||||
623f KEYQ
|
|
||||||
6249 KEYE
|
|
||||||
6257 WENDSON
|
|
||||||
625d KEYR
|
|
||||||
626b ROTMODEON
|
|
||||||
6271 KEYS
|
|
||||||
627f SSTEPON
|
|
||||||
6285 KEYW
|
|
||||||
6293 HQWIREON
|
|
||||||
6296 ENDKEY10
|
|
||||||
6299 KEYH
|
|
||||||
62a7 HHOLDON
|
|
||||||
62ac KEYN
|
|
||||||
62ba NNORMAL
|
|
||||||
62bf KEYL
|
|
||||||
62cd HHIDDEN
|
|
||||||
62d2 KEYF
|
|
||||||
62e0 RRFILL
|
|
||||||
62ea KEYP
|
|
||||||
62fc PPROJECT
|
|
||||||
6305 KEYC
|
|
||||||
6311 noKEYC
|
|
||||||
6314 clpCTRL
|
|
||||||
631e KEYJ
|
|
||||||
6327 KEYK
|
|
||||||
6330 KEYI
|
|
||||||
6339 KEYM
|
|
||||||
6340 clpEND
|
|
||||||
6345 ENDKEY
|
|
||||||
634e ENDKEY1
|
|
||||||
6351 NOKEY
|
|
||||||
6365 HHLD2
|
|
||||||
636a PB1LOOP
|
|
||||||
636c PB0CHK
|
|
||||||
637a chkHOLD
|
|
||||||
6387 DOSTEP
|
|
||||||
638a drwLOOP
|
|
||||||
63cc jp1
|
|
||||||
63ce udrws2
|
|
||||||
6404 DRWLNS
|
|
||||||
640f DRWSCR10
|
|
||||||
6411 DP1
|
|
||||||
641e swOBJ
|
|
||||||
644d OB_Init
|
|
||||||
6452 OB_Loop
|
|
||||||
6457 Show_2
|
|
||||||
64c4 Show_1
|
|
||||||
652f Modi_1
|
|
||||||
661b noHQWIRE
|
|
||||||
662c noCLP1
|
|
||||||
6630 ChkSize
|
|
||||||
6633 P2_24
|
|
||||||
6640 Chk1
|
|
||||||
6648 Chk2
|
|
||||||
664a P2_25
|
|
||||||
6655 Chk3
|
|
||||||
665d Chk4
|
|
||||||
6668 NoBBox
|
|
||||||
6672 F2_4
|
|
||||||
6678 F2_5
|
|
||||||
667e F2_6
|
|
||||||
6684 F2_7
|
|
||||||
6689 save_2
|
|
||||||
668c F2_8
|
|
||||||
6692 F2_9
|
|
||||||
6698 F2_10
|
|
||||||
669e F2_11
|
|
||||||
66a1 clr_flg
|
|
||||||
66a7 RSTPNT
|
|
||||||
66aa P1_1
|
|
||||||
66ad P2_1
|
|
||||||
66c1 OB_End
|
|
||||||
66d0 DISP2
|
|
||||||
66d9 GOLOOP
|
|
||||||
66dc END
|
|
||||||
66ed drwBBOX
|
|
||||||
6742 mvCLPleft
|
|
||||||
6772 CLPlRTS
|
|
||||||
6773 mvCLPright
|
|
||||||
67a3 CLPrRTS
|
|
||||||
67a4 mvCLPup
|
|
||||||
67d4 CLPuRTS
|
|
||||||
67d5 mvCLPdown
|
|
||||||
6805 CLPdRTS
|
|
||||||
6806 StoreOBData
|
|
||||||
6835 StoreOB1
|
|
||||||
685f GetOBData
|
|
||||||
688e GetOB1
|
|
||||||
68b8 DOCLIP
|
|
||||||
68bf setCLIP
|
|
||||||
68cf clpERASE0
|
|
||||||
6908 clpERASE
|
|
||||||
692c ToggleObject
|
|
||||||
6943 Tog2
|
|
||||||
6953 PRTVARS
|
|
||||||
6962 prtCont
|
|
||||||
699d CHKNEG
|
|
||||||
69ad equal
|
|
||||||
69b0 greater
|
|
||||||
69b5 smaller
|
|
||||||
69be cont
|
|
||||||
69c2 ctemp1
|
|
||||||
69c3 SETUP
|
|
||||||
6aaa SETUPCLP
|
|
||||||
6aca SETUPWHITE
|
|
||||||
6ad3 WhLp1
|
|
||||||
6adf SetWhite2
|
|
||||||
6ae1 WhLp2
|
|
||||||
6aec SetupRTS
|
|
||||||
6aed setANDMSK
|
|
||||||
6b0a clrANDMSK
|
|
||||||
6b27 INITPNTS
|
|
||||||
6b50 FACELOOP
|
|
||||||
6b58 GETPNT
|
|
||||||
6b5d F1_1
|
|
||||||
6b62 F2_1
|
|
||||||
6b68 F1_2
|
|
||||||
6b6d F2_2
|
|
||||||
6b73 F1_3
|
|
||||||
6b78 F2_3
|
|
||||||
6b80 F1_4
|
|
||||||
6b8c doROTMAT
|
|
||||||
6bbd NOPROJ
|
|
||||||
6bfb NOHIDDEN
|
|
||||||
6c06 NORFILL
|
|
||||||
6c07 DRWFAC
|
|
||||||
6c0c NXTLIN
|
|
||||||
6c14 DOLNDRW
|
|
||||||
6c16 DRWLIN
|
|
||||||
6c1e CHKLIN
|
|
||||||
6c26 DODRWLIN
|
|
||||||
6c29 NORFILL3
|
|
||||||
6c2a P1_4
|
|
||||||
6c31 P1_5
|
|
||||||
6c39 P2_2
|
|
||||||
6c41 DOPNT1
|
|
||||||
6c45 P2_4
|
|
||||||
6c4b P2_5
|
|
||||||
6c51 P2_6
|
|
||||||
6c7e NOPROJ2
|
|
||||||
6c8b P2_7
|
|
||||||
6c8f P2_8
|
|
||||||
6c99 P2_8a
|
|
||||||
6c9c CALCYD3
|
|
||||||
6ca9 P2_9
|
|
||||||
6cac DOPNT2
|
|
||||||
6cb1 P2_10
|
|
||||||
6cb9 DOPNT2_1
|
|
||||||
6cbd P2_12
|
|
||||||
6cc3 P2_13
|
|
||||||
6cc9 P2_14
|
|
||||||
6cf6 NOPROJ3
|
|
||||||
6d03 P2_15
|
|
||||||
6d07 P2_16
|
|
||||||
6d11 P2_16a
|
|
||||||
6d14 CALCYD2
|
|
||||||
6d21 P2_17
|
|
||||||
6d24 PNTSDONE
|
|
||||||
6d29 P2_18
|
|
||||||
6d2f P2_19
|
|
||||||
6d35 P2_20
|
|
||||||
6d3a DRWPNT2
|
|
||||||
6d3f P2_21
|
|
||||||
6d45 P2_22
|
|
||||||
6d4b P2_23
|
|
||||||
6d5a CHKCLP
|
|
||||||
6d62 NOCLIP
|
|
||||||
6d65 DRWLINEN
|
|
||||||
6d6a DRWFACEN
|
|
||||||
6d88 DoFill
|
|
||||||
6d8b WRTCONT
|
|
||||||
6d97 DRWCLPFR
|
|
||||||
6da5 DRWEND
|
|
||||||
6da6 doCLPFRM
|
|
||||||
6df1 erCLPFRM
|
|
||||||
6e3c FixRasterTable
|
|
||||||
6e47 ChkRastBot
|
|
||||||
6e52 StartFix
|
|
||||||
6e55 FixLoop
|
|
||||||
6e63 ChkRight
|
|
||||||
6e73 FixCont
|
|
||||||
6e7b FixEnd
|
|
||||||
6e7c CLIPLNS
|
|
||||||
6e94 CLIPLNS1
|
|
||||||
6e9a XDRAWLF
|
|
||||||
6ea4 CLPLL1
|
|
||||||
6ece NODIVI
|
|
||||||
6edd XTOLEFT
|
|
||||||
6f0e NODIVI2
|
|
||||||
6f1a CLPRIGHT
|
|
||||||
6f31 CLIPLNS2
|
|
||||||
6f37 XDRAWLF2
|
|
||||||
6f41 CLPLL2
|
|
||||||
6f69 NODIVI3
|
|
||||||
6f78 XTOLEFT2
|
|
||||||
6fa7 NODIVI4
|
|
||||||
6fb3 CLIPTOP
|
|
||||||
6fc6 CLIPLNS3
|
|
||||||
6fcc YDRAWBO
|
|
||||||
6fd6 CLIPTOP2
|
|
||||||
700d YTOBOT
|
|
||||||
7048 CLIPBOT
|
|
||||||
705b CLIPLNS4
|
|
||||||
7061 YDRAWBOT
|
|
||||||
706b CLPLL4
|
|
||||||
70a2 YTOBOT2
|
|
||||||
70dd CLIPEND4
|
|
||||||
70de ROTMAT
|
|
||||||
71cb LOAD1
|
|
||||||
71e1 poke1
|
|
||||||
71eb LOAD01
|
|
||||||
71f4 OBJNAME
|
|
||||||
720e PRINT
|
|
||||||
7216 P0
|
|
||||||
7221 PFIN
|
|
||||||
722f PEXIT
|
|
||||||
7230 UDRWLNS
|
|
||||||
7260 ldascr
|
|
||||||
726a UDRWSCR1
|
|
||||||
7294 UDRWLP1
|
|
||||||
729c UDRWBRA1
|
|
||||||
729f BRA1
|
|
||||||
741f RvtRTS1
|
|
||||||
7428 UDRW1RTS
|
|
||||||
742d UDRWSCR2
|
|
||||||
7457 UDRWLP2
|
|
||||||
745f UDRWBRA2
|
|
||||||
7462 BRA2
|
|
||||||
75e2 RvtRTS2
|
|
||||||
75eb UDRW2RTS
|
|
||||||
75f0 XCOMP
|
|
||||||
75f1 YJUMP2
|
|
||||||
75f3 YJUMP1
|
|
||||||
75f5 LINEDRAW
|
|
||||||
7603 LX0RIGHT
|
|
||||||
7612 NOINCHI
|
|
||||||
7624 CHKVERT
|
|
||||||
762f phorizontal
|
|
||||||
7630 LX0LEFT
|
|
||||||
7643 LNCOMMON
|
|
||||||
764f LPOSY
|
|
||||||
7655 GOTDY
|
|
||||||
7672 HORIDOM
|
|
||||||
768b LOTABLE
|
|
||||||
7691 GOTTAB
|
|
||||||
76a6 chkwdflag
|
|
||||||
76ad NOTWIDE
|
|
||||||
76b0 HRTS
|
|
||||||
76b1 HNOROLL
|
|
||||||
76b3 HDECC
|
|
||||||
76b7 NOPLT1
|
|
||||||
76c1 chkbot1
|
|
||||||
76cb chkrast1
|
|
||||||
76d5 chkrast1a
|
|
||||||
76df ldadiff2
|
|
||||||
76eb NOH8
|
|
||||||
76f8 LHMODY
|
|
||||||
7707 bnehdecc
|
|
||||||
770b VERTDOM
|
|
||||||
7713 ENDY0
|
|
||||||
772b LOTABLL
|
|
||||||
7731 GOTTAB2
|
|
||||||
773b VNOROLL
|
|
||||||
773d VERTLOOP
|
|
||||||
7751 chkbot2
|
|
||||||
775b chkrast2
|
|
||||||
7765 chkrast2a
|
|
||||||
776f LVCHK
|
|
||||||
7773 LVMODY
|
|
||||||
778c IS8
|
|
||||||
7793 VRTS
|
|
||||||
7794 WIDEDOM
|
|
||||||
77a5 NOPLT3
|
|
||||||
77b1 NOT7
|
|
||||||
77bb chkbot3
|
|
||||||
77c5 chkrast3
|
|
||||||
77cf chkrast3a
|
|
||||||
77d9 ldadiff
|
|
||||||
77ee LWMODY
|
|
||||||
77fb bnewdecc
|
|
||||||
77ff WNOROLL
|
|
||||||
7801 WDECC
|
|
||||||
780e VERTLINE
|
|
||||||
781a USEY0
|
|
||||||
7830 LOTABL2
|
|
||||||
7836 GOTIT
|
|
||||||
783b PVERLOOP
|
|
||||||
7851 chkbot4
|
|
||||||
785b chkrast4
|
|
||||||
7865 chkrast4a
|
|
||||||
786f NOPLT4
|
|
||||||
7872 LPVYTEST
|
|
||||||
7877 FASTLINE
|
|
||||||
7885 fLX0RIGHT
|
|
||||||
7893 fNOINCHI
|
|
||||||
78a5 fCHKVERT
|
|
||||||
78b0 fphorizontal
|
|
||||||
78d6 fphori_rts
|
|
||||||
78d7 fLX0LEFT
|
|
||||||
78ea fLNCOMMON
|
|
||||||
78f6 fLPOSY
|
|
||||||
78fc fGOTDY
|
|
||||||
7919 fHORIDOM
|
|
||||||
792e fLOTABLE
|
|
||||||
7934 fGOTTAB
|
|
||||||
7947 fNOTWIDE
|
|
||||||
794a fHRTS
|
|
||||||
794b fHNOROLL
|
|
||||||
794d fHDECC
|
|
||||||
7951 fHORZLOOP
|
|
||||||
7953 oraA1
|
|
||||||
7961 fNOH8
|
|
||||||
796e fLHMODY
|
|
||||||
797d fVERTDOM
|
|
||||||
7985 fENDY0
|
|
||||||
799b fLOTABLL
|
|
||||||
79a1 fGOTTAB2
|
|
||||||
79ab fVNOROLL
|
|
||||||
79ad fVERTLOOP
|
|
||||||
79bb oraA2
|
|
||||||
79bf fLVCHK
|
|
||||||
79c3 fLVMODY
|
|
||||||
79da fIS8
|
|
||||||
79e1 fVRTS
|
|
||||||
79e2 fWIDEDOM
|
|
||||||
79f3 fWIDELOOP
|
|
||||||
79f5 oraA3
|
|
||||||
7a03 fNOT7
|
|
||||||
7a1a fLWMODY
|
|
||||||
7a29 fWNOROLL
|
|
||||||
7a2b fWDECC
|
|
||||||
7a38 fVERTLINE
|
|
||||||
7a44 fUSEY0
|
|
||||||
7a58 fLOTABL2
|
|
||||||
7a5e fGOTIT
|
|
||||||
7a66 fPVERLOOP
|
|
||||||
7a74 fLPVAND
|
|
||||||
7a79 fLPVYTEST
|
|
||||||
7a7e DIVI
|
|
||||||
7a96 DORCHK
|
|
||||||
7aa3 DIVIGO
|
|
||||||
7aab DLOOP
|
|
||||||
7ab4 SUBTR
|
|
||||||
7ab8 DCONT
|
|
||||||
7ad7 DIVEND
|
|
||||||
7ad8 UMULT
|
|
||||||
7ada UMTCHK
|
|
||||||
7aee UMDONE
|
|
||||||
7af0 MULT
|
|
||||||
7af2 MTCHK
|
|
||||||
7b10 CHKT2
|
|
||||||
7b17 MDONE
|
|
||||||
7b1b FMULT
|
|
||||||
7b1f FMTCHK
|
|
||||||
7b35 FCHKT2
|
|
||||||
7b3c FMDONE
|
|
||||||
7b3e SetColor
|
|
||||||
7b54 ozunid_1
|
|
||||||
7b57 __xormsk
|
|
||||||
7b5c _done
|
|
||||||
7b5d fillraster
|
|
||||||
7b6b _goodmask
|
|
||||||
7b72 rastloop
|
|
||||||
7b7c _pg_or1
|
|
||||||
7b93 _firstre
|
|
||||||
7b9a _noflag
|
|
||||||
7ba8 _lotabl
|
|
||||||
7bb2 _gotlft
|
|
||||||
7bc6 _lotabr
|
|
||||||
7bd0 _repeat
|
|
||||||
7bf0 _not1byte
|
|
||||||
7c0e _rgtnospcl
|
|
||||||
7c2a _lftnospcl
|
|
||||||
7c2c _liny
|
|
||||||
7c35 __rastun
|
|
||||||
7c38 rastlinedone
|
|
||||||
7c52 WH2_1
|
|
||||||
7c64 WH2_2
|
|
||||||
7c66 noWENDS
|
|
||||||
7c68 INC_Y
|
|
||||||
7c6c rast_done
|
|
||||||
7c6d ]offset
|
|
||||||
7d00 rast_unroll
|
|
||||||
7dcb ]offset
|
|
||||||
7df4 RastReset
|
|
||||||
7dfa RSTLP1
|
|
||||||
7e08 RSTLP2
|
|
||||||
7e1a WLCMTXT
|
|
||||||
7e2a STDTEXT
|
|
||||||
7e5b TXTOUT
|
|
||||||
7e69 TXTLOOP
|
|
||||||
7e8b GETBYTE
|
|
||||||
7e90 G1
|
|
||||||
7ead ToPage2
|
|
||||||
7ebf TOutDone
|
|
||||||
7ecc TXTDONE
|
|
||||||
7ecd CHARCOUNT
|
|
||||||
7ece YLINENUM
|
|
||||||
7ecf TXTOB1
|
|
||||||
7edf TXTDAT
|
|
||||||
7f02 TXT2
|
|
||||||
7f06 TXT3
|
|
||||||
7f0a TXT4
|
|
||||||
7f2e TXT5
|
|
||||||
7f3d TXTO1
|
|
||||||
7f49 TXTO2
|
|
||||||
7f50 TXTO3
|
|
||||||
7f5c TXTO4
|
|
||||||
7f68 TXTO5
|
|
||||||
7f74 TXTDAT5
|
|
||||||
7f88 TXTDAT4
|
|
||||||
7f9f TXTDAT6
|
|
||||||
7fb4 TXTDAT7
|
|
||||||
7fca LCNT
|
|
||||||
7fcb XREGDUMP
|
|
||||||
7fcc YREGDUMP
|
|
||||||
7fcd SHPDUMP
|
|
||||||
7fce SHXPOS
|
|
||||||
7fcf DRAWBOX
|
|
||||||
800f DLTTXT
|
|
||||||
8017 DLTSTRT
|
|
||||||
801c DLTLOOP
|
|
||||||
8056 DLTEND
|
|
||||||
8057 YDELROW
|
|
||||||
8058 HEX2BCD
|
|
||||||
8061 CNVBIT
|
|
||||||
80ba setplus
|
|
||||||
80c1 setzero
|
|
||||||
80c6 BCDOUT
|
|
||||||
80d7 BIN
|
|
||||||
80d8 BCD
|
|
||||||
80da BSIGN
|
|
||||||
80db DIGITS
|
|
||||||
80dd DIG1
|
|
||||||
80de DIG2
|
|
||||||
80df DIG3
|
|
||||||
80e0 DIG4
|
|
||||||
80e1 ENDDIG
|
|
||||||
80e2 PRTLEG
|
|
||||||
8103 legCont
|
|
||||||
813a DISTLEG
|
|
||||||
8145 XSPEED
|
|
||||||
8150 YSPEED
|
|
||||||
815b ZSPEED
|
|
||||||
8166 PRTACT
|
|
||||||
817f actCont
|
|
||||||
81a0 ACTTEXT
|
|
||||||
81af ACTDEL
|
|
60
watches.h
60
watches.h
@ -1,60 +0,0 @@
|
|||||||
const uint16_t WATCH_COUNT = 9;
|
|
||||||
#define WATCH_U8 1
|
|
||||||
#define WATCH_U16 2
|
|
||||||
#define WATCH_S8 3
|
|
||||||
#define WATCH_S16 4
|
|
||||||
const char* WATCH_LABELS[] = {
|
|
||||||
"SINX",
|
|
||||||
"COSX",
|
|
||||||
"SINY",
|
|
||||||
"COSY",
|
|
||||||
"SINZ",
|
|
||||||
"COSZ",
|
|
||||||
"RX",
|
|
||||||
"RY",
|
|
||||||
"RZ"
|
|
||||||
};
|
|
||||||
const uint16_t WATCH_ADDRESSES[] = {
|
|
||||||
0x007b,
|
|
||||||
0x007c,
|
|
||||||
0x007d,
|
|
||||||
0x007e,
|
|
||||||
0x007f,
|
|
||||||
0x0080,
|
|
||||||
0x008d,
|
|
||||||
0x008f,
|
|
||||||
0x0091
|
|
||||||
};
|
|
||||||
const uint8_t WATCH_TYPES[] = {
|
|
||||||
3,
|
|
||||||
3,
|
|
||||||
3,
|
|
||||||
3,
|
|
||||||
3,
|
|
||||||
3,
|
|
||||||
4,
|
|
||||||
4,
|
|
||||||
4
|
|
||||||
};
|
|
||||||
const uint16_t ARG_WATCH_COUNT = 2;
|
|
||||||
const uint16_t ARG_WATCH_ADDRESSES[] = {
|
|
||||||
0x7c92,
|
|
||||||
0x7c92
|
|
||||||
};
|
|
||||||
const uint8_t ARG_WATCH_TYPES[] = {
|
|
||||||
3,
|
|
||||||
3
|
|
||||||
};
|
|
||||||
#define WATCH_A 1
|
|
||||||
#define WATCH_X 2
|
|
||||||
#define WATCH_Y 3
|
|
||||||
const uint8_t ARG_WATCH_REGISTERS[] = {
|
|
||||||
3,
|
|
||||||
1
|
|
||||||
};
|
|
||||||
const char* WATCH_REGISTER_LABELS[] = {
|
|
||||||
"",
|
|
||||||
"A",
|
|
||||||
"X",
|
|
||||||
"Y"
|
|
||||||
};
|
|
Loading…
Reference in New Issue
Block a user