the big update

This commit is contained in:
Michael Specht 2018-02-14 20:16:22 +01:00
parent 50f4dad58c
commit c6ff6ac331
12 changed files with 1196 additions and 5181 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
report.html
report-files
p65c02
pgif

View File

@ -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
View 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>

BIN
empty Normal file

Binary file not shown.

4213
labels.h

File diff suppressed because it is too large Load Diff

View File

@ -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);
}
void render_hires_screen()
{
uint8_t current_screen = ram[0x30b];
int x, y;
for (y = 0; y < 192; y++)
char *p = 0;
int i = strtol(s, &p, base);
if (p == s)
{
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);
}
fprintf(stderr, "Error parsing integer: %s", s);
exit(1);
}
return i;
}
int32_t find_address_for_label(const char* label)
void handle_watch(uint16_t pc, uint8_t post)
{
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 offset = watch_offset_for_pc_and_post[((int32_t)pc << 1) | post];
if (offset == -1)
return;
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)
last_cycles = cpu.total_cycles / 100000;
printf("cycles %d\n", last_cycles * 100000);
}
if (ram[0x30b] != old_screen_number)
{
old_screen_number = ram[0x30b];
uint8_t current_screen = old_screen_number;
int x, y;
printf("screen %d", cpu.total_cycles);
for (y = 0; y < 192; y++)
{
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)
{
old_screen_number = ram[0x30b];
render_hires_screen();
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
View File

@ -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

View File

@ -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
View 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
View 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

View File

@ -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

View File

@ -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"
};