--- /dev/null
+* text=auto
--- /dev/null
+# Build directory
+bin/
+obj/
+tags
+
+ Prerequisites
+*.d
+
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Linker output
+*.ilk
+*.map
+*.exp
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+*.su
+*.idb
+*.pdb
+vgcore.*
+
+# Kernel Module Compile Results
+*.mod*
+*.cmd
+.tmp_versions/
+modules.order
+Module.symvers
+Mkfile.old
+dkms.conf
+
+compile_commands.json
+.cache/
+.clang_complete
+.clangd
+.session
+
+assets/
+*.pyc
+
+pj.gb
+pj.mlb
+pj.lst
+testpj.gb
+testrg.mlb
+testrg.lst
+
+*.sav
+*.patch
+*.sym
+debugrc
--- /dev/null
+[submodule "assets"]
+ path = assets
+ url = ssh://git@codeberg.org/unlink2/gbrg-assets.git
--- /dev/null
+# Known Bugs
+
--- /dev/null
+Copyright 2025-2026 Lukas Krickl (lukas@krickl.dev)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction,
+including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS",
+WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null
+# picojump
+
+## Table of content
+
+- [Installation](#Installation)
+- [Usage](#Usage)
+- [License](#License)
+- [Contributing](#Contributing)
+- [TODO](#TODO)
+
+## Installation
+
+To compile this game you will need a copy of `ulas`.
+Then simply run `make` to build the rom.
+
+## Usage
+
+This game is compatible with any gameboy emulator.
+
+### Building assets
+
+### Directory structure
+
+- src: all source files
+- gen/gen.s: include file for all generated files
+- gen: generated source files based on assets
+
+## License
+
+This program is distributed under the terms of the MIT License.
+
+
--- /dev/null
+# TODO
+
+
--- /dev/null
+NAME=pj
+TEST_NAME=test$(NAME)
+AS=ulas
+FLAGS=-D DEBUG
+#SYMTYPE=sym
+SYMTYPE=mlb
+INCS=-I ./src -I ./gen
+
+bin:
+ $(AS) $(FLAGS) -o $(NAME).gb -l ${NAME}.lst -s $(NAME).$(SYMTYPE) -S $(SYMTYPE) $(INCS) src/main.s
+
+.PHONY: tiles
+tiles:
+ ./tools/png2chr.py assets/tiles/bank8000.png > gen/tiles/bank8000.inc
+ ./tools/png2chr.py assets/tiles/bank8800.png > gen/tiles/bank8800.inc
+ ./tools/png2chr.py assets/tiles/bank8C00.png > gen/tiles/bank8C00.inc
+ ./tools/png2chr.py assets/tiles/bank9000.png > gen/tiles/bank9000.inc
+
+.PHONY: maps
+maps:
+ ./tools/tmx2map.py assets/maps/l1.tmx > gen/maps/l1.inc
+
+.PHONY: clean
+clean:
+ rm -f ./gen/maps/*.inc
+ rm -f ./gen/tiles/*.inc
+
--- /dev/null
+; header
+ jp entry
+ nop
+logo:
+.db 0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, 0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D
+.db 0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E, 0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99
+.db 0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC, 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E
+logo_end:
+
+.str "tb"
+.fill 0, 0x147 - $
+
+; cartride type
+; MBC1+RAM+Battery
+.db 0x03
+ ; rom size 32K
+.db 0x00
+ ; ram size 8k
+.db 0x02
+.fill 0, 0x14B - $
+.chksm
+.fill 0, 0x150 - $
--- /dev/null
+; register defs
+
+#define SCRNW 159
+#define SCRNH 143
+
+; lcd registers
+
+; lcd y
+#define RLY 0xFF44
+#define RLYC 0xFF45
+#define RLCD 0xFF40
+
+#define LCDCF_BGON 0b00000001
+#define LCDCF_ON 0b10000000
+#define LCDCF_OBJON 0b00000010
+#define LCDCF_TILE_BANK 0b00010000
+#define LCDF_WINDOWON 0b00100000
+#define LCDF_OBJ_SIZE 0b00000100
+#define LCDF_WINBANKSELECT 0b01000000
+
+#define RSTAT 0xFF41
+
+#define STATF_LYC_INT_SELECT 0b01000000
+
+#define RBGP 0xFF47
+#define ROBP0 0xFF48
+#define ROBP1 0xFF49
+
+; screen scroll y and x
+#define RSCY 0xFF42
+#define RSCX 0xFF43
+
+; window y and x
+#define RWY 0xFF4A
+#define RWX 0xFF4B
+
+; P1: joy pad register
+#define RP1 0xFF00
+#define P1F5 0b00100000 ; get buttons
+#define P1F4 0b00010000 ; get dpad
+
+; buttons
+.def int BTNDOWN = 0x80
+.def int BTNUP = 0x40
+.def int BTNLEFT = 0x20
+.def int BTNRIGHT = 0x10
+.def int BTNSTART = 0x08
+.def int BTNSELECT = 0x04
+.def int BTNA = 0x01
+.def int BTNB = 0x02
+
+; obj off-screen offsets
+#define OBJ_OFF_X 8
+#define OBJ_OFF_Y 16
+
+; interrupts
+; interrupt flag
+#define IF 0xFF0F
+; interrupt enabled
+#define IE 0xFFFF
+#define IVBLANK 0b00000001
+#define ILCD 0b00000010
+
+; location where code for dma needs to be memcyp'd to
+.def int DMAFN = 0xFF80
+#define DMA 0xFF46
+
+#define OBJSMAX 40
+
+.def int P1FDPAD = P1F5
+.def int P1FBTN = P1F4
+.def int P1FNONE = P1F5 | P1F4
+
+; memory map
+.def int VRAM = 0x8000
+.def int VRAM8800 = VRAM+0x0800
+.def int VRAM9000 = VRAM+0x1000
+
+.def int VIEW_W = 20
+.def int VIEW_H = 20
+.def int SCRN_W = 32
+.def int SCRN_H = 32
+
+.def int SCRN0 = 0x9800
+.def int SCRN0_UI = SCRN0 + SCRN_W * 16
+
+.def int SCRN1 = 0x9C00
+.def int OAMRAM = 0xFE00
+.def int OBJSIZE = 4
+.def int OAMRAM_SIZE = OBJSMAX * OBJSIZE
+
+.def int OAM_FPRIO = 0b10000000
+#define OAM_FYFLIP 0b01000000
+#define OAM_FXFLIP 0b00100000
+#define OAM_DMG_PAL 0b00010000
+
+; MBC1 registers
+
+; write 0xA here to enable sram
+#define MBC1_SRAM_ENABLE 0x0000
+#define MBC1_ROM_BANKSEL 0x2000
+#define MBC1_SRAM_BANKSEL 0x4000
+
+; audio registers
+#define AUDIO_CTRL 0xFF26
+#define MASTER_VOLUME 0xFF24
+
+#define CH1_SWEEP 0xFF10
+#define CH1_LEN_DUTY 0xFF11
+#define CH1_VOL_ENV 0xFF12
+#define CH1_PERIOD_LO 0xFF13
+#define CH1_PERIOD_HI_CTRL 0xFF14
+
+#define CH4_LENGTH 0xFF20
+#define CH4_VOLUME_ENV 0xFF21
+#define CH4_FREQ_RAND 0xFF22
+#define CH4_CTRL 0xFF23
+
+#define AUDIO_ALL_ON 0b10000000
+#define AUDIO_ALL_OFF 0b00000000
+
+
--- /dev/null
+; RST $00
+; panic exception handler
+rst_panic:
+ di
+@forever:
+ jp @forever
+.fill 0, 0x08 - $
+
+ ; rst 0008
+ ; simple call to hl
+sys_call_hl:
+ jp hl
+
+.fill 0, 0x10 - $
+
+ ; rst 0010
+ ; farcall
+ ; inputs:
+ ; a: bank
+ ; hl: routine ptr
+sys_farcall:
+ ret
+
+
+.fill 0, 0x40 - $
+; interrupt vectors
+
+;=============
+; vblank 0x40
+;=============
+vec_vblank:
+ reti
+
+.fill 0, 0x48 - $
+;=============
+; STA 0x48
+;=============
+vec_stat:
+ reti
+ ; disable objects
+ push af
+ ld a, [RLCD]
+ and a, ~LCDCF_OBJON & 0xFF
+ ld [RLCD], a
+ pop af
+ reti
--- /dev/null
+
+#define BREAK ld b, b
+
+; relative jump: jr <label> RELB
+#define REL - $ - 2 & 0xFF
--- /dev/null
+#include "hw.s"
+#include "macros.s"
+
+.org 0x00
+.section prgrom
+#include "jmp.s"
+.fill 0, 0x100 - $
+#include "header.s"
+
+entry:
+
+main:
+@forever:
+ jr @forever REL
+
+
+#include "gen.s"
--- /dev/null
+#!/usr/bin/env python
+
+# this script converts any image to a char map compatible with the
+# assembler's .chr directive
+
+import sys
+import os
+from PIL import Image
+from itertools import product
+
+DW = 8
+
+if len(sys.argv) < 2:
+ print("Usage: png2chr.py <source> [8/16]")
+ sys.exit(-1)
+
+if len(sys.argv) == 3:
+ DW = int(sys.argv[2])
+
+
+# these colors are mapped
+# maps color tuple to .chr string
+COLORS = {(0, 0, 0, 255): '3', (0, 0, 0, 0): '0', (107, 107, 107, 255): '1', (181, 181, 181, 255): '2', (255, 255, 255, 255): '0'}
+
+src = sys.argv[1]
+
+def tile(src, d):
+ img = Image.open(src)
+ w, h = img.size
+ tile_index = 0
+
+ # split the image into even tiles
+ grid = product(range(0, h-h%d, d), range(0, w-w%d, d))
+ for i, j in grid:
+ box = (j, i, j+d, i+d)
+ cropped = img.crop(box)
+ cw, ch = cropped.size
+ cropped = cropped.load()
+
+ print('; tile ' + str(tile_index))
+ tile_index += 1
+ for y in range(0, ch):
+ print(".chr ", end='')
+ for x in range(0, cw):
+ color = cropped[x, y]
+ if color in COLORS:
+ print(COLORS[color], end='')
+ else:
+ print('Unknown color: ' + str(cropped[x,y]))
+ sys.exit(-1)
+ print("")
+
+def tile16(src, d):
+ img = Image.open(src)
+ w, h = img.size
+ tile_index = 0
+
+ # split the image into even tiles
+ grid = product(range(0, h-h%d*2, d*2), range(0, w-w%d, d))
+ for i, j in grid:
+ box = (j, i, j+d, i+d*2)
+ cropped = img.crop(box)
+ cw, ch = cropped.size
+ cropped = cropped.load()
+
+ print('; tile ' + str(tile_index))
+ tile_index += 1
+ for y in range(0, ch):
+ print(".chr ", end='')
+ for x in range(0, cw):
+ color = cropped[x, y]
+ if color in COLORS:
+ print(COLORS[color], end='')
+ else:
+ print('Unknown color: ' + str(cropped[x,y]))
+ sys.exit(-1)
+ print("")
+
+
+print("; this tileset was generated by png2chr.py")
+
+if DW == 8:
+ tile(src, 8)
+else:
+ tile16(src, 8)
--- /dev/null
+#!/usr/bin/env python
+import sys
+import os
+import xml.etree.ElementTree as ET
+
+TILE_SIZE = 8
+MAP_W = 20
+
+if len(sys.argv) < 2:
+ print("Usage: tmx2map.py <source> [tile_offset]")
+ sys.exit(-1)
+
+
+src = sys.argv[1]
+
+tile_offset = 1
+
+if len(sys.argv) > 2:
+ tile_offset = int(sys.argv[2])
+
+def print_bg_data(data):
+ print("; this map was generated by tmx2map.py")
+ split = data.split(",")
+
+ for i, b in enumerate(split):
+ end = ', '
+ if i % MAP_W == 0:
+ print("\n.db ", end = '')
+
+
+ val = int(b.strip()) - tile_offset
+
+ if (i+1) % MAP_W == 0:
+ end = ''
+
+ if i == len(split) - 1:
+ end = '\n'
+ print(hex(val), end=end)
+ print("")
+
+def convert(src):
+ tree = ET.parse(src)
+ root = tree.getroot()
+
+ for child in root:
+ if child.tag == "layer":
+ name = child.attrib['name']
+ for data in child:
+ print_bg_data(data.text)
+
+convert(src)
+