/
almosttx
executable file
·294 lines (246 loc) · 9.92 KB
/
almosttx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
#!/data/data/com.termux/files/usr/bin/python
# Almost Loaded
# Script that loads 83p files into an AlmostTI RAM file.
# Caveats:
# You need a clean RAM file. This is created by running AndieGraph the first time and quitting when you see "Mem Cleared"
# To start with this only works for 83p files.
# More variable types might be made available.
# ROM version 1.07000 is used, might not work on others
#
# Arguments:
# Valid RAM-file (preferrably clean)
# Name of desired output file.
# List of 83p files to load in sequential order.
import argparse
import os
def r2p(ram, address, order='lsb'):
"""
Read pointer value from memory address.
"""
p = [ram[address], ram[address+1]]
if order == 'lsb':
p = p[::-1]
return p[0] * 256 + p[1]
def v2r(ram, address, value, order='lsb'):
"""
Read pointer value from memory address.
"""
v = [value//256, value%256]
if order == 'lsb':
v = v[::-1]
ram[address] = v[0]
ram[address+1] = v[1]
# Constants
FATOVERHEAD = 4
DATAOVERHEAD = 2
# Memory adresses
# FAT END is the first byte of the FAT data reading it backwards.
FAT_START = 0xfd74
# Value in this location points to storage of Y-VARS
YVARS_POINTER_MSB = 0xfdb2
YVARS_POINTER_LSB = 0xfdb3
# Value in these locations probably represent the start/end of different data types
# Currently only programs YVARS and FAT are mapped and therefore these all point to program start now.
# Known for program related addresses
USER_VARIABLE_TYPE_01_LSB = 0x8098
USER_VARIABLE_TYPE_01_MSB = 0x8099
USER_VARIABLE_TYPE_02_LSB = 0x91e1
USER_VARIABLE_TYPE_02_MSB = 0x91e2
USER_VARIABLE_TYPE_03_LSB = 0x91e3
USER_VARIABLE_TYPE_03_MSB = 0x91e4
# Known for FAT related addresses
USER_VARIABLE_TYPE_04_LSB = 0x9309
USER_VARIABLE_TYPE_04_MSB = 0x930a
USER_VARIABLE_TYPE_05_LSB = 0x930b
USER_VARIABLE_TYPE_05_MSB = 0x930c
USER_VARIABLE_TYPE_06_LSB = 0x930d
USER_VARIABLE_TYPE_06_MSB = 0x930e
USER_VARIABLE_TYPE_07_LSB = 0x930f
USER_VARIABLE_TYPE_07_MSB = 0x9310
USER_VARIABLE_TYPE_08_LSB = 0x9311
USER_VARIABLE_TYPE_08_MSB = 0x9312
USER_VARIABLE_TYPE_09_LSB = 0x9317
USER_VARIABLE_TYPE_09_MSB = 0x9318
# Y-VARS start
USER_VARIABLE_TYPE_10_LSB = 0x931b
USER_VARIABLE_TYPE_10_MSB = 0x931c
# Value in this location indicates the memory used
# Exactly what is included is unclear but space of files can be added/subtracted.
MEM_USAGE_LSB = 0x809a
MEM_USAGE_MSB = 0x809b
parser = argparse.ArgumentParser(description='Load variable files into AlmostTI RAM-file.')
parser.add_argument('tivars', metavar='TI-varfile', type=str, nargs='+',
help='an 83p-file to be loaded.')
parser.add_argument('--parent', type=str, default='clean.ram',
help='RAM file to add variables into.')
parser.add_argument('--child', type=str, default='new.ram',
help='Resulting RAM file.')
parser.add_argument('--debug', default='False', action='store_true',
help='Output debug.ram (64KB) for analysis.')
args = parser.parse_args()
# The RAM-file starts with some internal state registry data.
# This data may be differently sized in different versions of AndieGraph depending on CPU architecture or actual code changes.
# Read 0x4000 bytes and then the next 16384 bytes
# Create 0x8000 null bytes and add the loaded ram blocks in reverse
file = open(args.parent, 'rb')
old_file_position = file.tell()
file.seek(0, os.SEEK_END)
OHsize = file.tell() - 32768
file.seek(old_file_position, os.SEEK_SET)
emulatorOverHead = file.read(OHsize)
RAM2 = file.read(16384)
RAM1 = file.read() # This should be another 16384 bytes
file.close()
length = len(RAM1)+len(RAM2)
assert length==32768, "Filesize doesn't match the size of expected RAM-file"
print("Loaded ",length," bytes of RAM memory")
ram = bytearray(0x8000*b'\x00' + RAM1 + RAM2)
# Idea:
# Set up a list of all object types and storage locations
# Ensuring smooth re-arrangements of them all.
# Example:
# var_types = [[start_ptr, end_ptr, 'Prgm', dataSize, FATsize],]
# then lists, variables, pictures
# Read each program and create a list with an entry for each. Name, type, data, length, total memconsumption (4+len(name)+len(data)+2)
variables = []
additionalSize = 0
additionalSizeFAT = 0
for variableName in args.tivars:
# Improvement: Lots of additional format checking can be done.
variableFile = open(variableName, "rb")
fileData = variableFile.read()
variableFile.close()
if fileData[0:8] != b'**TI83**':
print(variableName + ' is not TI83-file')
continue
variable = {}
variable['type'] = fileData[0x3b]
# Occasional examples that have not followed spec has appeared. Code below hopefully covers those.
variable['name'] = fileData[0x3c:0x44].split(b'\x00')[0].split(b' ')[0]
variable['data'] = fileData[0x48:-2]
sizeFAT = len(variable['name']) + FATOVERHEAD
additionalSize += sizeFAT + len(variable['data']) + DATAOVERHEAD
additionalSizeFAT += sizeFAT
variables.append(variable)
print("Read " + str(variable['name']) + " from file")
# Check free RAM
# While reading through FAT, modify for any new files added.
FAT_end = max(r2p(ram, USER_VARIABLE_TYPE_04_LSB), # max is used since these three pointers are a bit unknown but at least the FAT is farthest away in memory.
r2p(ram, USER_VARIABLE_TYPE_05_LSB),
r2p(ram, USER_VARIABLE_TYPE_06_LSB),
)
FAT_data = ram[FAT_end:FAT_START+1]
FAT_size = len(FAT_data)
FAT_data=FAT_data[::-1]
new_FAT=bytearray()
variables_size = 0
while len(FAT_data) > 0:
prgm_ptr = FAT_data[1] + FAT_data[2] * 256
variables_size += ram[prgm_ptr] + ram[prgm_ptr+1] * 256
FAT_data[1] = (prgm_ptr - additionalSizeFAT) % 256
FAT_data[2] = (prgm_ptr - additionalSizeFAT) // 256
new_FAT.extend(FAT_data[0:3+FAT_data[3]+1])
FAT_data = FAT_data[3+FAT_data[3]+1:]
ram[FAT_end:FAT_START+1] = new_FAT[::-1]
usedRAM = variables_size + FAT_size
freeRAM = 27118 - usedRAM
print("RAM already contains " + str(usedRAM) + " bytes.")
print(str(freeRAM) + " available in parent RAM")
# Count and verify that it totals no more than 27118 bytes, which is how much space we got in a clean RAM.
assert additionalSize <= freeRAM, "Not enough memory available in RAM image"
# Move existing program data to fit FAT extension
# move y-vars (currently just zeros anyway)
prgm_start = max(r2p(ram, USER_VARIABLE_TYPE_01_LSB), # min is used since these three pointers are a bit unknown but at least the value means that the memory is occupied from there.
r2p(ram, USER_VARIABLE_TYPE_02_LSB),
r2p(ram, USER_VARIABLE_TYPE_03_LSB),
)
to_ptr = prgm_start - additionalSizeFAT
from_ptr = prgm_start
for i in range(FAT_end - prgm_start):
ram[to_ptr+i] = ram[from_ptr+i]
# Re-map Y-vars links
for yvar in range(31):
yvar_ix = yvar*6
newAddress = ram[YVARS_POINTER_MSB+yvar_ix] * 256 + ram[YVARS_POINTER_LSB+yvar_ix] - additionalSizeFAT
msb = newAddress//256
lsb = newAddress%256
ram[YVARS_POINTER_MSB+yvar_ix] = msb
ram[YVARS_POINTER_LSB+yvar_ix] = lsb
# Storing start of Y-vars data
# Note that source has MSB first and destination has LSB first
ram[USER_VARIABLE_TYPE_10_LSB] = ram[YVARS_POINTER_LSB]
ram[USER_VARIABLE_TYPE_10_MSB] = ram[YVARS_POINTER_MSB]
yvars_start = ram[YVARS_POINTER_MSB]*256 + ram[YVARS_POINTER_LSB]
# Calculate need of FAT data and figure out good start adress for programdata
prgm_ptr = yvars_start - variables_size - additionalSize + additionalSizeFAT
# When these are explored in more detail,
# Remember to ensure that proper one is used for RAM free check.
ram[USER_VARIABLE_TYPE_01_LSB] = prgm_ptr % 256
ram[USER_VARIABLE_TYPE_01_MSB] = prgm_ptr // 256
ram[USER_VARIABLE_TYPE_02_LSB] = prgm_ptr % 256
ram[USER_VARIABLE_TYPE_02_MSB] = prgm_ptr // 256
ram[USER_VARIABLE_TYPE_03_LSB] = prgm_ptr % 256
ram[USER_VARIABLE_TYPE_03_MSB] = prgm_ptr // 256
someSize = ram[MEM_USAGE_LSB] + ram[MEM_USAGE_MSB]*256 + additionalSize - additionalSizeFAT
ram[MEM_USAGE_LSB] = someSize % 256
ram[MEM_USAGE_MSB] = someSize // 256
# Iterate variable list and write FAT and programdata for each program.
FAT_ptr = FAT_START
FAT_ptr = max(r2p(ram, USER_VARIABLE_TYPE_07_LSB),
r2p(ram, USER_VARIABLE_TYPE_08_LSB),
r2p(ram, USER_VARIABLE_TYPE_09_LSB),
)
for variable in variables:
ram[FAT_ptr] = variable['type']
FAT_ptr -= 1
ram[FAT_ptr] = prgm_ptr % 256
FAT_ptr -= 1
ram[FAT_ptr] = prgm_ptr // 256
FAT_ptr -= 1
ram[FAT_ptr] = len(variable['name'])
FAT_ptr -= 1
for char in variable['name']:
ram[FAT_ptr] = char
FAT_ptr -= 1
ram[prgm_ptr] = len(variable['data']) % 256
prgm_ptr += 1
ram[prgm_ptr] = len(variable['data']) // 256
prgm_ptr += 1
for byte in variable['data']:
ram[prgm_ptr] = byte
prgm_ptr += 1
# Update all pointers around 0x93.. and all pointers around fdb2-fe64
endOfFAT = FAT_START - additionalSizeFAT + 1
endOfFAT = FAT_ptr + 1
endOfFAT_msb = endOfFAT // 256
endOfFAT_lsb = endOfFAT % 256
ram[USER_VARIABLE_TYPE_04_LSB] = endOfFAT_lsb
ram[USER_VARIABLE_TYPE_04_MSB] = endOfFAT_msb
ram[USER_VARIABLE_TYPE_05_LSB] = endOfFAT_lsb
ram[USER_VARIABLE_TYPE_05_MSB] = endOfFAT_msb
ram[USER_VARIABLE_TYPE_06_LSB] = endOfFAT_lsb
ram[USER_VARIABLE_TYPE_06_MSB] = endOfFAT_msb
belowFAT = endOfFAT - 1
belowFAT_msb = belowFAT // 256
belowFAT_lsb = belowFAT % 256
ram[USER_VARIABLE_TYPE_07_LSB] = belowFAT_lsb
ram[USER_VARIABLE_TYPE_07_MSB] = belowFAT_msb
ram[USER_VARIABLE_TYPE_08_LSB] = belowFAT_lsb
ram[USER_VARIABLE_TYPE_08_MSB] = belowFAT_msb
ram[USER_VARIABLE_TYPE_09_LSB] = belowFAT_lsb
ram[USER_VARIABLE_TYPE_09_MSB] = belowFAT_msb
# Write back the 1118
# Write 0xC000-0xFFFF
# Write 0x8000-0xBFFF
# Happy!
newRAM = open(args.child, "wb")
newRAM.write(emulatorOverHead)
newRAM.write(ram[0xC000:])
newRAM.write(ram[0x8000:0xC000])
newRAM.close()
print("Wrote new RAM-file " + args.child)
if args.debug:
newRAM = open('debug.ram', "wb")
newRAM.write(ram)
newRAM.close()
print("Wrote debug.ram")