mirror of
https://github.com/specht/champ.git
synced 2024-11-24 02:31:29 +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 <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include "labels.h"
|
||||
#include "watches.h"
|
||||
|
||||
#define SCALE 4
|
||||
#define SCREEN_WIDTH 280
|
||||
#define SCREEN_HEIGHT 192
|
||||
#define SCREEN_REFRESH_EVERY_CYCLES 10000
|
||||
|
||||
#define CARRY 0x01
|
||||
#define ZERO 0x02
|
||||
@ -21,15 +16,6 @@
|
||||
#define OVERFLOW 0x40
|
||||
#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_call_stack = 0;
|
||||
|
||||
@ -44,41 +30,6 @@ uint64_t frame_count = 0;
|
||||
|
||||
uint16_t start_pc = 0x6000;
|
||||
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] = {
|
||||
0x0000, 0x0400, 0x0800, 0x0c00, 0x1000, 0x1400, 0x1800, 0x1c00,
|
||||
@ -108,7 +59,7 @@ uint16_t yoffset[192] = {
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint16_t ip;
|
||||
uint16_t pc;
|
||||
uint8_t sp;
|
||||
uint64_t total_cycles;
|
||||
uint8_t a, x, y, flags;
|
||||
@ -116,7 +67,7 @@ typedef struct {
|
||||
|
||||
void init_cpu(r_cpu* cpu)
|
||||
{
|
||||
cpu->ip = 0;
|
||||
cpu->pc = 0;
|
||||
cpu->sp = 0xff;
|
||||
cpu->total_cycles = 0;
|
||||
cpu->a = 0;
|
||||
@ -128,6 +79,27 @@ void init_cpu(r_cpu* cpu)
|
||||
uint8_t ram[0x10000];
|
||||
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)
|
||||
{
|
||||
uint8_t buffer[0x10000];
|
||||
@ -140,20 +112,19 @@ void load(char* path, uint16_t offset)
|
||||
exit(1);
|
||||
}
|
||||
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);
|
||||
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();
|
||||
result |= ((uint16_t)rip8()) << 8;
|
||||
uint16_t result = rpc8();
|
||||
result |= ((uint16_t)rpc8()) << 8;
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -169,41 +140,9 @@ uint16_t read16(uint16_t address)
|
||||
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)
|
||||
{
|
||||
ram[address] = value;
|
||||
if (watches_file)
|
||||
refresh_watches(address);
|
||||
}
|
||||
|
||||
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 OPCODE_VARIANT(opcode, cycles, addressing_mode) \
|
||||
if (opcode_from_ip == opcode) { \
|
||||
if (opcode_from_pc == opcode) { \
|
||||
*_opcode = test_opcode; \
|
||||
*_addressing_mode = addressing_mode; \
|
||||
*_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
|
||||
*/
|
||||
r_opcode test_opcode = NO_OPCODE;
|
||||
uint8_t opcode_from_ip = rip8();
|
||||
*_read_opcode = opcode_from_ip;
|
||||
uint8_t opcode_from_pc = rpc8();
|
||||
*_read_opcode = opcode_from_pc;
|
||||
|
||||
TEST_OPCODE(ADC)
|
||||
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(0xF6, 6, zero_page_x)
|
||||
OPCODE_VARIANT(0xEE, 6, absolute)
|
||||
OPCODE_VARIANT(0xFe, 7, absolute_x)
|
||||
OPCODE_VARIANT(0xFE, 7, absolute_x)
|
||||
TEST_OPCODE(JMP)
|
||||
OPCODE_VARIANT(0x4C, 3, absolute)
|
||||
OPCODE_VARIANT(0x6C, 5, indirect)
|
||||
@ -616,15 +555,15 @@ void branch(uint8_t condition, int8_t offset, uint8_t* cycles)
|
||||
{
|
||||
// branch succeeds
|
||||
*cycles += 1;
|
||||
if ((cpu.ip & 0xfff0) != ((cpu.ip + offset) & 0xfff0))
|
||||
if ((cpu.pc & 0xfff0) != ((cpu.pc + offset) & 0xfff0))
|
||||
*cycles += 1;
|
||||
cpu.ip += offset;
|
||||
cpu.pc += offset;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
uint8_t read_opcode = 0;
|
||||
@ -635,7 +574,7 @@ void handle_next_opcode()
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -646,47 +585,47 @@ void handle_next_opcode()
|
||||
switch (addressing_mode)
|
||||
{
|
||||
case immediate:
|
||||
immediate_value = rip8();
|
||||
immediate_value = rpc8();
|
||||
break;
|
||||
case relative:
|
||||
relative_offset = (int8_t)rip8();
|
||||
relative_offset = (int8_t)rpc8();
|
||||
break;
|
||||
case absolute:
|
||||
target_address = rip16();
|
||||
target_address = rpc16();
|
||||
break;
|
||||
case zero_page:
|
||||
target_address = rip8();
|
||||
target_address = rpc8();
|
||||
break;
|
||||
case indirect:
|
||||
target_address = read16(rip16());
|
||||
target_address = read16(rpc16());
|
||||
break;
|
||||
case zero_page_indirect:
|
||||
target_address = read16(rip8());
|
||||
target_address = read16(rpc8());
|
||||
break;
|
||||
case zero_page_x:
|
||||
target_address = (rip8() + cpu.x) % 0xff;
|
||||
target_address = (rpc8() + cpu.x) % 0xff;
|
||||
break;
|
||||
case zero_page_y:
|
||||
target_address = (rip8() + cpu.y) % 0xff;
|
||||
target_address = (rpc8() + cpu.y) % 0xff;
|
||||
break;
|
||||
case absolute_x:
|
||||
target_address = rip16();
|
||||
target_address = rpc16();
|
||||
if ((target_address >> 12) != ((target_address + cpu.x) >> 12))
|
||||
cycles += 1;
|
||||
target_address += cpu.x;
|
||||
break;
|
||||
case absolute_y:
|
||||
target_address = rip16();
|
||||
target_address = rpc16();
|
||||
if ((target_address >> 12) != ((target_address + cpu.y) >> 12))
|
||||
cycles += 1;
|
||||
target_address += cpu.y;
|
||||
break;
|
||||
case indexed_indirect_x:
|
||||
target_address = read16((rip8() + cpu.x) & 0xff);
|
||||
target_address = read16((rpc8() + cpu.x) & 0xff);
|
||||
break;
|
||||
case indirect_indexed_y:
|
||||
target_address = cpu.y;
|
||||
uint16_t temp = read16(rip8());
|
||||
uint16_t temp = read16(rpc8());
|
||||
if ((target_address >> 12) != ((target_address + temp) >> 12))
|
||||
cycles += 1;
|
||||
target_address += temp;
|
||||
@ -695,7 +634,7 @@ void handle_next_opcode()
|
||||
|
||||
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]
|
||||
);
|
||||
}
|
||||
@ -836,19 +775,21 @@ void handle_next_opcode()
|
||||
update_zero_and_negative_flags(cpu.y);
|
||||
break;
|
||||
case JMP:
|
||||
cpu.ip = target_address;
|
||||
cpu.pc = target_address;
|
||||
// TODO handle page boundary behaviour?
|
||||
break;
|
||||
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[trace_stack_pointer] = cpu.sp;
|
||||
trace_stack_pointer--;
|
||||
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.ip - 1) & 0xff);
|
||||
cpu.ip = target_address;
|
||||
push(((cpu.pc - 1) >> 8) & 0xff);
|
||||
push((cpu.pc - 1) & 0xff);
|
||||
cpu.pc = target_address;
|
||||
break;
|
||||
case LDA:
|
||||
cpu.a = (addressing_mode == immediate) ? immediate_value : read8(target_address);
|
||||
@ -939,17 +880,19 @@ void handle_next_opcode()
|
||||
cpu.flags = pop();
|
||||
t16 = pop();
|
||||
t16 |= ((uint16_t)pop()) << 8;
|
||||
cpu.ip = t16;
|
||||
cpu.pc = t16;
|
||||
break;
|
||||
case RTS:
|
||||
if (trace_stack[trace_stack_pointer + 1] == cpu.sp + 2)
|
||||
{
|
||||
printf("rts %d\n", cpu.total_cycles);
|
||||
fflush(stdout);
|
||||
trace_stack_pointer++;
|
||||
}
|
||||
|
||||
t16 = pop();
|
||||
t16 |= ((uint16_t)pop()) << 8;
|
||||
cpu.ip = t16 + 1;
|
||||
cpu.pc = t16 + 1;
|
||||
break;
|
||||
case SBC:
|
||||
sbc((addressing_mode == immediate) ? immediate_value : read8(target_address));
|
||||
@ -1004,7 +947,8 @@ void handle_next_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);
|
||||
}
|
||||
cpu.total_cycles += cycles;
|
||||
@ -1021,45 +965,88 @@ void handle_next_opcode()
|
||||
flags_str[5] = 0;
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
void set_pixel(int px, int py, unsigned int color)
|
||||
int parse_int(const char* s, int base)
|
||||
{
|
||||
int x, y;
|
||||
XSetForeground(dsp, gc, color);
|
||||
for (y = 0; y < SCALE; y++)
|
||||
for (x = 0; x < SCALE; x++)
|
||||
XDrawPoint(dsp, win, gc, px * SCALE + x, py * SCALE + y);
|
||||
char *p = 0;
|
||||
int i = strtol(s, &p, base);
|
||||
if (p == s)
|
||||
{
|
||||
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];
|
||||
int x, y;
|
||||
for (y = 0; y < 192; y++)
|
||||
{
|
||||
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 offset = watch_offset_for_pc_and_post[((int32_t)pc << 1) | post];
|
||||
if (offset == -1)
|
||||
return;
|
||||
|
||||
int32_t find_address_for_label(const char* label)
|
||||
{
|
||||
for (int i = 0; i < LABEL_COUNT; i++)
|
||||
if ((strlen(label) == strlen(LABELS[i])) &&
|
||||
(strcmp(label, LABELS[i]) == 0))
|
||||
for (int k = 0; k < 0x10000; k++)
|
||||
if (label_for_address[k] == i)
|
||||
return k;
|
||||
return -1;
|
||||
int32_t old_index = -1;
|
||||
while (watches[offset].pc == pc && watches[offset].post == post)
|
||||
{
|
||||
r_watch* watch = &watches[offset];
|
||||
if (old_index == -1)
|
||||
{
|
||||
printf("watch %d", watch->index);
|
||||
}
|
||||
else
|
||||
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)
|
||||
@ -1069,28 +1056,89 @@ int main(int argc, char** argv)
|
||||
printf("Usage: ./champ [options] <memory dump>\n");
|
||||
printf("\n");
|
||||
printf("Options:\n");
|
||||
printf(" --show-screen\n");
|
||||
printf(" --hide-log\n");
|
||||
printf(" --start-pc <address or label>\n");
|
||||
printf(" --frame-start <address or label>\n");
|
||||
printf(" --max-frames <n>\n");
|
||||
printf(" --watches <output filename>\n");
|
||||
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++)
|
||||
{
|
||||
if (strcmp(argv[i], "--show-screen") == 0)
|
||||
show_screen = 1;
|
||||
else if (strcmp(argv[i], "--hide-log") == 0)
|
||||
if (strcmp(argv[i], "--hide-log") == 0)
|
||||
show_log = 0;
|
||||
else if (strcmp(argv[i], "--start-pc") == 0)
|
||||
{
|
||||
char *temp = argv[++i];
|
||||
char *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);
|
||||
}
|
||||
else if (strcmp(argv[i], "--start-frame") == 0)
|
||||
@ -1098,30 +1146,8 @@ int main(int argc, char** argv)
|
||||
char *temp = argv[++i];
|
||||
char *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);
|
||||
}
|
||||
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
|
||||
{
|
||||
fprintf(stderr, "Unknown argument: %s\n", argv[i]);
|
||||
@ -1134,123 +1160,56 @@ int main(int argc, char** argv)
|
||||
|
||||
load(argv[argc - 1], 0);
|
||||
|
||||
if (show_screen)
|
||||
init_display();
|
||||
|
||||
init_cpu(&cpu);
|
||||
cpu.ip = start_pc;
|
||||
cpu.pc = start_pc;
|
||||
struct timespec tstart = {0, 0};
|
||||
clock_gettime(CLOCK_MONOTONIC, &tstart);
|
||||
unsigned long start_time = tstart.tv_sec * 1000000000 + tstart.tv_nsec;
|
||||
uint32_t next_display_refresh = 0;
|
||||
uint8_t old_screen_number = 0;
|
||||
int last_cycles = -1;
|
||||
while (1) {
|
||||
if (watches_file)
|
||||
{
|
||||
// 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_watch(cpu.pc, 0);
|
||||
uint16_t old_pc = cpu.pc;
|
||||
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)
|
||||
{
|
||||
frame_cycle_count += (cpu.total_cycles - last_frame_cycle_count);
|
||||
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;
|
||||
}
|
||||
// if (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 (cpu.total_cycles / 100000 != last_cycles)
|
||||
{
|
||||
if (XEventsQueued(dsp, QueuedAfterFlush) > 0)
|
||||
{
|
||||
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;
|
||||
last_cycles = cpu.total_cycles / 100000;
|
||||
printf("cycles %d\n", last_cycles * 100000);
|
||||
}
|
||||
if (ram[0x30b] != old_screen_number)
|
||||
{
|
||||
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, "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");
|
||||
for (uint32_t i = 0; i < 0x10000; i++)
|
||||
|
||||
if (watches)
|
||||
{
|
||||
if (cycles_per_function[i] > 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");
|
||||
free(watches);
|
||||
watches = 0;
|
||||
}
|
||||
}
|
||||
if (watches_file)
|
||||
fclose(watches_file);
|
||||
|
||||
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