Initial commit master origin/HEAD origin/master
authorLukas Krickl <lukas@krickl.dev>
Mon, 23 Mar 2026 18:13:35 +0000 (19:13 +0100)
committerLukas Krickl <lukas@krickl.dev>
Mon, 23 Mar 2026 18:13:35 +0000 (19:13 +0100)
16 files changed:
.gitattributes [new file with mode: 0644]
.gitignore [new file with mode: 0644]
.gitmodules [new file with mode: 0644]
BUGS.md [new file with mode: 0644]
LICENSE [new file with mode: 0644]
README.md [new file with mode: 0644]
TODO.md [new file with mode: 0644]
gen/gen.s [new file with mode: 0644]
makefile [new file with mode: 0644]
src/header.s [new file with mode: 0644]
src/hw.s [new file with mode: 0644]
src/jmp.s [new file with mode: 0644]
src/macros.s [new file with mode: 0644]
src/main.s [new file with mode: 0644]
tools/png2chr.py [new file with mode: 0755]
tools/tmx2map.py [new file with mode: 0755]

diff --git a/.gitattributes b/.gitattributes
new file mode 100644 (file)
index 0000000..176a458
--- /dev/null
@@ -0,0 +1 @@
+* text=auto
diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..cac7d12
--- /dev/null
@@ -0,0 +1,79 @@
+# 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
diff --git a/.gitmodules b/.gitmodules
new file mode 100644 (file)
index 0000000..3566de2
--- /dev/null
@@ -0,0 +1,3 @@
+[submodule "assets"]
+       path = assets
+       url = ssh://git@codeberg.org/unlink2/gbrg-assets.git
diff --git a/BUGS.md b/BUGS.md
new file mode 100644 (file)
index 0000000..97fed4b
--- /dev/null
+++ b/BUGS.md
@@ -0,0 +1,2 @@
+# Known Bugs
+
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..5e5b158
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,18 @@
+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.
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..2f49103
--- /dev/null
+++ b/README.md
@@ -0,0 +1,32 @@
+# 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.
+
+
diff --git a/TODO.md b/TODO.md
new file mode 100644 (file)
index 0000000..6bd48d0
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,3 @@
+# TODO
+
+
diff --git a/gen/gen.s b/gen/gen.s
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/makefile b/makefile
new file mode 100644 (file)
index 0000000..5b9affc
--- /dev/null
+++ b/makefile
@@ -0,0 +1,27 @@
+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
+
diff --git a/src/header.s b/src/header.s
new file mode 100644 (file)
index 0000000..14bb7a3
--- /dev/null
@@ -0,0 +1,22 @@
+; 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 - $
diff --git a/src/hw.s b/src/hw.s
new file mode 100644 (file)
index 0000000..4aa02f3
--- /dev/null
+++ b/src/hw.s
@@ -0,0 +1,122 @@
+; 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
+
+
diff --git a/src/jmp.s b/src/jmp.s
new file mode 100644 (file)
index 0000000..a5ea738
--- /dev/null
+++ b/src/jmp.s
@@ -0,0 +1,46 @@
+; 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
diff --git a/src/macros.s b/src/macros.s
new file mode 100644 (file)
index 0000000..c33be9d
--- /dev/null
@@ -0,0 +1,5 @@
+
+#define BREAK ld b, b
+
+; relative jump: jr <label> RELB 
+#define REL - $ - 2 & 0xFF 
diff --git a/src/main.s b/src/main.s
new file mode 100644 (file)
index 0000000..3aa330a
--- /dev/null
@@ -0,0 +1,17 @@
+#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"
diff --git a/tools/png2chr.py b/tools/png2chr.py
new file mode 100755 (executable)
index 0000000..f7c3af6
--- /dev/null
@@ -0,0 +1,85 @@
+#!/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)
diff --git a/tools/tmx2map.py b/tools/tmx2map.py
new file mode 100755 (executable)
index 0000000..e78eaf4
--- /dev/null
@@ -0,0 +1,52 @@
+#!/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)
+